mirror of
https://github.com/ViViDboarder/bitwarden_rs.git
synced 2024-11-22 21:26:38 +00:00
Add configuration options for Email 2FA
This commit is contained in:
parent
5609103a97
commit
ad2225b6e5
@ -4,21 +4,19 @@ use serde_json;
|
|||||||
|
|
||||||
use crate::api::{EmptyResult, JsonResult, JsonUpcase, PasswordData};
|
use crate::api::{EmptyResult, JsonResult, JsonUpcase, PasswordData};
|
||||||
use crate::auth::Headers;
|
use crate::auth::Headers;
|
||||||
|
use crate::crypto;
|
||||||
use crate::db::{
|
use crate::db::{
|
||||||
models::{TwoFactor, TwoFactorType},
|
models::{TwoFactor, TwoFactorType},
|
||||||
DbConn,
|
DbConn,
|
||||||
};
|
};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::mail;
|
use crate::mail;
|
||||||
use crate::crypto;
|
use crate::CONFIG;
|
||||||
|
|
||||||
use chrono::{Duration, NaiveDateTime, Utc};
|
use chrono::{Duration, NaiveDateTime, Utc};
|
||||||
use std::char;
|
use std::char;
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
|
|
||||||
const MAX_TIME_DIFFERENCE: i64 = 600;
|
|
||||||
const TOKEN_LEN: usize = 6;
|
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
routes![
|
routes![
|
||||||
get_email,
|
get_email,
|
||||||
@ -54,10 +52,14 @@ fn send_email_login(data: JsonUpcase<SendEmailLoginData>, conn: DbConn) -> Empty
|
|||||||
err!("Username or password is incorrect. Try again.")
|
err!("Username or password is incorrect. Try again.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !CONFIG._enable_email_2fa() {
|
||||||
|
err!("Email 2FA is disabled")
|
||||||
|
}
|
||||||
|
|
||||||
let type_ = TwoFactorType::Email as i32;
|
let type_ = TwoFactorType::Email as i32;
|
||||||
let mut twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn)?;
|
let mut twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn)?;
|
||||||
|
|
||||||
let generated_token = generate_token();
|
let generated_token = generate_token(CONFIG.email_token_size());
|
||||||
let mut twofactor_data = EmailTokenData::from_json(&twofactor.data)?;
|
let mut twofactor_data = EmailTokenData::from_json(&twofactor.data)?;
|
||||||
twofactor_data.set_token(generated_token);
|
twofactor_data.set_token(generated_token);
|
||||||
twofactor.data = twofactor_data.to_json();
|
twofactor.data = twofactor_data.to_json();
|
||||||
@ -68,6 +70,7 @@ fn send_email_login(data: JsonUpcase<SendEmailLoginData>, conn: DbConn) -> Empty
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When user clicks on Manage email 2FA show the user the related information
|
||||||
#[post("/two-factor/get-email", data = "<data>")]
|
#[post("/two-factor/get-email", data = "<data>")]
|
||||||
fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
|
fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
let data: PasswordData = data.into_inner().data;
|
let data: PasswordData = data.into_inner().data;
|
||||||
@ -98,12 +101,11 @@ struct SendEmailData {
|
|||||||
MasterPasswordHash: String,
|
MasterPasswordHash: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_token() -> String {
|
fn generate_token(token_size: u64) -> String {
|
||||||
crypto::get_random(vec![0; TOKEN_LEN])
|
crypto::get_random(vec![0; token_size as usize])
|
||||||
.iter()
|
.iter()
|
||||||
.map(|byte| { (byte % 10)})
|
.map(|byte| { (byte % 10)})
|
||||||
.map(|num| {
|
.map(|num| {
|
||||||
dbg!(num);
|
|
||||||
char::from_digit(num as u32, 10).unwrap()
|
char::from_digit(num as u32, 10).unwrap()
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
@ -119,13 +121,17 @@ fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) -
|
|||||||
err!("Invalid password");
|
err!("Invalid password");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !CONFIG._enable_email_2fa() {
|
||||||
|
err!("Email 2FA is disabled")
|
||||||
|
}
|
||||||
|
|
||||||
let type_ = TwoFactorType::Email as i32;
|
let type_ = TwoFactorType::Email as i32;
|
||||||
|
|
||||||
if let Some(tf) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn) {
|
if let Some(tf) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn) {
|
||||||
tf.delete(&conn)?;
|
tf.delete(&conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let generated_token = generate_token();
|
let generated_token = generate_token(CONFIG.email_token_size());
|
||||||
let twofactor_data = EmailTokenData::new(data.Email, generated_token);
|
let twofactor_data = EmailTokenData::new(data.Email, generated_token);
|
||||||
|
|
||||||
// Uses EmailVerificationChallenge as type to show that it's not verified yet.
|
// Uses EmailVerificationChallenge as type to show that it's not verified yet.
|
||||||
@ -170,7 +176,7 @@ fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonRes
|
|||||||
};
|
};
|
||||||
|
|
||||||
if issued_token != &data.Token {
|
if issued_token != &data.Token {
|
||||||
err!("Email token does not match")
|
err!("Token is invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
email_data.reset_token();
|
email_data.reset_token();
|
||||||
@ -195,7 +201,14 @@ pub fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: &
|
|||||||
};
|
};
|
||||||
|
|
||||||
if issued_token != &*token {
|
if issued_token != &*token {
|
||||||
err!("Email token does not match")
|
email_data.add_attempt();
|
||||||
|
if email_data.attempts >= CONFIG.email_attempts_limit() {
|
||||||
|
email_data.reset_token();
|
||||||
|
}
|
||||||
|
twofactor.data = email_data.to_json();
|
||||||
|
twofactor.save(&conn)?;
|
||||||
|
|
||||||
|
err!("Token is invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
email_data.reset_token();
|
email_data.reset_token();
|
||||||
@ -203,18 +216,25 @@ pub fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: &
|
|||||||
twofactor.save(&conn)?;
|
twofactor.save(&conn)?;
|
||||||
|
|
||||||
let date = NaiveDateTime::from_timestamp(email_data.token_sent, 0);
|
let date = NaiveDateTime::from_timestamp(email_data.token_sent, 0);
|
||||||
if date.add(Duration::seconds(MAX_TIME_DIFFERENCE)) < Utc::now().naive_utc() {
|
let max_time = CONFIG.email_expiration_time() as i64;
|
||||||
err!("Email token too old")
|
if date.add(Duration::seconds(max_time)) < Utc::now().naive_utc() {
|
||||||
|
err!("Token has expired")
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
/// Data stored in the TwoFactor table in the db
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct EmailTokenData {
|
pub struct EmailTokenData {
|
||||||
|
/// Email address where the token will be sent to. Can be different from account email.
|
||||||
pub email: String,
|
pub email: String,
|
||||||
|
/// Some(token): last valid token issued that has not been entered.
|
||||||
|
/// None: valid token was used and removed.
|
||||||
pub last_token: Option<String>,
|
pub last_token: Option<String>,
|
||||||
|
/// UNIX timestamp of token issue.
|
||||||
pub token_sent: i64,
|
pub token_sent: i64,
|
||||||
|
/// Amount of token entry attempts for last_token.
|
||||||
|
pub attempts: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EmailTokenData {
|
impl EmailTokenData {
|
||||||
@ -223,6 +243,7 @@ impl EmailTokenData {
|
|||||||
email,
|
email,
|
||||||
last_token: Some(token),
|
last_token: Some(token),
|
||||||
token_sent: Utc::now().naive_utc().timestamp(),
|
token_sent: Utc::now().naive_utc().timestamp(),
|
||||||
|
attempts: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,6 +254,11 @@ impl EmailTokenData {
|
|||||||
|
|
||||||
pub fn reset_token(&mut self) {
|
pub fn reset_token(&mut self) {
|
||||||
self.last_token = None;
|
self.last_token = None;
|
||||||
|
self.attempts = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_attempt(&mut self) {
|
||||||
|
self.attempts = self.attempts + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json(&self) -> String {
|
pub fn to_json(&self) -> String {
|
||||||
@ -295,8 +321,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_token() {
|
fn test_token() {
|
||||||
let result = generate_token();
|
let result = generate_token(100);
|
||||||
|
|
||||||
assert_eq!(result.chars().count(), 6);
|
assert_eq!(result.chars().count(), 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -318,6 +318,18 @@ make_config! {
|
|||||||
_duo_akey: Pass, false, option;
|
_duo_akey: Pass, false, option;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Email 2FA Settings
|
||||||
|
email_2fa: _enable_email_2fa {
|
||||||
|
/// Enabled |> Disabling will prevent users from setting up new email 2FA and using existing email 2FA configured
|
||||||
|
_enable_email_2fa: bool, true, def, true;
|
||||||
|
/// Token number length |> Length of the numbers in an email token
|
||||||
|
email_token_size: u64, true, def, 6;
|
||||||
|
/// Token expiration time |> Maximum time in seconds a token is valid. The time the user has to open email client and copy token.
|
||||||
|
email_expiration_time: u64, true, def, 600;
|
||||||
|
/// Maximum attempts |> Maximum attempts before an email token is reset and a new email will need to be sent
|
||||||
|
email_attempts_limit: u64, true, def, 3;
|
||||||
|
},
|
||||||
|
|
||||||
/// SMTP Email Settings
|
/// SMTP Email Settings
|
||||||
smtp: _enable_smtp {
|
smtp: _enable_smtp {
|
||||||
/// Enabled
|
/// Enabled
|
||||||
|
Loading…
Reference in New Issue
Block a user