Browse Source

Fix several scheduled job issues

Several jobs could cause a panic, or stop without running through all
items because of this panic. This could cause missed items until that
panic somehow resolved it self, and probably would never work fully
again.

This could cause emergency access for example to never work again.
Besides this, emergency access was also able to be panic if the
wait-time was out-of-bounds and date/time calculation might fail.
This is also fixed now by using `checked` functions and continue upon
those issues so all awaiting items will be handled.

Signed-off-by: BlackDex <black.dex@gmail.com>
pull/7858/head
BlackDex 1 week ago
parent
commit
1d8b27d8cc
No known key found for this signature in database GPG Key ID: 58C80A2AA6C765E1
  1. 284
      src/api/core/emergency_access.rs
  2. 11
      src/api/core/events.rs
  3. 18
      src/api/core/two_factor/mod.rs
  4. 18
      src/config.rs
  5. 2
      src/db/models/auth_request.rs
  6. 9
      src/db/models/cipher.rs
  7. 67
      src/db/models/emergency_access.rs
  8. 28
      src/db/models/event.rs
  9. 4
      src/db/models/mod.rs
  10. 4
      src/db/models/two_factor_incomplete.rs

284
src/api/core/emergency_access.rs

@ -1,4 +1,4 @@
use chrono::{TimeDelta, Utc}; use chrono::Utc;
use rocket::{Route, serde::json::Json}; use rocket::{Route, serde::json::Json};
use serde_json::Value; use serde_json::Value;
@ -15,8 +15,8 @@ use crate::{
db::{ db::{
DbConn, DbPool, DbConn, DbPool,
models::{ models::{
Cipher, EmergencyAccess, EmergencyAccessId, EmergencyAccessStatus, EmergencyAccessType, Invitation, Cipher, EMERGENCY_ACCESS_TIMEOUT_RANGE, EmergencyAccess, EmergencyAccessId, EmergencyAccessStatus,
Membership, MembershipType, OrgPolicy, TwoFactor, User, UserId, EmergencyAccessType, Invitation, Membership, MembershipType, OrgPolicy, TwoFactor, User, UserId,
}, },
}, },
mail, mail,
@ -136,19 +136,21 @@ async fn post_emergency_access(
let data: EmergencyAccessUpdateData = data.into_inner(); let data: EmergencyAccessUpdateData = data.into_inner();
let Some(new_type) = EmergencyAccessType::from_str(&data.r#type.into_string()) else {
err!("Invalid emergency access type.")
};
if !(EMERGENCY_ACCESS_TIMEOUT_RANGE).contains(&data.wait_time_days) {
err!("Emergency access wait time is out-of-bounds!")
}
let Some(mut emergency_access) = let Some(mut emergency_access) =
EmergencyAccess::find_by_uuid_and_grantor_uuid(&emer_id, &headers.user.uuid, &conn).await EmergencyAccess::find_by_uuid_and_grantor_uuid(&emer_id, &headers.user.uuid, &conn).await
else { else {
err!("Emergency access not valid.") err!("Emergency access not valid.")
}; };
let new_type = if let Some(new_type) = EmergencyAccessType::from_str(&data.r#type.into_string()) { emergency_access.atype = new_type as i32;
new_type as i32
} else {
err!("Invalid emergency access type.")
};
emergency_access.atype = new_type;
emergency_access.wait_time_days = data.wait_time_days; emergency_access.wait_time_days = data.wait_time_days;
if data.key_encrypted.is_some() { if data.key_encrypted.is_some() {
emergency_access.key_encrypted = data.key_encrypted; emergency_access.key_encrypted = data.key_encrypted;
@ -210,11 +212,11 @@ async fn send_invite(data: Json<EmergencyAccessInviteData>, headers: Headers, co
let email = data.email.to_lowercase(); let email = data.email.to_lowercase();
let wait_time_days = data.wait_time_days; let wait_time_days = data.wait_time_days;
let emergency_access_status = EmergencyAccessStatus::Invited as i32; if !(EMERGENCY_ACCESS_TIMEOUT_RANGE).contains(&wait_time_days) {
err!("Emergency access wait time is out-of-bounds!")
}
let new_type = if let Some(new_type) = EmergencyAccessType::from_str(&data.r#type.into_string()) { let Some(new_type) = EmergencyAccessType::from_str(&data.r#type.into_string()) else {
new_type as i32
} else {
err!("Invalid emergency access type.") err!("Invalid emergency access type.")
}; };
@ -260,6 +262,8 @@ async fn send_invite(data: Json<EmergencyAccessInviteData>, headers: Headers, co
err!(format!("Grantee user already invited: {}", &grantee_user.email)) err!(format!("Grantee user already invited: {}", &grantee_user.email))
} }
let new_type = new_type as i32;
let emergency_access_status = EmergencyAccessStatus::Invited as i32;
let mut new_emergency_access = let mut new_emergency_access =
EmergencyAccess::new(grantor_user.uuid, grantee_user.email, emergency_access_status, new_type, wait_time_days); EmergencyAccess::new(grantor_user.uuid, grantee_user.email, emergency_access_status, new_type, wait_time_days);
new_emergency_access.save(&conn).await?; new_emergency_access.save(&conn).await?;
@ -292,7 +296,7 @@ async fn resend_invite(emer_id: EmergencyAccessId, headers: Headers, conn: DbCon
}; };
if emergency_access.status != EmergencyAccessStatus::Invited as i32 { if emergency_access.status != EmergencyAccessStatus::Invited as i32 {
err!("The grantee user is already accepted or confirmed to the organization"); err!("The grantee user is already accepted or confirmed for emergency access");
} }
let Some(email) = emergency_access.email.clone() else { let Some(email) = emergency_access.email.clone() else {
@ -453,11 +457,24 @@ async fn initiate_emergency_access(emer_id: EmergencyAccessId, headers: Headers,
let Some(mut emergency_access) = let Some(mut emergency_access) =
EmergencyAccess::find_by_uuid_and_grantee_uuid(&emer_id, &initiating_user.uuid, &conn).await EmergencyAccess::find_by_uuid_and_grantee_uuid(&emer_id, &initiating_user.uuid, &conn).await
else { else {
err!("Emergency access not valid.") err!(
"Emergency access not valid.",
format!("Emergency access '{emer_id}', not found for user '{}'.", initiating_user.email)
)
}; };
if emergency_access.status != EmergencyAccessStatus::Confirmed as i32 { if emergency_access.status != EmergencyAccessStatus::Confirmed as i32 {
err!("Emergency access not valid.") err!(
"Emergency access not valid.",
format!("Emergency access '{emer_id}', not confirmed for user '{}'.", initiating_user.email)
)
}
if !EMERGENCY_ACCESS_TIMEOUT_RANGE.contains(&emergency_access.wait_time_days) {
err!(
"Emergency access not valid.",
format!("Emergency access '{emer_id}' timeout range '{}' is invalid", emergency_access.wait_time_days)
)
} }
let Some(grantor_user) = User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await else { let Some(grantor_user) = User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await else {
@ -490,11 +507,17 @@ async fn approve_emergency_access(emer_id: EmergencyAccessId, headers: Headers,
let Some(mut emergency_access) = let Some(mut emergency_access) =
EmergencyAccess::find_by_uuid_and_grantor_uuid(&emer_id, &headers.user.uuid, &conn).await EmergencyAccess::find_by_uuid_and_grantor_uuid(&emer_id, &headers.user.uuid, &conn).await
else { else {
err!("Emergency access not valid.") err!(
"Emergency access not valid.",
format!("Emergency access '{emer_id}', not found for user '{}'.", headers.user.email)
)
}; };
if emergency_access.status != EmergencyAccessStatus::RecoveryInitiated as i32 { if emergency_access.status != EmergencyAccessStatus::RecoveryInitiated as i32 {
err!("Emergency access not valid.") err!(
"Emergency access not valid.",
format!("Emergency access '{emer_id}', not initiated for user '{}'.", headers.user.email)
)
} }
let Some(grantor_user) = User::find_by_uuid(&headers.user.uuid, &conn).await else { let Some(grantor_user) = User::find_by_uuid(&headers.user.uuid, &conn).await else {
@ -747,52 +770,71 @@ pub async fn emergency_request_timeout_job(pool: DbPool) {
return; return;
} }
if let Ok(conn) = pool.get().await { let Ok(conn) = pool.get().await else {
let emergency_access_list = EmergencyAccess::find_all_recoveries_initiated(&conn).await; error!("Failed to get DB connection for emergency time-outs!");
return;
};
let emergency_access_list = EmergencyAccess::find_all_recoveries_initiated(&conn).await;
if emergency_access_list.is_empty() {
debug!("No emergency request timeout to approve");
return;
}
let now = Utc::now().naive_utc();
for mut emer in emergency_access_list {
match emer.is_recovery_allowed(&now) {
Ok(true) => {} // Recovery is allowed
Ok(false) => continue,
Err(e) => {
error!("Failed to check if recovery is allowed for '{}': {e}", emer.uuid);
continue;
}
}
// Get grantor user to send Accepted email
let Some(grantor_user) = User::find_by_uuid(&emer.grantor_uuid, &conn).await else {
error!("Grantor user '{}' not found for emergency access '{}'", emer.grantor_uuid, emer.uuid);
continue;
};
let Some(grantee_uuid) = emer.grantee_uuid.as_ref() else {
error!("Missing grantee for emergency access '{}'", emer.uuid);
continue;
};
if emergency_access_list.is_empty() { // Get grantee user to send Accepted email
debug!("No emergency request timeout to approve"); let Some(grantee_user) = User::find_by_uuid(grantee_uuid, &conn).await else {
error!("Grantee user '{grantee_uuid}' not found for emergency access '{}'", emer.uuid);
continue;
};
// Only update the access status
// Updating the whole record could cause issues when the emergency_notification_reminder_job is also active
let status_initiated = EmergencyAccessStatus::RecoveryInitiated as i32;
let status_approved = EmergencyAccessStatus::RecoveryApproved as i32;
if let Err(e) = emer.update_access_status_and_save(status_initiated, status_approved, &now, &conn).await {
error!("Unable to update emergency access status for '{}': {e}", emer.uuid);
continue;
} }
let now = Utc::now().naive_utc(); if CONFIG.mail_enabled() {
for mut emer in emergency_access_list { if let Err(e) = mail::send_emergency_access_recovery_timed_out(
// The find_all_recoveries_initiated already checks if the recovery_initiated_at is not null (None) &grantor_user.email,
let recovery_allowed_at = &grantee_user.name,
emer.recovery_initiated_at.unwrap() + TimeDelta::try_days(i64::from(emer.wait_time_days)).unwrap(); emer.get_type_as_str(),
if recovery_allowed_at.le(&now) { )
// Only update the access status .await
// Updating the whole record could cause issues when the emergency_notification_reminder_job is also active {
emer.update_access_status_and_save(EmergencyAccessStatus::RecoveryApproved as i32, &now, &conn) error!("Failed to send emergency access recovery time-out email for '{}': {e}", emer.uuid);
.await }
.expect("Unable to update emergency access status");
if let Err(e) = mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name).await
if CONFIG.mail_enabled() { {
// get grantor user to send Accepted email error!("Failed to send emergency access recovery approved email for '{}': {e}", emer.uuid);
let grantor_user =
User::find_by_uuid(&emer.grantor_uuid, &conn).await.expect("Grantor user not found");
// get grantee user to send Accepted email
let grantee_user =
User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &conn)
.await
.expect("Grantee user not found");
mail::send_emergency_access_recovery_timed_out(
&grantor_user.email,
&grantee_user.name,
emer.get_type_as_str(),
)
.await
.expect("Error on sending email");
mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name)
.await
.expect("Error on sending email");
}
} }
} }
} else {
error!("Failed to get DB connection while searching emergency request timed out");
} }
} }
@ -802,55 +844,85 @@ pub async fn emergency_notification_reminder_job(pool: DbPool) {
return; return;
} }
if let Ok(conn) = pool.get().await { let Ok(conn) = pool.get().await else {
let emergency_access_list = EmergencyAccess::find_all_recoveries_initiated(&conn).await; error!("Failed to get DB connection for emergency reminders!");
return;
};
let emergency_access_list = EmergencyAccess::find_all_recoveries_initiated(&conn).await;
if emergency_access_list.is_empty() {
debug!("No emergency request reminder notification to send");
return;
}
if emergency_access_list.is_empty() { let now = Utc::now().naive_utc();
debug!("No emergency request reminder notification to send"); for mut emer in emergency_access_list {
let Ok(deadline_at) = emer.deadline_at() else {
error!("Invalid deadline for emergency access '{}'", emer.uuid);
continue;
};
let Some(final_reminder_at) = deadline_at.checked_sub_days(chrono::Days::new(1)) else {
error!("Cannot calculate reminder date for emergency access '{}'", emer.uuid);
continue;
};
// Only send reminders during the last 24 hours, else continue
if now < final_reminder_at || now >= deadline_at {
continue;
} }
let now = Utc::now().naive_utc(); // Check if we did not sent a recovery mail in the past 24h already, else skip it.
for mut emer in emergency_access_list { // In case something goes wrong with the date calculation or `last_notification_at` is None we use now.
// The find_all_recoveries_initiated already checks if the recovery_initiated_at is not null (None) // This is to prevent possible missed reminders, this might cause an extra reminder,
// Calculate the day before the recovery will become active // but that is better then no reminder at all.
let final_recovery_reminder_at = let next_reminder_at = if let Some(last_notification_at) = emer.last_notification_at {
emer.recovery_initiated_at.unwrap() + TimeDelta::try_days(i64::from(emer.wait_time_days - 1)).unwrap(); last_notification_at.checked_add_days(chrono::Days::new(1)).unwrap_or(now)
// Calculate if a day has passed since the previous notification, else no notification has been sent before } else {
let next_recovery_reminder_at = if let Some(last_notification_at) = emer.last_notification_at { now
last_notification_at + TimeDelta::try_days(1).unwrap() };
} else {
now // Only send a reminder if the `next_reminder_at` is less then or equal to `now`
}; // Else we can skip the current emergency access this run.
if final_recovery_reminder_at.le(&now) && next_recovery_reminder_at.le(&now) { if next_reminder_at.gt(&now) {
// Only update the last notification date continue;
// Updating the whole record could cause issues when the emergency_request_timeout_job is also active }
emer.update_last_notification_date_and_save(&now, &conn)
.await // Get grantor user to send Reminder email
.expect("Unable to update emergency access notification date"); let Some(grantor_user) = User::find_by_uuid(&emer.grantor_uuid, &conn).await else {
error!("Grantor user '{}' not found for emergency access '{}'", emer.grantor_uuid, emer.uuid);
if CONFIG.mail_enabled() { continue;
// get grantor user to send Accepted email };
let grantor_user =
User::find_by_uuid(&emer.grantor_uuid, &conn).await.expect("Grantor user not found"); let Some(grantee_uuid) = emer.grantee_uuid.as_ref() else {
error!("Missing grantee for emergency access '{}'", emer.uuid);
// get grantee user to send Accepted email continue;
let grantee_user = };
User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &conn)
.await // Get grantee user to send Reminder email
.expect("Grantee user not found"); let Some(grantee_user) = User::find_by_uuid(grantee_uuid, &conn).await else {
error!("Grantee user '{grantee_uuid}' not found for emergency access '{}'", emer.uuid);
mail::send_emergency_access_recovery_reminder( continue;
&grantor_user.email, };
&grantee_user.name,
emer.get_type_as_str(), // Only update the last notification date
"1", // This notification is only triggered one day before the activation // Updating the whole record could cause issues when the emergency_request_timeout_job is also active
) if let Err(e) = emer.update_last_notification_date_and_save(&now, &conn).await {
.await error!("Unable to update emergency access reminder date for '{}': {e}", emer.uuid);
.expect("Error on sending email"); continue;
} }
}
if CONFIG.mail_enabled()
&& let Err(e) = mail::send_emergency_access_recovery_reminder(
&grantor_user.email,
&grantee_user.name,
emer.get_type_as_str(),
"1", // This notification is only triggered one day before the activation
)
.await
{
error!("Failed to send emergency access reminder email for '{}': {e}", emer.uuid);
} }
} else {
error!("Failed to get DB connection while searching emergency notification reminder");
} }
} }

11
src/api/core/events.rs

@ -348,13 +348,16 @@ async fn log_event_impl(
pub async fn event_cleanup_job(pool: DbPool) { pub async fn event_cleanup_job(pool: DbPool) {
debug!("Start events cleanup job"); debug!("Start events cleanup job");
if CONFIG.events_days_retain().is_none() { if CONFIG.events_days_retain().is_none() {
debug!("events_days_retain is not configured, abort"); debug!("events_days_retain is not configured, skipping");
return; return;
} }
if let Ok(conn) = pool.get().await { let Ok(conn) = pool.get().await else {
Event::clean_events(&conn).await.ok();
} else {
error!("Failed to get DB connection while trying to cleanup the events table"); error!("Failed to get DB connection while trying to cleanup the events table");
return;
};
if let Err(e) = Event::clean_events(&conn).await {
error!("Failed to cleanup the events table: {e}");
} }
} }

18
src/api/core/two_factor/mod.rs

@ -196,7 +196,7 @@ pub async fn enforce_2fa_policy_for_org(
pub async fn send_incomplete_2fa_notifications(pool: DbPool) { pub async fn send_incomplete_2fa_notifications(pool: DbPool) {
debug!("Sending notifications for incomplete 2FA logins"); debug!("Sending notifications for incomplete 2FA logins");
if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() { if CONFIG.incomplete_2fa_time_limit() == 0 || !CONFIG.mail_enabled() {
return; return;
} }
@ -206,11 +206,21 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) {
}; };
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
let time_limit = TimeDelta::try_minutes(CONFIG.incomplete_2fa_time_limit()).unwrap(); let Some(time_limit) = TimeDelta::try_minutes(CONFIG.incomplete_2fa_time_limit()) else {
let time_before = now - time_limit; error!("Failed to calculate the time limit for sending incomplete 2fa attempts");
return;
};
let Some(time_before) = now.checked_sub_signed(time_limit) else {
error!("Failed to calculate the timestamp for sending incomplete 2fa attempts");
return;
};
let incomplete_logins = TwoFactorIncomplete::find_logins_before(&time_before, &conn).await; let incomplete_logins = TwoFactorIncomplete::find_logins_before(&time_before, &conn).await;
for login in incomplete_logins { for login in incomplete_logins {
let user = User::find_by_uuid(&login.user_uuid, &conn).await.expect("User not found"); let Some(user) = User::find_by_uuid(&login.user_uuid, &conn).await else {
error!("User '{}', not found!", login.user_uuid);
continue;
};
info!( info!(
"User {} did not complete a 2FA login within the configured time limit. IP: {}", "User {} did not complete a 2FA login within the configured time limit. IP: {}",
user.email, login.ip_address user.email, login.ip_address

18
src/config.rs

@ -1274,6 +1274,24 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> {
err!("`PURGE_INCOMPLETE_SSO_AUTH` is not a valid cron expression") err!("`PURGE_INCOMPLETE_SSO_AUTH` is not a valid cron expression")
} }
if cfg.trash_auto_delete_days.is_some_and(|d| d < 0) {
err!(
"`TRASH_AUTO_DELETE_DAYS` is less than 0 days, negative values are not allowed, unset to disable this feature"
);
}
if cfg.incomplete_2fa_time_limit < 0 {
err!(
"`INCOMPLETE_2FA_TIME_LIMIT` is less than 0 minutes, negative values are not allowed, use 0 to disable this feature"
);
}
if cfg.events_days_retain.is_some_and(|d| d < 0) {
err!(
"`EVENTS_DAYS_RETAIN` is less than 0 days, negative values are not allowed, unset to disable this feature"
);
}
if !cfg.disable_admin_token { if !cfg.disable_admin_token {
match cfg.admin_token.as_ref() { match cfg.admin_token.as_ref() {
Some(t) if t.starts_with("$argon2") => { Some(t) if t.starts_with("$argon2") => {

2
src/db/models/auth_request.rs

@ -197,7 +197,7 @@ impl AuthRequest {
pub async fn purge_expired_auth_requests(conn: &DbConn) { pub async fn purge_expired_auth_requests(conn: &DbConn) {
// delete auth requests older than 15 minutes which is functionally equivalent to upstream: // delete auth requests older than 15 minutes which is functionally equivalent to upstream:
// https://github.com/bitwarden/server/blob/f8ee2270409f7a13125cd414c450740af605a175/src/Sql/dbo/Auth/Stored%20Procedures/AuthRequest_DeleteIfExpired.sql // https://github.com/bitwarden/server/blob/f8ee2270409f7a13125cd414c450740af605a175/src/Sql/dbo/Auth/Stored%20Procedures/AuthRequest_DeleteIfExpired.sql
let expiry_time = Utc::now().naive_utc() - chrono::TimeDelta::try_minutes(15).unwrap(); let expiry_time = Utc::now().naive_utc() - chrono::TimeDelta::minutes(15);
for auth_request in Self::find_created_before(&expiry_time, conn).await { for auth_request in Self::find_created_before(&expiry_time, conn).await {
auth_request.delete(conn).await.ok(); auth_request.delete(conn).await.ok();
} }

9
src/db/models/cipher.rs

@ -495,8 +495,15 @@ impl Cipher {
/// Purge all ciphers that are old enough to be auto-deleted. /// Purge all ciphers that are old enough to be auto-deleted.
pub async fn purge_trash(conn: &DbConn) { pub async fn purge_trash(conn: &DbConn) {
if let Some(auto_delete_days) = CONFIG.trash_auto_delete_days() { if let Some(auto_delete_days) = CONFIG.trash_auto_delete_days() {
let Some(days) = TimeDelta::try_days(auto_delete_days) else {
error!("Unable to calculate auto delete days!");
return;
};
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
let dt = now - TimeDelta::try_days(auto_delete_days).unwrap(); let Some(dt) = now.checked_sub_signed(days) else {
error!("Unable to calculate trash purge date!");
return;
};
for cipher in Self::find_deleted_before(&dt, conn).await { for cipher in Self::find_deleted_before(&dt, conn).await {
cipher.delete(conn).await.ok(); cipher.delete(conn).await.ok();
} }

67
src/db/models/emergency_access.rs

@ -1,4 +1,4 @@
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, TimeDelta, Utc};
use derive_more::{AsRef, Deref, Display, From}; use derive_more::{AsRef, Deref, Display, From};
use diesel::prelude::*; use diesel::prelude::*;
use serde_json::Value; use serde_json::Value;
@ -112,8 +112,26 @@ impl EmergencyAccess {
"object": "emergencyAccessGranteeDetails", "object": "emergencyAccessGranteeDetails",
})) }))
} }
pub fn deadline_at(&self) -> Result<NaiveDateTime, crate::Error> {
if !(EMERGENCY_ACCESS_TIMEOUT_RANGE).contains(&self.wait_time_days) {
err!("Emergency access wait time is out-of-bounds!")
}
let initiated_at = self.recovery_initiated_at.map_res("Emergency access initiation date missing!")?;
TimeDelta::try_days(i64::from(self.wait_time_days))
.and_then(|w| initiated_at.checked_add_signed(w))
.map_res("Emergency access deadline is invalid")
}
pub fn is_recovery_allowed(&self, now: &NaiveDateTime) -> Result<bool, crate::Error> {
Ok(self.deadline_at()?.le(now))
}
} }
pub const EMERGENCY_ACCESS_TIMEOUT_RANGE: std::ops::RangeInclusive<i32> = 1..=90;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub enum EmergencyAccessType { pub enum EmergencyAccessType {
View = 0, View = 0,
@ -169,27 +187,42 @@ impl EmergencyAccess {
pub async fn update_access_status_and_save( pub async fn update_access_status_and_save(
&mut self, &mut self,
status: i32, from_status: i32,
to_status: i32,
date: &NaiveDateTime, date: &NaiveDateTime,
conn: &DbConn, conn: &DbConn,
) -> EmptyResult { ) -> EmptyResult {
let uuid = self.uuid.clone();
let updated_at = *date;
let res = conn
.run(move |conn| {
crate::util::retry(
|| {
diesel::update(
emergency_access::table
.filter(emergency_access::uuid.eq(&uuid))
.filter(emergency_access::status.eq(from_status)),
)
.set((emergency_access::status.eq(to_status), emergency_access::updated_at.eq(updated_at)))
.execute(conn)
},
10,
)
})
.await;
match res {
Ok(1) => {} // One record updated, which is valid
Ok(_) => err!("Emergency access status was changed already"),
Err(e) => err!("Error updating emergency access status", format!("{e}")),
}
self.status = to_status;
self.updated_at = updated_at;
// Update the grantee so that it will refresh it's status. // Update the grantee so that it will refresh it's status.
User::update_uuid_revision(self.grantee_uuid.as_ref().expect("Error getting grantee"), conn).await; User::update_uuid_revision(self.grantee_uuid.as_ref().expect("Error getting grantee"), conn).await;
self.status = status; Ok(())
date.clone_into(&mut self.updated_at);
conn.run(move |conn| {
crate::util::retry(
|| {
diesel::update(emergency_access::table.filter(emergency_access::uuid.eq(&self.uuid)))
.set((emergency_access::status.eq(status), emergency_access::updated_at.eq(date)))
.execute(conn)
},
10,
)
.map_res("Error updating emergency access status")
})
.await
} }
pub async fn update_last_notification_date_and_save(&mut self, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult { pub async fn update_last_notification_date_and_save(&mut self, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult {

28
src/db/models/event.rs

@ -347,17 +347,23 @@ impl Event {
} }
pub async fn clean_events(conn: &DbConn) -> EmptyResult { pub async fn clean_events(conn: &DbConn) -> EmptyResult {
if let Some(days_to_retain) = CONFIG.events_days_retain() { let Some(days_to_retain) = CONFIG.events_days_retain() else {
let dt = Utc::now().naive_utc() - TimeDelta::try_days(days_to_retain).unwrap(); return Ok(());
conn.run(move |conn| { };
diesel::delete(event::table.filter(event::event_date.lt(dt)))
.execute(conn) let Some(days_to_retain) = TimeDelta::try_days(days_to_retain) else {
.map_res("Error cleaning old events") err!("Unable to calculate the event retention date, check 'EVENTS_DAYS_RETAIN'")
}) };
.await
} else { let Some(dt) = Utc::now().naive_utc().checked_sub_signed(days_to_retain) else {
Ok(()) err!("Unable to calculate the event retention date, check 'EVENTS_DAYS_RETAIN'")
} };
conn.run(move |conn| {
diesel::delete(event::table.filter(event::event_date.lt(dt)))
.execute(conn)
.map_res("Error cleaning old events")
})
.await
} }
} }

4
src/db/models/mod.rs

@ -24,7 +24,9 @@ pub use self::auth_request::{AuthRequest, AuthRequestId};
pub use self::cipher::{Cipher, CipherId, RepromptType}; pub use self::cipher::{Cipher, CipherId, RepromptType};
pub use self::collection::{Collection, CollectionCipher, CollectionId, CollectionUser}; pub use self::collection::{Collection, CollectionCipher, CollectionId, CollectionUser};
pub use self::device::{Device, DeviceId, DeviceType, DeviceWithAuthRequest, PushId}; pub use self::device::{Device, DeviceId, DeviceType, DeviceWithAuthRequest, PushId};
pub use self::emergency_access::{EmergencyAccess, EmergencyAccessId, EmergencyAccessStatus, EmergencyAccessType}; pub use self::emergency_access::{
EMERGENCY_ACCESS_TIMEOUT_RANGE, EmergencyAccess, EmergencyAccessId, EmergencyAccessStatus, EmergencyAccessType,
};
pub use self::event::{Event, EventType}; pub use self::event::{Event, EventType};
pub use self::favorite::Favorite; pub use self::favorite::Favorite;
pub use self::folder::{Folder, FolderCipher, FolderId}; pub use self::folder::{Folder, FolderCipher, FolderId};

4
src/db/models/two_factor_incomplete.rs

@ -37,7 +37,7 @@ impl TwoFactorIncomplete {
ip: &ClientIp, ip: &ClientIp,
conn: &DbConn, conn: &DbConn,
) -> EmptyResult { ) -> EmptyResult {
if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() { if CONFIG.incomplete_2fa_time_limit() == 0 || !CONFIG.mail_enabled() {
return Ok(()); return Ok(());
} }
@ -66,7 +66,7 @@ impl TwoFactorIncomplete {
} }
pub async fn mark_complete(user_uuid: &UserId, device_uuid: &DeviceId, conn: &DbConn) -> EmptyResult { pub async fn mark_complete(user_uuid: &UserId, device_uuid: &DeviceId, conn: &DbConn) -> EmptyResult {
if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() { if CONFIG.incomplete_2fa_time_limit() == 0 || !CONFIG.mail_enabled() {
return Ok(()); return Ok(());
} }

Loading…
Cancel
Save