use axum::Json;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use serde::{Deserialize, Serialize};
use systemprompt_identifiers::{ChallengeId, UserId};
use systemprompt_oauth::OAuthState;
use tracing::instrument;
use webauthn_rs::prelude::RegisterPublicKeyCredential;
use crate::routes::oauth::{OAuthHttpError, internal};
#[derive(Debug, Deserialize)]
pub struct FinishLinkRequest {
pub challenge_id: ChallengeId,
pub token: String,
pub credential: RegisterPublicKeyCredential,
}
#[derive(Debug, Serialize)]
pub(super) struct FinishLinkResponse {
pub success: bool,
pub user_id: UserId,
pub message: String,
}
#[instrument(skip(state, request), fields(challenge_id = %request.challenge_id))]
pub async fn finish_link(
State(state): State<OAuthState>,
Json(request): Json<FinishLinkRequest>,
) -> Result<Response, OAuthHttpError> {
let webauthn_service = state.webauthn()?;
let user_id = webauthn_service
.finish_registration_with_token(&request.challenge_id, &request.token, &request.credential)
.await
.map_err(|e| internal::reclassify(e, OAuthHttpError::link_failed))?;
tracing::info!(user_id = %user_id, "Credential linked successfully");
Ok((
StatusCode::OK,
Json(FinishLinkResponse {
success: true,
user_id,
message: "Passkey registered successfully".to_owned(),
}),
)
.into_response())
}