137 lines
4.5 KiB
Rust
137 lines
4.5 KiB
Rust
|
use lazy_static::lazy_static;
|
||
|
use lettre::Transport;
|
||
|
use ring::rand::SecureRandom;
|
||
|
use warp::Filter;
|
||
|
use crate::config::CONFIG;
|
||
|
use crate::db::{DBConnection, DBPool, with_db, TfaTypes};
|
||
|
use crate::dto;
|
||
|
use crate::routes::{AppError, get_reply};
|
||
|
use crate::routes::filters::{authenticated, UserInfo};
|
||
|
|
||
|
fn build_mail_sender() -> lettre::SmtpTransport {
|
||
|
lettre::SmtpTransport::builder_dangerous(CONFIG.smtp_server.clone())
|
||
|
.port(CONFIG.smtp_port)
|
||
|
.tls(
|
||
|
lettre::transport::smtp::client::Tls::Required(
|
||
|
lettre::transport::smtp::client::TlsParameters::new(
|
||
|
CONFIG.smtp_server.clone()
|
||
|
).unwrap()
|
||
|
)
|
||
|
)
|
||
|
.credentials(lettre::transport::smtp::authentication::Credentials::new(CONFIG.smtp_user.clone(), CONFIG.smtp_password.clone()))
|
||
|
.build()
|
||
|
}
|
||
|
|
||
|
lazy_static! {
|
||
|
static ref MAIL_SENDER: lettre::SmtpTransport = build_mail_sender();
|
||
|
}
|
||
|
|
||
|
fn get_totp(user: &crate::db::User) -> totp_rs::TOTP {
|
||
|
totp_rs::TOTP::from_rfc6238(
|
||
|
totp_rs::Rfc6238::new(
|
||
|
6,
|
||
|
user.tfa_secret.clone().unwrap(),
|
||
|
Some("MFileserver".to_owned()),
|
||
|
user.name.clone()
|
||
|
).unwrap()
|
||
|
).unwrap()
|
||
|
}
|
||
|
|
||
|
pub fn verify2fa(user: &crate::db::User, code: String) -> bool {
|
||
|
let allowed_skew = if user.tfa_type == TfaTypes::Totp {0} else {10};
|
||
|
let totp = get_totp(user);
|
||
|
let time = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
|
||
|
let base_step = time / totp.step - allowed_skew;
|
||
|
for i in 0..allowed_skew + 1 {
|
||
|
let step = (base_step + i) * totp.step;
|
||
|
if totp.generate(step).eq(&code) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
false
|
||
|
}
|
||
|
|
||
|
pub fn send_2fa_mail(user: &crate::db::User) {
|
||
|
let totp = get_totp(user);
|
||
|
let code = totp.generate_current().unwrap();
|
||
|
let mail = lettre::Message::builder()
|
||
|
.from("fileserver@mattv.de".parse().unwrap())
|
||
|
.to(user.name.parse().unwrap())
|
||
|
.subject("MFileserver - Email 2fa code")
|
||
|
.body(format!("Your code is: {}\r\nIt is valid for 5 minutes", code))
|
||
|
.unwrap();
|
||
|
|
||
|
MAIL_SENDER.send(&mail).expect("Failed to send mail");
|
||
|
}
|
||
|
|
||
|
pub fn build_routes(db: DBPool) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||
|
let tfa_setup = warp::path!("auth" / "2fa" / "setup")
|
||
|
.and(warp::post())
|
||
|
.and(warp::body::json())
|
||
|
.and(authenticated(db.clone()))
|
||
|
.and(with_db(db.clone()))
|
||
|
.and_then(tfa_setup);
|
||
|
let tfa_complete = warp::path!("auth" / "2fa" / "complete")
|
||
|
.and(warp::post())
|
||
|
.and(warp::body::json())
|
||
|
.and(authenticated(db.clone()))
|
||
|
.and(with_db(db.clone()))
|
||
|
.and_then(tfa_complete);
|
||
|
let tfa_disable = warp::path!("auth" / "2fa" / "disable")
|
||
|
.and(warp::post())
|
||
|
.and(authenticated(db.clone()))
|
||
|
.and(with_db(db))
|
||
|
.and_then(tfa_disable);
|
||
|
|
||
|
tfa_setup.or(tfa_complete).or(tfa_disable)
|
||
|
}
|
||
|
|
||
|
async fn tfa_setup(data: dto::requests::TfaSetup, mut info: UserInfo, mut db: DBConnection)
|
||
|
-> Result<impl warp::Reply, warp::Rejection> {
|
||
|
let mut secret: [u8; 32] = [0; 32];
|
||
|
super::SEC_RANDOM.fill(&mut secret).expect("Failed to generate secret");
|
||
|
let secret = Vec::from(secret);
|
||
|
info.0.tfa_secret = Some(secret);
|
||
|
db.save_user(&info.0);
|
||
|
|
||
|
if data.mail {
|
||
|
send_2fa_mail(&info.0);
|
||
|
get_reply(&dto::responses::Success {
|
||
|
statusCode: 200
|
||
|
})
|
||
|
} else {
|
||
|
let totp = get_totp(&info.0);
|
||
|
get_reply(&dto::responses::TfaSetup {
|
||
|
statusCode: 200,
|
||
|
secret: totp.get_secret_base32(),
|
||
|
qrCode: "data:image/png;base64,".to_owned() + &totp.get_qr().expect("Failed to generate qr code")
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async fn tfa_complete(data: dto::requests::TfaComplete, mut info: UserInfo, mut db: DBConnection)
|
||
|
-> Result<impl warp::Reply, warp::Rejection> {
|
||
|
info.0.tfa_type = if data.mail { TfaTypes::Email } else { TfaTypes::Totp };
|
||
|
|
||
|
if verify2fa(&info.0, data.code) {
|
||
|
db.save_user(&info.0);
|
||
|
db.delete_all_tokens(info.0.id);
|
||
|
get_reply(&dto::responses::Success {
|
||
|
statusCode: 200
|
||
|
})
|
||
|
} else {
|
||
|
AppError::BadRequest("Incorrect 2fa code").err()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async fn tfa_disable(mut info: UserInfo, mut db: DBConnection)
|
||
|
-> Result<impl warp::Reply, warp::Rejection> {
|
||
|
info.0.tfa_secret = None;
|
||
|
info.0.tfa_type = TfaTypes::None;
|
||
|
db.save_user(&info.0);
|
||
|
db.delete_all_tokens(info.0.id);
|
||
|
get_reply(&dto::responses::Success {
|
||
|
statusCode: 200
|
||
|
})
|
||
|
}
|