diff --git a/Cargo.lock b/Cargo.lock index 4d03f09a..08661388 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4027,7 +4027,7 @@ dependencies = [ [[package]] name = "rauthy" -version = "0.19.1-20231123" +version = "0.19.2-20231123" dependencies = [ "actix-service", "actix-web", @@ -4066,7 +4066,7 @@ dependencies = [ [[package]] name = "rauthy-common" -version = "0.19.1-20231123" +version = "0.19.2-20231123" dependencies = [ "actix-multipart", "actix-web", @@ -4103,7 +4103,7 @@ dependencies = [ [[package]] name = "rauthy-handlers" -version = "0.19.1-20231123" +version = "0.19.2-20231123" dependencies = [ "actix", "actix-multipart", @@ -4135,7 +4135,7 @@ dependencies = [ [[package]] name = "rauthy-models" -version = "0.19.1-20231123" +version = "0.19.2-20231123" dependencies = [ "accept-language", "actix", @@ -4198,7 +4198,7 @@ dependencies = [ [[package]] name = "rauthy-notify" -version = "0.19.1-20231123" +version = "0.19.2-20231123" dependencies = [ "async-trait", "chrono", @@ -4216,7 +4216,7 @@ dependencies = [ [[package]] name = "rauthy-service" -version = "0.19.1-20231123" +version = "0.19.2-20231123" dependencies = [ "actix-web", "argon2", diff --git a/Cargo.toml b/Cargo.toml index 68fedf90..3a11b631 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ exclude = ["rauthy-client"] [workspace.package] -version = "0.19.1-20231123" +version = "0.19.2-20231123" edition = "2021" authors = ["Sebastian Dobe "] license = "Apache-2.0" diff --git a/rauthy-handlers/src/middleware/principal.rs b/rauthy-handlers/src/middleware/principal.rs index ebfd668d..2a42c021 100644 --- a/rauthy-handlers/src/middleware/principal.rs +++ b/rauthy-handlers/src/middleware/principal.rs @@ -71,7 +71,6 @@ where principal.session = Some(s); } - // req.extensions_mut().insert(session); req.extensions_mut().insert(principal); service.call(req).await diff --git a/rauthy-handlers/src/users.rs b/rauthy-handlers/src/users.rs index e7e79e25..b98f8387 100644 --- a/rauthy-handlers/src/users.rs +++ b/rauthy-handlers/src/users.rs @@ -557,18 +557,25 @@ pub async fn get_user_webauthn_passkeys( pub async fn post_webauthn_auth_start( data: web::Data, id: web::Path, + // The principal here must be optional to make cases like user password reset in a + // fully new / different browser which does not have any lefter data or cookies principal: ReqPrincipal, req: HttpRequest, req_data: Json, ) -> Result { - principal.validate_session_auth_or_init()?; - let purpose = req_data.into_inner().purpose; let id = match purpose { // only for a Login purpose, this can be accessed without authentication (yet) - MfaPurpose::Login(_) => id.into_inner(), + MfaPurpose::Login(_) => { + // During Login, the session is allowed to be in init only state + principal.validate_session_auth_or_init()?; + id.into_inner() + } MfaPurpose::PasswordReset => { + // A password reset webauthn req can be opened without any session at all. + // This is mandatory to make password reset flows fully work, even with an old + // account with linked Passkeys. match req.cookie(PWD_RESET_COOKIE) { None => { return Err(ErrorResponse::new( @@ -591,6 +598,7 @@ pub async fn post_webauthn_auth_start( } _ => { + // for all other purposes, we need an authenticated session principal.validate_session_auth()?; // make sure the principal is this very user @@ -624,27 +632,15 @@ pub async fn post_webauthn_auth_start( pub async fn post_webauthn_auth_finish( data: web::Data, id: web::Path, - principal: ReqPrincipal, req_data: Json, ) -> Result { let id = id.into_inner(); - let res = if principal.validate_session_init().is_ok() { - // The Session is only in init state in a very tiny window, when the /oidc/authorize page has - // been received and until the credentials have been validated. - // As a double check, we have the 'code' from the /start endpoint. - webauthn::auth_finish(&data, id, req_data.into_inner()).await? - } else if principal.validate_session_auth().is_ok() { - // For any authenticated request, validate that Principal matches the user. - principal.is_user(&id)?; - webauthn::auth_finish(&data, id, req_data.into_inner()).await? - } else { - return Err(ErrorResponse::new( - ErrorResponseType::Unauthorized, - "No valid session".to_string(), - )); - }; + // We do not need to further validate the principal here. + // All of this is done at the /start endpoint. + // This here will simply fail, if the secret code from the /start does not exist. + let res = webauthn::auth_finish(&data, id, req_data.into_inner()).await?; Ok(res.into_response()) } diff --git a/rauthy-models/src/entity/users.rs b/rauthy-models/src/entity/users.rs index 296dcf27..944e54ad 100644 --- a/rauthy-models/src/entity/users.rs +++ b/rauthy-models/src/entity/users.rs @@ -423,7 +423,6 @@ impl User { id: String, upd_user: UpdateUserRequest, user: Option, - // OK((User, is_new_rauthy_admin)) ) -> Result<(User, bool), ErrorResponse> { let mut user = match user { None => User::find(data, id).await?, diff --git a/rauthy-models/src/entity/webauthn.rs b/rauthy-models/src/entity/webauthn.rs index 34fe0429..fab7ce0a 100644 --- a/rauthy-models/src/entity/webauthn.rs +++ b/rauthy-models/src/entity/webauthn.rs @@ -728,12 +728,12 @@ pub async fn auth_finish( user_id: String, req: WebauthnAuthFinishRequest, ) -> Result { - let mut user = User::find(data, user_id).await?; - let force_uv = user.account_type() == AccountType::Passkey || *WEBAUTHN_FORCE_UV; - let auth_data = WebauthnData::find(data, req.code).await?; let auth_state = serde_json::from_str(&auth_data.auth_state_json).unwrap(); + let mut user = User::find(data, user_id).await?; + let force_uv = user.account_type() == AccountType::Passkey || *WEBAUTHN_FORCE_UV; + let pks = PasskeyEntity::find_for_user(data, &user.id).await?; match data diff --git a/rauthy-service/src/password_reset.rs b/rauthy-service/src/password_reset.rs index 791a9f59..c4daa7ac 100644 --- a/rauthy-service/src/password_reset.rs +++ b/rauthy-service/src/password_reset.rs @@ -7,6 +7,7 @@ use rauthy_models::app_state::AppState; use rauthy_models::entity::colors::ColorEntity; use rauthy_models::entity::magic_links::{MagicLink, MagicLinkUsage}; use rauthy_models::entity::password::PasswordPolicy; +use rauthy_models::entity::sessions::Session; use rauthy_models::entity::users::User; use rauthy_models::entity::webauthn; use rauthy_models::entity::webauthn::WebauthnServiceReq; @@ -218,6 +219,9 @@ pub async fn handle_put_user_password_reset<'a>( .await .unwrap(); + // delete all existing user sessions to have a clean flow + Session::invalidate_for_user(data, &user.id).await?; + // delete the cookie let cookie = cookie::Cookie::build(PWD_RESET_COOKIE, "") .secure(true)