kych

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

commit aa1be322bd6184be11df33dd0f293c0f90ed31dd
parent 2eaed97d77e4f1341daaadf4cfe3e66935b84e0e
Author: Henrique Chan Carvalho Machado <henriqueccmachado@tecnico.ulisboa.pt>
Date:   Mon,  8 Dec 2025 20:20:18 +0100

oauth2_gateway: fix scope in /authorize

Diffstat:
Moauth2_gateway/oauth2_gatewaydb/oauth2gw-0001.sql | 3---
Moauth2_gateway/src/bin/client_management_cli.rs | 22++--------------------
Moauth2_gateway/src/db/clients.rs | 26+++++++++-----------------
Moauth2_gateway/src/db/sessions.rs | 7+++++--
Moauth2_gateway/src/handlers.rs | 6++++--
Moauth2_gateway/src/models.rs | 1+
6 files changed, 21 insertions(+), 44 deletions(-)

diff --git a/oauth2_gateway/oauth2_gatewaydb/oauth2gw-0001.sql b/oauth2_gateway/oauth2_gatewaydb/oauth2gw-0001.sql @@ -16,7 +16,6 @@ CREATE TABLE IF NOT EXISTS clients ( webhook_url TEXT NOT NULL, verifier_url TEXT NOT NULL, verifier_management_api_path VARCHAR(255) DEFAULT '/management/api/verifications', - scope TEXT NOT NULL DEFAULT 'age_over_18', created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); @@ -32,8 +31,6 @@ COMMENT ON COLUMN clients.verifier_url IS 'Client URL where oauth2 gateway will callback'; COMMENT ON COLUMN clients.verifier_management_api_path IS 'Swiyu verifier api endpoint to create verification requests'; -COMMENT ON COLUMN clients.scope - IS 'Space-delimited OAuth2 scope (requested verification attributes, e.g., "first_name last_name age_over_18")'; CREATE INDEX IF NOT EXISTS idx_clients_client_id ON clients(client_id); diff --git a/oauth2_gateway/src/bin/client_management_cli.rs b/oauth2_gateway/src/bin/client_management_cli.rs @@ -54,10 +54,6 @@ enum Commands { /// Verifier management API path (default: /management/api/verifications) #[arg(long)] verifier_api_path: Option<String>, - - /// OAuth2 scope (space-delimited attributes, default: "age_over_18") - #[arg(long)] - scope: Option<String>, }, Update { @@ -71,9 +67,6 @@ enum Commands { #[arg(long)] verifier_api_path: Option<String>, - - #[arg(long)] - scope: Option<String>, }, /// Delete a client (WARNING: cascades to all sessions) @@ -108,7 +101,6 @@ async fn main() -> Result<()> { webhook_url, verifier_url, verifier_api_path, - scope, } => { cmd_create_client( &pool, @@ -117,7 +109,6 @@ async fn main() -> Result<()> { &webhook_url, &verifier_url, verifier_api_path.as_deref(), - scope.as_deref(), ) .await? } @@ -126,7 +117,6 @@ async fn main() -> Result<()> { webhook_url, verifier_url, verifier_api_path, - scope, } => { cmd_update_client( &pool, @@ -134,7 +124,6 @@ async fn main() -> Result<()> { webhook_url.as_deref(), verifier_url.as_deref(), verifier_api_path.as_deref(), - scope.as_deref(), ) .await? } @@ -181,7 +170,6 @@ async fn cmd_show_client(pool: &sqlx::PgPool, client_id: &str) -> Result<()> { println!("Webhook URL: {}", client.webhook_url); println!("Verifier URL: {}", client.verifier_url); println!("Verifier API Path: {}", client.verifier_management_api_path); - println!("Scope: {}", client.scope); println!("Created: {}", client.created_at); println!("Updated: {}", client.updated_at); @@ -195,7 +183,6 @@ async fn cmd_create_client( webhook_url: &str, verifier_url: &str, verifier_api_path: Option<&str>, - scope: Option<&str>, ) -> Result<()> { let client = db::clients::register_client( pool, @@ -204,7 +191,6 @@ async fn cmd_create_client( webhook_url, verifier_url, verifier_api_path, - scope, ) .await .context("Failed to create client")?; @@ -214,7 +200,6 @@ async fn cmd_create_client( println!("UUID: {}", client.id); println!("Client ID: {}", client.client_id); println!("Webhook URL: {}", client.webhook_url); - println!("Scope: {}", client.scope); Ok(()) } @@ -225,10 +210,9 @@ async fn cmd_update_client( webhook_url: Option<&str>, verifier_url: Option<&str>, verifier_api_path: Option<&str>, - scope: Option<&str>, ) -> Result<()> { - if webhook_url.is_none() && verifier_url.is_none() && verifier_api_path.is_none() && scope.is_none() { - anyhow::bail!("No fields to update. Specify at least one of: --webhook-url, --verifier-url, --verifier-api-path, --scope"); + if webhook_url.is_none() && verifier_url.is_none() && verifier_api_path.is_none() { + anyhow::bail!("No fields to update. Specify at least one of: --webhook-url, --verifier-url, --verifier-api-path"); } let client = db::clients::get_client_by_id(pool, client_id) @@ -241,7 +225,6 @@ async fn cmd_update_client( webhook_url, verifier_url, verifier_api_path, - scope, ) .await .context("Failed to update client")?; @@ -253,7 +236,6 @@ async fn cmd_update_client( println!("Webhook URL: {}", updated.webhook_url); println!("Verifier URL: {}", updated.verifier_url); println!("Verifier API Path: {}", updated.verifier_management_api_path); - println!("Scope: {}", updated.scope); Ok(()) } diff --git a/oauth2_gateway/src/db/clients.rs b/oauth2_gateway/src/db/clients.rs @@ -14,7 +14,6 @@ pub struct Client { pub webhook_url: String, pub verifier_url: String, pub verifier_management_api_path: String, - pub scope: String, pub created_at: DateTime<Utc>, pub updated_at: DateTime<Utc>, } @@ -27,21 +26,19 @@ pub async fn register_client( webhook_url: &str, verifier_url: &str, verifier_management_api_path: Option<&str>, - scope: Option<&str>, ) -> Result<Client> { let api_path = verifier_management_api_path .unwrap_or("/management/api/verifications"); - let scope_value = scope.unwrap_or("age_over_18"); let secret_hash = bcrypt::hash(client_secret, bcrypt::DEFAULT_COST)?; let client = sqlx::query_as::<_, Client>( r#" INSERT INTO oauth2gw.clients - (client_id, secret_hash, webhook_url, verifier_url, verifier_management_api_path, scope) - VALUES ($1, $2, $3, $4, $5, $6) + (client_id, secret_hash, webhook_url, verifier_url, verifier_management_api_path) + VALUES ($1, $2, $3, $4, $5) RETURNING id, client_id, secret_hash, webhook_url, verifier_url, - verifier_management_api_path, scope, created_at, updated_at + verifier_management_api_path, created_at, updated_at "# ) .bind(client_id) @@ -49,7 +46,6 @@ pub async fn register_client( .bind(webhook_url) .bind(verifier_url) .bind(api_path) - .bind(scope_value) .fetch_one(pool) .await?; @@ -64,7 +60,7 @@ pub async fn get_client_by_id( let client = sqlx::query_as::<_, Client>( r#" SELECT id, client_id, secret_hash, webhook_url, verifier_url, - verifier_management_api_path, scope, created_at, updated_at + verifier_management_api_path, created_at, updated_at FROM oauth2gw.clients WHERE client_id = $1 "# @@ -84,7 +80,7 @@ pub async fn get_client_by_uuid( let client = sqlx::query_as::<_, Client>( r#" SELECT id, client_id, secret_hash, webhook_url, verifier_url, - verifier_management_api_path, scope, created_at, updated_at + verifier_management_api_path, created_at, updated_at FROM oauth2gw.clients WHERE id = $1 "# @@ -128,7 +124,7 @@ pub async fn authenticate_client( let client = sqlx::query_as::<_, Client>( r#" SELECT id, client_id, secret_hash, webhook_url, verifier_url, - verifier_management_api_path, scope, created_at, updated_at + verifier_management_api_path, created_at, updated_at FROM oauth2gw.clients WHERE client_id = $1 "# @@ -156,7 +152,6 @@ pub async fn update_client( webhook_url: Option<&str>, verifier_url: Option<&str>, verifier_management_api_path: Option<&str>, - scope: Option<&str>, ) -> Result<Client> { let current = get_client_by_uuid(pool, id).await? .ok_or_else(|| anyhow::anyhow!("Client not found"))?; @@ -165,7 +160,6 @@ pub async fn update_client( let new_verifier_url = verifier_url.unwrap_or(&current.verifier_url); let new_verifier_api_path = verifier_management_api_path .unwrap_or(&current.verifier_management_api_path); - let new_scope = scope.unwrap_or(&current.scope); let client = sqlx::query_as::<_, Client>( r#" @@ -174,17 +168,15 @@ pub async fn update_client( webhook_url = $1, verifier_url = $2, verifier_management_api_path = $3, - scope = $4, updated_at = CURRENT_TIMESTAMP - WHERE id = $5 + WHERE id = $4 RETURNING id, client_id, secret_hash, webhook_url, verifier_url, - verifier_management_api_path, scope, created_at, updated_at + verifier_management_api_path, created_at, updated_at "# ) .bind(new_webhook_url) .bind(new_verifier_url) .bind(new_verifier_api_path) - .bind(new_scope) .bind(id) .fetch_one(pool) .await?; @@ -217,7 +209,7 @@ pub async fn list_clients(pool: &PgPool) -> Result<Vec<Client>> { let clients = sqlx::query_as::<_, Client>( r#" SELECT id, client_id, secret_hash, webhook_url, verifier_url, - verifier_management_api_path, scope, created_at, updated_at + verifier_management_api_path, created_at, updated_at FROM oauth2gw.clients ORDER BY created_at DESC "# diff --git a/oauth2_gateway/src/db/sessions.rs b/oauth2_gateway/src/db/sessions.rs @@ -89,7 +89,7 @@ pub async fn create_session( r#" INSERT INTO oauth2gw.verification_sessions (client_id, nonce, scope, expires_at, status) - SELECT c.id, $1, c.scope, NOW() + $2 * INTERVAL '1 minute', 'pending' + SELECT c.id, $1, '', NOW() + $2 * INTERVAL '1 minute', 'pending' FROM oauth2gw.clients c WHERE c.client_id = $3 RETURNING @@ -110,17 +110,19 @@ pub async fn create_session( /// Fetch session and client data for /authorize endpoint /// /// Returns all data needed for backend validation and idempotent responses. +/// Updates the session scope with the provided scope parameter. /// /// Used by the /authorize endpoint pub async fn get_session_for_authorize( pool: &PgPool, nonce: &str, client_id: &str, + scope: &str, ) -> Result<Option<AuthorizeSessionData>> { let result = sqlx::query( r#" UPDATE oauth2gw.verification_sessions s - SET status = s.status + SET scope = $3 FROM oauth2gw.clients c WHERE s.client_id = c.id AND s.nonce = $1 @@ -140,6 +142,7 @@ pub async fn get_session_for_authorize( ) .bind(nonce) .bind(client_id) + .bind(scope) .fetch_optional(pool) .await?; diff --git a/oauth2_gateway/src/handlers.rs b/oauth2_gateway/src/handlers.rs @@ -123,11 +123,12 @@ pub async fn authorize( Query(params): Query<AuthorizeQuery>, ) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> { tracing::info!( - "Authorize request for client: {}, nonce: {}, state: {}, redirect_uri: {}", + "Authorize request for client: {}, nonce: {}, state: {}, redirect_uri: {}, scope: {}", params.client_id, nonce, params.state, - params.redirect_uri + params.redirect_uri, + params.scope ); if params.response_type != "code" { @@ -141,6 +142,7 @@ pub async fn authorize( &state.pool, &nonce, &params.client_id, + &params.scope, ) .await .map_err(|e| { diff --git a/oauth2_gateway/src/models.rs b/oauth2_gateway/src/models.rs @@ -13,6 +13,7 @@ pub struct AuthorizeQuery { pub client_id: String, pub redirect_uri: String, pub state: String, + pub scope: String, } #[derive(Debug, Deserialize, Serialize)]