Files
continuwuity/src/web/pages/account/mod.rs
T

174 lines
4.4 KiB
Rust

use axum::{
Extension, Router,
extract::{Query, State},
response::Redirect,
routing::get,
};
use conduwuit_core::utils::{IterStream, ReadyExt, stream::TryExpect};
use conduwuit_service::threepid::EmailRequirement;
use futures::StreamExt;
use ruma::{
OwnedClientSecret, OwnedDeviceId, OwnedSessionId,
api::client::discovery::get_authorization_server_metadata::v1::AccountManagementAction,
};
use serde::{Deserialize, Serialize};
use crate::{
WebError,
extract::Expect,
pages::{
Result, TemplateContext,
components::{DeviceCard, DeviceCardStyle, UserCard},
},
response,
session::{LoginTarget, User},
template,
};
pub(crate) mod cross_signing_reset;
pub(crate) mod deactivate;
pub(crate) mod device;
pub(crate) mod email;
pub(crate) mod login;
pub(crate) mod password;
pub(crate) mod register;
pub(crate) fn build() -> Router<crate::State> {
#[allow(clippy::wildcard_imports)]
use self::*;
Router::new()
.route("/", get(get_account))
.route("/deeplink", get(get_account_deeplink))
.merge(login::build())
.nest("/password/", password::build())
.nest("/email/", email::build())
.nest("/cross_signing_reset", cross_signing_reset::build())
.nest("/deactivate", deactivate::build())
.nest("/device/", device::build())
.nest("/register/", register::build())
}
#[derive(Deserialize, Serialize)]
struct ThreepidQuery {
client_secret: OwnedClientSecret,
session_id: OwnedSessionId,
}
template! {
struct Account use "account.html.j2" {
user_card: UserCard,
body: AccountBody
}
}
#[derive(Debug)]
enum AccountBody {
Unlocked {
suspended: bool,
email_requirement: EmailRequirement,
email: Option<String>,
devices: Vec<DeviceCard>,
},
Locked,
}
async fn get_account(
State(services): State<crate::State>,
Extension(context): Extension<TemplateContext>,
user: User<true>,
) -> Result {
let user_id = user.expect(LoginTarget::Account)?;
let user_card = UserCard::for_local_user(&services, user_id.clone()).await;
if services.users.is_locked(&user_id).await.unwrap() {
return response!(Account::new(context, user_card, AccountBody::Locked));
}
let email_requirement = services.threepid.email_requirement();
let email = services
.threepid
.get_email_for_localpart(user_id.localpart())
.await
.map(|address| address.to_string());
let dehydrated_device_id = services.users.get_dehydrated_device_id(&user_id).await.ok();
let mut devices: Vec<_> = services
.users
.all_device_ids(&user_id)
.then(async |device_id| {
services
.users
.get_device_metadata(&user_id, &device_id)
.await
})
.expect_ok()
.ready_filter(|device| {
dehydrated_device_id
.as_ref()
.is_none_or(|id| device.device_id != *id)
})
.collect()
.await;
devices.sort_unstable_by(|a, b| a.last_seen_ts.cmp(&b.last_seen_ts).reverse());
let device_cards = devices
.into_iter()
.stream()
.then(async |device| {
DeviceCard::for_device(&services, &user_id, device, DeviceCardStyle::Minimal).await
})
.collect()
.await;
let suspended = services.users.is_suspended(&user_id).await.unwrap();
response!(Account::new(context, user_card, AccountBody::Unlocked {
suspended,
email_requirement,
email,
devices: device_cards
}))
}
#[derive(Deserialize)]
struct AccountDeeplinkQuery {
action: Option<AccountManagementAction>,
device_id: Option<OwnedDeviceId>,
}
async fn get_account_deeplink(
Expect(Query(query)): Expect<Query<AccountDeeplinkQuery>>,
) -> Result {
let redirect_target = match query.action.unwrap_or(AccountManagementAction::Profile) {
| AccountManagementAction::AccountDeactivate => "deactivate".to_owned(),
| AccountManagementAction::CrossSigningReset => "cross_signing_reset".to_owned(),
| AccountManagementAction::DeviceDelete => {
let Some(device_id) = query.device_id else {
return response!(WebError::BadRequest(
"A device ID is required for this action".to_owned()
));
};
format!("device/{device_id}/delete")
},
| AccountManagementAction::DeviceView => {
let Some(device_id) = query.device_id else {
return response!(WebError::BadRequest(
"A device ID is required for this action".to_owned()
));
};
format!("device/{device_id}/")
},
| AccountManagementAction::DevicesList => "#devices".to_owned(),
| AccountManagementAction::Profile => String::new(),
| _ => return response!(WebError::BadRequest("Unknown action".to_owned())),
};
response!(Redirect::to(&format!("{}/account/{}", crate::ROUTE_PREFIX, redirect_target)))
}