mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-05-07 21:08:52 +03:00
Use Diesels MultiConnections Derive (#6279)
* Use Diesels MultiConnections Derive With this PR we remove almost all custom macro's to create the multiple database type code. This is now handled by Diesel it self. This removed the need of the following functions/macro's: - `db_object!` - `::to_db` - `.from_db()` It is also possible to just use one schema instead of multiple per type. Also done: - Refactored the SQLite backup function - Some formatting of queries so every call is one a separate line, this looks a bit better - Declare `conn` as mut inside each `db_run!` instead of having to declare it as `mut` in functions or calls - Added an `ACTIVE_DB_TYPE` static which holds the currently active database type - Removed `diesel_logger` crate and use Diesel's `set_default_instrumentation()` If you want debug queries you can now simply change the log level of `vaultwarden::db::query_logger` - Use PostgreSQL v17 in the Alpine images to match the Debian Trixie version - Optimized the Workflows since `diesel_logger` isn't needed anymore And on the extra plus-side, this lowers the compile-time and binary size too. Signed-off-by: BlackDex <black.dex@gmail.com> * Adjust query_logger and some other small items Signed-off-by: BlackDex <black.dex@gmail.com> * Remove macro, replaced with an function Signed-off-by: BlackDex <black.dex@gmail.com> * Implement custom connection manager Signed-off-by: BlackDex <black.dex@gmail.com> * Updated some crates to keep up2date Signed-off-by: BlackDex <black.dex@gmail.com> * Small adjustment Signed-off-by: BlackDex <black.dex@gmail.com> * crate updates Signed-off-by: BlackDex <black.dex@gmail.com> * Update crates Signed-off-by: BlackDex <black.dex@gmail.com> --------- Signed-off-by: BlackDex <black.dex@gmail.com>
This commit is contained in:
committed by
GitHub
parent
7c597e88f9
commit
2ee5819b56
@@ -20,14 +20,14 @@ pub fn routes() -> Vec<Route> {
|
||||
}
|
||||
|
||||
#[post("/two-factor/get-authenticator", data = "<data>")]
|
||||
async fn generate_authenticator(data: Json<PasswordOrOtpData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn generate_authenticator(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let data: PasswordOrOtpData = data.into_inner();
|
||||
let user = headers.user;
|
||||
|
||||
data.validate(&user, false, &mut conn).await?;
|
||||
data.validate(&user, false, &conn).await?;
|
||||
|
||||
let type_ = TwoFactorType::Authenticator as i32;
|
||||
let twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await;
|
||||
let twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await;
|
||||
|
||||
let (enabled, key) = match twofactor {
|
||||
Some(tf) => (true, tf.data),
|
||||
@@ -55,7 +55,7 @@ struct EnableAuthenticatorData {
|
||||
}
|
||||
|
||||
#[post("/two-factor/authenticator", data = "<data>")]
|
||||
async fn activate_authenticator(data: Json<EnableAuthenticatorData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn activate_authenticator(data: Json<EnableAuthenticatorData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let data: EnableAuthenticatorData = data.into_inner();
|
||||
let key = data.key;
|
||||
let token = data.token.into_string();
|
||||
@@ -66,7 +66,7 @@ async fn activate_authenticator(data: Json<EnableAuthenticatorData>, headers: He
|
||||
master_password_hash: data.master_password_hash,
|
||||
otp: data.otp,
|
||||
}
|
||||
.validate(&user, true, &mut conn)
|
||||
.validate(&user, true, &conn)
|
||||
.await?;
|
||||
|
||||
// Validate key as base32 and 20 bytes length
|
||||
@@ -80,11 +80,11 @@ async fn activate_authenticator(data: Json<EnableAuthenticatorData>, headers: He
|
||||
}
|
||||
|
||||
// Validate the token provided with the key, and save new twofactor
|
||||
validate_totp_code(&user.uuid, &token, &key.to_uppercase(), &headers.ip, &mut conn).await?;
|
||||
validate_totp_code(&user.uuid, &token, &key.to_uppercase(), &headers.ip, &conn).await?;
|
||||
|
||||
_generate_recover_code(&mut user, &mut conn).await;
|
||||
_generate_recover_code(&mut user, &conn).await;
|
||||
|
||||
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await;
|
||||
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await;
|
||||
|
||||
Ok(Json(json!({
|
||||
"enabled": true,
|
||||
@@ -103,7 +103,7 @@ pub async fn validate_totp_code_str(
|
||||
totp_code: &str,
|
||||
secret: &str,
|
||||
ip: &ClientIp,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
if !totp_code.chars().all(char::is_numeric) {
|
||||
err!("TOTP code is not a number");
|
||||
@@ -117,7 +117,7 @@ pub async fn validate_totp_code(
|
||||
totp_code: &str,
|
||||
secret: &str,
|
||||
ip: &ClientIp,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
use totp_lite::{totp_custom, Sha1};
|
||||
|
||||
@@ -189,7 +189,7 @@ struct DisableAuthenticatorData {
|
||||
}
|
||||
|
||||
#[delete("/two-factor/authenticator", data = "<data>")]
|
||||
async fn disable_authenticator(data: Json<DisableAuthenticatorData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn disable_authenticator(data: Json<DisableAuthenticatorData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let user = headers.user;
|
||||
let type_ = data.r#type.into_i32()?;
|
||||
|
||||
@@ -197,24 +197,18 @@ async fn disable_authenticator(data: Json<DisableAuthenticatorData>, headers: He
|
||||
err!("Invalid password");
|
||||
}
|
||||
|
||||
if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await {
|
||||
if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await {
|
||||
if twofactor.data == data.key {
|
||||
twofactor.delete(&mut conn).await?;
|
||||
log_user_event(
|
||||
EventType::UserDisabled2fa as i32,
|
||||
&user.uuid,
|
||||
headers.device.atype,
|
||||
&headers.ip.ip,
|
||||
&mut conn,
|
||||
)
|
||||
.await;
|
||||
twofactor.delete(&conn).await?;
|
||||
log_user_event(EventType::UserDisabled2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn)
|
||||
.await;
|
||||
} else {
|
||||
err!(format!("TOTP key for user {} does not match recorded value, cannot deactivate", &user.email));
|
||||
}
|
||||
}
|
||||
|
||||
if TwoFactor::find_by_user(&user.uuid, &mut conn).await.is_empty() {
|
||||
super::enforce_2fa_policy(&user, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await?;
|
||||
if TwoFactor::find_by_user(&user.uuid, &conn).await.is_empty() {
|
||||
super::enforce_2fa_policy(&user, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await?;
|
||||
}
|
||||
|
||||
Ok(Json(json!({
|
||||
|
||||
@@ -92,13 +92,13 @@ impl DuoStatus {
|
||||
const DISABLED_MESSAGE_DEFAULT: &str = "<To use the global Duo keys, please leave these fields untouched>";
|
||||
|
||||
#[post("/two-factor/get-duo", data = "<data>")]
|
||||
async fn get_duo(data: Json<PasswordOrOtpData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn get_duo(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let data: PasswordOrOtpData = data.into_inner();
|
||||
let user = headers.user;
|
||||
|
||||
data.validate(&user, false, &mut conn).await?;
|
||||
data.validate(&user, false, &conn).await?;
|
||||
|
||||
let data = get_user_duo_data(&user.uuid, &mut conn).await;
|
||||
let data = get_user_duo_data(&user.uuid, &conn).await;
|
||||
|
||||
let (enabled, data) = match data {
|
||||
DuoStatus::Global(_) => (true, Some(DuoData::secret())),
|
||||
@@ -158,7 +158,7 @@ fn check_duo_fields_custom(data: &EnableDuoData) -> bool {
|
||||
}
|
||||
|
||||
#[post("/two-factor/duo", data = "<data>")]
|
||||
async fn activate_duo(data: Json<EnableDuoData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn activate_duo(data: Json<EnableDuoData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let data: EnableDuoData = data.into_inner();
|
||||
let mut user = headers.user;
|
||||
|
||||
@@ -166,7 +166,7 @@ async fn activate_duo(data: Json<EnableDuoData>, headers: Headers, mut conn: DbC
|
||||
master_password_hash: data.master_password_hash.clone(),
|
||||
otp: data.otp.clone(),
|
||||
}
|
||||
.validate(&user, true, &mut conn)
|
||||
.validate(&user, true, &conn)
|
||||
.await?;
|
||||
|
||||
let (data, data_str) = if check_duo_fields_custom(&data) {
|
||||
@@ -180,11 +180,11 @@ async fn activate_duo(data: Json<EnableDuoData>, headers: Headers, mut conn: DbC
|
||||
|
||||
let type_ = TwoFactorType::Duo;
|
||||
let twofactor = TwoFactor::new(user.uuid.clone(), type_, data_str);
|
||||
twofactor.save(&mut conn).await?;
|
||||
twofactor.save(&conn).await?;
|
||||
|
||||
_generate_recover_code(&mut user, &mut conn).await;
|
||||
_generate_recover_code(&mut user, &conn).await;
|
||||
|
||||
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await;
|
||||
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await;
|
||||
|
||||
Ok(Json(json!({
|
||||
"enabled": true,
|
||||
@@ -231,7 +231,7 @@ const AUTH_PREFIX: &str = "AUTH";
|
||||
const DUO_PREFIX: &str = "TX";
|
||||
const APP_PREFIX: &str = "APP";
|
||||
|
||||
async fn get_user_duo_data(user_id: &UserId, conn: &mut DbConn) -> DuoStatus {
|
||||
async fn get_user_duo_data(user_id: &UserId, conn: &DbConn) -> DuoStatus {
|
||||
let type_ = TwoFactorType::Duo as i32;
|
||||
|
||||
// If the user doesn't have an entry, disabled
|
||||
@@ -254,7 +254,7 @@ async fn get_user_duo_data(user_id: &UserId, conn: &mut DbConn) -> DuoStatus {
|
||||
}
|
||||
|
||||
// let (ik, sk, ak, host) = get_duo_keys();
|
||||
pub(crate) async fn get_duo_keys_email(email: &str, conn: &mut DbConn) -> ApiResult<(String, String, String, String)> {
|
||||
pub(crate) async fn get_duo_keys_email(email: &str, conn: &DbConn) -> ApiResult<(String, String, String, String)> {
|
||||
let data = match User::find_by_mail(email, conn).await {
|
||||
Some(u) => get_user_duo_data(&u.uuid, conn).await.data(),
|
||||
_ => DuoData::global(),
|
||||
@@ -264,7 +264,7 @@ pub(crate) async fn get_duo_keys_email(email: &str, conn: &mut DbConn) -> ApiRes
|
||||
Ok((data.ik, data.sk, CONFIG.get_duo_akey().await, data.host))
|
||||
}
|
||||
|
||||
pub async fn generate_duo_signature(email: &str, conn: &mut DbConn) -> ApiResult<(String, String)> {
|
||||
pub async fn generate_duo_signature(email: &str, conn: &DbConn) -> ApiResult<(String, String)> {
|
||||
let now = Utc::now().timestamp();
|
||||
|
||||
let (ik, sk, ak, host) = get_duo_keys_email(email, conn).await?;
|
||||
@@ -282,7 +282,7 @@ fn sign_duo_values(key: &str, email: &str, ikey: &str, prefix: &str, expire: i64
|
||||
format!("{cookie}|{}", crypto::hmac_sign(key, &cookie))
|
||||
}
|
||||
|
||||
pub async fn validate_duo_login(email: &str, response: &str, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn validate_duo_login(email: &str, response: &str, conn: &DbConn) -> EmptyResult {
|
||||
let split: Vec<&str> = response.split(':').collect();
|
||||
if split.len() != 2 {
|
||||
err!(
|
||||
|
||||
@@ -317,7 +317,7 @@ struct DuoAuthContext {
|
||||
|
||||
// Given a state string, retrieve the associated Duo auth context and
|
||||
// delete the retrieved state from the database.
|
||||
async fn extract_context(state: &str, conn: &mut DbConn) -> Option<DuoAuthContext> {
|
||||
async fn extract_context(state: &str, conn: &DbConn) -> Option<DuoAuthContext> {
|
||||
let ctx: TwoFactorDuoContext = match TwoFactorDuoContext::find_by_state(state, conn).await {
|
||||
Some(c) => c,
|
||||
None => return None,
|
||||
@@ -344,8 +344,8 @@ async fn extract_context(state: &str, conn: &mut DbConn) -> Option<DuoAuthContex
|
||||
// Task to clean up expired Duo authentication contexts that may have accumulated in the database.
|
||||
pub async fn purge_duo_contexts(pool: DbPool) {
|
||||
debug!("Purging Duo authentication contexts");
|
||||
if let Ok(mut conn) = pool.get().await {
|
||||
TwoFactorDuoContext::purge_expired_duo_contexts(&mut conn).await;
|
||||
if let Ok(conn) = pool.get().await {
|
||||
TwoFactorDuoContext::purge_expired_duo_contexts(&conn).await;
|
||||
} else {
|
||||
error!("Failed to get DB connection while purging expired Duo authentications")
|
||||
}
|
||||
@@ -380,7 +380,7 @@ pub async fn get_duo_auth_url(
|
||||
email: &str,
|
||||
client_id: &str,
|
||||
device_identifier: &DeviceId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Result<String, Error> {
|
||||
let (ik, sk, _, host) = get_duo_keys_email(email, conn).await?;
|
||||
|
||||
@@ -418,7 +418,7 @@ pub async fn validate_duo_login(
|
||||
two_factor_token: &str,
|
||||
client_id: &str,
|
||||
device_identifier: &DeviceId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
// Result supplied to us by clients in the form "<authz code>|<state>"
|
||||
let split: Vec<&str> = two_factor_token.split('|').collect();
|
||||
|
||||
@@ -39,13 +39,13 @@ struct SendEmailLoginData {
|
||||
/// User is trying to login and wants to use email 2FA.
|
||||
/// Does not require Bearer token
|
||||
#[post("/two-factor/send-email-login", data = "<data>")] // JsonResult
|
||||
async fn send_email_login(data: Json<SendEmailLoginData>, mut conn: DbConn) -> EmptyResult {
|
||||
async fn send_email_login(data: Json<SendEmailLoginData>, conn: DbConn) -> EmptyResult {
|
||||
let data: SendEmailLoginData = data.into_inner();
|
||||
|
||||
use crate::db::models::User;
|
||||
|
||||
// Get the user
|
||||
let Some(user) = User::find_by_device_id(&data.device_identifier, &mut conn).await else {
|
||||
let Some(user) = User::find_by_device_id(&data.device_identifier, &conn).await else {
|
||||
err!("Cannot find user. Try again.")
|
||||
};
|
||||
|
||||
@@ -53,13 +53,13 @@ async fn send_email_login(data: Json<SendEmailLoginData>, mut conn: DbConn) -> E
|
||||
err!("Email 2FA is disabled")
|
||||
}
|
||||
|
||||
send_token(&user.uuid, &mut conn).await?;
|
||||
send_token(&user.uuid, &conn).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate the token, save the data for later verification and send email to user
|
||||
pub async fn send_token(user_id: &UserId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn send_token(user_id: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
let type_ = TwoFactorType::Email as i32;
|
||||
let mut twofactor = TwoFactor::find_by_user_and_type(user_id, type_, conn).await.map_res("Two factor not found")?;
|
||||
|
||||
@@ -77,14 +77,14 @@ pub async fn send_token(user_id: &UserId, conn: &mut DbConn) -> EmptyResult {
|
||||
|
||||
/// When user clicks on Manage email 2FA show the user the related information
|
||||
#[post("/two-factor/get-email", data = "<data>")]
|
||||
async fn get_email(data: Json<PasswordOrOtpData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn get_email(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let data: PasswordOrOtpData = data.into_inner();
|
||||
let user = headers.user;
|
||||
|
||||
data.validate(&user, false, &mut conn).await?;
|
||||
data.validate(&user, false, &conn).await?;
|
||||
|
||||
let (enabled, mfa_email) =
|
||||
match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email as i32, &mut conn).await {
|
||||
match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email as i32, &conn).await {
|
||||
Some(x) => {
|
||||
let twofactor_data = EmailTokenData::from_json(&x.data)?;
|
||||
(true, json!(twofactor_data.email))
|
||||
@@ -110,7 +110,7 @@ struct SendEmailData {
|
||||
|
||||
/// Send a verification email to the specified email address to check whether it exists/belongs to user.
|
||||
#[post("/two-factor/send-email", data = "<data>")]
|
||||
async fn send_email(data: Json<SendEmailData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
async fn send_email(data: Json<SendEmailData>, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||
let data: SendEmailData = data.into_inner();
|
||||
let user = headers.user;
|
||||
|
||||
@@ -118,7 +118,7 @@ async fn send_email(data: Json<SendEmailData>, headers: Headers, mut conn: DbCon
|
||||
master_password_hash: data.master_password_hash,
|
||||
otp: data.otp,
|
||||
}
|
||||
.validate(&user, false, &mut conn)
|
||||
.validate(&user, false, &conn)
|
||||
.await?;
|
||||
|
||||
if !CONFIG._enable_email_2fa() {
|
||||
@@ -127,8 +127,8 @@ async fn send_email(data: Json<SendEmailData>, headers: Headers, mut conn: DbCon
|
||||
|
||||
let type_ = TwoFactorType::Email as i32;
|
||||
|
||||
if let Some(tf) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await {
|
||||
tf.delete(&mut conn).await?;
|
||||
if let Some(tf) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await {
|
||||
tf.delete(&conn).await?;
|
||||
}
|
||||
|
||||
let generated_token = crypto::generate_email_token(CONFIG.email_token_size());
|
||||
@@ -136,7 +136,7 @@ async fn send_email(data: Json<SendEmailData>, headers: Headers, mut conn: DbCon
|
||||
|
||||
// Uses EmailVerificationChallenge as type to show that it's not verified yet.
|
||||
let twofactor = TwoFactor::new(user.uuid, TwoFactorType::EmailVerificationChallenge, twofactor_data.to_json());
|
||||
twofactor.save(&mut conn).await?;
|
||||
twofactor.save(&conn).await?;
|
||||
|
||||
mail::send_token(&twofactor_data.email, &twofactor_data.last_token.map_res("Token is empty")?).await?;
|
||||
|
||||
@@ -154,7 +154,7 @@ struct EmailData {
|
||||
|
||||
/// Verify email belongs to user and can be used for 2FA email codes.
|
||||
#[put("/two-factor/email", data = "<data>")]
|
||||
async fn email(data: Json<EmailData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn email(data: Json<EmailData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let data: EmailData = data.into_inner();
|
||||
let mut user = headers.user;
|
||||
|
||||
@@ -163,12 +163,12 @@ async fn email(data: Json<EmailData>, headers: Headers, mut conn: DbConn) -> Jso
|
||||
master_password_hash: data.master_password_hash,
|
||||
otp: data.otp,
|
||||
}
|
||||
.validate(&user, true, &mut conn)
|
||||
.validate(&user, true, &conn)
|
||||
.await?;
|
||||
|
||||
let type_ = TwoFactorType::EmailVerificationChallenge as i32;
|
||||
let mut twofactor =
|
||||
TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await.map_res("Two factor not found")?;
|
||||
TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await.map_res("Two factor not found")?;
|
||||
|
||||
let mut email_data = EmailTokenData::from_json(&twofactor.data)?;
|
||||
|
||||
@@ -183,11 +183,11 @@ async fn email(data: Json<EmailData>, headers: Headers, mut conn: DbConn) -> Jso
|
||||
email_data.reset_token();
|
||||
twofactor.atype = TwoFactorType::Email as i32;
|
||||
twofactor.data = email_data.to_json();
|
||||
twofactor.save(&mut conn).await?;
|
||||
twofactor.save(&conn).await?;
|
||||
|
||||
_generate_recover_code(&mut user, &mut conn).await;
|
||||
_generate_recover_code(&mut user, &conn).await;
|
||||
|
||||
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await;
|
||||
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await;
|
||||
|
||||
Ok(Json(json!({
|
||||
"email": email_data.email,
|
||||
@@ -202,7 +202,7 @@ pub async fn validate_email_code_str(
|
||||
token: &str,
|
||||
data: &str,
|
||||
ip: &std::net::IpAddr,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
let mut email_data = EmailTokenData::from_json(data)?;
|
||||
let mut twofactor = TwoFactor::find_by_user_and_type(user_id, TwoFactorType::Email as i32, conn)
|
||||
@@ -302,7 +302,7 @@ impl EmailTokenData {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn activate_email_2fa(user: &User, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn activate_email_2fa(user: &User, conn: &DbConn) -> EmptyResult {
|
||||
if user.verified_at.is_none() {
|
||||
err!("Auto-enabling of email 2FA failed because the users email address has not been verified!");
|
||||
}
|
||||
@@ -332,7 +332,7 @@ pub fn obscure_email(email: &str) -> String {
|
||||
format!("{new_name}@{domain}")
|
||||
}
|
||||
|
||||
pub async fn find_and_activate_email_2fa(user_id: &UserId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn find_and_activate_email_2fa(user_id: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
if let Some(user) = User::find_by_uuid(user_id, conn).await {
|
||||
activate_email_2fa(&user, conn).await
|
||||
} else {
|
||||
|
||||
@@ -11,7 +11,13 @@ use crate::{
|
||||
},
|
||||
auth::{ClientHeaders, Headers},
|
||||
crypto,
|
||||
db::{models::*, DbConn, DbPool},
|
||||
db::{
|
||||
models::{
|
||||
DeviceType, EventType, Membership, MembershipType, OrgPolicyType, Organization, OrganizationId, TwoFactor,
|
||||
TwoFactorIncomplete, User, UserId,
|
||||
},
|
||||
DbConn, DbPool,
|
||||
},
|
||||
mail,
|
||||
util::NumberOrString,
|
||||
CONFIG,
|
||||
@@ -46,8 +52,8 @@ pub fn routes() -> Vec<Route> {
|
||||
}
|
||||
|
||||
#[get("/two-factor")]
|
||||
async fn get_twofactor(headers: Headers, mut conn: DbConn) -> Json<Value> {
|
||||
let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &mut conn).await;
|
||||
async fn get_twofactor(headers: Headers, conn: DbConn) -> Json<Value> {
|
||||
let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &conn).await;
|
||||
let twofactors_json: Vec<Value> = twofactors.iter().map(TwoFactor::to_json_provider).collect();
|
||||
|
||||
Json(json!({
|
||||
@@ -58,11 +64,11 @@ async fn get_twofactor(headers: Headers, mut conn: DbConn) -> Json<Value> {
|
||||
}
|
||||
|
||||
#[post("/two-factor/get-recover", data = "<data>")]
|
||||
async fn get_recover(data: Json<PasswordOrOtpData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn get_recover(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let data: PasswordOrOtpData = data.into_inner();
|
||||
let user = headers.user;
|
||||
|
||||
data.validate(&user, true, &mut conn).await?;
|
||||
data.validate(&user, true, &conn).await?;
|
||||
|
||||
Ok(Json(json!({
|
||||
"code": user.totp_recover,
|
||||
@@ -79,13 +85,13 @@ struct RecoverTwoFactor {
|
||||
}
|
||||
|
||||
#[post("/two-factor/recover", data = "<data>")]
|
||||
async fn recover(data: Json<RecoverTwoFactor>, client_headers: ClientHeaders, mut conn: DbConn) -> JsonResult {
|
||||
async fn recover(data: Json<RecoverTwoFactor>, client_headers: ClientHeaders, conn: DbConn) -> JsonResult {
|
||||
let data: RecoverTwoFactor = data.into_inner();
|
||||
|
||||
use crate::db::models::User;
|
||||
|
||||
// Get the user
|
||||
let Some(mut user) = User::find_by_mail(&data.email, &mut conn).await else {
|
||||
let Some(mut user) = User::find_by_mail(&data.email, &conn).await else {
|
||||
err!("Username or password is incorrect. Try again.")
|
||||
};
|
||||
|
||||
@@ -100,25 +106,25 @@ async fn recover(data: Json<RecoverTwoFactor>, client_headers: ClientHeaders, mu
|
||||
}
|
||||
|
||||
// Remove all twofactors from the user
|
||||
TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?;
|
||||
enforce_2fa_policy(&user, &user.uuid, client_headers.device_type, &client_headers.ip.ip, &mut conn).await?;
|
||||
TwoFactor::delete_all_by_user(&user.uuid, &conn).await?;
|
||||
enforce_2fa_policy(&user, &user.uuid, client_headers.device_type, &client_headers.ip.ip, &conn).await?;
|
||||
|
||||
log_user_event(
|
||||
EventType::UserRecovered2fa as i32,
|
||||
&user.uuid,
|
||||
client_headers.device_type,
|
||||
&client_headers.ip.ip,
|
||||
&mut conn,
|
||||
&conn,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Remove the recovery code, not needed without twofactors
|
||||
user.totp_recover = None;
|
||||
user.save(&mut conn).await?;
|
||||
user.save(&conn).await?;
|
||||
Ok(Json(Value::Object(serde_json::Map::new())))
|
||||
}
|
||||
|
||||
async fn _generate_recover_code(user: &mut User, conn: &mut DbConn) {
|
||||
async fn _generate_recover_code(user: &mut User, conn: &DbConn) {
|
||||
if user.totp_recover.is_none() {
|
||||
let totp_recover = crypto::encode_random_bytes::<20>(BASE32);
|
||||
user.totp_recover = Some(totp_recover);
|
||||
@@ -135,7 +141,7 @@ struct DisableTwoFactorData {
|
||||
}
|
||||
|
||||
#[post("/two-factor/disable", data = "<data>")]
|
||||
async fn disable_twofactor(data: Json<DisableTwoFactorData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn disable_twofactor(data: Json<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let data: DisableTwoFactorData = data.into_inner();
|
||||
let user = headers.user;
|
||||
|
||||
@@ -144,19 +150,19 @@ async fn disable_twofactor(data: Json<DisableTwoFactorData>, headers: Headers, m
|
||||
master_password_hash: data.master_password_hash,
|
||||
otp: data.otp,
|
||||
}
|
||||
.validate(&user, true, &mut conn)
|
||||
.validate(&user, true, &conn)
|
||||
.await?;
|
||||
|
||||
let type_ = data.r#type.into_i32()?;
|
||||
|
||||
if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await {
|
||||
twofactor.delete(&mut conn).await?;
|
||||
log_user_event(EventType::UserDisabled2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn)
|
||||
if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await {
|
||||
twofactor.delete(&conn).await?;
|
||||
log_user_event(EventType::UserDisabled2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn)
|
||||
.await;
|
||||
}
|
||||
|
||||
if TwoFactor::find_by_user(&user.uuid, &mut conn).await.is_empty() {
|
||||
enforce_2fa_policy(&user, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await?;
|
||||
if TwoFactor::find_by_user(&user.uuid, &conn).await.is_empty() {
|
||||
enforce_2fa_policy(&user, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await?;
|
||||
}
|
||||
|
||||
Ok(Json(json!({
|
||||
@@ -176,7 +182,7 @@ pub async fn enforce_2fa_policy(
|
||||
act_user_id: &UserId,
|
||||
device_type: i32,
|
||||
ip: &std::net::IpAddr,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
for member in
|
||||
Membership::find_by_user_and_policy(&user.uuid, OrgPolicyType::TwoFactorAuthentication, conn).await.into_iter()
|
||||
@@ -212,7 +218,7 @@ pub async fn enforce_2fa_policy_for_org(
|
||||
act_user_id: &UserId,
|
||||
device_type: i32,
|
||||
ip: &std::net::IpAddr,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
let org = Organization::find_by_uuid(org_id, conn).await.unwrap();
|
||||
for member in Membership::find_confirmed_by_org(org_id, conn).await.into_iter() {
|
||||
@@ -249,7 +255,7 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut conn = match pool.get().await {
|
||||
let conn = match pool.get().await {
|
||||
Ok(conn) => conn,
|
||||
_ => {
|
||||
error!("Failed to get DB connection in send_incomplete_2fa_notifications()");
|
||||
@@ -260,9 +266,9 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) {
|
||||
let now = Utc::now().naive_utc();
|
||||
let time_limit = TimeDelta::try_minutes(CONFIG.incomplete_2fa_time_limit()).unwrap();
|
||||
let time_before = now - time_limit;
|
||||
let incomplete_logins = TwoFactorIncomplete::find_logins_before(&time_before, &mut conn).await;
|
||||
let incomplete_logins = TwoFactorIncomplete::find_logins_before(&time_before, &conn).await;
|
||||
for login in incomplete_logins {
|
||||
let user = User::find_by_uuid(&login.user_uuid, &mut conn).await.expect("User not found");
|
||||
let user = User::find_by_uuid(&login.user_uuid, &conn).await.expect("User not found");
|
||||
info!(
|
||||
"User {} did not complete a 2FA login within the configured time limit. IP: {}",
|
||||
user.email, login.ip_address
|
||||
@@ -277,7 +283,7 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) {
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
if let Err(e) = login.delete(&mut conn).await {
|
||||
if let Err(e) = login.delete(&conn).await {
|
||||
error!("Error deleting incomplete 2FA record: {e:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ impl ProtectedActionData {
|
||||
}
|
||||
|
||||
#[post("/accounts/request-otp")]
|
||||
async fn request_otp(headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
async fn request_otp(headers: Headers, conn: DbConn) -> EmptyResult {
|
||||
if !CONFIG.mail_enabled() {
|
||||
err!("Email is disabled for this server. Either enable email or login using your master password instead of login via device.");
|
||||
}
|
||||
@@ -63,10 +63,9 @@ async fn request_otp(headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
let user = headers.user;
|
||||
|
||||
// Only one Protected Action per user is allowed to take place, delete the previous one
|
||||
if let Some(pa) =
|
||||
TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::ProtectedActions as i32, &mut conn).await
|
||||
if let Some(pa) = TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::ProtectedActions as i32, &conn).await
|
||||
{
|
||||
pa.delete(&mut conn).await?;
|
||||
pa.delete(&conn).await?;
|
||||
}
|
||||
|
||||
let generated_token = crypto::generate_email_token(CONFIG.email_token_size());
|
||||
@@ -74,7 +73,7 @@ async fn request_otp(headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
|
||||
// Uses EmailVerificationChallenge as type to show that it's not verified yet.
|
||||
let twofactor = TwoFactor::new(user.uuid, TwoFactorType::ProtectedActions, pa_data.to_json());
|
||||
twofactor.save(&mut conn).await?;
|
||||
twofactor.save(&conn).await?;
|
||||
|
||||
mail::send_protected_action_token(&user.email, &pa_data.token).await?;
|
||||
|
||||
@@ -89,7 +88,7 @@ struct ProtectedActionVerify {
|
||||
}
|
||||
|
||||
#[post("/accounts/verify-otp", data = "<data>")]
|
||||
async fn verify_otp(data: Json<ProtectedActionVerify>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
async fn verify_otp(data: Json<ProtectedActionVerify>, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||
if !CONFIG.mail_enabled() {
|
||||
err!("Email is disabled for this server. Either enable email or login using your master password instead of login via device.");
|
||||
}
|
||||
@@ -99,14 +98,14 @@ async fn verify_otp(data: Json<ProtectedActionVerify>, headers: Headers, mut con
|
||||
|
||||
// Delete the token after one validation attempt
|
||||
// This endpoint only gets called for the vault export, and doesn't need a second attempt
|
||||
validate_protected_action_otp(&data.otp, &user.uuid, true, &mut conn).await
|
||||
validate_protected_action_otp(&data.otp, &user.uuid, true, &conn).await
|
||||
}
|
||||
|
||||
pub async fn validate_protected_action_otp(
|
||||
otp: &str,
|
||||
user_id: &UserId,
|
||||
delete_if_valid: bool,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
let pa = TwoFactor::find_by_user_and_type(user_id, TwoFactorType::ProtectedActions as i32, conn)
|
||||
.await
|
||||
|
||||
@@ -107,7 +107,7 @@ impl WebauthnRegistration {
|
||||
}
|
||||
|
||||
#[post("/two-factor/get-webauthn", data = "<data>")]
|
||||
async fn get_webauthn(data: Json<PasswordOrOtpData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn get_webauthn(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
if !CONFIG.domain_set() {
|
||||
err!("`DOMAIN` environment variable is not set. Webauthn disabled")
|
||||
}
|
||||
@@ -115,9 +115,9 @@ async fn get_webauthn(data: Json<PasswordOrOtpData>, headers: Headers, mut conn:
|
||||
let data: PasswordOrOtpData = data.into_inner();
|
||||
let user = headers.user;
|
||||
|
||||
data.validate(&user, false, &mut conn).await?;
|
||||
data.validate(&user, false, &conn).await?;
|
||||
|
||||
let (enabled, registrations) = get_webauthn_registrations(&user.uuid, &mut conn).await?;
|
||||
let (enabled, registrations) = get_webauthn_registrations(&user.uuid, &conn).await?;
|
||||
let registrations_json: Vec<Value> = registrations.iter().map(WebauthnRegistration::to_json).collect();
|
||||
|
||||
Ok(Json(json!({
|
||||
@@ -128,13 +128,13 @@ async fn get_webauthn(data: Json<PasswordOrOtpData>, headers: Headers, mut conn:
|
||||
}
|
||||
|
||||
#[post("/two-factor/get-webauthn-challenge", data = "<data>")]
|
||||
async fn generate_webauthn_challenge(data: Json<PasswordOrOtpData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn generate_webauthn_challenge(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let data: PasswordOrOtpData = data.into_inner();
|
||||
let user = headers.user;
|
||||
|
||||
data.validate(&user, false, &mut conn).await?;
|
||||
data.validate(&user, false, &conn).await?;
|
||||
|
||||
let registrations = get_webauthn_registrations(&user.uuid, &mut conn)
|
||||
let registrations = get_webauthn_registrations(&user.uuid, &conn)
|
||||
.await?
|
||||
.1
|
||||
.into_iter()
|
||||
@@ -153,7 +153,7 @@ async fn generate_webauthn_challenge(data: Json<PasswordOrOtpData>, headers: Hea
|
||||
state["rs"]["extensions"].as_object_mut().unwrap().clear();
|
||||
|
||||
let type_ = TwoFactorType::WebauthnRegisterChallenge;
|
||||
TwoFactor::new(user.uuid.clone(), type_, serde_json::to_string(&state)?).save(&mut conn).await?;
|
||||
TwoFactor::new(user.uuid.clone(), type_, serde_json::to_string(&state)?).save(&conn).await?;
|
||||
|
||||
// Because for this flow we abuse the passkeys as 2FA, and use it more like a securitykey
|
||||
// we need to modify some of the default settings defined by `start_passkey_registration()`.
|
||||
@@ -252,7 +252,7 @@ impl From<PublicKeyCredentialCopy> for PublicKeyCredential {
|
||||
}
|
||||
|
||||
#[post("/two-factor/webauthn", data = "<data>")]
|
||||
async fn activate_webauthn(data: Json<EnableWebauthnData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn activate_webauthn(data: Json<EnableWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let data: EnableWebauthnData = data.into_inner();
|
||||
let mut user = headers.user;
|
||||
|
||||
@@ -260,15 +260,15 @@ async fn activate_webauthn(data: Json<EnableWebauthnData>, headers: Headers, mut
|
||||
master_password_hash: data.master_password_hash,
|
||||
otp: data.otp,
|
||||
}
|
||||
.validate(&user, true, &mut conn)
|
||||
.validate(&user, true, &conn)
|
||||
.await?;
|
||||
|
||||
// Retrieve and delete the saved challenge state
|
||||
let type_ = TwoFactorType::WebauthnRegisterChallenge as i32;
|
||||
let state = match TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await {
|
||||
let state = match TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await {
|
||||
Some(tf) => {
|
||||
let state: PasskeyRegistration = serde_json::from_str(&tf.data)?;
|
||||
tf.delete(&mut conn).await?;
|
||||
tf.delete(&conn).await?;
|
||||
state
|
||||
}
|
||||
None => err!("Can't recover challenge"),
|
||||
@@ -277,7 +277,7 @@ async fn activate_webauthn(data: Json<EnableWebauthnData>, headers: Headers, mut
|
||||
// Verify the credentials with the saved state
|
||||
let credential = WEBAUTHN.finish_passkey_registration(&data.device_response.into(), &state)?;
|
||||
|
||||
let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &mut conn).await?.1;
|
||||
let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &conn).await?.1;
|
||||
// TODO: Check for repeated ID's
|
||||
registrations.push(WebauthnRegistration {
|
||||
id: data.id.into_i32()?,
|
||||
@@ -289,11 +289,11 @@ async fn activate_webauthn(data: Json<EnableWebauthnData>, headers: Headers, mut
|
||||
|
||||
// Save the registrations and return them
|
||||
TwoFactor::new(user.uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(®istrations)?)
|
||||
.save(&mut conn)
|
||||
.save(&conn)
|
||||
.await?;
|
||||
_generate_recover_code(&mut user, &mut conn).await;
|
||||
_generate_recover_code(&mut user, &conn).await;
|
||||
|
||||
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await;
|
||||
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await;
|
||||
|
||||
let keys_json: Vec<Value> = registrations.iter().map(WebauthnRegistration::to_json).collect();
|
||||
Ok(Json(json!({
|
||||
@@ -316,14 +316,14 @@ struct DeleteU2FData {
|
||||
}
|
||||
|
||||
#[delete("/two-factor/webauthn", data = "<data>")]
|
||||
async fn delete_webauthn(data: Json<DeleteU2FData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn delete_webauthn(data: Json<DeleteU2FData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let id = data.id.into_i32()?;
|
||||
if !headers.user.check_valid_password(&data.master_password_hash) {
|
||||
err!("Invalid password");
|
||||
}
|
||||
|
||||
let Some(mut tf) =
|
||||
TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::Webauthn as i32, &mut conn).await
|
||||
TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::Webauthn as i32, &conn).await
|
||||
else {
|
||||
err!("Webauthn data not found!")
|
||||
};
|
||||
@@ -336,12 +336,11 @@ async fn delete_webauthn(data: Json<DeleteU2FData>, headers: Headers, mut conn:
|
||||
|
||||
let removed_item = data.remove(item_pos);
|
||||
tf.data = serde_json::to_string(&data)?;
|
||||
tf.save(&mut conn).await?;
|
||||
tf.save(&conn).await?;
|
||||
drop(tf);
|
||||
|
||||
// If entry is migrated from u2f, delete the u2f entry as well
|
||||
if let Some(mut u2f) =
|
||||
TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::U2f as i32, &mut conn).await
|
||||
if let Some(mut u2f) = TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::U2f as i32, &conn).await
|
||||
{
|
||||
let mut data: Vec<U2FRegistration> = match serde_json::from_str(&u2f.data) {
|
||||
Ok(d) => d,
|
||||
@@ -352,7 +351,7 @@ async fn delete_webauthn(data: Json<DeleteU2FData>, headers: Headers, mut conn:
|
||||
let new_data_str = serde_json::to_string(&data)?;
|
||||
|
||||
u2f.data = new_data_str;
|
||||
u2f.save(&mut conn).await?;
|
||||
u2f.save(&conn).await?;
|
||||
}
|
||||
|
||||
let keys_json: Vec<Value> = data.iter().map(WebauthnRegistration::to_json).collect();
|
||||
@@ -366,7 +365,7 @@ async fn delete_webauthn(data: Json<DeleteU2FData>, headers: Headers, mut conn:
|
||||
|
||||
pub async fn get_webauthn_registrations(
|
||||
user_id: &UserId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Result<(bool, Vec<WebauthnRegistration>), Error> {
|
||||
let type_ = TwoFactorType::Webauthn as i32;
|
||||
match TwoFactor::find_by_user_and_type(user_id, type_, conn).await {
|
||||
@@ -375,7 +374,7 @@ pub async fn get_webauthn_registrations(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn generate_webauthn_login(user_id: &UserId, conn: &mut DbConn) -> JsonResult {
|
||||
pub async fn generate_webauthn_login(user_id: &UserId, conn: &DbConn) -> JsonResult {
|
||||
// Load saved credentials
|
||||
let creds: Vec<Passkey> =
|
||||
get_webauthn_registrations(user_id, conn).await?.1.into_iter().map(|r| r.credential).collect();
|
||||
@@ -415,7 +414,7 @@ pub async fn generate_webauthn_login(user_id: &UserId, conn: &mut DbConn) -> Jso
|
||||
Ok(Json(serde_json::to_value(response.public_key)?))
|
||||
}
|
||||
|
||||
pub async fn validate_webauthn_login(user_id: &UserId, response: &str, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn validate_webauthn_login(user_id: &UserId, response: &str, conn: &DbConn) -> EmptyResult {
|
||||
let type_ = TwoFactorType::WebauthnLoginChallenge as i32;
|
||||
let mut state = match TwoFactor::find_by_user_and_type(user_id, type_, conn).await {
|
||||
Some(tf) => {
|
||||
@@ -469,7 +468,7 @@ async fn check_and_update_backup_eligible(
|
||||
rsp: &PublicKeyCredential,
|
||||
registrations: &mut Vec<WebauthnRegistration>,
|
||||
state: &mut PasskeyAuthentication,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
// The feature flags from the response
|
||||
// For details see: https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data
|
||||
|
||||
@@ -83,19 +83,19 @@ async fn verify_yubikey_otp(otp: String) -> EmptyResult {
|
||||
}
|
||||
|
||||
#[post("/two-factor/get-yubikey", data = "<data>")]
|
||||
async fn generate_yubikey(data: Json<PasswordOrOtpData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn generate_yubikey(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
// Make sure the credentials are set
|
||||
get_yubico_credentials()?;
|
||||
|
||||
let data: PasswordOrOtpData = data.into_inner();
|
||||
let user = headers.user;
|
||||
|
||||
data.validate(&user, false, &mut conn).await?;
|
||||
data.validate(&user, false, &conn).await?;
|
||||
|
||||
let user_id = &user.uuid;
|
||||
let yubikey_type = TwoFactorType::YubiKey as i32;
|
||||
|
||||
let r = TwoFactor::find_by_user_and_type(user_id, yubikey_type, &mut conn).await;
|
||||
let r = TwoFactor::find_by_user_and_type(user_id, yubikey_type, &conn).await;
|
||||
|
||||
if let Some(r) = r {
|
||||
let yubikey_metadata: YubikeyMetadata = serde_json::from_str(&r.data)?;
|
||||
@@ -116,7 +116,7 @@ async fn generate_yubikey(data: Json<PasswordOrOtpData>, headers: Headers, mut c
|
||||
}
|
||||
|
||||
#[post("/two-factor/yubikey", data = "<data>")]
|
||||
async fn activate_yubikey(data: Json<EnableYubikeyData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn activate_yubikey(data: Json<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let data: EnableYubikeyData = data.into_inner();
|
||||
let mut user = headers.user;
|
||||
|
||||
@@ -124,12 +124,12 @@ async fn activate_yubikey(data: Json<EnableYubikeyData>, headers: Headers, mut c
|
||||
master_password_hash: data.master_password_hash.clone(),
|
||||
otp: data.otp.clone(),
|
||||
}
|
||||
.validate(&user, true, &mut conn)
|
||||
.validate(&user, true, &conn)
|
||||
.await?;
|
||||
|
||||
// Check if we already have some data
|
||||
let mut yubikey_data =
|
||||
match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::YubiKey as i32, &mut conn).await {
|
||||
match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::YubiKey as i32, &conn).await {
|
||||
Some(data) => data,
|
||||
None => TwoFactor::new(user.uuid.clone(), TwoFactorType::YubiKey, String::new()),
|
||||
};
|
||||
@@ -160,11 +160,11 @@ async fn activate_yubikey(data: Json<EnableYubikeyData>, headers: Headers, mut c
|
||||
};
|
||||
|
||||
yubikey_data.data = serde_json::to_string(&yubikey_metadata).unwrap();
|
||||
yubikey_data.save(&mut conn).await?;
|
||||
yubikey_data.save(&conn).await?;
|
||||
|
||||
_generate_recover_code(&mut user, &mut conn).await;
|
||||
_generate_recover_code(&mut user, &conn).await;
|
||||
|
||||
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await;
|
||||
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await;
|
||||
|
||||
let mut result = jsonify_yubikeys(yubikey_metadata.keys);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user