kych

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

clients.rs (6560B)


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