mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-11-13 04:06:54 +00:00
230 lines
7.7 KiB
Rust
230 lines
7.7 KiB
Rust
pub(super) mod account;
|
|
pub(super) mod dehydrated_device;
|
|
pub(super) mod device;
|
|
pub(super) mod filters;
|
|
pub(super) mod keys;
|
|
pub(super) mod profile;
|
|
|
|
use std::{mem, sync::Arc};
|
|
|
|
pub use account::AccessTokenStatus;
|
|
use conduwuit::{
|
|
Err, Error, Result, err,
|
|
utils::{self},
|
|
};
|
|
use database::Map;
|
|
use ruma::{UserId, api::error::ErrorKind, encryption::CrossSigningKey, serde::Raw};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{
|
|
Dep, account_data, admin,
|
|
appservice,
|
|
config, firstrun, globals, oauth,
|
|
rooms::{self, alias, membership},
|
|
threepid,
|
|
};
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct UserSuspension {
|
|
/// Whether the user is currently suspended
|
|
pub suspended: bool,
|
|
/// When the user was suspended (Unix timestamp in milliseconds)
|
|
pub suspended_at: u64,
|
|
/// User ID of who suspended this user
|
|
pub suspended_by: String,
|
|
}
|
|
|
|
/// A password hash. This is only for use when setting a user's password,
|
|
/// if the hash needs to be kept around for a while without keeping the password
|
|
/// in memory.
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct HashedPassword(String);
|
|
|
|
impl HashedPassword {
|
|
pub fn new(password: &str) -> Result<Self> {
|
|
Ok(Self(utils::hash::password(password).map_err(|e| {
|
|
err!(Request(InvalidParam("Password does not meet the requirements: {e}")))
|
|
})?))
|
|
}
|
|
}
|
|
|
|
pub struct Service {
|
|
services: Services,
|
|
db: Data,
|
|
}
|
|
|
|
struct Services {
|
|
account_data: Dep<account_data::Service>,
|
|
admin: Dep<admin::Service>,
|
|
alias: Dep<alias::Service>,
|
|
appservice: Dep<appservice::Service>,
|
|
config: Dep<config::Service>,
|
|
firstrun: Dep<firstrun::Service>,
|
|
globals: Dep<globals::Service>,
|
|
membership: Dep<membership::Service>,
|
|
oauth: Dep<oauth::Service>,
|
|
state_accessor: Dep<rooms::state_accessor::Service>,
|
|
state_cache: Dep<rooms::state_cache::Service>,
|
|
threepid: Dep<threepid::Service>,
|
|
}
|
|
|
|
struct Data {
|
|
keychangeid_userid: Arc<Map>,
|
|
keyid_key: Arc<Map>,
|
|
onetimekeyid_onetimekeys: Arc<Map>,
|
|
fallbackkeyid_fallbackkey: Arc<Map>,
|
|
openidtoken_expiresatuserid: Arc<Map>,
|
|
logintoken_expiresatuserid: Arc<Map>,
|
|
todeviceid_events: Arc<Map>,
|
|
token_userdeviceid: Arc<Map>,
|
|
userdeviceid_tokenexpires: Arc<Map>,
|
|
userdeviceid_metadata: Arc<Map>,
|
|
userdeviceid_token: Arc<Map>,
|
|
userfilterid_filter: Arc<Map>,
|
|
userid_avatarurl: Arc<Map>,
|
|
userid_dehydrateddevice: Arc<Map>,
|
|
userid_devicelistversion: Arc<Map>,
|
|
userid_displayname: Arc<Map>,
|
|
userid_lastonetimekeyupdate: Arc<Map>,
|
|
userid_masterkeyid: Arc<Map>,
|
|
userid_password: Arc<Map>,
|
|
userid_suspension: Arc<Map>,
|
|
userid_lock: Arc<Map>,
|
|
userid_logindisabled: Arc<Map>,
|
|
userid_selfsigningkeyid: Arc<Map>,
|
|
userid_usersigningkeyid: Arc<Map>,
|
|
useridprofilekey_value: Arc<Map>,
|
|
}
|
|
|
|
impl crate::Service for Service {
|
|
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
|
Ok(Arc::new(Self {
|
|
services: Services {
|
|
account_data: args.depend::<account_data::Service>("account_data"),
|
|
admin: args.depend::<admin::Service>("admin"),
|
|
alias: args.depend::<alias::Service>("alias"),
|
|
appservice: args.depend::<appservice::Service>("appservice"),
|
|
config: args.depend::<config::Service>("config"),
|
|
firstrun: args.depend::<firstrun::Service>("firstrun"),
|
|
globals: args.depend::<globals::Service>("globals"),
|
|
membership: args.depend::<membership::Service>("membership"),
|
|
oauth: args.depend::<oauth::Service>("oauth"),
|
|
state_accessor: args
|
|
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
|
|
state_cache: args.depend::<rooms::state_cache::Service>("rooms::state_cache"),
|
|
threepid: args.depend::<threepid::Service>("threepid"),
|
|
},
|
|
db: Data {
|
|
keychangeid_userid: args.db["keychangeid_userid"].clone(),
|
|
keyid_key: args.db["keyid_key"].clone(),
|
|
onetimekeyid_onetimekeys: args.db["onetimekeyid_onetimekeys"].clone(),
|
|
fallbackkeyid_fallbackkey: args.db["fallbackkeyid_fallbackkey"].clone(),
|
|
openidtoken_expiresatuserid: args.db["openidtoken_expiresatuserid"].clone(),
|
|
logintoken_expiresatuserid: args.db["logintoken_expiresatuserid"].clone(),
|
|
todeviceid_events: args.db["todeviceid_events"].clone(),
|
|
token_userdeviceid: args.db["token_userdeviceid"].clone(),
|
|
userdeviceid_metadata: args.db["userdeviceid_metadata"].clone(),
|
|
userdeviceid_token: args.db["userdeviceid_token"].clone(),
|
|
userfilterid_filter: args.db["userfilterid_filter"].clone(),
|
|
userid_avatarurl: args.db["userid_avatarurl"].clone(),
|
|
userid_dehydrateddevice: args.db["userid_dehydrateddevice"].clone(),
|
|
userid_devicelistversion: args.db["userid_devicelistversion"].clone(),
|
|
userid_displayname: args.db["userid_displayname"].clone(),
|
|
userid_lastonetimekeyupdate: args.db["userid_lastonetimekeyupdate"].clone(),
|
|
userid_masterkeyid: args.db["userid_masterkeyid"].clone(),
|
|
userid_password: args.db["userid_password"].clone(),
|
|
userid_suspension: args.db["userid_suspension"].clone(),
|
|
userid_lock: args.db["userid_lock"].clone(),
|
|
userid_logindisabled: args.db["userid_logindisabled"].clone(),
|
|
userid_selfsigningkeyid: args.db["userid_selfsigningkeyid"].clone(),
|
|
userid_usersigningkeyid: args.db["userid_usersigningkeyid"].clone(),
|
|
useridprofilekey_value: args.db["useridprofilekey_value"].clone(),
|
|
userdeviceid_tokenexpires: args.db["userdeviceid_tokenexpires"].clone(),
|
|
},
|
|
}))
|
|
}
|
|
|
|
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
|
}
|
|
|
|
pub fn parse_master_key(
|
|
user_id: &UserId,
|
|
master_key: &Raw<CrossSigningKey>,
|
|
) -> Result<(Vec<u8>, CrossSigningKey)> {
|
|
let mut prefix = user_id.as_bytes().to_vec();
|
|
prefix.push(0xFF);
|
|
|
|
let master_key = master_key
|
|
.deserialize()
|
|
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid master key"))?;
|
|
let mut master_key_ids = master_key.keys.values();
|
|
let master_key_id = master_key_ids
|
|
.next()
|
|
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Master key contained no key."))?;
|
|
if master_key_ids.next().is_some() {
|
|
return Err(Error::BadRequest(
|
|
ErrorKind::InvalidParam,
|
|
"Master key contained more than one key.",
|
|
));
|
|
}
|
|
let mut master_key_key = prefix.clone();
|
|
master_key_key.extend_from_slice(master_key_id.as_bytes());
|
|
Ok((master_key_key, master_key))
|
|
}
|
|
|
|
pub fn parse_user_signing_key(user_signing_key: &Raw<CrossSigningKey>) -> Result<String> {
|
|
let mut user_signing_key_ids = user_signing_key
|
|
.deserialize()
|
|
.map_err(|_| err!(Request(InvalidParam("Invalid user signing key"))))?
|
|
.keys
|
|
.into_values();
|
|
|
|
let user_signing_key_id = user_signing_key_ids
|
|
.next()
|
|
.ok_or(err!(Request(InvalidParam("User signing key contained no key."))))?;
|
|
|
|
if user_signing_key_ids.next().is_some() {
|
|
return Err!(Request(InvalidParam("User signing key contained more than one key.")));
|
|
}
|
|
|
|
Ok(user_signing_key_id)
|
|
}
|
|
|
|
/// Ensure that a user only sees signatures from themselves and the target user
|
|
fn clean_signatures<F>(
|
|
mut cross_signing_key: serde_json::Value,
|
|
sender_user: Option<&UserId>,
|
|
user_id: &UserId,
|
|
allowed_signatures: &F,
|
|
) -> Result<serde_json::Value>
|
|
where
|
|
F: Fn(&UserId) -> bool + Send + Sync,
|
|
{
|
|
if let Some(signatures) = cross_signing_key
|
|
.get_mut("signatures")
|
|
.and_then(|v| v.as_object_mut())
|
|
{
|
|
// Don't allocate for the full size of the current signatures, but require
|
|
// at most one resize if nothing is dropped
|
|
let new_capacity = signatures.len() / 2;
|
|
for (user, signature) in
|
|
mem::replace(signatures, serde_json::Map::with_capacity(new_capacity))
|
|
{
|
|
let sid = <&UserId>::try_from(user.as_str())
|
|
.map_err(|_| Error::bad_database("Invalid user ID in database."))?;
|
|
if sender_user == Some(user_id) || sid == user_id || allowed_signatures(sid) {
|
|
signatures.insert(user, signature);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(cross_signing_key)
|
|
}
|
|
|
|
//TODO: this is an ABA
|
|
fn increment(db: &Arc<Map>, key: &[u8]) {
|
|
let old = db.get_blocking(key);
|
|
let new = utils::increment(old.ok().as_deref());
|
|
db.insert(key, new);
|
|
}
|