fix: verify authenticated user in passkey add finish

Add AuthenticatedUser extractor to passkey_add_finish and verify the
session user matches the challenge owner, preventing one user from
completing another user's passkey registration.
This commit is contained in:
Jon Seager 2026-02-13 13:05:14 +00:00
parent 55f2de1e92
commit 0bf30d3475
No known key found for this signature in database

View file

@ -8,7 +8,7 @@ use tracing::{error, info, warn};
use uuid::Uuid; use uuid::Uuid;
use webauthn_rs::prelude::*; use webauthn_rs::prelude::*;
use crate::application::auth::AuthenticatedUser; use crate::application::auth::{AuthenticatedUser, SESSION_COOKIE_NAME};
use crate::application::state::AppState; use crate::application::state::AppState;
use crate::domain::passkey_credentials::NewPasskeyCredential; use crate::domain::passkey_credentials::NewPasskeyCredential;
use crate::domain::sessions::NewSession; use crate::domain::sessions::NewSession;
@ -17,8 +17,6 @@ use crate::domain::users::NewUser;
use crate::infrastructure::auth::{generate_session_token, generate_token, hash_token}; use crate::infrastructure::auth::{generate_session_token, generate_token, hash_token};
use crate::infrastructure::webauthn::CliCallbackInfo; use crate::infrastructure::webauthn::CliCallbackInfo;
const SESSION_COOKIE_NAME: &str = "brewlog_session";
// --- Request/Response types --- // --- Request/Response types ---
#[derive(Deserialize)] #[derive(Deserialize)]
@ -432,9 +430,10 @@ pub(crate) async fn passkey_add_start(
})) }))
} }
#[tracing::instrument(skip(state, payload))] #[tracing::instrument(skip(state, auth_user, payload))]
pub(crate) async fn passkey_add_finish( pub(crate) async fn passkey_add_finish(
State(state): State<AppState>, State(state): State<AppState>,
auth_user: AuthenticatedUser,
Json(payload): Json<PasskeyAddFinishRequest>, Json(payload): Json<PasskeyAddFinishRequest>,
) -> Result<StatusCode, StatusCode> { ) -> Result<StatusCode, StatusCode> {
let (user_id, reg_state) = state let (user_id, reg_state) = state
@ -443,6 +442,16 @@ pub(crate) async fn passkey_add_finish(
.await .await
.ok_or(StatusCode::BAD_REQUEST)?; .ok_or(StatusCode::BAD_REQUEST)?;
// Verify the authenticated user matches the challenge owner
if auth_user.0.id != user_id {
warn!(
auth_user_id = %auth_user.0.id,
challenge_user_id = %user_id,
"passkey add finish: user mismatch"
);
return Err(StatusCode::FORBIDDEN);
}
let passkey = state let passkey = state
.webauthn .webauthn
.finish_passkey_registration(&payload.credential, &reg_state) .finish_passkey_registration(&payload.credential, &reg_state)