Files
BasicOIDC/src/controllers/two_factor_api.rs
Pierre HUBERT b77e7895b7
Some checks failed
continuous-integration/drone/push Build is failing
Rust Edition 2024
2025-03-28 14:40:35 +01:00

165 lines
4.7 KiB
Rust

use actix::Addr;
use actix_web::{HttpResponse, Responder, web};
use uuid::Uuid;
use webauthn_rs::prelude::RegisterPublicKeyCredential;
use crate::actors::users_actor;
use crate::actors::users_actor::UsersActor;
use crate::constants::MAX_SECOND_FACTOR_NAME_LEN;
use crate::data::action_logger::{Action, ActionLogger};
use crate::data::critical_route::CriticalRoute;
use crate::data::current_user::CurrentUser;
use crate::data::totp_key::TotpKey;
use crate::data::user::{FactorID, TwoFactor, TwoFactorType};
use crate::data::webauthn_manager::WebAuthManagerReq;
fn preprocess_factor_name(name: &str) -> String {
name.replace('<', "&lt;")
.replace('>', "&gt;")
.chars()
.take(MAX_SECOND_FACTOR_NAME_LEN)
.filter(|c| *c != '\n' && *c != '\t' && *c != '\r' && c.is_ascii())
.collect()
}
#[derive(serde::Deserialize)]
pub struct AddTOTPRequest {
factor_name: String,
secret: String,
first_code: String,
}
pub async fn save_totp_factor(
_critical: CriticalRoute,
user: CurrentUser,
form: web::Json<AddTOTPRequest>,
users: web::Data<Addr<UsersActor>>,
logger: ActionLogger,
) -> impl Responder {
let key = TotpKey::from_encoded_secret(&form.secret);
if !key.check_code(&form.first_code).unwrap_or(false) {
return HttpResponse::BadRequest().body(format!(
"Given code is invalid (expected {}, {} or {})!",
key.previous_code().unwrap_or_default(),
key.current_code().unwrap_or_default(),
key.following_code().unwrap_or_default(),
));
}
let factor_name = preprocess_factor_name(&form.factor_name);
if factor_name.is_empty() {
return HttpResponse::BadRequest().body("Please give a valid name to the factor!");
}
let factor = TwoFactor {
id: FactorID(Uuid::new_v4().to_string()),
name: factor_name,
kind: TwoFactorType::TOTP(key),
};
logger.log(Action::AddNewFactor(&factor));
let res = users
.send(users_actor::Add2FAFactor(user.uid.clone(), factor))
.await
.unwrap();
if !res {
HttpResponse::InternalServerError().body("Failed to update user information!")
} else {
HttpResponse::Ok().body("Added new factor!")
}
}
#[derive(serde::Deserialize)]
pub struct AddWebauthnRequest {
opaque_state: String,
factor_name: String,
credential: RegisterPublicKeyCredential,
}
pub async fn save_webauthn_factor(
_critical: CriticalRoute,
user: CurrentUser,
form: web::Json<AddWebauthnRequest>,
users: web::Data<Addr<UsersActor>>,
manager: WebAuthManagerReq,
logger: ActionLogger,
) -> impl Responder {
let factor_name = preprocess_factor_name(&form.factor_name);
if factor_name.is_empty() {
return HttpResponse::BadRequest().body("Please give a valid name to the factor!");
}
let key = match manager.finish_registration(&user, &form.0.opaque_state, form.0.credential) {
Ok(k) => k,
Err(e) => {
log::error!("Failed to register security key! {:?}", e);
return HttpResponse::InternalServerError().body("Failed to register key!");
}
};
let factor = TwoFactor {
id: FactorID(Uuid::new_v4().to_string()),
name: factor_name,
kind: TwoFactorType::WEBAUTHN(Box::new(key)),
};
logger.log(Action::AddNewFactor(&factor));
let res = users
.send(users_actor::Add2FAFactor(user.uid.clone(), factor))
.await
.unwrap();
if !res {
HttpResponse::InternalServerError().body("Failed to update user information!")
} else {
HttpResponse::Ok().body("Added new factor!")
}
}
#[derive(serde::Deserialize)]
pub struct DeleteFactorRequest {
id: FactorID,
}
pub async fn delete_factor(
_critical: CriticalRoute,
user: CurrentUser,
form: web::Json<DeleteFactorRequest>,
users: web::Data<Addr<UsersActor>>,
logger: ActionLogger,
) -> impl Responder {
let res = users
.send(users_actor::Remove2FAFactor(
user.uid.clone(),
form.id.clone(),
))
.await
.unwrap();
if !res {
HttpResponse::InternalServerError().body("Failed to update user information!")
} else {
logger.log(Action::Removed2FAFactor {
factor_id: &form.0.id,
});
HttpResponse::Ok().body("Removed factor!")
}
}
pub async fn clear_login_history(
_critical: CriticalRoute,
user: CurrentUser,
users: web::Data<Addr<UsersActor>>,
logger: ActionLogger,
) -> impl Responder {
users
.send(users_actor::Clear2FALoginHistory(user.uid.clone()))
.await
.unwrap();
logger.log(Action::ClearedHisLoginHistory);
HttpResponse::Ok().body("History successfully cleared")
}