feat: implement password changing (#138)

Password: Fixes

Password: Implement logging out all devices except current

Password: Implement password changing

Co-authored-by: the0 <theo@localhost>
Reviewed-on: https://git.koesters.xyz/timo/conduit/pulls/138
Reviewed-by: Timo Kösters <timo@koesters.xyz>
next
the0 2020-07-02 20:38:25 +02:00 committed by Timo Kösters
parent e809d819ac
commit 67a1f21f5d
3 changed files with 67 additions and 1 deletions

View File

@ -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 = "<body>")]
pub fn change_password_route(
db: State<'_, Database>,
body: Ruma<change_password::Request>,
) -> ConduitResult<change_password::Response> {
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<get_capabilities::Response> {
let mut available = BTreeMap::new();

View File

@ -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<Option<String>> {
self.userid_displayname

View File

@ -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,