Skip to main content

arcly_http_identity/oidc/
code.rs

1//! Single-use authorization-code storage.
2
3use std::collections::HashMap;
4use std::sync::RwLock;
5
6use async_trait::async_trait;
7
8use crate::error::Result;
9
10/// A pending authorization code, bound to the client, user, redirect, scope, and
11/// (for PKCE) the code challenge it was issued with.
12#[derive(Debug, Clone)]
13pub struct AuthCode {
14    pub code: String,
15    pub client_id: String,
16    pub user_id: String,
17    pub redirect_uri: String,
18    pub scope: String,
19    pub nonce: Option<String>,
20    pub code_challenge: Option<String>,
21    pub expires_at: u64,
22}
23
24/// Storage for authorization codes. Codes are single-use: [`take`](Self::take)
25/// must atomically fetch-and-delete. Back with Redis (per-key TTL) in prod.
26#[async_trait]
27pub trait AuthCodeStore: Send + Sync + 'static {
28    async fn put(&self, code: AuthCode) -> Result<()>;
29    async fn take(&self, code: &str) -> Result<Option<AuthCode>>;
30}
31
32/// In-memory [`AuthCodeStore`] — tests / dev only.
33#[derive(Default)]
34pub struct MemoryAuthCodeStore {
35    inner: RwLock<HashMap<String, AuthCode>>,
36}
37
38impl MemoryAuthCodeStore {
39    pub fn new() -> Self {
40        Self::default()
41    }
42}
43
44#[async_trait]
45impl AuthCodeStore for MemoryAuthCodeStore {
46    async fn put(&self, code: AuthCode) -> Result<()> {
47        self.inner.write().unwrap().insert(code.code.clone(), code);
48        Ok(())
49    }
50    async fn take(&self, code: &str) -> Result<Option<AuthCode>> {
51        Ok(self.inner.write().unwrap().remove(code))
52    }
53}