tuwunel_service/emergency/
mod.rs1use std::sync::Arc;
2
3use async_trait::async_trait;
4use ruma::{
5 events::{
6 GlobalAccountDataEvent, GlobalAccountDataEventType, push_rules::PushRulesEventContent,
7 },
8 push::Ruleset,
9};
10use serde_json::to_value;
11use tuwunel_core::{Result, debug_warn, error, implement, warn};
12
13pub struct Service {
14 services: Arc<crate::services::OnceServices>,
15}
16
17#[async_trait]
18impl crate::Service for Service {
19 fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
20 Ok(Arc::new(Self { services: args.services.clone() }))
21 }
22
23 async fn worker(self: Arc<Self>) -> Result {
24 let password = self
25 .services
26 .config
27 .emergency_password
28 .as_deref()
29 .filter(|password| !password.is_empty());
30
31 if password.is_none() && !self.emergency_access_remains().await {
34 return Ok(());
35 }
36
37 if self.services.globals.is_read_only() {
38 debug_warn!("emergency password feature ignored in read_only mode.");
39 return Ok(());
40 }
41
42 if password.is_some() && self.services.config.ldap.enable {
43 warn!("emergency password feature not available with LDAP enabled.");
44 return Ok(());
45 }
46
47 self.set_emergency_access(password)
48 .await
49 .inspect_err(|e| error!(%e, "Failed to update emergency access for the server user"))
50 }
51
52 fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
53}
54
55#[implement(Service)]
59async fn emergency_access_remains(&self) -> bool {
60 self.services
61 .users
62 .has_password(&self.services.globals.server_user)
63 .await
64 .unwrap_or(false)
65}
66
67#[implement(Service)]
72#[tracing::instrument(level = "debug", skip_all)]
73async fn set_emergency_access(&self, password: Option<&str>) -> Result {
74 let server_user = &self.services.globals.server_user;
75
76 if password.is_some() {
79 self.services
80 .users
81 .set_password(server_user, password)
82 .await?;
83 }
84
85 let ruleset = password.map_or_else(Ruleset::new, |_| Ruleset::server_default(server_user));
86 let event_type = GlobalAccountDataEventType::PushRules
87 .to_string()
88 .into();
89
90 let content = to_value(&GlobalAccountDataEvent {
91 content: PushRulesEventContent { global: ruleset },
92 })
93 .expect("to json value always works");
94
95 self.services
96 .account_data
97 .update(None, server_user, event_type, &content)
98 .await?;
99
100 if password.is_some() {
101 warn!(
102 "The server account emergency password is set! Please unset it as soon as you \
103 finish admin account recovery! You will be logged out of the server service \
104 account when you finish."
105 );
106
107 return Ok(());
108 }
109
110 self.services
112 .oauth
113 .revoke_user_tokens(server_user)
114 .await;
115
116 self.services
118 .users
119 .deactivate_account(server_user)
120 .await
121}