Skip to main content

alibi_core/store/stateless/
mod.rs

1//! Ephemeral no-database identity provisioning and cookie-only sessions.
2//!
3//! This store has no database connection. The initialized store wrapper keeps
4//! ephemeral session records for cookie bypass and instance-local logout.
5//! User/account/verification provisioning is instance-local, as in the pinned
6//! no-database memory adapter. Native two-factor/passkey/API-key/device-code/JWK/organization records share that
7//! instance-local lifetime; other optional records require an application store.
8//! Applications can also use cookie-only sessions with durable SQL user storage.
9use crate::types::OrganizationRole;
10use crate::{
11    ApiKey, AuthError, AuthResult, AuthSchema, DeviceCode, Invitation, Jwk, Member, Organization,
12    Passkey, Team, TeamMember, TwoFactor, WalletAddressStore,
13};
14use indexmap::IndexMap;
15use std::sync::{Mutex, MutexGuard};
16mod accounts;
17mod sessions;
18mod users;
19mod verifications;
20
21mod api_keys;
22mod device_codes;
23mod invitations;
24mod jwks;
25mod members;
26mod optional_records;
27mod organizations;
28mod roles;
29mod teams;
30mod transaction;
31
32use crate::{AccountView, SessionView, UserView, VerificationView};
33
34/// Wire schema for deployments that do not configure a database.
35pub struct StatelessSchema;
36impl AuthSchema for StatelessSchema {
37    fn user_from_cookie_cache(user: UserView) -> Option<UserView> {
38        Some(user)
39    }
40    type User = UserView;
41    type Session = SessionView;
42    type Account = AccountView;
43    type Verification = VerificationView;
44}
45
46/// Instance-local provisioning without durable persistence. Session ownership
47/// comes from trusted issuance; the initialized wrapper retains ephemeral sessions.
48pub struct StatelessStore {
49    state: Mutex<IdentityState>,
50    organizations: Mutex<OrganizationState>,
51    find_many_limit: usize,
52}
53
54#[derive(Default)]
55struct IdentityState {
56    users: IndexMap<String, UserView>,
57    accounts: IndexMap<String, AccountView>,
58    verifications: IndexMap<String, VerificationView>,
59    two_factors: IndexMap<String, TwoFactor>,
60    passkeys: IndexMap<String, Passkey>,
61    api_keys: IndexMap<String, ApiKey>,
62    device_codes: IndexMap<String, DeviceCode>,
63    device_code_fields: IndexMap<String, serde_json::Map<String, serde_json::Value>>,
64    jwks: IndexMap<String, Jwk>,
65}
66
67#[derive(Default, Clone)]
68struct OrganizationState {
69    organizations: IndexMap<String, Organization>,
70    members: IndexMap<String, Member>,
71    invitations: IndexMap<String, Invitation>,
72    teams: IndexMap<String, Team>,
73    team_members: IndexMap<String, TeamMember>,
74    roles: IndexMap<String, OrganizationRole>,
75}
76
77impl Default for StatelessStore {
78    fn default() -> Self {
79        Self::with_find_many_limit(100)
80    }
81}
82
83impl StatelessStore {
84    /// Use the configured adapter page bound for native organization rows.
85    #[must_use]
86    pub fn with_find_many_limit(find_many_limit: usize) -> Self {
87        Self {
88            state: Mutex::default(),
89            organizations: Mutex::default(),
90            find_many_limit,
91        }
92    }
93
94    fn organization_state(&self) -> AuthResult<MutexGuard<'_, OrganizationState>> {
95        self.organizations
96            .lock()
97            .map_err(|_| AuthError::internal("No-database organization state poisoned"))
98    }
99
100    fn lock(&self) -> AuthResult<MutexGuard<'_, IdentityState>> {
101        self.state
102            .lock()
103            .map_err(|_| AuthError::internal("No-database identity state poisoned"))
104    }
105}
106
107impl WalletAddressStore for StatelessStore {}
108
109impl crate::store::OAuthClientStore for StatelessStore {}
110
111impl crate::store::SsoProviderStore for StatelessStore {}
112
113impl crate::store::ScimManagedStore for StatelessStore {}