Skip to content

Commit

Permalink
enforce the 2fa policy on login
Browse files Browse the repository at this point in the history
if a user doesn't have a second factor check if they are in an
organization that has the 2fa policy enabled to revoke their access
  • Loading branch information
stefan0xC committed Dec 17, 2023
1 parent cc86350 commit 0b9d033
Showing 1 changed file with 21 additions and 25 deletions.
46 changes: 21 additions & 25 deletions src/api/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ use serde_json::Value;

use crate::{
api::{
core::accounts::{PreloginData, RegisterData, _prelogin, _register},
core::log_user_event,
core::two_factor::{duo, email, email::EmailTokenData, yubikey},
core::{
accounts::{PreloginData, RegisterData, _prelogin, _register},
log_user_event,
two_factor::{self, authenticator, duo, email, webauthn, yubikey},
},
ApiResult, EmptyResult, JsonResult, JsonUpcase,
},
auth::{generate_organization_api_key_login_claims, ClientHeaders, ClientIp},
Expand Down Expand Up @@ -247,7 +249,7 @@ async fn _password_login(

let (mut device, new_device) = get_device(&data, conn, &user).await;

let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, ip, conn).await?;
let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, conn).await?;

if CONFIG.mail_enabled() && new_device {
if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device.name).await {
Expand Down Expand Up @@ -468,50 +470,48 @@ async fn get_device(data: &ConnectData, conn: &mut DbConn, user: &User) -> (Devi
}

async fn twofactor_auth(
user_uuid: &str,
user: &User,
data: &ConnectData,
device: &mut Device,
ip: &ClientIp,
conn: &mut DbConn,
) -> ApiResult<Option<String>> {
let twofactors = TwoFactor::find_by_user(user_uuid, conn).await;
let twofactors = TwoFactor::find_by_user(&user.uuid, conn).await;

// No twofactor token if twofactor is disabled
if twofactors.is_empty() {
two_factor::enforce_2fa_policy(user, &user.uuid, device.atype, &ip.ip, conn).await?;
return Ok(None);
}

TwoFactorIncomplete::mark_incomplete(user_uuid, &device.uuid, &device.name, ip, conn).await?;
TwoFactorIncomplete::mark_incomplete(&user.uuid, &device.uuid, &device.name, ip, conn).await?;

let twofactor_ids: Vec<_> = twofactors.iter().map(|tf| tf.atype).collect();
let selected_id = data.two_factor_provider.unwrap_or(twofactor_ids[0]); // If we aren't given a two factor provider, assume the first one

let twofactor_code = match data.two_factor_token {
Some(ref code) => code,
None => err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn).await?, "2FA token not provided"),
None => err_json!(_json_err_twofactor(&twofactor_ids, &user.uuid, conn).await?, "2FA token not provided"),
};

let selected_twofactor = twofactors.into_iter().find(|tf| tf.atype == selected_id && tf.enabled);

use crate::api::core::two_factor as _tf;
use crate::crypto::ct_eq;

let selected_data = _selected_data(selected_twofactor);
let mut remember = data.two_factor_remember.unwrap_or(0);

match TwoFactorType::from_i32(selected_id) {
Some(TwoFactorType::Authenticator) => {
_tf::authenticator::validate_totp_code_str(user_uuid, twofactor_code, &selected_data?, ip, conn).await?
}
Some(TwoFactorType::Webauthn) => {
_tf::webauthn::validate_webauthn_login(user_uuid, twofactor_code, conn).await?
authenticator::validate_totp_code_str(&user.uuid, twofactor_code, &selected_data?, ip, conn).await?
}
Some(TwoFactorType::YubiKey) => _tf::yubikey::validate_yubikey_login(twofactor_code, &selected_data?).await?,
Some(TwoFactorType::Webauthn) => webauthn::validate_webauthn_login(&user.uuid, twofactor_code, conn).await?,
Some(TwoFactorType::YubiKey) => yubikey::validate_yubikey_login(twofactor_code, &selected_data?).await?,
Some(TwoFactorType::Duo) => {
_tf::duo::validate_duo_login(data.username.as_ref().unwrap().trim(), twofactor_code, conn).await?
duo::validate_duo_login(data.username.as_ref().unwrap().trim(), twofactor_code, conn).await?
}
Some(TwoFactorType::Email) => {
_tf::email::validate_email_code_str(user_uuid, twofactor_code, &selected_data?, conn).await?
email::validate_email_code_str(&user.uuid, twofactor_code, &selected_data?, conn).await?
}

Some(TwoFactorType::Remember) => {
Expand All @@ -521,7 +521,7 @@ async fn twofactor_auth(
}
_ => {
err_json!(
_json_err_twofactor(&twofactor_ids, user_uuid, conn).await?,
_json_err_twofactor(&twofactor_ids, &user.uuid, conn).await?,
"2FA Remember token not provided"
)
}
Expand All @@ -535,7 +535,7 @@ async fn twofactor_auth(
),
}

TwoFactorIncomplete::mark_complete(user_uuid, &device.uuid, conn).await?;
TwoFactorIncomplete::mark_complete(&user.uuid, &device.uuid, conn).await?;

if !CONFIG.disable_2fa_remember() && remember == 1 {
Ok(Some(device.refresh_twofactor_remember()))
Expand All @@ -550,8 +550,6 @@ fn _selected_data(tf: Option<TwoFactor>) -> ApiResult<String> {
}

async fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &mut DbConn) -> ApiResult<Value> {
use crate::api::core::two_factor;

let mut result = json!({
"error" : "invalid_grant",
"error_description" : "Two factor required.",
Expand All @@ -566,7 +564,7 @@ async fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &mut DbCo
Some(TwoFactorType::Authenticator) => { /* Nothing to do for TOTP */ }

Some(TwoFactorType::Webauthn) if CONFIG.domain_set() => {
let request = two_factor::webauthn::generate_webauthn_login(user_uuid, conn).await?;
let request = webauthn::generate_webauthn_login(user_uuid, conn).await?;
result["TwoFactorProviders2"][provider.to_string()] = request.0;
}

Expand Down Expand Up @@ -598,19 +596,17 @@ async fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &mut DbCo
}

Some(tf_type @ TwoFactorType::Email) => {
use crate::api::core::two_factor as _tf;

let twofactor = match TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, conn).await {
Some(tf) => tf,
None => err!("No twofactor email registered"),
};

// Send email immediately if email is the only 2FA option
if providers.len() == 1 {
_tf::email::send_token(user_uuid, conn).await?
email::send_token(user_uuid, conn).await?
}

let email_data = EmailTokenData::from_json(&twofactor.data)?;
let email_data = email::EmailTokenData::from_json(&twofactor.data)?;
result["TwoFactorProviders2"][provider.to_string()] = json!({
"Email": email::obscure_email(&email_data.email),
})
Expand Down

0 comments on commit 0b9d033

Please sign in to comment.