Skip to main content

soma_auth/
config_providers.rs

1//! Per-provider OAuth config structs (`GoogleConfig`, `AutheliaConfig`,
2//! `GitHubConfig`) and their default-value helpers, split out of
3//! `config.rs` to keep it under the xtask patterns file-size gate. Declared
4//! in `config.rs` via `#[path = "config_providers.rs"] mod config_providers;`.
5
6use serde::{Deserialize, Serialize};
7use url::Url;
8
9use super::DEFAULT_CALLBACK_PATH;
10
11#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct GoogleConfig {
13    #[serde(default)]
14    pub client_id: String,
15    #[serde(default)]
16    pub client_secret: String,
17    #[serde(default = "default_callback_path")]
18    pub callback_path: String,
19    #[serde(default = "default_google_scopes")]
20    pub scopes: Vec<String>,
21}
22
23// Hand-rolled to match the `#[serde(default = "fn")]` attributes above:
24// `#[derive(Default)]` would give `callback_path`/`scopes` their
25// `String`/`Vec` zero values instead, since serde's per-field `default =`
26// only wires into `Deserialize`, never into `impl Default`. Struct-literal
27// callers using `..GoogleConfig::default()` (or `AuthConfig::default()`,
28// which embeds this) need the same non-empty defaults a deserialized empty
29// config would get, or `AuthConfig::validate()`'s callback-path checks
30// reject them even when Google isn't configured at all.
31impl Default for GoogleConfig {
32    fn default() -> Self {
33        Self {
34            client_id: String::new(),
35            client_secret: String::new(),
36            callback_path: default_callback_path(),
37            scopes: default_google_scopes(),
38        }
39    }
40}
41
42impl std::fmt::Debug for GoogleConfig {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        f.debug_struct("GoogleConfig")
45            .field("client_id", &self.client_id)
46            .field("callback_path", &self.callback_path)
47            .field("scopes", &self.scopes)
48            .finish_non_exhaustive()
49    }
50}
51
52#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct AutheliaConfig {
54    #[serde(default)]
55    pub issuer_url: Option<Url>,
56    #[serde(default)]
57    pub client_id: String,
58    #[serde(default)]
59    pub client_secret: String,
60    #[serde(default = "default_authelia_callback_path")]
61    pub callback_path: String,
62    #[serde(default = "default_authelia_scopes")]
63    pub scopes: Vec<String>,
64}
65
66// See `impl Default for GoogleConfig` above for why this can't be derived.
67impl Default for AutheliaConfig {
68    fn default() -> Self {
69        Self {
70            issuer_url: None,
71            client_id: String::new(),
72            client_secret: String::new(),
73            callback_path: default_authelia_callback_path(),
74            scopes: default_authelia_scopes(),
75        }
76    }
77}
78
79impl std::fmt::Debug for AutheliaConfig {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        f.debug_struct("AutheliaConfig")
82            .field("issuer_url", &self.issuer_url)
83            .field("client_id", &self.client_id)
84            .field("callback_path", &self.callback_path)
85            .field("scopes", &self.scopes)
86            .finish_non_exhaustive()
87    }
88}
89
90#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
91pub struct GitHubConfig {
92    #[serde(default)]
93    pub client_id: String,
94    #[serde(default)]
95    pub client_secret: String,
96    #[serde(default = "default_github_callback_path")]
97    pub callback_path: String,
98    #[serde(default = "default_github_scopes")]
99    pub scopes: Vec<String>,
100}
101
102// See `impl Default for GoogleConfig` above for why this can't be derived.
103impl Default for GitHubConfig {
104    fn default() -> Self {
105        Self {
106            client_id: String::new(),
107            client_secret: String::new(),
108            callback_path: default_github_callback_path(),
109            scopes: default_github_scopes(),
110        }
111    }
112}
113
114impl std::fmt::Debug for GitHubConfig {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        f.debug_struct("GitHubConfig")
117            .field("client_id", &self.client_id)
118            .field("callback_path", &self.callback_path)
119            .field("scopes", &self.scopes)
120            .finish_non_exhaustive()
121    }
122}
123
124pub(super) fn default_callback_path() -> String {
125    DEFAULT_CALLBACK_PATH.to_string()
126}
127
128pub(super) fn default_google_scopes() -> Vec<String> {
129    vec![
130        "openid".to_string(),
131        "email".to_string(),
132        "profile".to_string(),
133    ]
134}
135
136pub(super) fn default_authelia_callback_path() -> String {
137    "/auth/authelia/callback".to_string()
138}
139
140pub(super) fn default_authelia_scopes() -> Vec<String> {
141    vec![
142        "openid".to_string(),
143        "email".to_string(),
144        "profile".to_string(),
145        "offline_access".to_string(),
146    ]
147}
148
149pub(super) fn default_github_callback_path() -> String {
150    "/auth/github/callback".to_string()
151}
152
153pub(super) fn default_github_scopes() -> Vec<String> {
154    vec!["read:user".to_string(), "user:email".to_string()]
155}