Browse Source

do not display unavailable 2FA options

0x484558 2 weeks ago
parent
commit
bbdc52438c
Failed to extract signature
  1. 40
      src/api/core/two_factor/mod.rs
  2. 20
      src/api/identity.rs

40
src/api/core/two_factor/mod.rs

@ -2,6 +2,7 @@ use chrono::{TimeDelta, Utc};
use data_encoding::BASE32;
use rocket::serde::json::Json;
use rocket::Route;
use serde::Deserialize;
use serde_json::Value;
use crate::{
@ -14,7 +15,7 @@ use crate::{
db::{
models::{
DeviceType, EventType, Membership, MembershipType, OrgPolicyType, Organization, OrganizationId, TwoFactor,
TwoFactorIncomplete, User, UserId,
TwoFactorIncomplete, TwoFactorType, User, UserId,
},
DbConn, DbPool,
},
@ -31,6 +32,37 @@ pub mod protected_actions;
pub mod webauthn;
pub mod yubikey;
fn has_global_duo_credentials() -> bool {
CONFIG._enable_duo() && CONFIG.duo_host().is_some() && CONFIG.duo_ikey().is_some() && CONFIG.duo_skey().is_some()
}
pub fn is_twofactor_provider_usable(provider_type: i32, provider_data: Option<&str>) -> bool {
#[derive(Deserialize)]
struct DuoProviderData {
host: String,
ik: String,
sk: String,
}
match provider_type {
x if x == TwoFactorType::Authenticator as i32 => true,
x if x == TwoFactorType::Email as i32 => CONFIG._enable_email_2fa(),
x if x == TwoFactorType::Duo as i32 || x == TwoFactorType::OrganizationDuo as i32 => {
provider_data
.and_then(|raw| serde_json::from_str::<DuoProviderData>(raw).ok())
.is_some_and(|duo| !duo.host.is_empty() && !duo.ik.is_empty() && !duo.sk.is_empty())
|| has_global_duo_credentials()
}
x if x == TwoFactorType::YubiKey as i32 => {
CONFIG._enable_yubico() && CONFIG.yubico_client_id().is_some() && CONFIG.yubico_secret_key().is_some()
}
x if x == TwoFactorType::Webauthn as i32 => CONFIG.domain_set(),
x if x == TwoFactorType::Remember as i32 => !CONFIG.disable_2fa_remember(),
x if x == TwoFactorType::RecoveryCode as i32 => true,
_ => false,
}
}
pub fn routes() -> Vec<Route> {
let mut routes = routes![
get_twofactor,
@ -53,7 +85,11 @@ pub fn routes() -> Vec<Route> {
#[get("/two-factor")]
async fn get_twofactor(headers: Headers, conn: DbConn) -> Json<Value> {
let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &conn).await;
let twofactors_json: Vec<Value> = twofactors.iter().map(TwoFactor::to_json_provider).collect();
let twofactors_json: Vec<Value> = twofactors
.iter()
.filter(|tf| is_twofactor_provider_usable(tf.atype, Some(&tf.data)))
.map(TwoFactor::to_json_provider)
.collect();
Json(json!({
"data": twofactors_json,

20
src/api/identity.rs

@ -14,7 +14,9 @@ use crate::{
core::{
accounts::{PreloginData, RegisterData, _prelogin, _register, kdf_upgrade},
log_user_event,
two_factor::{authenticator, duo, duo_oidc, email, enforce_2fa_policy, webauthn, yubikey},
two_factor::{
authenticator, duo, duo_oidc, email, enforce_2fa_policy, is_twofactor_provider_usable, webauthn, yubikey,
},
},
master_password_policy,
push::register_push_device,
@ -739,8 +741,22 @@ async fn twofactor_auth(
TwoFactorIncomplete::mark_incomplete(&user.uuid, &device.uuid, &device.name, device.atype, ip, conn).await?;
let twofactor_ids: Vec<_> = twofactors.iter().map(|tf| tf.atype).collect();
let twofactor_ids: Vec<_> = twofactors
.iter()
.filter(|tf| tf.enabled && is_twofactor_provider_usable(tf.atype, Some(&tf.data)))
.map(|tf| tf.atype)
.collect();
if twofactor_ids.is_empty() {
err!("No enabled and usable two factor providers are available for this account")
}
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
if !twofactor_ids.contains(&selected_id) {
err_json!(
_json_err_twofactor(&twofactor_ids, &user.uuid, data, client_version, conn).await?,
"Invalid two factor provider"
)
}
let twofactor_code = match data.two_factor_token {
Some(ref code) => code,

Loading…
Cancel
Save