Skip to main content

authx_storage/memory/
mod.rs

1use std::collections::HashMap;
2use std::sync::{Arc, RwLock};
3
4#[cfg(test)]
5mod tests;
6
7use async_trait::async_trait;
8use chrono::Utc;
9use uuid::Uuid;
10
11use authx_core::{
12    error::{AuthError, Result, StorageError},
13    models::{
14        ApiKey, AuditLog, AuthorizationCode, CreateApiKey, CreateAuditLog, CreateAuthorizationCode,
15        CreateCredential, CreateDeviceCode, CreateInvite, CreateOidcClient,
16        CreateOidcFederationProvider, CreateOidcToken, CreateOrg, CreateSession, CreateUser,
17        Credential, CredentialKind, DeviceCode, Invite, Membership, OAuthAccount, OidcClient,
18        OidcFederationProvider, OidcToken, Organization, Role, Session, UpdateUser,
19        UpsertOAuthAccount, User,
20    },
21};
22
23use crate::ports::{
24    ApiKeyRepository, AuditLogRepository, AuthorizationCodeRepository, CredentialRepository,
25    DeviceCodeRepository, InviteRepository, OAuthAccountRepository, OidcClientRepository,
26    OidcFederationProviderRepository, OidcTokenRepository, OrgRepository, SessionRepository,
27    UserRepository,
28};
29
30/// Acquire a read guard, recovering from a poisoned lock instead of panicking.
31macro_rules! rlock {
32    ($lock:expr, $label:literal) => {
33        match $lock.read() {
34            Ok(g) => g,
35            Err(e) => {
36                tracing::error!(concat!(
37                    "memory store read-lock poisoned (",
38                    $label,
39                    ") — recovering"
40                ));
41                e.into_inner()
42            }
43        }
44    };
45}
46
47/// Acquire a write guard, recovering from a poisoned lock instead of panicking.
48macro_rules! wlock {
49    ($lock:expr, $label:literal) => {
50        match $lock.write() {
51            Ok(g) => g,
52            Err(e) => {
53                tracing::error!(concat!(
54                    "memory store write-lock poisoned (",
55                    $label,
56                    ") — recovering"
57                ));
58                e.into_inner()
59            }
60        }
61    };
62}
63
64#[derive(Clone, Default)]
65pub struct MemoryStore {
66    users: Arc<RwLock<HashMap<Uuid, User>>>,
67    sessions: Arc<RwLock<HashMap<Uuid, Session>>>,
68    credentials: Arc<RwLock<Vec<Credential>>>,
69    audit_logs: Arc<RwLock<Vec<AuditLog>>>,
70    orgs: Arc<RwLock<HashMap<Uuid, Organization>>>,
71    roles: Arc<RwLock<HashMap<Uuid, Role>>>,
72    memberships: Arc<RwLock<Vec<Membership>>>,
73    api_keys: Arc<RwLock<Vec<ApiKey>>>,
74    oauth_accounts: Arc<RwLock<Vec<OAuthAccount>>>,
75    invites: Arc<RwLock<Vec<Invite>>>,
76    oidc_clients: Arc<RwLock<Vec<OidcClient>>>,
77    authorization_codes: Arc<RwLock<Vec<AuthorizationCode>>>,
78    oidc_tokens: Arc<RwLock<Vec<OidcToken>>>,
79    oidc_federation_providers: Arc<RwLock<Vec<OidcFederationProvider>>>,
80    device_codes: Arc<RwLock<Vec<DeviceCode>>>,
81}
82
83impl MemoryStore {
84    pub fn new() -> Self {
85        Self::default()
86    }
87}
88
89// ── UserRepository ────────────────────────────────────────────────────────────
90
91#[async_trait]
92impl UserRepository for MemoryStore {
93    async fn find_by_id(&self, id: Uuid) -> Result<Option<User>> {
94        Ok(rlock!(self.users, "users").get(&id).cloned())
95    }
96
97    async fn find_by_email(&self, email: &str) -> Result<Option<User>> {
98        Ok(rlock!(self.users, "users")
99            .values()
100            .find(|u| u.email == email)
101            .cloned())
102    }
103
104    async fn find_by_username(&self, username: &str) -> Result<Option<User>> {
105        Ok(rlock!(self.users, "users")
106            .values()
107            .find(|u| u.username.as_deref() == Some(username))
108            .cloned())
109    }
110
111    async fn list(&self, offset: u32, limit: u32) -> Result<Vec<User>> {
112        let users = rlock!(self.users, "users");
113        let mut sorted: Vec<User> = users.values().cloned().collect();
114        sorted.sort_by_key(|u| u.created_at);
115        Ok(sorted
116            .into_iter()
117            .skip(offset as usize)
118            .take(limit as usize)
119            .collect())
120    }
121
122    async fn create(&self, data: CreateUser) -> Result<User> {
123        let mut users = wlock!(self.users, "users");
124        if users.values().any(|u| u.email == data.email) {
125            return Err(AuthError::EmailTaken);
126        }
127        if let Some(ref uname) = data.username {
128            if users
129                .values()
130                .any(|u| u.username.as_deref() == Some(uname.as_str()))
131            {
132                return Err(AuthError::Storage(StorageError::Conflict(format!(
133                    "username '{}' already taken",
134                    uname
135                ))));
136            }
137        }
138        let user = User {
139            id: Uuid::new_v4(),
140            email: data.email,
141            email_verified: false,
142            username: data.username,
143            created_at: Utc::now(),
144            updated_at: Utc::now(),
145            metadata: data.metadata.unwrap_or(serde_json::Value::Null),
146        };
147        users.insert(user.id, user.clone());
148        Ok(user)
149    }
150
151    async fn update(&self, id: Uuid, data: UpdateUser) -> Result<User> {
152        let mut users = wlock!(self.users, "users");
153        let user = users.get_mut(&id).ok_or(AuthError::UserNotFound)?;
154        if let Some(email) = data.email {
155            user.email = email;
156        }
157        if let Some(verified) = data.email_verified {
158            user.email_verified = verified;
159        }
160        if let Some(uname) = data.username {
161            user.username = Some(uname);
162        }
163        if let Some(meta) = data.metadata {
164            user.metadata = meta;
165        }
166        user.updated_at = Utc::now();
167        Ok(user.clone())
168    }
169
170    async fn delete(&self, id: Uuid) -> Result<()> {
171        wlock!(self.users, "users")
172            .remove(&id)
173            .ok_or(AuthError::UserNotFound)?;
174        Ok(())
175    }
176}
177
178// ── SessionRepository ─────────────────────────────────────────────────────────
179
180#[async_trait]
181impl SessionRepository for MemoryStore {
182    async fn create(&self, data: CreateSession) -> Result<Session> {
183        let session = Session {
184            id: Uuid::new_v4(),
185            user_id: data.user_id,
186            token_hash: data.token_hash,
187            device_info: data.device_info,
188            ip_address: data.ip_address,
189            org_id: data.org_id,
190            expires_at: data.expires_at,
191            created_at: Utc::now(),
192        };
193        wlock!(self.sessions, "sessions").insert(session.id, session.clone());
194        Ok(session)
195    }
196
197    async fn find_by_token_hash(&self, hash: &str) -> Result<Option<Session>> {
198        Ok(rlock!(self.sessions, "sessions")
199            .values()
200            .find(|s| s.token_hash == hash && s.expires_at > Utc::now())
201            .cloned())
202    }
203
204    async fn find_by_user(&self, user_id: Uuid) -> Result<Vec<Session>> {
205        Ok(rlock!(self.sessions, "sessions")
206            .values()
207            .filter(|s| s.user_id == user_id)
208            .cloned()
209            .collect())
210    }
211
212    async fn invalidate(&self, session_id: Uuid) -> Result<()> {
213        wlock!(self.sessions, "sessions")
214            .remove(&session_id)
215            .ok_or(AuthError::Storage(StorageError::NotFound))?;
216        Ok(())
217    }
218
219    async fn invalidate_all_for_user(&self, user_id: Uuid) -> Result<()> {
220        wlock!(self.sessions, "sessions").retain(|_, s| s.user_id != user_id);
221        Ok(())
222    }
223
224    async fn set_org(&self, session_id: Uuid, org_id: Option<Uuid>) -> Result<Session> {
225        let mut sessions = wlock!(self.sessions, "sessions");
226        let session = sessions
227            .get_mut(&session_id)
228            .ok_or(AuthError::Storage(StorageError::NotFound))?;
229        session.org_id = org_id;
230        Ok(session.clone())
231    }
232}
233
234// ── CredentialRepository ──────────────────────────────────────────────────────
235
236#[async_trait]
237impl CredentialRepository for MemoryStore {
238    async fn create(&self, data: CreateCredential) -> Result<Credential> {
239        let cred = Credential {
240            id: Uuid::new_v4(),
241            user_id: data.user_id,
242            kind: data.kind,
243            credential_hash: data.credential_hash,
244            metadata: data.metadata.unwrap_or(serde_json::Value::Null),
245        };
246        wlock!(self.credentials, "credentials").push(cred.clone());
247        Ok(cred)
248    }
249
250    async fn find_password_hash(&self, user_id: Uuid) -> Result<Option<String>> {
251        Ok(rlock!(self.credentials, "credentials")
252            .iter()
253            .find(|c| c.user_id == user_id && c.kind == CredentialKind::Password)
254            .map(|c| c.credential_hash.clone()))
255    }
256
257    async fn find_by_user_and_kind(
258        &self,
259        user_id: Uuid,
260        kind: CredentialKind,
261    ) -> Result<Option<Credential>> {
262        Ok(rlock!(self.credentials, "credentials")
263            .iter()
264            .find(|c| c.user_id == user_id && c.kind == kind)
265            .cloned())
266    }
267
268    async fn delete_by_user_and_kind(&self, user_id: Uuid, kind: CredentialKind) -> Result<()> {
269        let mut creds = wlock!(self.credentials, "credentials");
270        let before = creds.len();
271        creds.retain(|c| !(c.user_id == user_id && c.kind == kind));
272        if creds.len() == before {
273            return Err(AuthError::Storage(StorageError::NotFound));
274        }
275        Ok(())
276    }
277}
278
279// ── OrgRepository ─────────────────────────────────────────────────────────────
280
281#[async_trait]
282impl OrgRepository for MemoryStore {
283    async fn create(&self, data: CreateOrg) -> Result<Organization> {
284        let mut orgs = wlock!(self.orgs, "orgs");
285        if orgs.values().any(|o| o.slug == data.slug) {
286            return Err(AuthError::Storage(StorageError::Conflict(format!(
287                "slug '{}' already taken",
288                data.slug
289            ))));
290        }
291        let org = Organization {
292            id: Uuid::new_v4(),
293            name: data.name,
294            slug: data.slug,
295            metadata: data.metadata.unwrap_or(serde_json::Value::Null),
296            created_at: Utc::now(),
297        };
298        orgs.insert(org.id, org.clone());
299        Ok(org)
300    }
301
302    async fn find_by_id(&self, id: Uuid) -> Result<Option<Organization>> {
303        Ok(rlock!(self.orgs, "orgs").get(&id).cloned())
304    }
305
306    async fn find_by_slug(&self, slug: &str) -> Result<Option<Organization>> {
307        Ok(rlock!(self.orgs, "orgs")
308            .values()
309            .find(|o| o.slug == slug)
310            .cloned())
311    }
312
313    async fn add_member(&self, org_id: Uuid, user_id: Uuid, role_id: Uuid) -> Result<Membership> {
314        let role = rlock!(self.roles, "roles")
315            .get(&role_id)
316            .cloned()
317            .ok_or(AuthError::Storage(StorageError::NotFound))?;
318        let membership = Membership {
319            id: Uuid::new_v4(),
320            user_id,
321            org_id,
322            role,
323            created_at: Utc::now(),
324        };
325        wlock!(self.memberships, "memberships").push(membership.clone());
326        Ok(membership)
327    }
328
329    async fn remove_member(&self, org_id: Uuid, user_id: Uuid) -> Result<()> {
330        let mut memberships = wlock!(self.memberships, "memberships");
331        let before = memberships.len();
332        memberships.retain(|m| !(m.org_id == org_id && m.user_id == user_id));
333        if memberships.len() == before {
334            return Err(AuthError::Storage(StorageError::NotFound));
335        }
336        Ok(())
337    }
338
339    async fn get_members(&self, org_id: Uuid) -> Result<Vec<Membership>> {
340        Ok(rlock!(self.memberships, "memberships")
341            .iter()
342            .filter(|m| m.org_id == org_id)
343            .cloned()
344            .collect())
345    }
346
347    async fn find_roles(&self, org_id: Uuid) -> Result<Vec<Role>> {
348        Ok(rlock!(self.roles, "roles")
349            .values()
350            .filter(|r| r.org_id == org_id)
351            .cloned()
352            .collect())
353    }
354
355    async fn create_role(
356        &self,
357        org_id: Uuid,
358        name: String,
359        permissions: Vec<String>,
360    ) -> Result<Role> {
361        let role = Role {
362            id: Uuid::new_v4(),
363            org_id,
364            name,
365            permissions,
366        };
367        wlock!(self.roles, "roles").insert(role.id, role.clone());
368        Ok(role)
369    }
370
371    async fn update_member_role(
372        &self,
373        org_id: Uuid,
374        user_id: Uuid,
375        role_id: Uuid,
376    ) -> Result<Membership> {
377        let role = rlock!(self.roles, "roles")
378            .get(&role_id)
379            .cloned()
380            .ok_or(AuthError::Storage(StorageError::NotFound))?;
381
382        let mut memberships = wlock!(self.memberships, "memberships");
383        let m = memberships
384            .iter_mut()
385            .find(|m| m.org_id == org_id && m.user_id == user_id)
386            .ok_or(AuthError::Storage(StorageError::NotFound))?;
387        m.role = role;
388        Ok(m.clone())
389    }
390}
391
392// ── AuditLogRepository ────────────────────────────────────────────────────────
393
394#[async_trait]
395impl AuditLogRepository for MemoryStore {
396    async fn append(&self, entry: CreateAuditLog) -> Result<AuditLog> {
397        let log = AuditLog {
398            id: Uuid::new_v4(),
399            user_id: entry.user_id,
400            org_id: entry.org_id,
401            action: entry.action,
402            resource_type: entry.resource_type,
403            resource_id: entry.resource_id,
404            ip_address: entry.ip_address,
405            metadata: entry.metadata.unwrap_or(serde_json::Value::Null),
406            created_at: Utc::now(),
407        };
408        wlock!(self.audit_logs, "audit_logs").push(log.clone());
409        Ok(log)
410    }
411
412    async fn find_by_user(&self, user_id: Uuid, limit: u32) -> Result<Vec<AuditLog>> {
413        Ok(rlock!(self.audit_logs, "audit_logs")
414            .iter()
415            .filter(|l| l.user_id == Some(user_id))
416            .take(limit as usize)
417            .cloned()
418            .collect())
419    }
420
421    async fn find_by_org(&self, org_id: Uuid, limit: u32) -> Result<Vec<AuditLog>> {
422        Ok(rlock!(self.audit_logs, "audit_logs")
423            .iter()
424            .filter(|l| l.org_id == Some(org_id))
425            .take(limit as usize)
426            .cloned()
427            .collect())
428    }
429}
430
431// ── ApiKeyRepository ──────────────────────────────────────────────────────────
432
433#[async_trait]
434impl ApiKeyRepository for MemoryStore {
435    async fn create(&self, data: CreateApiKey) -> Result<ApiKey> {
436        let key = ApiKey {
437            id: Uuid::new_v4(),
438            user_id: data.user_id,
439            org_id: data.org_id,
440            key_hash: data.key_hash,
441            prefix: data.prefix,
442            name: data.name,
443            scopes: data.scopes,
444            expires_at: data.expires_at,
445            last_used_at: None,
446        };
447        wlock!(self.api_keys, "api_keys").push(key.clone());
448        Ok(key)
449    }
450
451    async fn find_by_hash(&self, key_hash: &str) -> Result<Option<ApiKey>> {
452        Ok(rlock!(self.api_keys, "api_keys")
453            .iter()
454            .find(|k| k.key_hash == key_hash)
455            .cloned())
456    }
457
458    async fn find_by_user(&self, user_id: Uuid) -> Result<Vec<ApiKey>> {
459        Ok(rlock!(self.api_keys, "api_keys")
460            .iter()
461            .filter(|k| k.user_id == user_id)
462            .cloned()
463            .collect())
464    }
465
466    async fn revoke(&self, key_id: Uuid, user_id: Uuid) -> Result<()> {
467        let mut keys = wlock!(self.api_keys, "api_keys");
468        let before = keys.len();
469        keys.retain(|k| !(k.id == key_id && k.user_id == user_id));
470        if keys.len() == before {
471            return Err(AuthError::Storage(StorageError::NotFound));
472        }
473        Ok(())
474    }
475
476    async fn touch_last_used(&self, key_id: Uuid, at: chrono::DateTime<Utc>) -> Result<()> {
477        let mut keys = wlock!(self.api_keys, "api_keys");
478        if let Some(k) = keys.iter_mut().find(|k| k.id == key_id) {
479            k.last_used_at = Some(at);
480        }
481        Ok(())
482    }
483}
484
485// ── OAuthAccountRepository ────────────────────────────────────────────────────
486
487#[async_trait]
488impl OAuthAccountRepository for MemoryStore {
489    async fn upsert(&self, data: UpsertOAuthAccount) -> Result<OAuthAccount> {
490        let mut accounts = wlock!(self.oauth_accounts, "oauth_accounts");
491        if let Some(existing) = accounts
492            .iter_mut()
493            .find(|a| a.provider == data.provider && a.provider_user_id == data.provider_user_id)
494        {
495            existing.access_token_enc = data.access_token_enc;
496            existing.refresh_token_enc = data.refresh_token_enc;
497            existing.expires_at = data.expires_at;
498            return Ok(existing.clone());
499        }
500        let account = OAuthAccount {
501            id: Uuid::new_v4(),
502            user_id: data.user_id,
503            provider: data.provider,
504            provider_user_id: data.provider_user_id,
505            access_token_enc: data.access_token_enc,
506            refresh_token_enc: data.refresh_token_enc,
507            expires_at: data.expires_at,
508        };
509        accounts.push(account.clone());
510        Ok(account)
511    }
512
513    async fn find_by_provider(
514        &self,
515        provider: &str,
516        provider_user_id: &str,
517    ) -> Result<Option<OAuthAccount>> {
518        Ok(rlock!(self.oauth_accounts, "oauth_accounts")
519            .iter()
520            .find(|a| a.provider == provider && a.provider_user_id == provider_user_id)
521            .cloned())
522    }
523
524    async fn find_by_user(&self, user_id: Uuid) -> Result<Vec<OAuthAccount>> {
525        Ok(rlock!(self.oauth_accounts, "oauth_accounts")
526            .iter()
527            .filter(|a| a.user_id == user_id)
528            .cloned()
529            .collect())
530    }
531
532    async fn delete(&self, id: Uuid) -> Result<()> {
533        let mut accounts = wlock!(self.oauth_accounts, "oauth_accounts");
534        let before = accounts.len();
535        accounts.retain(|a| a.id != id);
536        if accounts.len() == before {
537            return Err(AuthError::Storage(StorageError::NotFound));
538        }
539        Ok(())
540    }
541}
542
543// ── InviteRepository ──────────────────────────────────────────────────────────
544
545#[async_trait]
546impl InviteRepository for MemoryStore {
547    async fn create(&self, data: CreateInvite) -> Result<Invite> {
548        let invite = Invite {
549            id: Uuid::new_v4(),
550            org_id: data.org_id,
551            email: data.email,
552            role_id: data.role_id,
553            token_hash: data.token_hash,
554            expires_at: data.expires_at,
555            accepted_at: None,
556        };
557        wlock!(self.invites, "invites").push(invite.clone());
558        Ok(invite)
559    }
560
561    async fn find_by_token_hash(&self, hash: &str) -> Result<Option<Invite>> {
562        Ok(rlock!(self.invites, "invites")
563            .iter()
564            .find(|i| i.token_hash == hash)
565            .cloned())
566    }
567
568    async fn accept(&self, invite_id: Uuid) -> Result<Invite> {
569        let mut invites = wlock!(self.invites, "invites");
570        let invite = invites
571            .iter_mut()
572            .find(|i| i.id == invite_id)
573            .ok_or(AuthError::Storage(StorageError::NotFound))?;
574        invite.accepted_at = Some(Utc::now());
575        Ok(invite.clone())
576    }
577
578    async fn delete_expired(&self) -> Result<u64> {
579        let mut invites = wlock!(self.invites, "invites");
580        let before = invites.len();
581        let now = Utc::now();
582        invites.retain(|i| i.accepted_at.is_some() || i.expires_at > now);
583        Ok((before - invites.len()) as u64)
584    }
585}
586
587// ── OidcClientRepository ───────────────────────────────────────────────────────
588
589#[async_trait]
590impl OidcClientRepository for MemoryStore {
591    async fn create(&self, data: CreateOidcClient) -> Result<OidcClient> {
592        let client_id = Uuid::new_v4().to_string();
593        let client = OidcClient {
594            id: Uuid::new_v4(),
595            client_id: client_id.clone(),
596            secret_hash: data.secret_hash,
597            name: data.name,
598            redirect_uris: data.redirect_uris,
599            grant_types: data.grant_types,
600            response_types: data.response_types,
601            allowed_scopes: data.allowed_scopes,
602            created_at: Utc::now(),
603        };
604        wlock!(self.oidc_clients, "oidc_clients").push(client.clone());
605        Ok(client)
606    }
607
608    async fn find_by_client_id(&self, client_id: &str) -> Result<Option<OidcClient>> {
609        Ok(rlock!(self.oidc_clients, "oidc_clients")
610            .iter()
611            .find(|c| c.client_id == client_id)
612            .cloned())
613    }
614
615    async fn list(&self, offset: u32, limit: u32) -> Result<Vec<OidcClient>> {
616        let clients = rlock!(self.oidc_clients, "oidc_clients");
617        Ok(clients
618            .iter()
619            .skip(offset as usize)
620            .take(limit as usize)
621            .cloned()
622            .collect())
623    }
624}
625
626// ── AuthorizationCodeRepository ────────────────────────────────────────────────
627
628#[async_trait]
629impl AuthorizationCodeRepository for MemoryStore {
630    async fn create(&self, data: CreateAuthorizationCode) -> Result<AuthorizationCode> {
631        let code = AuthorizationCode {
632            id: Uuid::new_v4(),
633            code_hash: data.code_hash,
634            client_id: data.client_id,
635            user_id: data.user_id,
636            redirect_uri: data.redirect_uri,
637            scope: data.scope,
638            nonce: data.nonce,
639            code_challenge: data.code_challenge,
640            expires_at: data.expires_at,
641            used: false,
642        };
643        wlock!(self.authorization_codes, "authorization_codes").push(code.clone());
644        Ok(code)
645    }
646
647    async fn find_by_code_hash(&self, hash: &str) -> Result<Option<AuthorizationCode>> {
648        let now = Utc::now();
649        Ok(rlock!(self.authorization_codes, "authorization_codes")
650            .iter()
651            .find(|c| c.code_hash == hash && c.expires_at > now && !c.used)
652            .cloned())
653    }
654
655    async fn mark_used(&self, id: Uuid) -> Result<()> {
656        let mut codes = wlock!(self.authorization_codes, "authorization_codes");
657        let code = codes
658            .iter_mut()
659            .find(|c| c.id == id)
660            .ok_or(AuthError::Storage(StorageError::NotFound))?;
661        code.used = true;
662        Ok(())
663    }
664
665    async fn delete_expired(&self) -> Result<u64> {
666        let mut codes = wlock!(self.authorization_codes, "authorization_codes");
667        let before = codes.len();
668        let now = Utc::now();
669        codes.retain(|c| c.expires_at > now);
670        Ok((before - codes.len()) as u64)
671    }
672}
673
674// ── OidcTokenRepository ────────────────────────────────────────────────────────
675
676#[async_trait]
677impl OidcTokenRepository for MemoryStore {
678    async fn create(&self, data: CreateOidcToken) -> Result<OidcToken> {
679        let token = OidcToken {
680            id: Uuid::new_v4(),
681            token_hash: data.token_hash,
682            client_id: data.client_id,
683            user_id: data.user_id,
684            scope: data.scope,
685            token_type: data.token_type,
686            expires_at: data.expires_at,
687            revoked: false,
688            created_at: Utc::now(),
689        };
690        wlock!(self.oidc_tokens, "oidc_tokens").push(token.clone());
691        Ok(token)
692    }
693
694    async fn find_by_token_hash(&self, hash: &str) -> Result<Option<OidcToken>> {
695        let now = Utc::now();
696        Ok(rlock!(self.oidc_tokens, "oidc_tokens")
697            .iter()
698            .find(|t| {
699                t.token_hash == hash && !t.revoked && t.expires_at.map(|e| e > now).unwrap_or(true)
700            })
701            .cloned())
702    }
703
704    async fn revoke(&self, id: Uuid) -> Result<()> {
705        let mut tokens = wlock!(self.oidc_tokens, "oidc_tokens");
706        let t = tokens
707            .iter_mut()
708            .find(|t| t.id == id)
709            .ok_or(AuthError::Storage(StorageError::NotFound))?;
710        t.revoked = true;
711        Ok(())
712    }
713
714    async fn revoke_all_for_user_client(&self, user_id: Uuid, client_id: &str) -> Result<()> {
715        for t in wlock!(self.oidc_tokens, "oidc_tokens")
716            .iter_mut()
717            .filter(|t| t.user_id == user_id && t.client_id == client_id)
718        {
719            t.revoked = true;
720        }
721        Ok(())
722    }
723}
724
725// ── OidcFederationProviderRepository ──────────────────────────────────────────
726
727#[async_trait]
728impl OidcFederationProviderRepository for MemoryStore {
729    async fn create(&self, data: CreateOidcFederationProvider) -> Result<OidcFederationProvider> {
730        let provider = OidcFederationProvider {
731            id: Uuid::new_v4(),
732            name: data.name,
733            issuer: data.issuer,
734            client_id: data.client_id,
735            secret_enc: data.secret_enc,
736            scopes: data.scopes,
737            org_id: data.org_id,
738            enabled: true,
739            created_at: Utc::now(),
740            claim_mapping: data.claim_mapping,
741        };
742        wlock!(self.oidc_federation_providers, "oidc_federation_providers").push(provider.clone());
743        Ok(provider)
744    }
745
746    async fn find_by_id(&self, id: Uuid) -> Result<Option<OidcFederationProvider>> {
747        Ok(
748            rlock!(self.oidc_federation_providers, "oidc_federation_providers")
749                .iter()
750                .find(|p| p.id == id)
751                .cloned(),
752        )
753    }
754
755    async fn find_by_name(&self, name: &str) -> Result<Option<OidcFederationProvider>> {
756        Ok(
757            rlock!(self.oidc_federation_providers, "oidc_federation_providers")
758                .iter()
759                .find(|p| p.name == name)
760                .cloned(),
761        )
762    }
763
764    async fn list_enabled(&self) -> Result<Vec<OidcFederationProvider>> {
765        Ok(
766            rlock!(self.oidc_federation_providers, "oidc_federation_providers")
767                .iter()
768                .filter(|p| p.enabled)
769                .cloned()
770                .collect(),
771        )
772    }
773}
774
775// ── DeviceCodeRepository ─────────────────────────────────────────────────────
776
777#[async_trait]
778impl DeviceCodeRepository for MemoryStore {
779    async fn create(&self, data: CreateDeviceCode) -> Result<DeviceCode> {
780        let dc = DeviceCode {
781            id: Uuid::new_v4(),
782            device_code_hash: data.device_code_hash,
783            user_code_hash: data.user_code_hash,
784            user_code: data.user_code,
785            client_id: data.client_id,
786            scope: data.scope,
787            expires_at: data.expires_at,
788            interval_secs: data.interval_secs,
789            authorized: false,
790            denied: false,
791            user_id: None,
792            last_polled_at: None,
793        };
794        wlock!(self.device_codes, "device_codes").push(dc.clone());
795        Ok(dc)
796    }
797
798    async fn find_by_device_code_hash(&self, hash: &str) -> Result<Option<DeviceCode>> {
799        let now = Utc::now();
800        Ok(rlock!(self.device_codes, "device_codes")
801            .iter()
802            .find(|d| d.device_code_hash == hash && d.expires_at > now)
803            .cloned())
804    }
805
806    async fn find_by_user_code_hash(&self, hash: &str) -> Result<Option<DeviceCode>> {
807        let now = Utc::now();
808        Ok(rlock!(self.device_codes, "device_codes")
809            .iter()
810            .find(|d| d.user_code_hash == hash && d.expires_at > now && !d.authorized && !d.denied)
811            .cloned())
812    }
813
814    async fn authorize(&self, id: Uuid, user_id: Uuid) -> Result<()> {
815        let mut codes = wlock!(self.device_codes, "device_codes");
816        let dc = codes
817            .iter_mut()
818            .find(|d| d.id == id)
819            .ok_or(AuthError::Storage(StorageError::NotFound))?;
820        dc.authorized = true;
821        dc.user_id = Some(user_id);
822        Ok(())
823    }
824
825    async fn deny(&self, id: Uuid) -> Result<()> {
826        let mut codes = wlock!(self.device_codes, "device_codes");
827        let dc = codes
828            .iter_mut()
829            .find(|d| d.id == id)
830            .ok_or(AuthError::Storage(StorageError::NotFound))?;
831        dc.denied = true;
832        Ok(())
833    }
834
835    async fn update_last_polled(&self, id: Uuid, interval_secs: u32) -> Result<()> {
836        let mut codes = wlock!(self.device_codes, "device_codes");
837        if let Some(dc) = codes.iter_mut().find(|d| d.id == id) {
838            dc.last_polled_at = Some(Utc::now());
839            dc.interval_secs = interval_secs;
840        }
841        Ok(())
842    }
843
844    async fn delete(&self, id: Uuid) -> Result<()> {
845        let mut codes = wlock!(self.device_codes, "device_codes");
846        codes.retain(|d| d.id != id);
847        Ok(())
848    }
849
850    async fn delete_expired(&self) -> Result<u64> {
851        let mut codes = wlock!(self.device_codes, "device_codes");
852        let before = codes.len();
853        let now = Utc::now();
854        codes.retain(|d| d.expires_at > now);
855        Ok((before - codes.len()) as u64)
856    }
857
858    async fn list_by_client(
859        &self,
860        client_id: &str,
861        offset: u32,
862        limit: u32,
863    ) -> Result<Vec<DeviceCode>> {
864        Ok(rlock!(self.device_codes, "device_codes")
865            .iter()
866            .filter(|d| d.client_id == client_id)
867            .skip(offset as usize)
868            .take(limit as usize)
869            .cloned()
870            .collect())
871    }
872}