mirror of
https://gitlab.com/famedly/conduit.git
synced 2024-12-29 22:13:48 +03:00
Create admin room and hide migration messages on first run
This commit is contained in:
parent
86a9ec9f44
commit
e1c0dcb6bb
3 changed files with 419 additions and 317 deletions
|
@ -1,7 +1,11 @@
|
||||||
use std::{collections::BTreeMap, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
|
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
|
||||||
use crate::{database::DatabaseGuard, pdu::PduBuilder, utils, ConduitResult, Error, Ruma};
|
use crate::{
|
||||||
|
database::{admin::make_user_admin, DatabaseGuard},
|
||||||
|
pdu::PduBuilder,
|
||||||
|
utils, ConduitResult, Error, Ruma,
|
||||||
|
};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::{
|
api::client::{
|
||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
|
@ -14,25 +18,13 @@ use ruma::{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
events::{
|
events::{
|
||||||
room::{
|
room::member::{MembershipState, RoomMemberEventContent},
|
||||||
canonical_alias::RoomCanonicalAliasEventContent,
|
|
||||||
create::RoomCreateEventContent,
|
|
||||||
guest_access::{GuestAccess, RoomGuestAccessEventContent},
|
|
||||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
|
||||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
|
||||||
member::{MembershipState, RoomMemberEventContent},
|
|
||||||
message::RoomMessageEventContent,
|
|
||||||
name::RoomNameEventContent,
|
|
||||||
power_levels::RoomPowerLevelsEventContent,
|
|
||||||
topic::RoomTopicEventContent,
|
|
||||||
},
|
|
||||||
EventType,
|
EventType,
|
||||||
},
|
},
|
||||||
identifiers::RoomName,
|
push, UserId,
|
||||||
push, RoomAliasId, RoomId, RoomVersionId, UserId,
|
|
||||||
};
|
};
|
||||||
use serde_json::value::to_raw_value;
|
use serde_json::value::to_raw_value;
|
||||||
use tracing::info;
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use register::RegistrationKind;
|
use register::RegistrationKind;
|
||||||
#[cfg(feature = "conduit_bin")]
|
#[cfg(feature = "conduit_bin")]
|
||||||
|
@ -253,276 +245,16 @@ pub async fn register_route(
|
||||||
body.initial_device_display_name.clone(),
|
body.initial_device_display_name.clone(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// If this is the first user on this server, create the admin room
|
|
||||||
if db.users.count()? == 1 {
|
|
||||||
// Create a user for the server
|
|
||||||
let conduit_user = UserId::parse_with_server_name("conduit", db.globals.server_name())
|
|
||||||
.expect("@conduit:server_name is valid");
|
|
||||||
|
|
||||||
db.users.create(&conduit_user, None)?;
|
|
||||||
|
|
||||||
let room_id = RoomId::new(db.globals.server_name());
|
|
||||||
|
|
||||||
db.rooms.get_or_create_shortroomid(&room_id, &db.globals)?;
|
|
||||||
|
|
||||||
let mutex_state = Arc::clone(
|
|
||||||
db.globals
|
|
||||||
.roomid_mutex_state
|
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.entry(room_id.clone())
|
|
||||||
.or_default(),
|
|
||||||
);
|
|
||||||
let state_lock = mutex_state.lock().await;
|
|
||||||
|
|
||||||
let mut content = RoomCreateEventContent::new(conduit_user.clone());
|
|
||||||
content.federate = true;
|
|
||||||
content.predecessor = None;
|
|
||||||
content.room_version = RoomVersionId::V6;
|
|
||||||
|
|
||||||
// 1. The room create event
|
|
||||||
db.rooms.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: EventType::RoomCreate,
|
|
||||||
content: to_raw_value(&content).expect("event is valid, we just created it"),
|
|
||||||
unsigned: None,
|
|
||||||
state_key: Some("".to_owned()),
|
|
||||||
redacts: None,
|
|
||||||
},
|
|
||||||
&conduit_user,
|
|
||||||
&room_id,
|
|
||||||
&db,
|
|
||||||
&state_lock,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// 2. Make conduit bot join
|
|
||||||
db.rooms.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: EventType::RoomMember,
|
|
||||||
content: to_raw_value(&RoomMemberEventContent {
|
|
||||||
membership: MembershipState::Join,
|
|
||||||
displayname: None,
|
|
||||||
avatar_url: None,
|
|
||||||
is_direct: None,
|
|
||||||
third_party_invite: None,
|
|
||||||
blurhash: None,
|
|
||||||
reason: None,
|
|
||||||
join_authorized_via_users_server: None,
|
|
||||||
})
|
|
||||||
.expect("event is valid, we just created it"),
|
|
||||||
unsigned: None,
|
|
||||||
state_key: Some(conduit_user.to_string()),
|
|
||||||
redacts: None,
|
|
||||||
},
|
|
||||||
&conduit_user,
|
|
||||||
&room_id,
|
|
||||||
&db,
|
|
||||||
&state_lock,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// 3. Power levels
|
|
||||||
let mut users = BTreeMap::new();
|
|
||||||
users.insert(conduit_user.clone(), 100.into());
|
|
||||||
users.insert(user_id.clone(), 100.into());
|
|
||||||
|
|
||||||
db.rooms.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: EventType::RoomPowerLevels,
|
|
||||||
content: to_raw_value(&RoomPowerLevelsEventContent {
|
|
||||||
users,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.expect("event is valid, we just created it"),
|
|
||||||
unsigned: None,
|
|
||||||
state_key: Some("".to_owned()),
|
|
||||||
redacts: None,
|
|
||||||
},
|
|
||||||
&conduit_user,
|
|
||||||
&room_id,
|
|
||||||
&db,
|
|
||||||
&state_lock,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// 4.1 Join Rules
|
|
||||||
db.rooms.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: EventType::RoomJoinRules,
|
|
||||||
content: to_raw_value(&RoomJoinRulesEventContent::new(JoinRule::Invite))
|
|
||||||
.expect("event is valid, we just created it"),
|
|
||||||
unsigned: None,
|
|
||||||
state_key: Some("".to_owned()),
|
|
||||||
redacts: None,
|
|
||||||
},
|
|
||||||
&conduit_user,
|
|
||||||
&room_id,
|
|
||||||
&db,
|
|
||||||
&state_lock,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// 4.2 History Visibility
|
|
||||||
db.rooms.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: EventType::RoomHistoryVisibility,
|
|
||||||
content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
|
|
||||||
HistoryVisibility::Shared,
|
|
||||||
))
|
|
||||||
.expect("event is valid, we just created it"),
|
|
||||||
unsigned: None,
|
|
||||||
state_key: Some("".to_owned()),
|
|
||||||
redacts: None,
|
|
||||||
},
|
|
||||||
&conduit_user,
|
|
||||||
&room_id,
|
|
||||||
&db,
|
|
||||||
&state_lock,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// 4.3 Guest Access
|
|
||||||
db.rooms.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: EventType::RoomGuestAccess,
|
|
||||||
content: to_raw_value(&RoomGuestAccessEventContent::new(GuestAccess::Forbidden))
|
|
||||||
.expect("event is valid, we just created it"),
|
|
||||||
unsigned: None,
|
|
||||||
state_key: Some("".to_owned()),
|
|
||||||
redacts: None,
|
|
||||||
},
|
|
||||||
&conduit_user,
|
|
||||||
&room_id,
|
|
||||||
&db,
|
|
||||||
&state_lock,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// 6. Events implied by name and topic
|
|
||||||
let room_name = RoomName::parse(format!("{} Admin Room", db.globals.server_name()))
|
|
||||||
.expect("Room name is valid");
|
|
||||||
db.rooms.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: EventType::RoomName,
|
|
||||||
content: to_raw_value(&RoomNameEventContent::new(Some(room_name)))
|
|
||||||
.expect("event is valid, we just created it"),
|
|
||||||
unsigned: None,
|
|
||||||
state_key: Some("".to_owned()),
|
|
||||||
redacts: None,
|
|
||||||
},
|
|
||||||
&conduit_user,
|
|
||||||
&room_id,
|
|
||||||
&db,
|
|
||||||
&state_lock,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
db.rooms.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: EventType::RoomTopic,
|
|
||||||
content: to_raw_value(&RoomTopicEventContent {
|
|
||||||
topic: format!("Manage {}", db.globals.server_name()),
|
|
||||||
})
|
|
||||||
.expect("event is valid, we just created it"),
|
|
||||||
unsigned: None,
|
|
||||||
state_key: Some("".to_owned()),
|
|
||||||
redacts: None,
|
|
||||||
},
|
|
||||||
&conduit_user,
|
|
||||||
&room_id,
|
|
||||||
&db,
|
|
||||||
&state_lock,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Room alias
|
|
||||||
let alias: Box<RoomAliasId> = format!("#admins:{}", db.globals.server_name())
|
|
||||||
.try_into()
|
|
||||||
.expect("#admins:server_name is a valid alias name");
|
|
||||||
|
|
||||||
db.rooms.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: EventType::RoomCanonicalAlias,
|
|
||||||
content: to_raw_value(&RoomCanonicalAliasEventContent {
|
|
||||||
alias: Some(alias.clone()),
|
|
||||||
alt_aliases: Vec::new(),
|
|
||||||
})
|
|
||||||
.expect("event is valid, we just created it"),
|
|
||||||
unsigned: None,
|
|
||||||
state_key: Some("".to_owned()),
|
|
||||||
redacts: None,
|
|
||||||
},
|
|
||||||
&conduit_user,
|
|
||||||
&room_id,
|
|
||||||
&db,
|
|
||||||
&state_lock,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
db.rooms.set_alias(&alias, Some(&room_id), &db.globals)?;
|
|
||||||
|
|
||||||
// Invite and join the real user
|
|
||||||
db.rooms.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: EventType::RoomMember,
|
|
||||||
content: to_raw_value(&RoomMemberEventContent {
|
|
||||||
membership: MembershipState::Invite,
|
|
||||||
displayname: None,
|
|
||||||
avatar_url: None,
|
|
||||||
is_direct: None,
|
|
||||||
third_party_invite: None,
|
|
||||||
blurhash: None,
|
|
||||||
reason: None,
|
|
||||||
join_authorized_via_users_server: None,
|
|
||||||
})
|
|
||||||
.expect("event is valid, we just created it"),
|
|
||||||
unsigned: None,
|
|
||||||
state_key: Some(user_id.to_string()),
|
|
||||||
redacts: None,
|
|
||||||
},
|
|
||||||
&conduit_user,
|
|
||||||
&room_id,
|
|
||||||
&db,
|
|
||||||
&state_lock,
|
|
||||||
)?;
|
|
||||||
db.rooms.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: EventType::RoomMember,
|
|
||||||
content: to_raw_value(&RoomMemberEventContent {
|
|
||||||
membership: MembershipState::Join,
|
|
||||||
displayname: Some(displayname),
|
|
||||||
avatar_url: None,
|
|
||||||
is_direct: None,
|
|
||||||
third_party_invite: None,
|
|
||||||
blurhash: None,
|
|
||||||
reason: None,
|
|
||||||
join_authorized_via_users_server: None,
|
|
||||||
})
|
|
||||||
.expect("event is valid, we just created it"),
|
|
||||||
unsigned: None,
|
|
||||||
state_key: Some(user_id.to_string()),
|
|
||||||
redacts: None,
|
|
||||||
},
|
|
||||||
&user_id,
|
|
||||||
&room_id,
|
|
||||||
&db,
|
|
||||||
&state_lock,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Send welcome message
|
|
||||||
db.rooms.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: EventType::RoomMessage,
|
|
||||||
content: to_raw_value(&RoomMessageEventContent::text_html(
|
|
||||||
"## Thank you for trying out Conduit!\n\nConduit is currently in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.\n\nHelpful links:\n> Website: https://conduit.rs\n> Git and Documentation: https://gitlab.com/famedly/conduit\n> Report issues: https://gitlab.com/famedly/conduit/-/issues\n\nHere are some rooms you can join (by typing the command):\n\nConduit room (Ask questions and get notified on updates):\n`/join #conduit:fachschaften.org`\n\nConduit lounge (Off-topic, only Conduit users are allowed to join)\n`/join #conduit-lounge:conduit.rs`".to_owned(),
|
|
||||||
"<h2>Thank you for trying out Conduit!</h2>\n<p>Conduit is currently in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.</p>\n<p>Helpful links:</p>\n<blockquote>\n<p>Website: https://conduit.rs<br>Git and Documentation: https://gitlab.com/famedly/conduit<br>Report issues: https://gitlab.com/famedly/conduit/-/issues</p>\n</blockquote>\n<p>Here are some rooms you can join (by typing the command):</p>\n<p>Conduit room (Ask questions and get notified on updates):<br><code>/join #conduit:fachschaften.org</code></p>\n<p>Conduit lounge (Off-topic, only Conduit users are allowed to join)<br><code>/join #conduit-lounge:conduit.rs</code></p>\n".to_owned(),
|
|
||||||
))
|
|
||||||
.expect("event is valid, we just created it"),
|
|
||||||
unsigned: None,
|
|
||||||
state_key: None,
|
|
||||||
redacts: None,
|
|
||||||
},
|
|
||||||
&conduit_user,
|
|
||||||
&room_id,
|
|
||||||
&db,
|
|
||||||
&state_lock,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("{} registered on this server", user_id);
|
info!("{} registered on this server", user_id);
|
||||||
|
|
||||||
|
// If this is the first real user, grant them admin privileges
|
||||||
|
// Note: the server user, @conduit:servername, is generated first
|
||||||
|
if db.users.count()? == 2 {
|
||||||
|
make_user_admin(&db, &user_id, displayname).await?;
|
||||||
|
|
||||||
|
warn!("Granting {} admin privileges as the first user", user_id);
|
||||||
|
}
|
||||||
|
|
||||||
db.flush()?;
|
db.flush()?;
|
||||||
|
|
||||||
Ok(register::Response {
|
Ok(register::Response {
|
||||||
|
|
|
@ -34,7 +34,9 @@ use std::{
|
||||||
sync::{Arc, Mutex, RwLock},
|
sync::{Arc, Mutex, RwLock},
|
||||||
};
|
};
|
||||||
use tokio::sync::{OwnedRwLockReadGuard, RwLock as TokioRwLock, Semaphore};
|
use tokio::sync::{OwnedRwLockReadGuard, RwLock as TokioRwLock, Semaphore};
|
||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
|
use self::admin::create_admin_room;
|
||||||
|
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
_db: Arc<dyn DatabaseEngine>,
|
_db: Arc<dyn DatabaseEngine>,
|
||||||
|
@ -301,10 +303,32 @@ impl Database {
|
||||||
)?,
|
)?,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
{
|
let guard = db.read().await;
|
||||||
let db = db.read().await;
|
|
||||||
|
// Matrix resource ownership is based on the server name; changing it
|
||||||
|
// requires recreating the database from scratch.
|
||||||
|
if guard.users.count()? > 0 {
|
||||||
|
let conduit_user =
|
||||||
|
UserId::parse_with_server_name("conduit", guard.globals.server_name())
|
||||||
|
.expect("@conduit:server_name is valid");
|
||||||
|
|
||||||
|
if !guard.users.exists(&conduit_user)? {
|
||||||
|
error!(
|
||||||
|
"The {} server user does not exist, and the database is not new.",
|
||||||
|
conduit_user
|
||||||
|
);
|
||||||
|
return Err(Error::bad_database(
|
||||||
|
"Cannot reuse an existing database after changing the server name, please delete the old one first."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the database has any data, perform data migrations before starting
|
||||||
|
let latest_database_version = 11;
|
||||||
|
|
||||||
|
if guard.users.count()? > 0 {
|
||||||
|
let db = &*guard;
|
||||||
// MIGRATIONS
|
// MIGRATIONS
|
||||||
// TODO: database versions of new dbs should probably not be 0
|
|
||||||
if db.globals.database_version()? < 1 {
|
if db.globals.database_version()? < 1 {
|
||||||
for (roomserverid, _) in db.rooms.roomserverids.iter() {
|
for (roomserverid, _) in db.rooms.roomserverids.iter() {
|
||||||
let mut parts = roomserverid.split(|&b| b == 0xff);
|
let mut parts = roomserverid.split(|&b| b == 0xff);
|
||||||
|
@ -325,7 +349,7 @@ impl Database {
|
||||||
|
|
||||||
db.globals.bump_database_version(1)?;
|
db.globals.bump_database_version(1)?;
|
||||||
|
|
||||||
println!("Migration: 0 -> 1 finished");
|
warn!("Migration: 0 -> 1 finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
if db.globals.database_version()? < 2 {
|
if db.globals.database_version()? < 2 {
|
||||||
|
@ -344,7 +368,7 @@ impl Database {
|
||||||
|
|
||||||
db.globals.bump_database_version(2)?;
|
db.globals.bump_database_version(2)?;
|
||||||
|
|
||||||
println!("Migration: 1 -> 2 finished");
|
warn!("Migration: 1 -> 2 finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
if db.globals.database_version()? < 3 {
|
if db.globals.database_version()? < 3 {
|
||||||
|
@ -362,7 +386,7 @@ impl Database {
|
||||||
|
|
||||||
db.globals.bump_database_version(3)?;
|
db.globals.bump_database_version(3)?;
|
||||||
|
|
||||||
println!("Migration: 2 -> 3 finished");
|
warn!("Migration: 2 -> 3 finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
if db.globals.database_version()? < 4 {
|
if db.globals.database_version()? < 4 {
|
||||||
|
@ -385,7 +409,7 @@ impl Database {
|
||||||
|
|
||||||
db.globals.bump_database_version(4)?;
|
db.globals.bump_database_version(4)?;
|
||||||
|
|
||||||
println!("Migration: 3 -> 4 finished");
|
warn!("Migration: 3 -> 4 finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
if db.globals.database_version()? < 5 {
|
if db.globals.database_version()? < 5 {
|
||||||
|
@ -409,7 +433,7 @@ impl Database {
|
||||||
|
|
||||||
db.globals.bump_database_version(5)?;
|
db.globals.bump_database_version(5)?;
|
||||||
|
|
||||||
println!("Migration: 4 -> 5 finished");
|
warn!("Migration: 4 -> 5 finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
if db.globals.database_version()? < 6 {
|
if db.globals.database_version()? < 6 {
|
||||||
|
@ -422,7 +446,7 @@ impl Database {
|
||||||
|
|
||||||
db.globals.bump_database_version(6)?;
|
db.globals.bump_database_version(6)?;
|
||||||
|
|
||||||
println!("Migration: 5 -> 6 finished");
|
warn!("Migration: 5 -> 6 finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
if db.globals.database_version()? < 7 {
|
if db.globals.database_version()? < 7 {
|
||||||
|
@ -549,7 +573,7 @@ impl Database {
|
||||||
|
|
||||||
db.globals.bump_database_version(7)?;
|
db.globals.bump_database_version(7)?;
|
||||||
|
|
||||||
println!("Migration: 6 -> 7 finished");
|
warn!("Migration: 6 -> 7 finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
if db.globals.database_version()? < 8 {
|
if db.globals.database_version()? < 8 {
|
||||||
|
@ -557,7 +581,7 @@ impl Database {
|
||||||
for (room_id, _) in db.rooms.roomid_shortstatehash.iter() {
|
for (room_id, _) in db.rooms.roomid_shortstatehash.iter() {
|
||||||
let shortroomid = db.globals.next_count()?.to_be_bytes();
|
let shortroomid = db.globals.next_count()?.to_be_bytes();
|
||||||
db.rooms.roomid_shortroomid.insert(&room_id, &shortroomid)?;
|
db.rooms.roomid_shortroomid.insert(&room_id, &shortroomid)?;
|
||||||
println!("Migration: 8");
|
info!("Migration: 8");
|
||||||
}
|
}
|
||||||
// Update pduids db layout
|
// Update pduids db layout
|
||||||
let mut batch = db.rooms.pduid_pdu.iter().filter_map(|(key, v)| {
|
let mut batch = db.rooms.pduid_pdu.iter().filter_map(|(key, v)| {
|
||||||
|
@ -608,7 +632,7 @@ impl Database {
|
||||||
|
|
||||||
db.globals.bump_database_version(8)?;
|
db.globals.bump_database_version(8)?;
|
||||||
|
|
||||||
println!("Migration: 7 -> 8 finished");
|
warn!("Migration: 7 -> 8 finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
if db.globals.database_version()? < 9 {
|
if db.globals.database_version()? < 9 {
|
||||||
|
@ -650,7 +674,7 @@ impl Database {
|
||||||
println!("smaller batch done");
|
println!("smaller batch done");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Deleting starts");
|
info!("Deleting starts");
|
||||||
|
|
||||||
let batch2: Vec<_> = db
|
let batch2: Vec<_> = db
|
||||||
.rooms
|
.rooms
|
||||||
|
@ -673,7 +697,7 @@ impl Database {
|
||||||
|
|
||||||
db.globals.bump_database_version(9)?;
|
db.globals.bump_database_version(9)?;
|
||||||
|
|
||||||
println!("Migration: 8 -> 9 finished");
|
warn!("Migration: 8 -> 9 finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
if db.globals.database_version()? < 10 {
|
if db.globals.database_version()? < 10 {
|
||||||
|
@ -692,7 +716,7 @@ impl Database {
|
||||||
|
|
||||||
db.globals.bump_database_version(10)?;
|
db.globals.bump_database_version(10)?;
|
||||||
|
|
||||||
println!("Migration: 9 -> 10 finished");
|
warn!("Migration: 9 -> 10 finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
if db.globals.database_version()? < 11 {
|
if db.globals.database_version()? < 11 {
|
||||||
|
@ -701,11 +725,28 @@ impl Database {
|
||||||
.clear()?;
|
.clear()?;
|
||||||
db.globals.bump_database_version(11)?;
|
db.globals.bump_database_version(11)?;
|
||||||
|
|
||||||
println!("Migration: 10 -> 11 finished");
|
warn!("Migration: 10 -> 11 finished");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let guard = db.read().await;
|
assert_eq!(11, latest_database_version);
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Loaded {} database with version {}",
|
||||||
|
config.database_backend, latest_database_version
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
guard
|
||||||
|
.globals
|
||||||
|
.bump_database_version(latest_database_version)?;
|
||||||
|
|
||||||
|
// Create the admin room and server user on first run
|
||||||
|
create_admin_room(&guard).await?;
|
||||||
|
|
||||||
|
warn!(
|
||||||
|
"Created new {} database with version {}",
|
||||||
|
config.database_backend, latest_database_version
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// This data is probably outdated
|
// This data is probably outdated
|
||||||
guard.rooms.edus.presenceid_presence.clear()?;
|
guard.rooms.edus.presenceid_presence.clear()?;
|
||||||
|
@ -724,8 +765,6 @@ impl Database {
|
||||||
|
|
||||||
#[cfg(feature = "conduit_bin")]
|
#[cfg(feature = "conduit_bin")]
|
||||||
pub async fn start_on_shutdown_tasks(db: Arc<TokioRwLock<Self>>, shutdown: Shutdown) {
|
pub async fn start_on_shutdown_tasks(db: Arc<TokioRwLock<Self>>, shutdown: Shutdown) {
|
||||||
use tracing::info;
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
shutdown.await;
|
shutdown.await;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{convert::TryFrom, convert::TryInto, sync::Arc, time::Instant};
|
use std::{collections::BTreeMap, convert::TryFrom, convert::TryInto, sync::Arc, time::Instant};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
|
@ -12,12 +12,22 @@ use rocket::{
|
||||||
http::RawStr,
|
http::RawStr,
|
||||||
};
|
};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
|
events::room::{
|
||||||
|
canonical_alias::RoomCanonicalAliasEventContent,
|
||||||
|
create::RoomCreateEventContent,
|
||||||
|
guest_access::{GuestAccess, RoomGuestAccessEventContent},
|
||||||
|
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||||
|
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||||
|
member::{MembershipState, RoomMemberEventContent},
|
||||||
|
name::RoomNameEventContent,
|
||||||
|
power_levels::RoomPowerLevelsEventContent,
|
||||||
|
topic::RoomTopicEventContent,
|
||||||
|
},
|
||||||
events::{room::message::RoomMessageEventContent, EventType},
|
events::{room::message::RoomMessageEventContent, EventType},
|
||||||
EventId, RoomId, RoomVersionId, ServerName, UserId,
|
identifiers::{EventId, RoomAliasId, RoomId, RoomName, RoomVersionId, ServerName, UserId},
|
||||||
};
|
};
|
||||||
use serde_json::value::to_raw_value;
|
use serde_json::value::to_raw_value;
|
||||||
use tokio::sync::{MutexGuard, RwLock, RwLockReadGuard};
|
use tokio::sync::{MutexGuard, RwLock, RwLockReadGuard};
|
||||||
use tracing::warn;
|
|
||||||
|
|
||||||
pub enum AdminRoomEvent {
|
pub enum AdminRoomEvent {
|
||||||
ProcessMessage(String),
|
ProcessMessage(String),
|
||||||
|
@ -52,16 +62,9 @@ impl Admin {
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("#admins:server_name is a valid room alias"),
|
.expect("#admins:server_name is a valid room alias"),
|
||||||
)
|
)
|
||||||
|
.expect("Database data for admin room alias must be valid")
|
||||||
.expect("Admin room must exist");
|
.expect("Admin room must exist");
|
||||||
|
|
||||||
let conduit_room = match conduit_room {
|
|
||||||
None => {
|
|
||||||
warn!("Conduit instance does not have an #admins room. Logging to that room will not work. Restart Conduit after creating a user to fix this.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Some(r) => r,
|
|
||||||
};
|
|
||||||
|
|
||||||
drop(guard);
|
drop(guard);
|
||||||
|
|
||||||
let send_message = |message: RoomMessageEventContent,
|
let send_message = |message: RoomMessageEventContent,
|
||||||
|
@ -500,3 +503,331 @@ fn usage_to_html(text: &str, server_name: &ServerName) -> String {
|
||||||
|
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create the admin room.
|
||||||
|
///
|
||||||
|
/// Users in this room are considered admins by conduit, and the room can be
|
||||||
|
/// used to issue admin commands by talking to the server user inside it.
|
||||||
|
pub(crate) async fn create_admin_room(db: &Database) -> Result<()> {
|
||||||
|
let room_id = RoomId::new(db.globals.server_name());
|
||||||
|
|
||||||
|
db.rooms.get_or_create_shortroomid(&room_id, &db.globals)?;
|
||||||
|
|
||||||
|
let mutex_state = Arc::clone(
|
||||||
|
db.globals
|
||||||
|
.roomid_mutex_state
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.entry(room_id.clone())
|
||||||
|
.or_default(),
|
||||||
|
);
|
||||||
|
let state_lock = mutex_state.lock().await;
|
||||||
|
|
||||||
|
// Create a user for the server
|
||||||
|
let conduit_user = UserId::parse_with_server_name("conduit", db.globals.server_name())
|
||||||
|
.expect("@conduit:server_name is valid");
|
||||||
|
|
||||||
|
db.users.create(&conduit_user, None)?;
|
||||||
|
|
||||||
|
let mut content = RoomCreateEventContent::new(conduit_user.clone());
|
||||||
|
content.federate = true;
|
||||||
|
content.predecessor = None;
|
||||||
|
content.room_version = RoomVersionId::V6;
|
||||||
|
|
||||||
|
// 1. The room create event
|
||||||
|
db.rooms.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: EventType::RoomCreate,
|
||||||
|
content: to_raw_value(&content).expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: Some("".to_owned()),
|
||||||
|
redacts: None,
|
||||||
|
},
|
||||||
|
&conduit_user,
|
||||||
|
&room_id,
|
||||||
|
&db,
|
||||||
|
&state_lock,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 2. Make conduit bot join
|
||||||
|
db.rooms.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: EventType::RoomMember,
|
||||||
|
content: to_raw_value(&RoomMemberEventContent {
|
||||||
|
membership: MembershipState::Join,
|
||||||
|
displayname: None,
|
||||||
|
avatar_url: None,
|
||||||
|
is_direct: None,
|
||||||
|
third_party_invite: None,
|
||||||
|
blurhash: None,
|
||||||
|
reason: None,
|
||||||
|
join_authorized_via_users_server: None,
|
||||||
|
})
|
||||||
|
.expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: Some(conduit_user.to_string()),
|
||||||
|
redacts: None,
|
||||||
|
},
|
||||||
|
&conduit_user,
|
||||||
|
&room_id,
|
||||||
|
&db,
|
||||||
|
&state_lock,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 3. Power levels
|
||||||
|
let mut users = BTreeMap::new();
|
||||||
|
users.insert(conduit_user.clone(), 100.into());
|
||||||
|
|
||||||
|
db.rooms.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: EventType::RoomPowerLevels,
|
||||||
|
content: to_raw_value(&RoomPowerLevelsEventContent {
|
||||||
|
users,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: Some("".to_owned()),
|
||||||
|
redacts: None,
|
||||||
|
},
|
||||||
|
&conduit_user,
|
||||||
|
&room_id,
|
||||||
|
&db,
|
||||||
|
&state_lock,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 4.1 Join Rules
|
||||||
|
db.rooms.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: EventType::RoomJoinRules,
|
||||||
|
content: to_raw_value(&RoomJoinRulesEventContent::new(JoinRule::Invite))
|
||||||
|
.expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: Some("".to_owned()),
|
||||||
|
redacts: None,
|
||||||
|
},
|
||||||
|
&conduit_user,
|
||||||
|
&room_id,
|
||||||
|
&db,
|
||||||
|
&state_lock,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 4.2 History Visibility
|
||||||
|
db.rooms.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: EventType::RoomHistoryVisibility,
|
||||||
|
content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
|
||||||
|
HistoryVisibility::Shared,
|
||||||
|
))
|
||||||
|
.expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: Some("".to_owned()),
|
||||||
|
redacts: None,
|
||||||
|
},
|
||||||
|
&conduit_user,
|
||||||
|
&room_id,
|
||||||
|
&db,
|
||||||
|
&state_lock,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 4.3 Guest Access
|
||||||
|
db.rooms.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: EventType::RoomGuestAccess,
|
||||||
|
content: to_raw_value(&RoomGuestAccessEventContent::new(GuestAccess::Forbidden))
|
||||||
|
.expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: Some("".to_owned()),
|
||||||
|
redacts: None,
|
||||||
|
},
|
||||||
|
&conduit_user,
|
||||||
|
&room_id,
|
||||||
|
&db,
|
||||||
|
&state_lock,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 5. Events implied by name and topic
|
||||||
|
let room_name = RoomName::parse(format!("{} Admin Room", db.globals.server_name()))
|
||||||
|
.expect("Room name is valid");
|
||||||
|
db.rooms.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: EventType::RoomName,
|
||||||
|
content: to_raw_value(&RoomNameEventContent::new(Some(room_name)))
|
||||||
|
.expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: Some("".to_owned()),
|
||||||
|
redacts: None,
|
||||||
|
},
|
||||||
|
&conduit_user,
|
||||||
|
&room_id,
|
||||||
|
&db,
|
||||||
|
&state_lock,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
db.rooms.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: EventType::RoomTopic,
|
||||||
|
content: to_raw_value(&RoomTopicEventContent {
|
||||||
|
topic: format!("Manage {}", db.globals.server_name()),
|
||||||
|
})
|
||||||
|
.expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: Some("".to_owned()),
|
||||||
|
redacts: None,
|
||||||
|
},
|
||||||
|
&conduit_user,
|
||||||
|
&room_id,
|
||||||
|
&db,
|
||||||
|
&state_lock,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 6. Room alias
|
||||||
|
let alias: Box<RoomAliasId> = format!("#admins:{}", db.globals.server_name())
|
||||||
|
.try_into()
|
||||||
|
.expect("#admins:server_name is a valid alias name");
|
||||||
|
|
||||||
|
db.rooms.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: EventType::RoomCanonicalAlias,
|
||||||
|
content: to_raw_value(&RoomCanonicalAliasEventContent {
|
||||||
|
alias: Some(alias.clone()),
|
||||||
|
alt_aliases: Vec::new(),
|
||||||
|
})
|
||||||
|
.expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: Some("".to_owned()),
|
||||||
|
redacts: None,
|
||||||
|
},
|
||||||
|
&conduit_user,
|
||||||
|
&room_id,
|
||||||
|
&db,
|
||||||
|
&state_lock,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
db.rooms.set_alias(&alias, Some(&room_id), &db.globals)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invite the user to the conduit admin room.
|
||||||
|
///
|
||||||
|
/// In conduit, this is equivalent to granting admin privileges.
|
||||||
|
pub(crate) async fn make_user_admin(
|
||||||
|
db: &Database,
|
||||||
|
user_id: &UserId,
|
||||||
|
displayname: String,
|
||||||
|
) -> Result<()> {
|
||||||
|
let admin_room_alias: Box<RoomAliasId> = format!("#admins:{}", db.globals.server_name())
|
||||||
|
.try_into()
|
||||||
|
.expect("#admins:server_name is a valid alias name");
|
||||||
|
let room_id = db
|
||||||
|
.rooms
|
||||||
|
.id_from_alias(&admin_room_alias)?
|
||||||
|
.expect("Admin room must exist");
|
||||||
|
|
||||||
|
let mutex_state = Arc::clone(
|
||||||
|
db.globals
|
||||||
|
.roomid_mutex_state
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.entry(room_id.clone())
|
||||||
|
.or_default(),
|
||||||
|
);
|
||||||
|
let state_lock = mutex_state.lock().await;
|
||||||
|
|
||||||
|
// Use the server user to grant the new admin's power level
|
||||||
|
let conduit_user = UserId::parse_with_server_name("conduit", db.globals.server_name())
|
||||||
|
.expect("@conduit:server_name is valid");
|
||||||
|
|
||||||
|
// Invite and join the real user
|
||||||
|
db.rooms.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: EventType::RoomMember,
|
||||||
|
content: to_raw_value(&RoomMemberEventContent {
|
||||||
|
membership: MembershipState::Invite,
|
||||||
|
displayname: None,
|
||||||
|
avatar_url: None,
|
||||||
|
is_direct: None,
|
||||||
|
third_party_invite: None,
|
||||||
|
blurhash: None,
|
||||||
|
reason: None,
|
||||||
|
join_authorized_via_users_server: None,
|
||||||
|
})
|
||||||
|
.expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: Some(user_id.to_string()),
|
||||||
|
redacts: None,
|
||||||
|
},
|
||||||
|
&conduit_user,
|
||||||
|
&room_id,
|
||||||
|
&db,
|
||||||
|
&state_lock,
|
||||||
|
)?;
|
||||||
|
db.rooms.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: EventType::RoomMember,
|
||||||
|
content: to_raw_value(&RoomMemberEventContent {
|
||||||
|
membership: MembershipState::Join,
|
||||||
|
displayname: Some(displayname),
|
||||||
|
avatar_url: None,
|
||||||
|
is_direct: None,
|
||||||
|
third_party_invite: None,
|
||||||
|
blurhash: None,
|
||||||
|
reason: None,
|
||||||
|
join_authorized_via_users_server: None,
|
||||||
|
})
|
||||||
|
.expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: Some(user_id.to_string()),
|
||||||
|
redacts: None,
|
||||||
|
},
|
||||||
|
&user_id,
|
||||||
|
&room_id,
|
||||||
|
&db,
|
||||||
|
&state_lock,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Set power level
|
||||||
|
let mut users = BTreeMap::new();
|
||||||
|
users.insert(conduit_user.to_owned(), 100.into());
|
||||||
|
users.insert(user_id.to_owned(), 100.into());
|
||||||
|
|
||||||
|
db.rooms.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: EventType::RoomPowerLevels,
|
||||||
|
content: to_raw_value(&RoomPowerLevelsEventContent {
|
||||||
|
users,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: Some("".to_owned()),
|
||||||
|
redacts: None,
|
||||||
|
},
|
||||||
|
&conduit_user,
|
||||||
|
&room_id,
|
||||||
|
&db,
|
||||||
|
&state_lock,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Send welcome message
|
||||||
|
db.rooms.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: EventType::RoomMessage,
|
||||||
|
content: to_raw_value(&RoomMessageEventContent::text_html(
|
||||||
|
"## Thank you for trying out Conduit!\n\nConduit is currently in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.\n\nHelpful links:\n> Website: https://conduit.rs\n> Git and Documentation: https://gitlab.com/famedly/conduit\n> Report issues: https://gitlab.com/famedly/conduit/-/issues\n\nHere are some rooms you can join (by typing the command):\n\nConduit room (Ask questions and get notified on updates):\n`/join #conduit:fachschaften.org`\n\nConduit lounge (Off-topic, only Conduit users are allowed to join)\n`/join #conduit-lounge:conduit.rs`".to_owned(),
|
||||||
|
"<h2>Thank you for trying out Conduit!</h2>\n<p>Conduit is currently in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.</p>\n<p>Helpful links:</p>\n<blockquote>\n<p>Website: https://conduit.rs<br>Git and Documentation: https://gitlab.com/famedly/conduit<br>Report issues: https://gitlab.com/famedly/conduit/-/issues</p>\n</blockquote>\n<p>Here are some rooms you can join (by typing the command):</p>\n<p>Conduit room (Ask questions and get notified on updates):<br><code>/join #conduit:fachschaften.org</code></p>\n<p>Conduit lounge (Off-topic, only Conduit users are allowed to join)<br><code>/join #conduit-lounge:conduit.rs</code></p>\n".to_owned(),
|
||||||
|
))
|
||||||
|
.expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: None,
|
||||||
|
redacts: None,
|
||||||
|
},
|
||||||
|
&conduit_user,
|
||||||
|
&room_id,
|
||||||
|
&db,
|
||||||
|
&state_lock,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue