Skip to content
Snippets Groups Projects
Commit 9e29dc80 authored by Timo Kösters's avatar Timo Kösters
Browse files

Merge branch '198-support-user-password-resets' into 'next'

feat: support user password resets

Closes #198

See merge request famedly/conduit!339
parents 2556e299 ada07de2
No related branches found
No related tags found
No related merge requests found
...@@ -68,6 +68,8 @@ pub struct Config { ...@@ -68,6 +68,8 @@ pub struct Config {
#[serde(default = "default_turn_ttl")] #[serde(default = "default_turn_ttl")]
pub turn_ttl: u64, pub turn_ttl: u64,
pub emergency_password: Option<String>,
#[serde(flatten)] #[serde(flatten)]
pub catchall: BTreeMap<String, IgnoredAny>, pub catchall: BTreeMap<String, IgnoredAny>,
} }
......
...@@ -19,7 +19,14 @@ ...@@ -19,7 +19,14 @@
use directories::ProjectDirs; use directories::ProjectDirs;
use futures_util::{stream::FuturesUnordered, StreamExt}; use futures_util::{stream::FuturesUnordered, StreamExt};
use lru_cache::LruCache; use lru_cache::LruCache;
use ruma::{DeviceId, EventId, RoomId, UserId}; use ruma::{
events::{
push_rules::PushRulesEventContent, room::message::RoomMessageEventContent, EventType,
GlobalAccountDataEvent,
},
push::Ruleset,
DeviceId, EventId, RoomId, UserId,
};
use std::{ use std::{
collections::{BTreeMap, HashMap, HashSet}, collections::{BTreeMap, HashMap, HashSet},
fs::{self, remove_dir_all}, fs::{self, remove_dir_all},
...@@ -747,6 +754,23 @@ pub async fn load_or_create(config: &Config) -> Result<Arc<TokioRwLock<Self>>> { ...@@ -747,6 +754,23 @@ pub async fn load_or_create(config: &Config) -> Result<Arc<TokioRwLock<Self>>> {
guard.rooms.edus.presenceid_presence.clear()?; guard.rooms.edus.presenceid_presence.clear()?;
guard.admin.start_handler(Arc::clone(&db), admin_receiver); guard.admin.start_handler(Arc::clone(&db), admin_receiver);
// Set emergency access for the conduit user
match set_emergency_access(&guard) {
Ok(pwd_set) => {
if pwd_set {
warn!("The Conduit account emergency password is set! Please unset it as soon as you finish admin account recovery!");
guard.admin.send_message(RoomMessageEventContent::text_plain("The Conduit account emergency password is set! Please unset it as soon as you finish admin account recovery!"));
}
}
Err(e) => {
error!(
"Could not set the configured emergency password for the conduit user: {}",
e
)
}
};
guard guard
.sending .sending
.start_handler(Arc::clone(&db), sending_receiver); .start_handler(Arc::clone(&db), sending_receiver);
...@@ -928,6 +952,32 @@ pub async fn start_cleanup_task(db: Arc<TokioRwLock<Self>>, config: &Config) { ...@@ -928,6 +952,32 @@ pub async fn start_cleanup_task(db: Arc<TokioRwLock<Self>>, config: &Config) {
} }
} }
/// Sets the emergency password and push rules for the @conduit account in case emergency password is set
fn set_emergency_access(db: &Database) -> Result<bool> {
let conduit_user = UserId::parse_with_server_name("conduit", db.globals.server_name())
.expect("@conduit:server_name is a valid UserId");
db.users
.set_password(&conduit_user, db.globals.emergency_password().as_deref())?;
let (ruleset, res) = match db.globals.emergency_password() {
Some(_) => (Ruleset::server_default(&conduit_user), Ok(true)),
None => (Ruleset::new(), Ok(false)),
};
db.account_data.update(
None,
&conduit_user,
EventType::PushRules,
&GlobalAccountDataEvent {
content: PushRulesEventContent { global: ruleset },
},
&db.globals,
)?;
res
}
pub struct DatabaseGuard(OwnedRwLockReadGuard<Database>); pub struct DatabaseGuard(OwnedRwLockReadGuard<Database>);
impl Deref for DatabaseGuard { impl Deref for DatabaseGuard {
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
use crate::{ use crate::{
error::{Error, Result}, error::{Error, Result},
pdu::PduBuilder, pdu::PduBuilder,
server_server, server_server, utils,
utils::HtmlEscape, utils::HtmlEscape,
Database, PduEvent, Database, PduEvent,
}; };
...@@ -262,6 +262,12 @@ enum AdminCommand { ...@@ -262,6 +262,12 @@ enum AdminCommand {
/// Show configuration values /// Show configuration values
ShowConfig, ShowConfig,
/// Reset user password
ResetPassword {
/// Username of the user for whom the password should be reset
username: String,
},
} }
fn process_admin_command( fn process_admin_command(
...@@ -435,6 +441,45 @@ fn process_admin_command( ...@@ -435,6 +441,45 @@ fn process_admin_command(
// Construct and send the response // Construct and send the response
RoomMessageEventContent::text_plain(format!("{}", db.globals.config)) RoomMessageEventContent::text_plain(format!("{}", db.globals.config))
} }
AdminCommand::ResetPassword { username } => {
let user_id = match UserId::parse_with_server_name(
username.as_str().to_lowercase(),
db.globals.server_name(),
) {
Ok(id) => id,
Err(e) => {
return Ok(RoomMessageEventContent::text_plain(format!(
"The supplied username is not a valid username: {}",
e
)))
}
};
// Check if the specified user is valid
if !db.users.exists(&user_id)?
|| db.users.is_deactivated(&user_id)?
|| user_id
== UserId::parse_with_server_name("conduit", db.globals.server_name())
.expect("conduit user exists")
{
return Ok(RoomMessageEventContent::text_plain(
"The specified user does not exist or is deactivated!",
));
}
let new_password = utils::random_string(20);
match db.users.set_password(&user_id, Some(new_password.as_str())) {
Ok(()) => RoomMessageEventContent::text_plain(format!(
"Successfully reset the password for user {}: {}",
user_id, new_password
)),
Err(e) => RoomMessageEventContent::text_plain(format!(
"Couldn't reset the password for user {}: {}",
user_id, e
)),
}
}
}; };
Ok(reply_message_content) Ok(reply_message_content)
......
...@@ -264,6 +264,10 @@ pub fn turn_secret(&self) -> &String { ...@@ -264,6 +264,10 @@ pub fn turn_secret(&self) -> &String {
&self.config.turn_secret &self.config.turn_secret
} }
pub fn emergency_password(&self) -> &Option<String> {
&self.config.emergency_password
}
/// TODO: the key valid until timestamp is only honored in room version > 4 /// TODO: the key valid until timestamp is only honored in room version > 4
/// Remove the outdated keys and insert the new ones. /// Remove the outdated keys and insert the new ones.
/// ///
......
...@@ -1496,7 +1496,11 @@ struct ExtractBody<'a> { ...@@ -1496,7 +1496,11 @@ struct ExtractBody<'a> {
let server_user = format!("@conduit:{}", db.globals.server_name()); let server_user = format!("@conduit:{}", db.globals.server_name());
let to_conduit = body.starts_with(&format!("{}: ", server_user)); let to_conduit = body.starts_with(&format!("{}: ", server_user));
let from_conduit = pdu.sender == server_user;
// This will evaluate to false if the emergency password is set up so that
// the administrator can execute commands as conduit
let from_conduit =
pdu.sender == server_user && db.globals.emergency_password().is_none();
if to_conduit && !from_conduit && admin_room.as_ref() == Some(&pdu.room_id) { if to_conduit && !from_conduit && admin_room.as_ref() == Some(&pdu.room_id) {
db.admin.process_message(body.to_string()); db.admin.process_message(body.to_string());
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment