geforkt von mirrored/vaultwarden
Fix recover-2fa not working.
When audit logging was introduced there entered a small bug preventing the recover-2fa from working. This PR fixes that by add a new headers check to extract the device-type when possible and use that for the logging. Fixes #2985
Dieser Commit ist enthalten in:
Ursprung
d0b53a6a3d
Commit
6154e03c05
3 geänderte Dateien mit 41 neuen und 10 gelöschten Zeilen
|
@ -6,7 +6,7 @@ use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{core::log_user_event, JsonResult, JsonUpcase, NumberOrString, PasswordData},
|
api::{core::log_user_event, JsonResult, JsonUpcase, NumberOrString, PasswordData},
|
||||||
auth::{ClientIp, Headers},
|
auth::{ClientHeaders, ClientIp, Headers},
|
||||||
crypto,
|
crypto,
|
||||||
db::{models::*, DbConn, DbPool},
|
db::{models::*, DbConn, DbPool},
|
||||||
mail, CONFIG,
|
mail, CONFIG,
|
||||||
|
@ -73,7 +73,12 @@ struct RecoverTwoFactor {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/two-factor/recover", data = "<data>")]
|
#[post("/two-factor/recover", data = "<data>")]
|
||||||
async fn recover(data: JsonUpcase<RecoverTwoFactor>, headers: Headers, mut conn: DbConn, ip: ClientIp) -> JsonResult {
|
async fn recover(
|
||||||
|
data: JsonUpcase<RecoverTwoFactor>,
|
||||||
|
client_headers: ClientHeaders,
|
||||||
|
mut conn: DbConn,
|
||||||
|
ip: ClientIp,
|
||||||
|
) -> JsonResult {
|
||||||
let data: RecoverTwoFactor = data.into_inner().data;
|
let data: RecoverTwoFactor = data.into_inner().data;
|
||||||
|
|
||||||
use crate::db::models::User;
|
use crate::db::models::User;
|
||||||
|
@ -97,7 +102,7 @@ async fn recover(data: JsonUpcase<RecoverTwoFactor>, headers: Headers, mut conn:
|
||||||
// Remove all twofactors from the user
|
// Remove all twofactors from the user
|
||||||
TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?;
|
TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?;
|
||||||
|
|
||||||
log_user_event(EventType::UserRecovered2fa as i32, &user.uuid, headers.device.atype, &ip.ip, &mut conn).await;
|
log_user_event(EventType::UserRecovered2fa as i32, &user.uuid, client_headers.device_type, &ip.ip, &mut conn).await;
|
||||||
|
|
||||||
// Remove the recovery code, not needed without twofactors
|
// Remove the recovery code, not needed without twofactors
|
||||||
user.totp_recover = None;
|
user.totp_recover = None;
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::{
|
||||||
core::two_factor::{duo, email, email::EmailTokenData, yubikey},
|
core::two_factor::{duo, email, email::EmailTokenData, yubikey},
|
||||||
ApiResult, EmptyResult, JsonResult, JsonUpcase,
|
ApiResult, EmptyResult, JsonResult, JsonUpcase,
|
||||||
},
|
},
|
||||||
auth::ClientIp,
|
auth::{ClientHeaders, ClientIp},
|
||||||
db::{models::*, DbConn},
|
db::{models::*, DbConn},
|
||||||
error::MapResult,
|
error::MapResult,
|
||||||
mail, util, CONFIG,
|
mail, util, CONFIG,
|
||||||
|
@ -25,11 +25,10 @@ pub fn routes() -> Vec<Route> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/connect/token", data = "<data>")]
|
#[post("/connect/token", data = "<data>")]
|
||||||
async fn login(data: Form<ConnectData>, mut conn: DbConn, ip: ClientIp) -> JsonResult {
|
async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn: DbConn, ip: ClientIp) -> JsonResult {
|
||||||
let data: ConnectData = data.into_inner();
|
let data: ConnectData = data.into_inner();
|
||||||
|
|
||||||
let mut user_uuid: Option<String> = None;
|
let mut user_uuid: Option<String> = None;
|
||||||
let device_type = data.device_type.clone();
|
|
||||||
|
|
||||||
let login_result = match data.grant_type.as_ref() {
|
let login_result = match data.grant_type.as_ref() {
|
||||||
"refresh_token" => {
|
"refresh_token" => {
|
||||||
|
@ -59,15 +58,20 @@ async fn login(data: Form<ConnectData>, mut conn: DbConn, ip: ClientIp) -> JsonR
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(user_uuid) = user_uuid {
|
if let Some(user_uuid) = user_uuid {
|
||||||
// When unknown or unable to parse, return 14, which is 'Unknown Browser'
|
|
||||||
let device_type = util::try_parse_string(device_type).unwrap_or(14);
|
|
||||||
match &login_result {
|
match &login_result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
log_user_event(EventType::UserLoggedIn as i32, &user_uuid, device_type, &ip.ip, &mut conn).await;
|
log_user_event(
|
||||||
|
EventType::UserLoggedIn as i32,
|
||||||
|
&user_uuid,
|
||||||
|
client_header.device_type,
|
||||||
|
&ip.ip,
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let Some(ev) = e.get_event() {
|
if let Some(ev) = e.get_event() {
|
||||||
log_user_event(ev.event as i32, &user_uuid, device_type, &ip.ip, &mut conn).await
|
log_user_event(ev.event as i32, &user_uuid, client_header.device_type, &ip.ip, &mut conn).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
22
src/auth.rs
22
src/auth.rs
|
@ -315,6 +315,28 @@ impl<'r> FromRequest<'r> for Host {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ClientHeaders {
|
||||||
|
pub host: String,
|
||||||
|
pub device_type: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'r> FromRequest<'r> for ClientHeaders {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
|
let host = try_outcome!(Host::from_request(request).await).host;
|
||||||
|
// When unknown or unable to parse, return 14, which is 'Unknown Browser'
|
||||||
|
let device_type: i32 =
|
||||||
|
request.headers().get_one("device-type").map(|d| d.parse().unwrap_or(14)).unwrap_or_else(|| 14);
|
||||||
|
|
||||||
|
Outcome::Success(ClientHeaders {
|
||||||
|
host,
|
||||||
|
device_type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Headers {
|
pub struct Headers {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub device: Device,
|
pub device: Device,
|
||||||
|
|
Laden …
In neuem Issue referenzieren