1
0
Fork 0
mirror of https://forgejo.ellis.link/continuwuation/continuwuity.git synced 2025-08-01 12:48:31 +00:00
continuwuity/src/service/uiaa/mod.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

150 lines
4 KiB
Rust
Raw Normal View History

mod data;
2022-10-08 13:04:55 +02:00
use std::sync::Arc;
use argon2::{PasswordHash, PasswordVerifier};
use conduit::{utils, Error, Result};
use data::Data;
2022-10-05 20:34:31 +02:00
use ruma::{
api::client::{
error::ErrorKind,
2022-12-14 13:09:10 +01:00
uiaa::{AuthData, AuthType, Password, UiaaInfo, UserIdentifier},
2022-10-05 20:34:31 +02:00
},
2022-10-09 17:25:06 +02:00
CanonicalJsonValue, DeviceId, UserId,
2022-10-05 20:34:31 +02:00
};
use tracing::error;
2021-06-08 18:10:00 +02:00
use crate::services;
pub const SESSION_ID_LENGTH: usize = 32;
2020-06-06 18:44:50 +02:00
pub struct Service {
pub(super) db: Arc<dyn Data>,
2020-06-06 18:44:50 +02:00
}
2022-10-05 12:45:54 +02:00
impl Service {
2020-06-06 18:44:50 +02:00
/// Creates a new Uiaa session. Make sure the session token is unique.
pub fn create(
&self, user_id: &UserId, device_id: &DeviceId, uiaainfo: &UiaaInfo, json_body: &CanonicalJsonValue,
2020-07-25 14:25:24 -04:00
) -> Result<()> {
2022-10-05 20:33:55 +02:00
self.db.set_uiaa_request(
user_id,
device_id,
uiaainfo.session.as_ref().expect("session should be set"), /* TODO: better session error handling (why
* is it optional in ruma?) */
json_body,
)?;
2022-10-05 20:33:55 +02:00
self.db.update_uiaa_session(
user_id,
device_id,
uiaainfo.session.as_ref().expect("session should be set"),
Some(uiaainfo),
)
2020-06-06 18:44:50 +02:00
}
pub fn try_auth(
2020-07-25 14:25:24 -04:00
&self, user_id: &UserId, device_id: &DeviceId, auth: &AuthData, uiaainfo: &UiaaInfo,
2020-06-06 18:44:50 +02:00
) -> Result<(bool, UiaaInfo)> {
let mut uiaainfo = auth.session().map_or_else(
|| Ok(uiaainfo.clone()),
|session| self.db.get_uiaa_session(user_id, device_id, session),
)?;
if uiaainfo.session.is_none() {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
}
match auth {
2020-06-06 18:44:50 +02:00
// Find out what the user completed
2022-12-14 13:09:10 +01:00
AuthData::Password(Password {
identifier,
password,
..
}) => {
let UserIdentifier::UserIdOrLocalpart(username) = identifier else {
return Err(Error::BadRequest(ErrorKind::Unrecognized, "Identifier type not recognized."));
};
2022-10-05 20:34:31 +02:00
let user_id = UserId::parse_with_server_name(username.clone(), services().globals.server_name())
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "User ID is invalid."))?;
// Check if password is correct
2022-09-07 13:25:51 +02:00
if let Some(hash) = services().users.password_hash(&user_id)? {
let hash_matches = services()
.globals
.argon
.verify_password(
password.as_bytes(),
&PasswordHash::new(&hash).expect("valid hash in database"),
)
.is_ok();
if !hash_matches {
2022-12-14 13:09:10 +01:00
uiaainfo.auth_error = Some(ruma::api::client::error::StandardErrorBody {
kind: ErrorKind::forbidden(),
message: "Invalid username or password.".to_owned(),
});
return Ok((false, uiaainfo));
}
2020-06-06 18:44:50 +02:00
}
// Password was correct! Let's add it to `completed`
uiaainfo.completed.push(AuthType::Password);
},
2023-08-09 18:27:30 +02:00
AuthData::RegistrationToken(t) => {
if Some(t.token.trim()) == services().globals.config.registration_token.as_deref() {
uiaainfo.completed.push(AuthType::RegistrationToken);
} else {
uiaainfo.auth_error = Some(ruma::api::client::error::StandardErrorBody {
kind: ErrorKind::forbidden(),
2023-08-09 18:27:30 +02:00
message: "Invalid registration token.".to_owned(),
});
return Ok((false, uiaainfo));
}
},
2022-12-14 13:09:10 +01:00
AuthData::Dummy(_) => {
uiaainfo.completed.push(AuthType::Dummy);
2020-06-06 18:44:50 +02:00
},
k => error!("type not supported: {:?}", k),
}
// Check if a flow now succeeds
let mut completed = false;
'flows: for flow in &mut uiaainfo.flows {
for stage in &flow.stages {
if !uiaainfo.completed.contains(stage) {
continue 'flows;
}
}
// We didn't break, so this flow succeeded!
completed = true;
}
if !completed {
2022-10-05 20:33:55 +02:00
self.db.update_uiaa_session(
user_id,
device_id,
uiaainfo.session.as_ref().expect("session is always set"),
Some(&uiaainfo),
)?;
return Ok((false, uiaainfo));
2020-06-06 18:44:50 +02:00
}
// UIAA was successful! Remove this session and return true
2022-10-05 20:33:55 +02:00
self.db.update_uiaa_session(
user_id,
device_id,
uiaainfo.session.as_ref().expect("session is always set"),
None,
)?;
Ok((true, uiaainfo))
2020-06-06 18:44:50 +02:00
}
#[must_use]
pub fn get_uiaa_request(
&self, user_id: &UserId, device_id: &DeviceId, session: &str,
2022-01-19 23:56:55 +01:00
) -> Option<CanonicalJsonValue> {
self.db.get_uiaa_request(user_id, device_id, session)
}
2020-06-06 18:44:50 +02:00
}