commit 0780ea77a0f829e99a2c92b25c5bc4a67df9fca1
parent c90333cab497dcf2d801255023502464dd76b0ce
Author: Henrique Chan Carvalho Machado <henriqueccmachado@tecnico.ulisboa.pt>
Date: Sun, 7 Dec 2025 16:57:53 +0100
oauth2_gateway: add verification deeplink to authorize response
Diffstat:
5 files changed, 97 insertions(+), 10 deletions(-)
diff --git a/oauth2_gateway/oauth2_gatewaydb/oauth2gw-0001.sql b/oauth2_gateway/oauth2_gatewaydb/oauth2gw-0001.sql
@@ -43,6 +43,7 @@ CREATE TABLE verification_sessions (
nonce VARCHAR(255) UNIQUE NOT NULL,
scope TEXT NOT NULL,
verification_url TEXT,
+ verification_deeplink TEXT,
request_id VARCHAR(255),
verifier_nonce VARCHAR(255),
verifiable_credential JSONB,
diff --git a/oauth2_gateway/src/db/sessions.rs b/oauth2_gateway/src/db/sessions.rs
@@ -51,6 +51,7 @@ pub struct AuthorizeSessionData {
pub expires_at: DateTime<Utc>,
pub scope: String,
pub verification_url: Option<String>,
+ pub verification_deeplink: Option<String>,
pub request_id: Option<String>,
pub verifier_nonce: Option<String>,
// Client fields
@@ -130,6 +131,7 @@ pub async fn get_session_for_authorize(
s.expires_at,
s.scope,
s.verification_url,
+ s.verification_deeplink,
s.request_id,
s.verifier_nonce,
c.verifier_url,
@@ -149,6 +151,7 @@ pub async fn get_session_for_authorize(
expires_at: row.get("expires_at"),
scope: row.get("scope"),
verification_url: row.get("verification_url"),
+ verification_deeplink: row.get("verification_deeplink"),
request_id: row.get("request_id"),
verifier_nonce: row.get("verifier_nonce"),
verifier_url: row.get("verifier_url"),
@@ -219,6 +222,7 @@ pub async fn update_session_authorized(
pool: &PgPool,
session_id: Uuid,
verification_url: &str,
+ verification_deeplink: Option<&str>,
request_id: &str,
verifier_nonce: Option<&str>,
) -> Result<AuthorizedSessionResult> {
@@ -227,14 +231,16 @@ pub async fn update_session_authorized(
UPDATE oauth2gw.verification_sessions
SET status = 'authorized',
verification_url = $1,
- request_id = $2,
- verifier_nonce = $3,
+ verification_deeplink = $2,
+ request_id = $3,
+ verifier_nonce = $4,
authorized_at = NOW()
- WHERE id = $4
+ WHERE id = $5
RETURNING request_id, verification_url
"#
)
.bind(verification_url)
+ .bind(verification_deeplink)
.bind(request_id)
.bind(verifier_nonce)
.bind(session_id)
diff --git a/oauth2_gateway/src/handlers.rs b/oauth2_gateway/src/handlers.rs
@@ -123,9 +123,11 @@ pub async fn authorize(
Query(params): Query<AuthorizeQuery>,
) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
tracing::info!(
- "Authorize request for client: {}, nonce: {}",
+ "Authorize request for client: {}, nonce: {}, state: {}, redirect_uri: {}",
params.client_id,
- nonce
+ nonce,
+ params.state,
+ params.redirect_uri
);
if params.response_type != "code" {
@@ -169,12 +171,35 @@ pub async fn authorize(
));
}
- // Check status for idempotency
+ // Check status for idempotency and completion
match data.status {
SessionStatus::Authorized => {
- // Already authorized - return cached response
tracing::info!(
- "Session {} already authorized, returning cached response",
+ "Session {} already authorized, returning pending status",
+ data.session_id
+ );
+
+ let verification_id = data
+ .request_id
+ .and_then(|id| uuid::Uuid::parse_str(&id).ok())
+ .unwrap_or(uuid::Uuid::nil());
+
+ return Ok((
+ StatusCode::OK,
+ Json(AuthorizeResponse {
+ status: "pending".to_string(),
+ verification_id,
+ verification_url: data.verification_url.clone().unwrap_or_default(),
+ verification_deeplink: data.verification_deeplink,
+ state: params.state.clone(),
+ redirect_uri: None,
+ }),
+ ));
+ }
+
+ SessionStatus::Verified => {
+ tracing::info!(
+ "Session {} verified, returning redirect URI",
data.session_id
);
@@ -183,11 +208,54 @@ pub async fn authorize(
.and_then(|id| uuid::Uuid::parse_str(&id).ok())
.unwrap_or(uuid::Uuid::nil());
+ let auth_code = crate::db::authorization_codes::get_code_by_session(&state.pool, data.session_id)
+ .await
+ .map_err(|e| {
+ tracing::error!("Failed to get authorization code: {}", e);
+ (StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse::new("internal_error")))
+ })?;
+
+ let redirect_url = match auth_code {
+ Some(code) => {
+ let separator = if params.redirect_uri.contains('?') { "&" } else { "?" };
+ format!("{}{}code={}&state={}", params.redirect_uri, separator, code, params.state)
+ }
+ None => {
+ tracing::error!("No authorization code found for verified session");
+ return Err((StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse::new("internal_error"))));
+ }
+ };
+
return Ok((
StatusCode::OK,
Json(AuthorizeResponse {
+ status: "completed".to_string(),
verification_id,
- verification_url: data.verification_url.unwrap_or_default(),
+ verification_url: data.verification_url.clone().unwrap_or_default(),
+ verification_deeplink: data.verification_deeplink,
+ state: params.state.clone(),
+ redirect_uri: Some(redirect_url),
+ }),
+ ));
+ }
+
+ SessionStatus::Failed => {
+ tracing::warn!("Session {} failed", data.session_id);
+
+ let verification_id = data
+ .request_id
+ .and_then(|id| uuid::Uuid::parse_str(&id).ok())
+ .unwrap_or(uuid::Uuid::nil());
+
+ return Ok((
+ StatusCode::OK,
+ Json(AuthorizeResponse {
+ status: "failed".to_string(),
+ verification_id,
+ verification_url: data.verification_url.clone().unwrap_or_default(),
+ verification_deeplink: data.verification_deeplink,
+ state: params.state.clone(),
+ redirect_uri: None,
}),
));
}
@@ -195,6 +263,7 @@ pub async fn authorize(
SessionStatus::Pending => {
// Proceed with authorization
}
+
_ => {
tracing::warn!(
"Session {} in invalid status: {:?}",
@@ -268,6 +337,7 @@ pub async fn authorize(
&state.pool,
data.session_id,
&swiyu_response.verification_url,
+ swiyu_response.verification_deeplink.as_deref(),
&swiyu_response.id.to_string(),
swiyu_response.request_nonce.as_deref(),
)
@@ -289,8 +359,12 @@ pub async fn authorize(
Ok((
StatusCode::OK,
Json(AuthorizeResponse {
+ status: "pending".to_string(),
verification_id: swiyu_response.id,
verification_url: result.verification_url,
+ verification_deeplink: swiyu_response.verification_deeplink,
+ state: params.state.clone(),
+ redirect_uri: None,
}),
))
}
diff --git a/oauth2_gateway/src/models.rs b/oauth2_gateway/src/models.rs
@@ -17,9 +17,15 @@ pub struct AuthorizeQuery {
#[derive(Debug, Deserialize, Serialize)]
pub struct AuthorizeResponse {
+ pub status: String,
#[serde(rename = "verificationId")]
pub verification_id: Uuid,
pub verification_url: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub verification_deeplink: Option<String>,
+ pub state: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub redirect_uri: Option<String>,
}
// Token endpoint
diff --git a/swiyu-verifier/api_requests/post_management_api_verifications.sh b/swiyu-verifier/api_requests/post_management_api_verifications.sh
@@ -25,7 +25,7 @@ base_name="${input_file%.json}"
response_file="${base_name}_response.json"
qr_code_file="${base_name}_qr_code.png"
-curl -X POST http://localhost:8080/management/api/verifications \
+curl -v -X POST http://localhost:8080/management/api/verifications \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d @"$input_file" \