kych

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

clients.rs (6212B)


      1 // Database operations for clients table
      2 
      3 use sqlx::PgPool;
      4 use anyhow::Result;
      5 use uuid::Uuid;
      6 use chrono::{DateTime, Utc};
      7 
      8 /// Client registration record
      9 #[derive(Debug, Clone, sqlx::FromRow)]
     10 pub struct Client {
     11     pub id: Uuid,
     12     pub client_id: String,
     13     pub secret_hash: String,
     14     pub verifier_url: String,
     15     pub verifier_management_api_path: String,
     16     pub redirect_uri: String,
     17     pub accepted_issuer_dids: Option<String>,
     18     pub created_at: DateTime<Utc>,
     19     pub updated_at: DateTime<Utc>,
     20 }
     21 
     22 /// Register a new client
     23 pub async fn register_client(
     24     pool: &PgPool,
     25     client_id: &str,
     26     client_secret: &str,
     27     verifier_url: &str,
     28     verifier_management_api_path: Option<&str>,
     29     redirect_uri: &str,
     30     accepted_issuer_dids: Option<&str>,
     31 ) -> Result<Client> {
     32     let api_path = verifier_management_api_path
     33         .unwrap_or("/management/api/verifications");
     34 
     35     let secret_hash = bcrypt::hash(client_secret, bcrypt::DEFAULT_COST)?;
     36 
     37     let client = sqlx::query_as::<_, Client>(
     38         r#"
     39         INSERT INTO oauth2gw.clients
     40             (client_id, secret_hash, verifier_url, verifier_management_api_path, redirect_uri, accepted_issuer_dids)
     41         VALUES ($1, $2, $3, $4, $5, $6)
     42         RETURNING id, client_id, secret_hash, verifier_url,
     43                   verifier_management_api_path, redirect_uri, accepted_issuer_dids, created_at, updated_at
     44         "#
     45     )
     46     .bind(client_id)
     47     .bind(secret_hash)
     48     .bind(verifier_url)
     49     .bind(api_path)
     50     .bind(redirect_uri)
     51     .bind(accepted_issuer_dids)
     52     .fetch_one(pool)
     53     .await?;
     54 
     55     Ok(client)
     56 }
     57 
     58 /// Lookup client by client_id
     59 pub async fn get_client_by_id(
     60     pool: &PgPool,
     61     client_id: &str
     62 ) -> Result<Option<Client>> {
     63     let client = sqlx::query_as::<_, Client>(
     64         r#"
     65         SELECT id, client_id, secret_hash, verifier_url,
     66                verifier_management_api_path, redirect_uri, accepted_issuer_dids, created_at, updated_at
     67         FROM oauth2gw.clients
     68         WHERE client_id = $1
     69         "#
     70     )
     71     .bind(client_id)
     72     .fetch_optional(pool)
     73     .await?;
     74 
     75     Ok(client)
     76 }
     77 
     78 /// Lookup client by UUID
     79 pub async fn get_client_by_uuid(
     80     pool: &PgPool,
     81     id: Uuid
     82 ) -> Result<Option<Client>> {
     83     let client = sqlx::query_as::<_, Client>(
     84         r#"
     85         SELECT id, client_id, secret_hash, verifier_url,
     86                verifier_management_api_path, redirect_uri, accepted_issuer_dids, created_at, updated_at
     87         FROM oauth2gw.clients
     88         WHERE id = $1
     89         "#
     90     )
     91     .bind(id)
     92     .fetch_optional(pool)
     93     .await?;
     94 
     95     Ok(client)
     96 }
     97 
     98 /// Get client secret hash by client_id
     99 ///
    100 /// Returns the secret hash if client exists, None otherwise
    101 pub async fn get_client_secret_hash(
    102     pool: &PgPool,
    103     client_id: &str,
    104 ) -> Result<Option<String>> {
    105     let result = sqlx::query_scalar::<_, String>(
    106         r#"
    107         SELECT secret_hash
    108         FROM oauth2gw.clients
    109         WHERE client_id = $1
    110         "#
    111     )
    112     .bind(client_id)
    113     .fetch_optional(pool)
    114     .await?;
    115 
    116     Ok(result)
    117 }
    118 
    119 /// Authenticate a client by validating client_secret
    120 ///
    121 /// Returns the client if authentication succeeds, None otherwise
    122 pub async fn authenticate_client(
    123     pool: &PgPool,
    124     client_id: &str,
    125     client_secret: &str
    126 ) -> Result<Option<Client>> {
    127     let client = sqlx::query_as::<_, Client>(
    128         r#"
    129         SELECT id, client_id, secret_hash, verifier_url,
    130                verifier_management_api_path, redirect_uri, accepted_issuer_dids, created_at, updated_at
    131         FROM oauth2gw.clients
    132         WHERE client_id = $1
    133         "#
    134     )
    135     .bind(client_id)
    136     .fetch_optional(pool)
    137     .await?;
    138 
    139     match client {
    140         Some(c) => {
    141             if bcrypt::verify(client_secret, &c.secret_hash)? {
    142                 Ok(Some(c))
    143             } else {
    144                 Ok(None)
    145             }
    146         }
    147         None => Ok(None)
    148     }
    149 }
    150 
    151 /// Update client configuration
    152 pub async fn update_client(
    153     pool: &PgPool,
    154     id: Uuid,
    155     verifier_url: Option<&str>,
    156     verifier_management_api_path: Option<&str>,
    157     redirect_uri: Option<&str>,
    158     accepted_issuer_dids: Option<&str>,
    159 ) -> Result<Client> {
    160     let current = get_client_by_uuid(pool, id).await?
    161         .ok_or_else(|| anyhow::anyhow!("Client not found"))?;
    162 
    163     let new_verifier_url = verifier_url.unwrap_or(&current.verifier_url);
    164     let new_verifier_api_path = verifier_management_api_path
    165         .unwrap_or(&current.verifier_management_api_path);
    166     let new_redirect_uri = redirect_uri.unwrap_or(&current.redirect_uri);
    167     let new_accepted_issuer_dids = accepted_issuer_dids.or(current.accepted_issuer_dids.as_deref());
    168 
    169     let client = sqlx::query_as::<_, Client>(
    170         r#"
    171         UPDATE oauth2gw.clients
    172         SET
    173             verifier_url = $1,
    174             verifier_management_api_path = $2,
    175             redirect_uri = $3,
    176             accepted_issuer_dids = $4,
    177             updated_at = CURRENT_TIMESTAMP
    178         WHERE id = $5
    179         RETURNING id, client_id, secret_hash, verifier_url,
    180                   verifier_management_api_path, redirect_uri, accepted_issuer_dids, created_at, updated_at
    181         "#
    182     )
    183     .bind(new_verifier_url)
    184     .bind(new_verifier_api_path)
    185     .bind(new_redirect_uri)
    186     .bind(new_accepted_issuer_dids)
    187     .bind(id)
    188     .fetch_one(pool)
    189     .await?;
    190 
    191     Ok(client)
    192 }
    193 
    194 /// Delete a client (and cascade delete all associated sessions)
    195 /// 
    196 /// WARNING: Deletes all associated data as well! (e.g., verifications)!
    197 pub async fn delete_client(
    198     pool: &PgPool,
    199     id: Uuid
    200 ) -> Result<bool> {
    201     let result = sqlx::query(
    202         r#"
    203         DELETE FROM oauth2gw.clients
    204         WHERE id = $1
    205         "#
    206     )
    207     .bind(id)
    208     .execute(pool)
    209     .await?;
    210 
    211     Ok(result.rows_affected() > 0)
    212 }
    213 
    214 /// List all registered clients
    215 pub async fn list_clients(pool: &PgPool) -> Result<Vec<Client>> {
    216     let clients = sqlx::query_as::<_, Client>(
    217         r#"
    218         SELECT id, client_id, secret_hash, verifier_url,
    219                verifier_management_api_path, redirect_uri, accepted_issuer_dids, created_at, updated_at
    220         FROM oauth2gw.clients
    221         ORDER BY created_at DESC
    222         "#
    223     )
    224     .fetch_all(pool)
    225     .await?;
    226 
    227     Ok(clients)
    228 }