kych

OAuth 2.0 API for Swiyu to enable Taler integration of Swiyu for KYC (experimental)
Log | Files | Refs

tokens.rs (3100B)


      1 // Database operations for access_tokens table
      2 
      3 use sqlx::PgPool;
      4 use anyhow::Result;
      5 use uuid::Uuid;
      6 use chrono::{DateTime, Utc};
      7 
      8 /// Access token record
      9 #[derive(Debug, Clone, sqlx::FromRow)]
     10 pub struct AccessToken {
     11     pub id: Uuid,
     12     pub session_id: Uuid,
     13     pub token: String,
     14     pub token_type: String,
     15     pub expires_at: DateTime<Utc>,
     16     pub created_at: DateTime<Utc>,
     17     pub revoked: bool,
     18     pub revoked_at: Option<DateTime<Utc>>,
     19 }
     20 
     21 use super::sessions::SessionStatus;
     22 
     23 /// Data returned by get_token_with_session for backend validation
     24 #[derive(Debug, Clone)]
     25 pub struct TokenWithSessionData {
     26     // Token fields
     27     pub token_id: Uuid,
     28     pub revoked: bool,
     29     // Session fields
     30     pub session_status: SessionStatus,
     31     pub verifiable_credential: Option<serde_json::Value>,
     32 }
     33 
     34 /// Fetch token and session data for /info endpoint
     35 ///
     36 /// Uses UPDATE...SET revoked = revoked (no-op) for row-level locking.
     37 /// Returns all data needed for backend validation.
     38 /// Does NOT filter by revoked status - backend handles validation.
     39 ///
     40 /// Used by the /info endpoint
     41 pub async fn get_token_with_session(
     42     pool: &PgPool,
     43     token: &str,
     44 ) -> Result<Option<TokenWithSessionData>> {
     45     let result = sqlx::query(
     46         r#"
     47         UPDATE oauth2gw.access_tokens t
     48         SET revoked = t.revoked
     49         FROM oauth2gw.verification_sessions s
     50         WHERE t.session_id = s.id
     51           AND t.token = $1
     52           AND t.expires_at > NOW()
     53         RETURNING
     54             t.id AS token_id,
     55             t.revoked,
     56             s.status AS session_status,
     57             s.verifiable_credential
     58         "#
     59     )
     60     .bind(token)
     61     .fetch_optional(pool)
     62     .await?;
     63 
     64     Ok(result.map(|row: sqlx::postgres::PgRow| {
     65         use sqlx::Row;
     66         TokenWithSessionData {
     67             token_id: row.get("token_id"),
     68             revoked: row.get("revoked"),
     69             session_status: row.get("session_status"),
     70             verifiable_credential: row.get("verifiable_credential"),
     71         }
     72     }))
     73 }
     74 
     75 /// Atomically create access token and update session to completed
     76 ///
     77 /// This is the atomicZ operation for /token endpoint.
     78 /// Both operations succeed or fail together.
     79 ///
     80 /// Used by the /token endpoint after validating authorization code
     81 pub async fn create_token_and_complete_session(
     82     pool: &PgPool,
     83     session_id: Uuid,
     84     token: &str,
     85     token_expires_in_seconds: i64,
     86 ) -> Result<AccessToken> {
     87     let access_token = sqlx::query_as::<_, AccessToken>(
     88         r#"
     89         WITH updated_session AS (
     90             UPDATE oauth2gw.verification_sessions
     91             SET status = 'completed', completed_at = NOW()
     92             WHERE id = $1
     93             RETURNING id
     94         )
     95         INSERT INTO oauth2gw.access_tokens (session_id, token, token_type, expires_at)
     96         VALUES ($1, $2, 'Bearer', NOW() + $3 * INTERVAL '1 second')
     97         RETURNING id, session_id, token, token_type, expires_at, created_at, revoked, revoked_at
     98         "#
     99     )
    100     .bind(session_id)
    101     .bind(token)
    102     .bind(token_expires_in_seconds)
    103     .fetch_one(pool)
    104     .await?;
    105 
    106     Ok(access_token)
    107 }