aperion_shield/identity/
config.rs1use serde::{Deserialize, Serialize};
36use std::path::{Path, PathBuf};
37
38#[derive(Debug, Deserialize)]
42struct Root {
43 identity: IdentityConfig,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct IdentityConfig {
48 #[serde(default = "default_true")]
49 pub enabled: bool,
50
51 #[serde(default = "default_callback_host")]
52 pub callback_host: String,
53
54 #[serde(default)]
55 pub callback_port: u16,
56
57 #[serde(default = "default_hold")]
58 pub hold_seconds: u64,
59
60 #[serde(default)]
61 pub providers: Vec<ProviderConfig>,
62}
63
64impl Default for IdentityConfig {
65 fn default() -> Self {
66 Self {
67 enabled: true,
68 callback_host: default_callback_host(),
69 callback_port: 0,
70 hold_seconds: default_hold(),
71 providers: vec![ProviderConfig::default_mock()],
72 }
73 }
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct ProviderConfig {
78 pub id: String,
80 pub kind: ProviderKind,
81
82 #[serde(default)]
84 pub sandbox: bool,
85 #[serde(default)]
87 pub client_id_env: Option<String>,
88 #[serde(default)]
90 pub client_secret_env: Option<String>,
91 #[serde(default = "default_scopes")]
93 pub scopes: Vec<String>,
94 #[serde(default)]
97 pub authorize_url: Option<String>,
98 #[serde(default)]
99 pub token_url: Option<String>,
100 #[serde(default)]
101 pub userinfo_url: Option<String>,
102
103 #[serde(default)]
107 pub subject: Option<String>,
108 #[serde(default)]
110 pub email: Option<String>,
111 #[serde(default)]
113 pub loa: u8,
114}
115
116impl ProviderConfig {
117 pub fn default_mock() -> Self {
118 Self {
119 id: "mock".to_string(),
120 kind: ProviderKind::Mock,
121 sandbox: false,
122 client_id_env: None,
123 client_secret_env: None,
124 scopes: default_scopes(),
125 authorize_url: None,
126 token_url: None,
127 userinfo_url: None,
128 subject: Some("mock-subject-0001".to_string()),
129 email: Some("[email protected]".to_string()),
130 loa: 2,
131 }
132 }
133}
134
135#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
136#[serde(rename_all = "snake_case")]
137pub enum ProviderKind {
138 IdMe,
140 Mock,
142}
143
144fn default_true() -> bool { true }
145fn default_callback_host() -> String { "127.0.0.1".to_string() }
146fn default_hold() -> u64 { 120 }
147fn default_scopes() -> Vec<String> { vec!["openid".to_string()] }
148
149impl IdentityConfig {
150 pub fn from_yaml(raw: &str) -> anyhow::Result<Self> {
152 let root: Root = serde_yaml::from_str(raw)?;
153 Ok(root.identity)
154 }
155
156 pub fn load(explicit: Option<&Path>) -> anyhow::Result<Self> {
159 if let Some(p) = explicit {
160 let raw = std::fs::read_to_string(p)?;
161 return Self::from_yaml(&raw);
162 }
163 if let Ok(p) = std::env::var("APERION_SHIELD_IDENTITY_CONFIG") {
164 if !p.is_empty() {
165 let raw = std::fs::read_to_string(&p)?;
166 return Self::from_yaml(&raw);
167 }
168 }
169 if let Some(home) = dirs::home_dir() {
170 let p = home.join(".aperion-shield").join("identity.yaml");
171 if p.exists() {
172 let raw = std::fs::read_to_string(&p)?;
173 return Self::from_yaml(&raw);
174 }
175 }
176 Ok(Self::default())
177 }
178
179 pub fn state_dir() -> PathBuf {
183 if let Ok(d) = std::env::var("APERION_SHIELD_STATE_DIR") {
184 if !d.is_empty() {
185 return PathBuf::from(d);
186 }
187 }
188 dirs::home_dir()
189 .map(|h| h.join(".aperion-shield"))
190 .unwrap_or_else(|| PathBuf::from(".aperion-shield"))
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn defaults_have_mock_provider() {
200 let c = IdentityConfig::default();
201 assert!(c.enabled);
202 assert_eq!(c.callback_host, "127.0.0.1");
203 assert_eq!(c.hold_seconds, 120);
204 assert_eq!(c.providers.len(), 1);
205 assert_eq!(c.providers[0].id, "mock");
206 assert_eq!(c.providers[0].kind, ProviderKind::Mock);
207 }
208
209 #[test]
210 fn parses_full_yaml() {
211 let yaml = r#"
212identity:
213 enabled: true
214 callback_host: 127.0.0.1
215 callback_port: 0
216 hold_seconds: 90
217 providers:
218 - id: id_me
219 kind: id_me
220 sandbox: true
221 client_id_env: IDME_CLIENT_ID
222 client_secret_env: IDME_CLIENT_SECRET
223 scopes: ["openid", "ial2"]
224 - id: mock
225 kind: mock
226 subject: "[email protected]"
227 loa: 2
228"#;
229 let c = IdentityConfig::from_yaml(yaml).unwrap();
230 assert_eq!(c.hold_seconds, 90);
231 assert_eq!(c.providers.len(), 2);
232 assert_eq!(c.providers[0].kind, ProviderKind::IdMe);
233 assert!(c.providers[0].sandbox);
234 assert_eq!(c.providers[0].scopes, vec!["openid".to_string(), "ial2".into()]);
235 assert_eq!(c.providers[1].kind, ProviderKind::Mock);
236 }
237}