use crate::tina::data::AppResult;
use crate::{app_error_from, app_system_error};
use once_cell::sync::Lazy;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use qrcode::render::svg::Color;
use qrcode::QrCode;
use regex::{Regex, RegexBuilder};
use std::panic::{catch_unwind, AssertUnwindSafe};
type Authenticator = google_authenticator::GoogleAuthenticator;
pub struct GoogleAuthenticator;
impl GoogleAuthenticator {
pub fn create_secret() -> AppResult<String> {
let r = catch_unwind(AssertUnwindSafe(|| {
let auth = Authenticator::new();
auth.create_secret(32)
}));
r.map_err(|_| app_system_error!("create secret failed"))
}
pub fn get_qrcode_svg(secret: &str, name: &str, title: &str, _width: u32, _height: u32) -> AppResult<String> {
let r = catch_unwind(AssertUnwindSafe(|| {
fn create_scheme(name: &str, secret: &str, title: &str) -> String {
let name = utf8_percent_encode(name, NON_ALPHANUMERIC);
let title = utf8_percent_encode(title, NON_ALPHANUMERIC);
format!("otpauth://totp/{}?secret={}&issuer={}", name, secret, title)
}
let scheme = create_scheme(name, secret, title);
let image = QrCode::new(scheme.as_bytes())
.map_err(app_error_from!())?
.render::<Color>()
.dark_color(Color("#000"))
.light_color(Color("#FFF")) .quiet_zone(true) .min_dimensions(180, 180) .build();
Ok(image) as AppResult<String>
}));
let svg: String = r.map_err(|_| app_system_error!("get qrcode failed"))??;
static SVG_HEADER_REGEX: Lazy<Regex> =
Lazy::new(|| RegexBuilder::new("^(<\\?.*?\\?>)").case_insensitive(true).build().expect("build SVG_HEADER_REGEX failed"));
let svg = SVG_HEADER_REGEX.replace(svg.as_str(), "").into_owned();
Ok(svg)
}
pub fn verify_code(secret: &str, code: &str) -> AppResult<bool> {
let r = catch_unwind(AssertUnwindSafe(|| {
let auth = Authenticator::new();
auth.verify_code(secret, code, 1, 0)
}));
r.map_err(|_| app_system_error!("verify code failed"))
}
}