1use crate::config::AuthConfig;
4use crate::error::AuthError;
5use crate::jwt::JwtStrategy;
6use crate::oauth::{providers_from_config, OAuthProvider};
7use crate::registry::{all_custom_strategies, get_strategy};
8use crate::strategy::AuthStrategy;
9use doido_core::Result;
10use doido_model::sea_orm::DatabaseConnection;
11use std::collections::HashMap;
12use std::sync::{Arc, OnceLock, RwLock};
13
14pub struct AuthState {
16 pub db: DatabaseConnection,
17 pub config: AuthConfig,
18 pub strategies: Vec<Arc<dyn AuthStrategy>>,
19 pub jwt: Option<Arc<JwtStrategy>>,
20 pub oauth: HashMap<String, Arc<dyn OAuthProvider>>,
21}
22
23static AUTH_STATE: OnceLock<RwLock<Option<Arc<AuthState>>>> = OnceLock::new();
24
25fn slot() -> &'static RwLock<Option<Arc<AuthState>>> {
26 AUTH_STATE.get_or_init(|| RwLock::new(None))
27}
28
29impl AuthState {
30 pub(crate) fn build(db: DatabaseConnection, config: AuthConfig) -> Result<Self, AuthError> {
31 config.validate()?;
32 let mut strategies: Vec<Arc<dyn AuthStrategy>> = Vec::new();
33 let mut jwt = None;
34
35 for name in &config.strategies {
36 match name.as_str() {
37 "cookie" => {
38 strategies.push(Arc::new(crate::session::from_config(&config)));
39 }
40 "jwt" => {
41 let jwt_cfg = config
42 .jwt
43 .clone()
44 .ok_or_else(|| AuthError::Config("missing auth.jwt".into()))?;
45 let strategy = Arc::new(JwtStrategy::new(jwt_cfg)?);
46 jwt = Some(strategy.clone());
47 strategies.push(strategy);
48 }
49 custom => {
50 let strategy = get_strategy(custom)
51 .ok_or_else(|| AuthError::UnknownStrategy(custom.to_string()))?;
52 strategies.push(strategy);
53 }
54 }
55 }
56
57 for custom in all_custom_strategies() {
58 if !strategies.iter().any(|s| s.name() == custom.name())
59 && config.strategies.iter().any(|n| n == custom.name())
60 {
61 strategies.push(custom);
62 }
63 }
64
65 let oauth = providers_from_config(&config.oauth);
66
67 Ok(Self {
68 db,
69 config,
70 strategies,
71 jwt,
72 oauth,
73 })
74 }
75}
76
77pub async fn init(db: DatabaseConnection, config: &AuthConfig) -> Result<()> {
79 let mut guard = slot().write().expect("auth state lock");
80 if guard.is_some() {
81 return Ok(());
82 }
83 let state = AuthState::build(db, config.clone()).map_err(|e| doido_core::anyhow::anyhow!(e))?;
84 *guard = Some(Arc::new(state));
85 Ok(())
86}
87
88pub fn set_state(state: AuthState) {
90 *slot().write().expect("auth state lock") = Some(Arc::new(state));
91}
92
93pub fn global() -> Arc<AuthState> {
95 try_global().expect("auth not initialised; call doido_auth::init() at boot")
96}
97
98pub fn try_global() -> Option<Arc<AuthState>> {
100 slot().read().expect("auth state lock").clone()
101}
102
103pub fn reset_state() {
105 *slot().write().expect("auth state lock") = None;
106}