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
This commit is contained in:
BlackDex 2022-12-15 15:57:30 +01:00 committed by Daniel García
parent b60a4a68c7
commit 8e5f03972e
No known key found for this signature in database
GPG Key ID: FC8A7D14C3CD543A
3 changed files with 41 additions and 10 deletions

View File

@ -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;

View File

@ -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
} }
} }
} }

View File

@ -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,