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.
9mod accounts;
10mod sessions;
11mod users;
12mod verifications;
13
14mod api_keys;
15mod device_codes;
16mod invitations;
17mod jwks;
18mod members;
19mod optional_records;
20mod organizations;
21mod roles;
22mod teams;
23mod transaction;
24
25use super::*;
26use crate::{AccountView, SessionView, UserView, VerificationView};
27use chrono::{DateTime, Utc};
28
29/// Wire schema for deployments that do not configure a database.
30pub struct StatelessSchema;
31impl AuthSchema for StatelessSchema {
32    fn user_from_cookie_cache(user: UserView) -> Option<UserView> {
33        Some(user)
34    }
35    type User = UserView;
36    type Session = SessionView;
37    type Account = AccountView;
38    type Verification = VerificationView;
39}
40
41/// Instance-local provisioning without durable persistence. Session ownership
42/// comes from trusted issuance; the initialized wrapper retains ephemeral sessions.
43pub struct StatelessStore {
44    state: std::sync::Mutex<IdentityState>,
45    organizations: std::sync::Mutex<OrganizationState>,
46    find_many_limit: usize,
47}
48
49#[derive(Default)]
50struct IdentityState {
51    users: indexmap::IndexMap<String, UserView>,
52    accounts: indexmap::IndexMap<String, AccountView>,
53    verifications: indexmap::IndexMap<String, VerificationView>,
54    two_factors: indexmap::IndexMap<String, TwoFactor>,
55    passkeys: indexmap::IndexMap<String, Passkey>,
56    api_keys: indexmap::IndexMap<String, ApiKey>,
57    device_codes: indexmap::IndexMap<String, DeviceCode>,
58    jwks: indexmap::IndexMap<String, Jwk>,
59}
60
61#[derive(Default, Clone)]
62struct OrganizationState {
63    organizations: indexmap::IndexMap<String, Organization>,
64    members: indexmap::IndexMap<String, Member>,
65    invitations: indexmap::IndexMap<String, Invitation>,
66    teams: indexmap::IndexMap<String, Team>,
67    team_members: indexmap::IndexMap<String, TeamMember>,
68    roles: indexmap::IndexMap<String, OrganizationRole>,
69}
70
71impl Default for StatelessStore {
72    fn default() -> Self {
73        Self::with_find_many_limit(100)
74    }
75}
76
77impl StatelessStore {
78    /// Use the configured adapter page bound for native organization rows.
79    #[must_use]
80    pub fn with_find_many_limit(find_many_limit: usize) -> Self {
81        Self {
82            state: Default::default(),
83            organizations: Default::default(),
84            find_many_limit,
85        }
86    }
87
88    fn organization_state(&self) -> AuthResult<std::sync::MutexGuard<'_, OrganizationState>> {
89        self.organizations
90            .lock()
91            .map_err(|_| AuthError::internal("No-database organization state poisoned"))
92    }
93
94    fn lock(&self) -> AuthResult<std::sync::MutexGuard<'_, IdentityState>> {
95        self.state
96            .lock()
97            .map_err(|_| AuthError::internal("No-database identity state poisoned"))
98    }
99}
100
101impl WalletAddressStore for StatelessStore {}