From 67a1f21f5d2a8e881cf447b1d8ef0927171610d8 Mon Sep 17 00:00:00 2001 From: the0 Date: Thu, 2 Jul 2020 20:38:25 +0200 Subject: [PATCH] feat: implement password changing (#138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Password: Fixes Password: Implement logging out all devices except current Password: Implement password changing Co-authored-by: the0 Reviewed-on: https://git.koesters.xyz/timo/conduit/pulls/138 Reviewed-by: Timo Kösters --- src/client_server.rs | 54 ++++++++++++++++++++++++++++++++++++++++++- src/database/users.rs | 13 +++++++++++ src/main.rs | 1 + 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/client_server.rs b/src/client_server.rs index 49692a2..65b2c86 100644 --- a/src/client_server.rs +++ b/src/client_server.rs @@ -12,7 +12,7 @@ use ruma::{ api::client::{ error::ErrorKind, r0::{ - account::{get_username_availability, register}, + account::{change_password, get_username_availability, register}, alias::{create_alias, delete_alias, get_alias}, backup::{ add_backup_keys, create_backup, get_backup, get_backup_keys, get_latest_backup, @@ -305,6 +305,58 @@ pub fn logout_route( Ok(logout::Response.into()) } +#[post("/_matrix/client/r0/account/password", data = "")] +pub fn change_password_route( + db: State<'_, Database>, + body: Ruma, +) -> ConduitResult { + let user_id = body.user_id.as_ref().expect("user is authenticated"); + let device_id = body.device_id.as_ref().expect("user is authenticated"); + let mut uiaainfo = UiaaInfo { + flows: vec![AuthFlow { + stages: vec!["m.login.password".to_owned()], + }], + completed: Vec::new(), + params: Default::default(), + session: None, + auth_error: None, + }; + + if let Some(auth) = &body.auth { + let (worked, uiaainfo) = db.uiaa.try_auth( + &user_id, + &device_id, + auth, + &uiaainfo, + &db.users, + &db.globals, + )?; + if !worked { + return Err(Error::Uiaa(uiaainfo)); + } + } else { + uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); + db.uiaa.create(&user_id, &device_id, &uiaainfo)?; + return Err(Error::Uiaa(uiaainfo)); + } + + db.users.set_password(&user_id, &body.new_password)?; + + // TODO: Read logout_devices field when it's available and respect that, currently not supported in Ruma + // See: https://github.com/ruma/ruma/issues/107 + // Logout all devices except the current one + for id in db + .users + .all_device_ids(&user_id) + .filter_map(|id| id.ok()) + .filter(|id| id != device_id) + { + db.users.remove_device(&user_id, &id)?; + } + + Ok(change_password::Response.into()) +} + #[get("/_matrix/client/r0/capabilities")] pub fn get_capabilities_route() -> ConduitResult { let mut available = BTreeMap::new(); diff --git a/src/database/users.rs b/src/database/users.rs index 9f4eb40..e05cf2e 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -93,6 +93,19 @@ impl Users { }) } + /// Hash and set the user's password to the Argon2 hash + pub fn set_password(&self, user_id: &UserId, password: &str) -> Result<()> { + if let Ok(hash) = utils::calculate_hash(&password) { + self.userid_password.insert(user_id.to_string(), &*hash)?; + } else { + return Err(Error::BadRequest( + ErrorKind::InvalidParam, + "Password does not meet the requirements.", + )); + } + Ok(()) + } + /// Returns the displayname of a user on this homeserver. pub fn displayname(&self, user_id: &UserId) -> Result> { self.userid_displayname diff --git a/src/main.rs b/src/main.rs index a6c1afc..1147572 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,6 +29,7 @@ fn setup_rocket() -> rocket::Rocket { client_server::get_login_route, client_server::login_route, client_server::logout_route, + client_server::change_password_route, client_server::get_capabilities_route, client_server::get_pushrules_all_route, client_server::set_pushrule_route,