// SPDX-License-Identifier: AGPL-3.0-or-later use crate::{ acl, api::{ audit::SearchAuditLogsParams, client::{AdminApiClient, ApiResultExt}, types::AccountIdentityMode, }, config::AdminConfig, templates::{ components::audit_logs_for_target::audit_logs_for_target, pages::user_detail_tabs as tabs, }, }; pub struct TabQuery { pub reports_sent_page: Option, pub reports_received_page: Option, pub reports_limit: Option, pub dm_before: Option, pub dm_after: Option, pub dm_limit: Option, pub audit_logs_page: Option, pub message_shred_job_id: Option, pub delete_all_messages_dry_run: Option, pub delete_all_messages_channel_count: Option, pub delete_all_messages_message_count: Option, } #[allow(clippy::too_many_arguments)] pub async fn render( client: &AdminApiClient, config: &AdminConfig, csrf_token: &str, user_id: &str, tab: &str, query: &TabQuery, admin_acls: &[String], account_identity: AccountIdentityMode, ) -> Option { match tab { "overview" => { let u = client .get_user_by_id(user_id) .await .log_error("load user overview")?; let change_log = if acl::has_permission(admin_acls, acl::USER_VIEW_CONTACT_LOG) { client .list_user_change_log(user_id, Some(50)) .await .log_error("load user change log") } else { None }; let limit_config = client .get_limit_config() .await .log_error("load limit config for user overview"); Some(tabs::overview::overview_tab_with_limit_config( config, &u, admin_acls, csrf_token, change_log.as_ref(), limit_config.as_ref(), account_identity.is_username(), )) } "account" => { render_account( client, config, csrf_token, user_id, &tabs::account::AccountTabOptions { admin_acls, account_identity, password_reset_link: None, }, ) .await } "moderation" => { let u = client .get_user_by_id(user_id) .await .log_error("load user moderation")?; let message_shred_status = if let Some(job_id) = query .message_shred_job_id .as_deref() .filter(|job_id| !job_id.trim().is_empty()) { Some(client.get_message_shred_status(job_id).await) } else { None }; let delete_all_messages_dry_run = parse_bool_flag( query .delete_all_messages_dry_run .as_deref() .unwrap_or_default(), ) .unwrap_or(false) .then_some(( query.delete_all_messages_channel_count.unwrap_or(0), query.delete_all_messages_message_count.unwrap_or(0), )); let deletion_scheduler = match u.deletion_scheduled_by.as_deref() { Some(scheduler_id) if u.pending_deletion_at.is_some() && scheduler_id != u.id => { client .get_user_by_id(scheduler_id) .await .log_error("load deletion scheduler") } _ => None, }; let ban_logs = if u.temp_banned_until.is_some() && acl::has_permission(admin_acls, acl::AUDIT_LOG_VIEW) { client .search_audit_logs(&SearchAuditLogsParams { query: None, admin_user_id: None, target_id: Some(user_id.to_owned()), target_type: Some("user".to_owned()), access: Some("write".to_owned()), sort_by: Some("created_at".to_owned()), sort_order: Some("desc".to_owned()), limit: 100, offset: 0, }) .await .log_error("load ban audit logs") .map(|response| response.logs) .unwrap_or_default() } else { Vec::new() }; let context = tabs::moderation::ModerationContext { deletion_scheduler: deletion_scheduler.as_ref(), current_ban: tabs::moderation::find_current_ban(&u, &ban_logs), username_sign_in: account_identity.is_username(), }; Some(tabs::moderation::moderation_tab( config, &u, csrf_token, admin_acls, &context, query.message_shred_job_id.as_deref(), message_shred_status.as_ref(), delete_all_messages_dry_run, )) } "applications" => { if !acl::has_permission(admin_acls, acl::APPLICATION_LIST_BY_OWNER) { return None; } let apps = client .list_user_applications(user_id) .await .map_err(|error| tracing::warn!(%error, user_id, "admin API request failed: list user applications")) .unwrap_or_default(); Some(tabs::applications::applications_tab( config, &apps, "No applications owned", "This user does not own any applications.", )) } "archives" => { if !acl::has_any_permission( admin_acls, &[acl::ARCHIVE_VIEW_ALL, acl::ARCHIVE_TRIGGER_USER], ) { return None; } let a = client .list_archives("user", Some(user_id), false, None) .await; Some(tabs::archives::archives_tab( config, user_id, &a.map_err(|error| tracing::warn!(%error, user_id, "admin API request failed: list user archives")) .map(|r| r.archives) .unwrap_or_default(), csrf_token, )) } "guilds" => { let g = client .get_user_guilds(user_id, Some(200), None, None, Some(true)) .await .map_err(|error| tracing::warn!(%error, user_id, "admin API request failed: list user guilds")) .unwrap_or_default(); Some(tabs::guilds::guilds_tab(config, user_id, &g)) } "reports" => { let limit = query.reports_limit.unwrap_or(25); let sent_page = query.reports_sent_page.unwrap_or(0); let received_page = query.reports_received_page.unwrap_or(0); let sent = client .search_reports_by_reporter(user_id, limit, u64::from(sent_page) * u64::from(limit)) .await .log_error("load reports sent by user"); let received = client .search_reports_by_reported_user( user_id, limit, u64::from(received_page) * u64::from(limit), ) .await .log_error("load reports against user"); Some(tabs::reports::reports_tab( config, user_id, sent.as_ref(), received.as_ref(), sent_page, received_page, limit, )) } "relationships" => { if !acl::has_permission(admin_acls, acl::USER_LIST_RELATIONSHIPS) { return None; } let d = client .list_user_relationships(user_id) .await .log_error("load user relationships")?; Some(tabs::relationships::relationships_tab( config, user_id, &d, acl::has_permission(admin_acls, acl::USER_REMOVE_RELATIONSHIP), csrf_token, )) } "dm-history" | "dm_history" => { let limit = query.dm_limit.unwrap_or(50); let before = query.dm_before.as_deref(); let after = query.dm_after.as_deref(); let ch = client .list_user_dm_channels(user_id, before, after, Some(limit)) .await .map(|r| r.channels) .map_err(|error| tracing::warn!(%error, user_id, "admin API request failed: list user DM channels")) .unwrap_or_default(); Some(tabs::dm_history::dm_history_tab( config, user_id, &ch, before, after, limit as usize, )) } "group-dm" | "group_dms" => { let ch = client .list_user_group_dm_channels(user_id) .await .map(|r| r.channels) .map_err(|error| tracing::warn!(%error, user_id, "admin API request failed: list user group DM channels")) .unwrap_or_default(); Some(tabs::group_dm::group_dm_tab(config, user_id, &ch)) } "audit_logs" | "audit-logs" => { if !acl::has_permission(admin_acls, acl::AUDIT_LOG_VIEW) { return None; } let page = query.audit_logs_page.unwrap_or(0); let limit = 50; let resp = client .search_audit_logs(&SearchAuditLogsParams { query: None, admin_user_id: None, target_id: Some(user_id.to_owned()), target_type: None, access: Some("write".to_owned()), sort_by: Some("created_at".to_owned()), sort_order: Some("desc".to_owned()), limit, offset: u64::from(page) * u64::from(limit), }) .await .log_error("load user admin audit logs")?; Some(audit_logs_for_target( &config.base_path, &resp.logs, user_id, page, resp.total, &format!("{}/users/{}?tab=audit_logs", config.base_path, user_id), )) } _ => None, } } pub async fn render_account( client: &AdminApiClient, config: &AdminConfig, csrf_token: &str, user_id: &str, options: &tabs::account::AccountTabOptions<'_>, ) -> Option { let u = client .get_user_by_id(user_id) .await .log_error("load user account")?; let s = client .list_user_sessions(user_id) .await .map(|r| r.sessions) .map_err( |error| tracing::warn!(%error, user_id, "admin API request failed: list user sessions"), ) .unwrap_or_default(); let webauthn_credentials = client .list_webauthn_credentials(user_id) .await .map_err(|error| tracing::warn!(%error, user_id, "admin API request failed: list webauthn credentials")) .unwrap_or_default(); Some(tabs::account::account_tab( config, &u, &s, &webauthn_credentials, csrf_token, options, )) } fn parse_bool_flag(value: &str) -> Option { match value.trim().to_ascii_lowercase().as_str() { "1" | "true" => Some(true), "0" | "false" => Some(false), _ => None, } }