2022-08-14 13:38:21 +02:00
|
|
|
mod data;
|
2022-10-08 13:04:55 +02:00
|
|
|
|
2024-05-09 15:59:08 -07:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
2023-12-25 16:28:56 +01:00
|
|
|
use argon2::{PasswordHash, PasswordVerifier};
|
2024-05-09 15:59:08 -07:00
|
|
|
use conduit::{utils, Error, Result};
|
2024-05-26 21:29:19 +00:00
|
|
|
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
|
|
|
};
|
2022-09-06 23:15:09 +02:00
|
|
|
use tracing::error;
|
2021-06-08 18:10:00 +02:00
|
|
|
|
2024-05-09 15:59:08 -07:00
|
|
|
use crate::services;
|
|
|
|
|
|
|
|
pub const SESSION_ID_LENGTH: usize = 32;
|
2020-06-06 18:44:50 +02:00
|
|
|
|
2024-05-09 15:59:08 -07:00
|
|
|
pub struct Service {
|
2024-05-26 21:29:19 +00:00
|
|
|
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.
|
2024-05-09 15:59:08 -07:00
|
|
|
pub fn create(
|
2021-05-04 19:03:18 +02:00
|
|
|
&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(
|
2021-05-04 19:03:18 +02:00
|
|
|
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(
|
2021-05-04 19:03:18 +02:00
|
|
|
user_id,
|
|
|
|
device_id,
|
|
|
|
uiaainfo.session.as_ref().expect("session should be set"),
|
|
|
|
Some(uiaainfo),
|
|
|
|
)
|
2020-06-06 18:44:50 +02:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2024-05-09 15:59:08 -07: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)> {
|
2024-03-02 20:55:02 -05:00
|
|
|
let mut uiaainfo = auth.session().map_or_else(
|
|
|
|
|| Ok(uiaainfo.clone()),
|
|
|
|
|session| self.db.get_uiaa_session(user_id, device_id, session),
|
|
|
|
)?;
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2021-08-19 11:01:18 +02:00
|
|
|
if uiaainfo.session.is_none() {
|
|
|
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2021-08-19 11:01:18 +02:00
|
|
|
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 {
|
2021-08-19 11:01:18 +02:00
|
|
|
identifier,
|
|
|
|
password,
|
|
|
|
..
|
|
|
|
}) => {
|
2024-03-02 20:55:02 -05:00
|
|
|
let UserIdentifier::UserIdOrLocalpart(username) = identifier else {
|
|
|
|
return Err(Error::BadRequest(ErrorKind::Unrecognized, "Identifier type not recognized."));
|
2021-08-19 11:01:18 +02:00
|
|
|
};
|
2024-03-05 19:48:54 -05:00
|
|
|
|
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."))?;
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2021-08-19 11:01:18 +02:00
|
|
|
// Check if password is correct
|
2022-09-07 13:25:51 +02:00
|
|
|
if let Some(hash) = services().users.password_hash(&user_id)? {
|
2023-12-25 16:28:56 +01:00
|
|
|
let hash_matches = services()
|
|
|
|
.globals
|
|
|
|
.argon
|
|
|
|
.verify_password(
|
|
|
|
password.as_bytes(),
|
|
|
|
&PasswordHash::new(&hash).expect("valid hash in database"),
|
|
|
|
)
|
|
|
|
.is_ok();
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2021-08-19 11:01:18 +02:00
|
|
|
if !hash_matches {
|
2022-12-14 13:09:10 +01:00
|
|
|
uiaainfo.auth_error = Some(ruma::api::client::error::StandardErrorBody {
|
2024-04-03 15:59:03 -04:00
|
|
|
kind: ErrorKind::forbidden(),
|
2021-08-19 11:01:18 +02:00
|
|
|
message: "Invalid username or password.".to_owned(),
|
|
|
|
});
|
|
|
|
return Ok((false, uiaainfo));
|
2024-03-05 19:48:54 -05:00
|
|
|
}
|
2020-06-06 18:44:50 +02:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2021-08-19 11:01:18 +02:00
|
|
|
// Password was correct! Let's add it to `completed`
|
2021-10-13 10:16:45 +02:00
|
|
|
uiaainfo.completed.push(AuthType::Password);
|
2021-08-19 11:01:18 +02:00
|
|
|
},
|
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 {
|
2024-04-03 15:59:03 -04:00
|
|
|
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(_) => {
|
2021-10-13 10:16:45 +02:00
|
|
|
uiaainfo.completed.push(AuthType::Dummy);
|
2020-06-06 18:44:50 +02:00
|
|
|
},
|
2021-08-19 11:01:18 +02:00
|
|
|
k => error!("type not supported: {:?}", k),
|
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2021-08-19 11:01:18 +02:00
|
|
|
// 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;
|
2024-03-05 19:48:54 -05:00
|
|
|
}
|
2021-08-19 11:01:18 +02:00
|
|
|
}
|
|
|
|
// We didn't break, so this flow succeeded!
|
|
|
|
completed = true;
|
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2021-08-19 11:01:18 +02:00
|
|
|
if !completed {
|
2022-10-05 20:33:55 +02:00
|
|
|
self.db.update_uiaa_session(
|
2021-05-04 19:03:18 +02:00
|
|
|
user_id,
|
|
|
|
device_id,
|
|
|
|
uiaainfo.session.as_ref().expect("session is always set"),
|
2021-08-19 11:01:18 +02:00
|
|
|
Some(&uiaainfo),
|
2021-05-04 19:03:18 +02:00
|
|
|
)?;
|
2021-08-19 11:01:18 +02:00
|
|
|
return Ok((false, uiaainfo));
|
2020-06-06 18:44:50 +02:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2021-08-19 11:01:18 +02:00
|
|
|
// UIAA was successful! Remove this session and return true
|
2022-10-05 20:33:55 +02:00
|
|
|
self.db.update_uiaa_session(
|
2021-08-19 11:01:18 +02:00
|
|
|
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
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2024-05-09 15:59:08 -07:00
|
|
|
#[must_use]
|
|
|
|
pub fn get_uiaa_request(
|
2021-05-04 19:03:18 +02:00
|
|
|
&self, user_id: &UserId, device_id: &DeviceId, session: &str,
|
2022-01-19 23:56:55 +01:00
|
|
|
) -> Option<CanonicalJsonValue> {
|
2022-08-14 13:38:21 +02:00
|
|
|
self.db.get_uiaa_request(user_id, device_id, session)
|
2021-05-04 19:03:18 +02:00
|
|
|
}
|
2020-06-06 18:44:50 +02:00
|
|
|
}
|