Skip to main content

tuwunel_service/emergency/
mod.rs

1use 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		// Once the option is removed, the server user must be signed out again. That
32		// only has work to do while a password from an earlier start remains.
33		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/// Whether the server user still holds a password.
56///
57/// Only an earlier start with `emergency_password` set gives it one.
58#[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/// Sets or removes the server user's emergency access.
68///
69/// A given password is set along with the default push rules. Without one, the
70/// push rules are cleared and the account is signed out and deactivated.
71#[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	// The password marks the account for cleanup on a later start, so on
77	// removal only `deactivate_account`, the last step, clears it.
78	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	// Before the password is cleared, so an interrupted revocation retries next start.
111	self.services
112		.oauth
113		.revoke_user_tokens(server_user)
114		.await;
115
116	// Never refused: the last-admin check does not count the server user.
117	self.services
118		.users
119		.deactivate_account(server_user)
120		.await
121}