Skip to main content

shared/settings/
app.rs

1use crate::prelude::StringExt;
2
3use super::{
4    ExtensionSettings, SettingsDeserializeExt, SettingsDeserializer, SettingsSerializeExt,
5    SettingsSerializer,
6};
7use compact_str::ToCompactString;
8use serde::{Deserialize, Serialize};
9use utoipa::ToSchema;
10
11#[derive(ToSchema, Serialize, Deserialize, Clone, Copy)]
12#[serde(rename_all = "snake_case")]
13pub enum TwoFactorRequirement {
14    Admins,
15    AllUsers,
16    None,
17}
18
19#[derive(ToSchema, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
20#[serde(rename_all = "snake_case")]
21pub enum TwoFactorMethod {
22    Totp,
23    SecurityKey,
24    Email,
25}
26
27impl TwoFactorMethod {
28    pub const ALL: &'static [TwoFactorMethod] = &[
29        TwoFactorMethod::Totp,
30        TwoFactorMethod::SecurityKey,
31        TwoFactorMethod::Email,
32    ];
33    pub const DEFAULT_ACCEPTED: &'static [TwoFactorMethod] =
34        &[TwoFactorMethod::Totp, TwoFactorMethod::SecurityKey];
35}
36
37#[derive(Clone, ToSchema, Serialize, Deserialize)]
38pub struct AppSettingsApp {
39    pub name: compact_str::CompactString,
40    pub icon: compact_str::CompactString,
41    pub icon_light: Option<compact_str::CompactString>,
42    pub banner: Option<compact_str::CompactString>,
43    pub banner_light: Option<compact_str::CompactString>,
44    pub url: compact_str::CompactString,
45    pub language: compact_str::CompactString,
46    pub two_factor_requirement: TwoFactorRequirement,
47    pub email_two_factor_enabled: bool,
48    pub two_factor_accepted_methods: Vec<TwoFactorMethod>,
49
50    pub email_verification_required: bool,
51
52    pub session_cookie: compact_str::CompactString,
53    pub session_duration_seconds: u64,
54
55    pub telemetry_enabled: bool,
56    pub registration_enabled: bool,
57}
58
59#[async_trait::async_trait]
60impl SettingsSerializeExt for AppSettingsApp {
61    async fn serialize(
62        &self,
63        serializer: SettingsSerializer,
64    ) -> Result<SettingsSerializer, anyhow::Error> {
65        Ok(serializer
66            .write_raw_setting("name", &*self.name)
67            .write_raw_setting("icon", &*self.icon)
68            .write_raw_setting("icon_light", self.icon_light.as_deref().unwrap_or(""))
69            .write_raw_setting("banner", self.banner.as_deref().unwrap_or(""))
70            .write_raw_setting("banner_light", self.banner_light.as_deref().unwrap_or(""))
71            .write_raw_setting("url", &*self.url)
72            .write_raw_setting("language", &*self.language)
73            .write_raw_setting(
74                "two_factor_requirement",
75                match self.two_factor_requirement {
76                    TwoFactorRequirement::Admins => "admins",
77                    TwoFactorRequirement::AllUsers => "all_users",
78                    TwoFactorRequirement::None => "none",
79                },
80            )
81            .write_raw_setting(
82                "email_two_factor_enabled",
83                self.email_two_factor_enabled.to_compact_string(),
84            )
85            .write_serde_setting(
86                "two_factor_accepted_methods",
87                &self.two_factor_accepted_methods,
88            )?
89            .write_raw_setting(
90                "email_verification_required",
91                self.email_verification_required.to_compact_string(),
92            )
93            .write_raw_setting("session_cookie", &*self.session_cookie)
94            .write_raw_setting(
95                "session_duration_seconds",
96                self.session_duration_seconds.to_compact_string(),
97            )
98            .write_raw_setting(
99                "telemetry_enabled",
100                self.telemetry_enabled.to_compact_string(),
101            )
102            .write_raw_setting(
103                "registration_enabled",
104                self.registration_enabled.to_compact_string(),
105            ))
106    }
107}
108
109pub struct AppSettingsAppDeserializer;
110
111#[async_trait::async_trait]
112impl SettingsDeserializeExt for AppSettingsAppDeserializer {
113    async fn deserialize_boxed(
114        &self,
115        mut deserializer: SettingsDeserializer<'_>,
116    ) -> Result<ExtensionSettings, anyhow::Error> {
117        Ok(Box::new(AppSettingsApp {
118            name: deserializer
119                .take_raw_setting("name")
120                .unwrap_or_else(|| "Calagopus".into()),
121            icon: deserializer
122                .take_raw_setting("icon")
123                .unwrap_or_else(|| "/icon.svg".into()),
124            icon_light: deserializer
125                .take_raw_setting("icon_light")
126                .and_then(|s| s.into_optional()),
127            banner: deserializer
128                .take_raw_setting("banner")
129                .and_then(|s| s.into_optional()),
130            banner_light: deserializer
131                .take_raw_setting("banner_light")
132                .and_then(|s| s.into_optional()),
133            url: deserializer
134                .take_raw_setting("url")
135                .unwrap_or_else(|| "http://localhost:8000".into()),
136            language: deserializer
137                .take_raw_setting("language")
138                .unwrap_or_else(|| "en".into()),
139            two_factor_requirement: match deserializer
140                .take_raw_setting("two_factor_requirement")
141                .as_deref()
142            {
143                Some("admins") => TwoFactorRequirement::Admins,
144                Some("all_users") => TwoFactorRequirement::AllUsers,
145                _ => TwoFactorRequirement::None,
146            },
147            email_two_factor_enabled: deserializer
148                .take_raw_setting("email_two_factor_enabled")
149                .map(|s| s == "true")
150                .unwrap_or(false),
151            two_factor_accepted_methods: deserializer
152                .read_serde_setting("two_factor_accepted_methods")
153                .unwrap_or_else(|_| TwoFactorMethod::DEFAULT_ACCEPTED.to_vec()),
154            email_verification_required: deserializer
155                .take_raw_setting("email_verification_required")
156                .map(|s| s == "true")
157                .unwrap_or(false),
158            session_cookie: deserializer
159                .take_raw_setting("session_cookie")
160                .unwrap_or_else(|| "calagopus_session".into()),
161            session_duration_seconds: deserializer
162                .take_raw_setting("session_duration_seconds")
163                .and_then(|s| s.parse::<u64>().ok())
164                .unwrap_or(7 * 24 * 3600),
165            telemetry_enabled: deserializer
166                .take_raw_setting("telemetry_enabled")
167                .map(|s| s == "true")
168                .unwrap_or(true),
169            registration_enabled: deserializer
170                .take_raw_setting("registration_enabled")
171                .map(|s| s == "true")
172                .unwrap_or(true),
173        }))
174    }
175}