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 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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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}