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 if config.has_module(crate::config::AuthModule::Rememberable) {
68 strategies.push(Arc::new(crate::rememberable::RememberStrategy));
69 }
70
71 let oauth = providers_from_config(&config.oauth);
72
73 Ok(Self {
74 db,
75 config,
76 strategies,
77 jwt,
78 oauth,
79 })
80 }
81}
82
83pub async fn init(db: DatabaseConnection, config: &AuthConfig) -> Result<()> {
85 let mut guard = slot().write().expect("auth state lock");
86 if guard.is_some() {
87 return Ok(());
88 }
89 let state = AuthState::build(db, config.clone()).map_err(|e| doido_core::anyhow::anyhow!(e))?;
90 *guard = Some(Arc::new(state));
91 Ok(())
92}
93
94pub fn set_state(state: AuthState) {
96 *slot().write().expect("auth state lock") = Some(Arc::new(state));
97}
98
99pub fn global() -> Arc<AuthState> {
101 try_global().expect("auth not initialised; call doido_auth::init() at boot")
102}
103
104pub fn try_global() -> Option<Arc<AuthState>> {
106 slot().read().expect("auth state lock").clone()
107}
108
109pub fn reset_state() {
111 *slot().write().expect("auth state lock") = None;
112}