Generate QrCode to enroll Authenticator App
This commit is contained in:
@ -1,10 +1,13 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use actix_web::{HttpResponse, Responder};
|
||||
use actix_web::{HttpResponse, Responder, web};
|
||||
use askama::Template;
|
||||
use qrcode_generator::QrCodeEcc;
|
||||
|
||||
use crate::controllers::settings_controller::BaseSettingsPage;
|
||||
use crate::data::app_config::AppConfig;
|
||||
use crate::data::current_user::CurrentUser;
|
||||
use crate::data::totp_key::TotpKey;
|
||||
use crate::data::user::User;
|
||||
|
||||
#[derive(Template)]
|
||||
@ -18,6 +21,9 @@ struct TwoFactorsPage<'a> {
|
||||
#[template(path = "settings/add_2fa_totp_page.html")]
|
||||
struct AddTotpPage {
|
||||
_p: BaseSettingsPage,
|
||||
qr_code: String,
|
||||
account_name: String,
|
||||
secret_key: String,
|
||||
}
|
||||
|
||||
|
||||
@ -36,7 +42,22 @@ pub async fn two_factors_route(user: CurrentUser) -> impl Responder {
|
||||
|
||||
|
||||
/// Configure a new TOTP authentication factor
|
||||
pub async fn add_totp_factor_route(user: CurrentUser) -> impl Responder {
|
||||
pub async fn add_totp_factor_route(user: CurrentUser, app_conf: web::Data<AppConfig>) -> impl Responder {
|
||||
let key = TotpKey::new_random();
|
||||
|
||||
let qr_code = qrcode_generator::to_png_to_vec(
|
||||
key.url_for_user(&user, &app_conf),
|
||||
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(
|
||||
@ -44,5 +65,8 @@ pub async fn add_totp_factor_route(user: CurrentUser) -> impl Responder {
|
||||
&user,
|
||||
None,
|
||||
None),
|
||||
qr_code: base64::encode(qr_code),
|
||||
account_name: key.account_name(&user, &app_conf),
|
||||
secret_key: key.get_secret(),
|
||||
}.render().unwrap())
|
||||
}
|
@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::constants::{CLIENTS_LIST_FILE, USERS_LIST_FILE};
|
||||
use crate::constants::{APP_NAME, CLIENTS_LIST_FILE, USERS_LIST_FILE};
|
||||
|
||||
/// Basic OIDC provider
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
@ -53,4 +53,8 @@ impl AppConfig {
|
||||
format!("{}/{}", self.website_origin, uri)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn domain_name(&self) -> &str {
|
||||
self.website_origin.split('/').skip(2).next().unwrap_or(APP_NAME)
|
||||
}
|
||||
}
|
||||
|
@ -10,4 +10,5 @@ pub mod jwt_signer;
|
||||
pub mod id_token;
|
||||
pub mod code_challenge;
|
||||
pub mod open_id_user_info;
|
||||
pub mod access_token;
|
||||
pub mod access_token;
|
||||
pub mod totp_key;
|
53
src/data/totp_key.rs
Normal file
53
src/data/totp_key.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use base32::Alphabet;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::data::app_config::AppConfig;
|
||||
use crate::data::user::User;
|
||||
|
||||
const BASE32_ALPHABET: Alphabet = Alphabet::RFC4648 { padding: true };
|
||||
const NUM_DIGITS: i32 = 6;
|
||||
const PERIOD: i32 = 30;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
||||
pub struct TotpKey {
|
||||
encoded: String,
|
||||
}
|
||||
|
||||
impl TotpKey {
|
||||
/// Generate a new TOTP key
|
||||
pub fn new_random() -> Self {
|
||||
let random_bytes = rand::thread_rng().gen::<[u8; 10]>();
|
||||
TotpKey {
|
||||
encoded: base32::encode(BASE32_ALPHABET, &random_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get QrCode URL for user
|
||||
///
|
||||
/// Based on https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
||||
pub fn url_for_user(&self, u: &User, conf: &AppConfig) -> String {
|
||||
format!(
|
||||
"otpauth://totp/{}:{}?secret={}&issuer={}&algorithm=SHA1&digits={}&period={}",
|
||||
urlencoding::encode(&conf.domain_name()),
|
||||
urlencoding::encode(&u.username),
|
||||
self.encoded,
|
||||
urlencoding::encode(&conf.domain_name()),
|
||||
NUM_DIGITS,
|
||||
PERIOD,
|
||||
)
|
||||
}
|
||||
|
||||
/// Get account name
|
||||
pub fn account_name(&self, u: &User, conf: &AppConfig) -> String {
|
||||
format!(
|
||||
"{}:{}",
|
||||
urlencoding::encode(conf.domain_name()),
|
||||
urlencoding::encode(&u.username)
|
||||
)
|
||||
}
|
||||
|
||||
/// Get current secret in base32 format
|
||||
pub fn get_secret(&self) -> String {
|
||||
self.encoded.to_string()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user