tuwunel_service/users/
server_user.rs1use tuwunel_core::{
2 Err, Result,
3 config::ServerUserLocalpart,
4 err, implement,
5 utils::{BoolExt, result::NotFound},
6 warn,
7};
8use tuwunel_database::Deserialized;
9
10pub const SERVER_USER_KEY: &[u8] = b"server_user_localpart";
14
15#[implement(super::Service)]
20#[tracing::instrument(level = "debug", skip_all)]
21pub async fn validate_server_user(&self) -> Result {
22 let config = &self.services.server.config;
23 let configured = config.server_user_localpart.as_str();
24
25 let established = self.services.db["global"]
26 .get(SERVER_USER_KEY)
27 .await
28 .deserialized::<ServerUserLocalpart>()
29 .optional()?;
30
31 match established {
32 | None => self.backfill_server_user(configured).await,
33 | Some(established) if established == configured => Ok(()),
34 | Some(established) => Err!(Database(
35 "server_user_localpart is {configured} but this database established {established}; \
36 changing it after first boot is unsupported"
37 )),
38 }
39}
40
41#[implement(super::Service)]
49#[tracing::instrument(level = "debug", skip_all)]
50async fn backfill_server_user(&self, configured: &str) -> Result {
51 let established = match self.admin_room_joined().await? {
52 | Some(joined) => joined,
53 | None => self.is_empty().await?,
54 };
55
56 let legacy = configured == "conduit";
57
58 if !established {
59 return BoolExt::ok_or_else(legacy, || {
60 err!(Database(
61 "server_user_localpart is {configured} but neither the admin room nor an empty \
62 user table establishes that identity; changing it after first boot is \
63 unsupported"
64 ))
65 });
66 }
67
68 if !self.services.globals.is_read_only() {
69 self.services.db["global"].insert(SERVER_USER_KEY, configured);
70 }
71
72 Ok(())
73}
74
75#[implement(super::Service)]
80#[tracing::instrument(level = "debug", skip_all)]
81async fn admin_room_joined(&self) -> Result<Option<bool>> {
82 let services = &self.services;
83
84 let Some(room_id) = services
85 .alias
86 .resolve_local_alias(&services.admin.admin_alias)
87 .await
88 .optional()?
89 else {
90 return Ok(None);
91 };
92
93 let server_user = services.globals.server_user.as_ref();
94 let joined = services
95 .state_cache
96 .is_joined(server_user, &room_id)
97 .await;
98
99 if !joined {
100 warn!(
101 %room_id,
102 %server_user,
103 "The admin room does not include the server user; admin commands are unavailable \
104 until it does"
105 );
106 }
107
108 Ok(Some(joined))
109}