fix: avoid panic in emergency access jobs and attachment save

This commit is contained in:
Pham Quang Nghi 2026-04-25 21:41:14 +07:00 committed by GitHub
parent 7cf0c5d67e
commit 645164382e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 89 additions and 44 deletions

View file

@ -11,6 +11,7 @@ use rocket::{
use serde_json::Value;
use crate::auth::ClientVersion;
use crate::error::MapResult;
use crate::util::{deser_opt_nonempty_str, save_temp_file, NumberOrString};
use crate::{
api::{self, core::log_event, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType},
@ -1132,7 +1133,7 @@ async fn post_attachment_v2(
let attachment_id = crypto::generate_attachment_id();
let attachment =
Attachment::new(attachment_id.clone(), cipher.uuid.clone(), data.file_name, file_size, Some(data.key));
attachment.save(&conn).await.expect("Error saving attachment");
attachment.save(&conn).await.map_res("Error saving attachment")?;
let url = format!("/ciphers/{}/attachment/{attachment_id}", cipher.uuid);
let response_key = match data.admin_request {
@ -1272,7 +1273,7 @@ async fn save_attachment(
if size != attachment.file_size {
// Update the attachment with the actual file size.
attachment.file_size = size;
attachment.save(&conn).await.expect("Error updating attachment");
attachment.save(&conn).await.map_res("Error updating attachment")?;
}
} else {
attachment.delete(&conn).await.ok();
@ -1285,17 +1286,14 @@ async fn save_attachment(
// SAFETY: This value is only stored in the database and is not used to access the file system.
// As a result, the conditions specified by Rocket [0] are met and this is safe to use.
// [0]: https://docs.rs/rocket/latest/rocket/fs/struct.FileName.html#-danger-
let encrypted_filename = data.data.raw_name().map(|s| s.dangerous_unsafe_unsanitized_raw().to_string());
if encrypted_filename.is_none() {
let Some(filename) = data.data.raw_name().map(|s| s.dangerous_unsafe_unsanitized_raw().to_string()) else {
err!("No filename provided")
}
};
if data.key.is_none() {
err!("No attachment key provided")
}
let attachment =
Attachment::new(file_id.clone(), cipher_id.clone(), encrypted_filename.unwrap(), size, data.key);
attachment.save(&conn).await.expect("Error saving attachment");
let attachment = Attachment::new(file_id.clone(), cipher_id.clone(), filename, size, data.key);
attachment.save(&conn).await.map_res("Error saving attachment")?;
}
save_temp_file(&PathType::Attachments, &format!("{cipher_id}/{file_id}"), data.data, true).await?;

View file

@ -90,9 +90,10 @@ async fn get_emergency_access(emer_id: EmergencyAccessId, headers: Headers, conn
check_emergency_access_enabled()?;
match EmergencyAccess::find_by_uuid_and_grantor_uuid(&emer_id, &headers.user.uuid, &conn).await {
Some(emergency_access) => Ok(Json(
emergency_access.to_json_grantee_details(&conn).await.expect("Grantee user should exist but does not!"),
)),
Some(emergency_access) => match emergency_access.to_json_grantee_details(&conn).await {
Some(details) => Ok(Json(details)),
None => err!("Grantee user does not exist for this emergency access."),
},
None => err!("Emergency access not valid."),
}
}
@ -257,8 +258,11 @@ async fn send_invite(data: Json<EmergencyAccessInviteData>, headers: Headers, co
new_emergency_access.save(&conn).await?;
if CONFIG.mail_enabled() {
let Some(invite_email) = new_emergency_access.email.clone() else {
err!("Grantee email does not exist on emergency access record")
};
mail::send_emergency_access_invite(
&new_emergency_access.email.expect("Grantee email does not exists"),
&invite_email,
grantee_user.uuid,
new_emergency_access.uuid,
&grantor_user.name,
@ -702,8 +706,7 @@ fn is_valid_request(
requesting_user_id: &UserId,
requested_access_type: EmergencyAccessType,
) -> bool {
emergency_access.grantee_uuid.is_some()
&& emergency_access.grantee_uuid.as_ref().unwrap() == requesting_user_id
emergency_access.grantee_uuid.as_ref() == Some(requesting_user_id)
&& emergency_access.status == EmergencyAccessStatus::RecoveryApproved as i32
&& emergency_access.atype == requested_access_type as i32
}
@ -731,37 +734,58 @@ pub async fn emergency_request_timeout_job(pool: DbPool) {
let now = Utc::now().naive_utc();
for mut emer in emergency_access_list {
// The find_all_recoveries_initiated already checks if the recovery_initiated_at is not null (None)
let recovery_allowed_at =
emer.recovery_initiated_at.unwrap() + TimeDelta::try_days(i64::from(emer.wait_time_days)).unwrap();
let Some(recovery_initiated_at) = emer.recovery_initiated_at else {
error!("Emergency access {} has no recovery_initiated_at; skipping", emer.uuid);
continue;
};
let Some(wait_time) = TimeDelta::try_days(i64::from(emer.wait_time_days)) else {
error!("Emergency access {} has invalid wait_time_days={}; skipping", emer.uuid, emer.wait_time_days);
continue;
};
let recovery_allowed_at = recovery_initiated_at + wait_time;
if recovery_allowed_at.le(&now) {
// Only update the access status
// 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)
if let Err(e) = emer
.update_access_status_and_save(EmergencyAccessStatus::RecoveryApproved as i32, &now, &conn)
.await
.expect("Unable to update emergency access status");
{
error!("Unable to update emergency access {} status: {e:?}", emer.uuid);
continue;
}
if CONFIG.mail_enabled() {
// 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(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;
};
// 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");
let Some(grantee_uuid) = emer.grantee_uuid.as_ref() else {
error!("Grantee uuid missing for emergency access {}", emer.uuid);
continue;
};
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;
};
mail::send_emergency_access_recovery_timed_out(
if let Err(e) = mail::send_emergency_access_recovery_timed_out(
&grantor_user.email,
&grantee_user.name,
emer.get_type_as_str(),
)
.await
.expect("Error on sending email");
{
error!("Error sending recovery timed out email for {}: {e:?}", emer.uuid);
}
mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name)
.await
.expect("Error on sending email");
if let Err(e) =
mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name).await
{
error!("Error sending recovery approved email for {}: {e:?}", emer.uuid);
}
}
}
}
@ -784,43 +808,66 @@ pub async fn emergency_notification_reminder_job(pool: DbPool) {
}
let now = Utc::now().naive_utc();
let one_day = match TimeDelta::try_days(1) {
Some(d) => d,
None => {
error!("Failed to construct 1-day TimeDelta; aborting reminder job");
return;
}
};
for mut emer in emergency_access_list {
// The find_all_recoveries_initiated already checks if the recovery_initiated_at is not null (None)
// Calculate the day before the recovery will become active
let final_recovery_reminder_at =
emer.recovery_initiated_at.unwrap() + TimeDelta::try_days(i64::from(emer.wait_time_days - 1)).unwrap();
let Some(recovery_initiated_at) = emer.recovery_initiated_at else {
error!("Emergency access {} has no recovery_initiated_at; skipping", emer.uuid);
continue;
};
let Some(wait_time) = TimeDelta::try_days(i64::from(emer.wait_time_days - 1)) else {
error!("Emergency access {} has invalid wait_time_days={}; skipping", emer.uuid, emer.wait_time_days);
continue;
};
let final_recovery_reminder_at = recovery_initiated_at + wait_time;
// Calculate if a day has passed since the previous notification, else no notification has been sent before
let next_recovery_reminder_at = if let Some(last_notification_at) = emer.last_notification_at {
last_notification_at + TimeDelta::try_days(1).unwrap()
last_notification_at + one_day
} else {
now
};
if final_recovery_reminder_at.le(&now) && next_recovery_reminder_at.le(&now) {
// Only update the last notification date
// 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
.expect("Unable to update emergency access notification date");
if let Err(e) = emer.update_last_notification_date_and_save(&now, &conn).await {
error!("Unable to update emergency access {} notification date: {e:?}", emer.uuid);
continue;
}
if CONFIG.mail_enabled() {
// 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(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;
};
// 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");
let Some(grantee_uuid) = emer.grantee_uuid.as_ref() else {
error!("Grantee uuid missing for emergency access {}", emer.uuid);
continue;
};
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;
};
mail::send_emergency_access_recovery_reminder(
if 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
.expect("Error on sending email");
{
error!("Error sending recovery reminder email for {}: {e:?}", emer.uuid);
}
}
}
}