use std::ops::Deref; use actix_web::{HttpResponse, Responder}; use askama::Template; use base64::Engine as _; use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; use qrcode_generator::QrCodeEcc; use crate::constants::MAX_SECOND_FACTOR_NAME_LEN; use crate::controllers::settings_controller::BaseSettingsPage; use crate::data::app_config::AppConfig; use crate::data::critical_route::CriticalRoute; use crate::data::current_user::CurrentUser; use crate::data::totp_key::TotpKey; use crate::data::user::User; use crate::data::webauthn_manager::WebAuthManagerReq; use crate::utils::time::fmt_time; #[derive(Template)] #[template(path = "settings/two_factors_page.html")] struct TwoFactorsPage<'a> { p: BaseSettingsPage<'a>, user: &'a User, last_2fa_auth: Option, } #[derive(Template)] #[template(path = "settings/add_2fa_totp_page.html")] struct AddTotpPage<'a> { p: BaseSettingsPage<'a>, qr_code: String, account_name: String, secret_key: String, max_name_len: usize, } #[derive(Template)] #[template(path = "settings/add_webauthn_page.html")] struct AddWebauhtnPage<'a> { p: BaseSettingsPage<'a>, opaque_state: String, challenge_json: String, max_name_len: usize, } /// Manage two factors authentication methods route pub async fn two_factors_route(_critical: CriticalRoute, user: CurrentUser) -> impl Responder { HttpResponse::Ok().body( TwoFactorsPage { p: BaseSettingsPage::get("Two factor auth", &user, None, None), user: user.deref(), last_2fa_auth: user.last_2fa_auth.map(fmt_time), } .render() .unwrap(), ) } /// Configure a new TOTP authentication factor pub async fn add_totp_factor_route(_critical: CriticalRoute, user: CurrentUser) -> impl Responder { let key = TotpKey::new_random(); let qr_code = qrcode_generator::to_png_to_vec( key.url_for_user(&user, AppConfig::get()), QrCodeEcc::Low, 1024, ); let qr_code = match qr_code { Ok(q) => q, Err(e) => { log::error!("Failed to generate QrCode! {:?}", e); return HttpResponse::InternalServerError().body("Failed to generate QrCode!"); } }; HttpResponse::Ok().body( AddTotpPage { p: BaseSettingsPage::get("New authenticator app", &user, None, None), qr_code: BASE64_STANDARD.encode(qr_code), account_name: key.account_name(&user, AppConfig::get()), secret_key: key.get_secret(), max_name_len: MAX_SECOND_FACTOR_NAME_LEN, } .render() .unwrap(), ) } /// Configure a new security key factor pub async fn add_webauthn_factor_route( _critical: CriticalRoute, user: CurrentUser, manager: WebAuthManagerReq, ) -> impl Responder { let registration_request = match manager.start_register(&user) { Ok(r) => r, Err(e) => { log::error!("Failed to request new key! {:?}", e); return HttpResponse::InternalServerError() .body("Failed to generate request for registration!"); } }; let challenge_json = match serde_json::to_string(®istration_request.creation_challenge) { Ok(r) => r, Err(e) => { log::error!("Failed to serialize challenge! {:?}", e); return HttpResponse::InternalServerError().body("Failed to serialize challenge!"); } }; HttpResponse::Ok().body( AddWebauhtnPage { p: BaseSettingsPage::get("New security key", &user, None, None), opaque_state: registration_request.opaque_state, challenge_json: urlencoding::encode(&challenge_json).to_string(), max_name_len: MAX_SECOND_FACTOR_NAME_LEN, } .render() .unwrap(), ) }