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