geforkt von mirrored/vaultwarden
Cleanups and Fixes for Emergency Access
- Several cleanups and code optimizations for Emergency Access - Fixed a race-condition regarding jobs for Emergency Access - Some other small changes like `allow(clippy::)` removals Fixes #2925
Dieser Commit ist enthalten in:
Ursprung
1b64b9e164
Commit
c0e3c2c5e1
12 geänderte Dateien mit 173 neuen und 153 gelöschten Zeilen
|
@ -119,12 +119,12 @@
|
||||||
# INCOMPLETE_2FA_SCHEDULE="30 * * * * *"
|
# INCOMPLETE_2FA_SCHEDULE="30 * * * * *"
|
||||||
##
|
##
|
||||||
## Cron schedule of the job that sends expiration reminders to emergency access grantors.
|
## Cron schedule of the job that sends expiration reminders to emergency access grantors.
|
||||||
## Defaults to hourly (5 minutes after the hour). Set blank to disable this job.
|
## Defaults to hourly (3 minutes after the hour). Set blank to disable this job.
|
||||||
# EMERGENCY_NOTIFICATION_REMINDER_SCHEDULE="0 5 * * * *"
|
# EMERGENCY_NOTIFICATION_REMINDER_SCHEDULE="0 3 * * * *"
|
||||||
##
|
##
|
||||||
## Cron schedule of the job that grants emergency access requests that have met the required wait time.
|
## Cron schedule of the job that grants emergency access requests that have met the required wait time.
|
||||||
## Defaults to hourly (5 minutes after the hour). Set blank to disable this job.
|
## Defaults to hourly (7 minutes after the hour). Set blank to disable this job.
|
||||||
# EMERGENCY_REQUEST_TIMEOUT_SCHEDULE="0 5 * * * *"
|
# EMERGENCY_REQUEST_TIMEOUT_SCHEDULE="0 7 * * * *"
|
||||||
##
|
##
|
||||||
## Cron schedule of the job that cleans old events from the event table.
|
## Cron schedule of the job that cleans old events from the event table.
|
||||||
## Defaults to daily. Set blank to disable this job. Also without EVENTS_DAYS_RETAIN set, this job will not start.
|
## Defaults to daily. Set blank to disable this job. Also without EVENTS_DAYS_RETAIN set, this job will not start.
|
||||||
|
|
|
@ -284,7 +284,7 @@ async fn invite_user(data: Json<InviteData>, _token: AdminToken, mut conn: DbCon
|
||||||
if CONFIG.mail_enabled() {
|
if CONFIG.mail_enabled() {
|
||||||
mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None).await
|
mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None).await
|
||||||
} else {
|
} else {
|
||||||
let invitation = Invitation::new(user.email.clone());
|
let invitation = Invitation::new(&user.email);
|
||||||
invitation.save(conn).await
|
invitation.save(conn).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
use rocket::serde::json::Json;
|
use rocket::{serde::json::Json, Route};
|
||||||
use rocket::Route;
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -41,9 +40,10 @@ pub fn routes() -> Vec<Route> {
|
||||||
async fn get_contacts(headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn get_contacts(headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let mut emergency_access_list_json = Vec::new();
|
let emergency_access_list = EmergencyAccess::find_all_by_grantor_uuid(&headers.user.uuid, &mut conn).await;
|
||||||
for e in EmergencyAccess::find_all_by_grantor_uuid(&headers.user.uuid, &mut conn).await {
|
let mut emergency_access_list_json = Vec::with_capacity(emergency_access_list.len());
|
||||||
emergency_access_list_json.push(e.to_json_grantee_details(&mut conn).await);
|
for ea in emergency_access_list {
|
||||||
|
emergency_access_list_json.push(ea.to_json_grantee_details(&mut conn).await);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(json!({
|
Ok(Json(json!({
|
||||||
|
@ -57,9 +57,10 @@ async fn get_contacts(headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
async fn get_grantees(headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn get_grantees(headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let mut emergency_access_list_json = Vec::new();
|
let emergency_access_list = EmergencyAccess::find_all_by_grantee_uuid(&headers.user.uuid, &mut conn).await;
|
||||||
for e in EmergencyAccess::find_all_by_grantee_uuid(&headers.user.uuid, &mut conn).await {
|
let mut emergency_access_list_json = Vec::with_capacity(emergency_access_list.len());
|
||||||
emergency_access_list_json.push(e.to_json_grantor_details(&mut conn).await);
|
for ea in emergency_access_list {
|
||||||
|
emergency_access_list_json.push(ea.to_json_grantor_details(&mut conn).await);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(json!({
|
Ok(Json(json!({
|
||||||
|
@ -83,7 +84,7 @@ async fn get_emergency_access(emer_id: String, mut conn: DbConn) -> JsonResult {
|
||||||
|
|
||||||
// region put/post
|
// region put/post
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
struct EmergencyAccessUpdateData {
|
struct EmergencyAccessUpdateData {
|
||||||
Type: NumberOrString,
|
Type: NumberOrString,
|
||||||
|
@ -160,7 +161,7 @@ async fn post_delete_emergency_access(emer_id: String, headers: Headers, conn: D
|
||||||
|
|
||||||
// region invite
|
// region invite
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
struct EmergencyAccessInviteData {
|
struct EmergencyAccessInviteData {
|
||||||
Email: String,
|
Email: String,
|
||||||
|
@ -193,7 +194,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
|
||||||
let grantee_user = match User::find_by_mail(&email, &mut conn).await {
|
let grantee_user = match User::find_by_mail(&email, &mut conn).await {
|
||||||
None => {
|
None => {
|
||||||
if !CONFIG.invitations_allowed() {
|
if !CONFIG.invitations_allowed() {
|
||||||
err!(format!("Grantee user does not exist: {}", email))
|
err!(format!("Grantee user does not exist: {}", &email))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !CONFIG.is_email_domain_allowed(&email) {
|
if !CONFIG.is_email_domain_allowed(&email) {
|
||||||
|
@ -201,7 +202,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
|
||||||
}
|
}
|
||||||
|
|
||||||
if !CONFIG.mail_enabled() {
|
if !CONFIG.mail_enabled() {
|
||||||
let invitation = Invitation::new(email.clone());
|
let invitation = Invitation::new(&email);
|
||||||
invitation.save(&mut conn).await?;
|
invitation.save(&mut conn).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,36 +222,29 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
|
||||||
.await
|
.await
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
err!(format!("Grantee user already invited: {}", email))
|
err!(format!("Grantee user already invited: {}", &grantee_user.email))
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut new_emergency_access = EmergencyAccess::new(
|
let mut new_emergency_access =
|
||||||
grantor_user.uuid.clone(),
|
EmergencyAccess::new(grantor_user.uuid, grantee_user.email, emergency_access_status, new_type, wait_time_days);
|
||||||
Some(grantee_user.email.clone()),
|
|
||||||
emergency_access_status,
|
|
||||||
new_type,
|
|
||||||
wait_time_days,
|
|
||||||
);
|
|
||||||
new_emergency_access.save(&mut conn).await?;
|
new_emergency_access.save(&mut conn).await?;
|
||||||
|
|
||||||
if CONFIG.mail_enabled() {
|
if CONFIG.mail_enabled() {
|
||||||
mail::send_emergency_access_invite(
|
mail::send_emergency_access_invite(
|
||||||
&grantee_user.email,
|
&new_emergency_access.email.expect("Grantee email does not exists"),
|
||||||
&grantee_user.uuid,
|
&grantee_user.uuid,
|
||||||
Some(new_emergency_access.uuid),
|
&new_emergency_access.uuid,
|
||||||
Some(grantor_user.name.clone()),
|
&grantor_user.name,
|
||||||
Some(grantor_user.email),
|
&grantor_user.email,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
// Automatically mark user as accepted if no email invites
|
// Automatically mark user as accepted if no email invites
|
||||||
match User::find_by_mail(&email, &mut conn).await {
|
match User::find_by_mail(&email, &mut conn).await {
|
||||||
Some(user) => {
|
Some(user) => match accept_invite_process(user.uuid, &mut new_emergency_access, &email, &mut conn).await {
|
||||||
match accept_invite_process(user.uuid, new_emergency_access.uuid, Some(email), &mut conn).await {
|
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => err!(e.to_string()),
|
Err(e) => err!(e.to_string()),
|
||||||
}
|
},
|
||||||
}
|
|
||||||
None => err!("Grantee user not found."),
|
None => err!("Grantee user not found."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,7 +256,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
|
||||||
async fn resend_invite(emer_id: String, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
async fn resend_invite(emer_id: String, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
|
@ -291,19 +285,19 @@ async fn resend_invite(emer_id: String, headers: Headers, mut conn: DbConn) -> E
|
||||||
mail::send_emergency_access_invite(
|
mail::send_emergency_access_invite(
|
||||||
&email,
|
&email,
|
||||||
&grantor_user.uuid,
|
&grantor_user.uuid,
|
||||||
Some(emergency_access.uuid),
|
&emergency_access.uuid,
|
||||||
Some(grantor_user.name.clone()),
|
&grantor_user.name,
|
||||||
Some(grantor_user.email),
|
&grantor_user.email,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
if Invitation::find_by_mail(&email, &mut conn).await.is_none() {
|
if Invitation::find_by_mail(&email, &mut conn).await.is_none() {
|
||||||
let invitation = Invitation::new(email);
|
let invitation = Invitation::new(&email);
|
||||||
invitation.save(&mut conn).await?;
|
invitation.save(&mut conn).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatically mark user as accepted if no email invites
|
// Automatically mark user as accepted if no email invites
|
||||||
match accept_invite_process(grantee_user.uuid, emergency_access.uuid, emergency_access.email, &mut conn).await {
|
match accept_invite_process(grantee_user.uuid, &mut emergency_access, &email, &mut conn).await {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => err!(e.to_string()),
|
Err(e) => err!(e.to_string()),
|
||||||
}
|
}
|
||||||
|
@ -319,13 +313,24 @@ struct AcceptData {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/emergency-access/<emer_id>/accept", data = "<data>")]
|
#[post("/emergency-access/<emer_id>/accept", data = "<data>")]
|
||||||
async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, mut conn: DbConn) -> EmptyResult {
|
async fn accept_invite(
|
||||||
|
emer_id: String,
|
||||||
|
data: JsonUpcase<AcceptData>,
|
||||||
|
headers: Headers,
|
||||||
|
mut conn: DbConn,
|
||||||
|
) -> EmptyResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let data: AcceptData = data.into_inner().data;
|
let data: AcceptData = data.into_inner().data;
|
||||||
let token = &data.Token;
|
let token = &data.Token;
|
||||||
let claims = decode_emergency_access_invite(token)?;
|
let claims = decode_emergency_access_invite(token)?;
|
||||||
|
|
||||||
|
// This can happen if the user who received the invite used a different email to signup.
|
||||||
|
// Since we do not know if this is intented, we error out here and do nothing with the invite.
|
||||||
|
if claims.email != headers.user.email {
|
||||||
|
err!("Claim email does not match current users email")
|
||||||
|
}
|
||||||
|
|
||||||
let grantee_user = match User::find_by_mail(&claims.email, &mut conn).await {
|
let grantee_user = match User::find_by_mail(&claims.email, &mut conn).await {
|
||||||
Some(user) => {
|
Some(user) => {
|
||||||
Invitation::take(&claims.email, &mut conn).await;
|
Invitation::take(&claims.email, &mut conn).await;
|
||||||
|
@ -334,7 +339,7 @@ async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, mut conn:
|
||||||
None => err!("Invited user not found"),
|
None => err!("Invited user not found"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
|
@ -345,13 +350,11 @@ async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, mut conn:
|
||||||
None => err!("Grantor user not found."),
|
None => err!("Grantor user not found."),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (claims.emer_id.is_some() && emer_id == claims.emer_id.unwrap())
|
if emer_id == claims.emer_id
|
||||||
&& (claims.grantor_name.is_some() && grantor_user.name == claims.grantor_name.unwrap())
|
&& grantor_user.name == claims.grantor_name
|
||||||
&& (claims.grantor_email.is_some() && grantor_user.email == claims.grantor_email.unwrap())
|
&& grantor_user.email == claims.grantor_email
|
||||||
{
|
|
||||||
match accept_invite_process(grantee_user.uuid.clone(), emer_id, Some(grantee_user.email.clone()), &mut conn)
|
|
||||||
.await
|
|
||||||
{
|
{
|
||||||
|
match accept_invite_process(grantee_user.uuid, &mut emergency_access, &grantee_user.email, &mut conn).await {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => err!(e.to_string()),
|
Err(e) => err!(e.to_string()),
|
||||||
}
|
}
|
||||||
|
@ -368,17 +371,11 @@ async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, mut conn:
|
||||||
|
|
||||||
async fn accept_invite_process(
|
async fn accept_invite_process(
|
||||||
grantee_uuid: String,
|
grantee_uuid: String,
|
||||||
emer_id: String,
|
emergency_access: &mut EmergencyAccess,
|
||||||
email: Option<String>,
|
grantee_email: &str,
|
||||||
conn: &mut DbConn,
|
conn: &mut DbConn,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, conn).await {
|
if emergency_access.email.is_none() || emergency_access.email.as_ref().unwrap() != grantee_email {
|
||||||
Some(emer) => emer,
|
|
||||||
None => err!("Emergency access not valid."),
|
|
||||||
};
|
|
||||||
|
|
||||||
let emer_email = emergency_access.email;
|
|
||||||
if emer_email.is_none() || emer_email != email {
|
|
||||||
err!("User email does not match invite.");
|
err!("User email does not match invite.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,7 +460,7 @@ async fn initiate_emergency_access(emer_id: String, headers: Headers, mut conn:
|
||||||
};
|
};
|
||||||
|
|
||||||
if emergency_access.status != EmergencyAccessStatus::Confirmed as i32
|
if emergency_access.status != EmergencyAccessStatus::Confirmed as i32
|
||||||
|| emergency_access.grantee_uuid != Some(initiating_user.uuid.clone())
|
|| emergency_access.grantee_uuid != Some(initiating_user.uuid)
|
||||||
{
|
{
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
}
|
}
|
||||||
|
@ -485,7 +482,7 @@ async fn initiate_emergency_access(emer_id: String, headers: Headers, mut conn:
|
||||||
&grantor_user.email,
|
&grantor_user.email,
|
||||||
&initiating_user.name,
|
&initiating_user.name,
|
||||||
emergency_access.get_type_as_str(),
|
emergency_access.get_type_as_str(),
|
||||||
&emergency_access.wait_time_days.clone().to_string(),
|
&emergency_access.wait_time_days,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
@ -496,19 +493,18 @@ async fn initiate_emergency_access(emer_id: String, headers: Headers, mut conn:
|
||||||
async fn approve_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn approve_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let approving_user = headers.user;
|
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
|
|
||||||
if emergency_access.status != EmergencyAccessStatus::RecoveryInitiated as i32
|
if emergency_access.status != EmergencyAccessStatus::RecoveryInitiated as i32
|
||||||
|| emergency_access.grantor_uuid != approving_user.uuid
|
|| emergency_access.grantor_uuid != headers.user.uuid
|
||||||
{
|
{
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
}
|
}
|
||||||
|
|
||||||
let grantor_user = match User::find_by_uuid(&approving_user.uuid, &mut conn).await {
|
let grantor_user = match User::find_by_uuid(&headers.user.uuid, &mut conn).await {
|
||||||
Some(user) => user,
|
Some(user) => user,
|
||||||
None => err!("Grantor user not found."),
|
None => err!("Grantor user not found."),
|
||||||
};
|
};
|
||||||
|
@ -535,7 +531,6 @@ async fn approve_emergency_access(emer_id: String, headers: Headers, mut conn: D
|
||||||
async fn reject_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn reject_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let rejecting_user = headers.user;
|
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
|
@ -543,12 +538,12 @@ async fn reject_emergency_access(emer_id: String, headers: Headers, mut conn: Db
|
||||||
|
|
||||||
if (emergency_access.status != EmergencyAccessStatus::RecoveryInitiated as i32
|
if (emergency_access.status != EmergencyAccessStatus::RecoveryInitiated as i32
|
||||||
&& emergency_access.status != EmergencyAccessStatus::RecoveryApproved as i32)
|
&& emergency_access.status != EmergencyAccessStatus::RecoveryApproved as i32)
|
||||||
|| emergency_access.grantor_uuid != rejecting_user.uuid
|
|| emergency_access.grantor_uuid != headers.user.uuid
|
||||||
{
|
{
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
}
|
}
|
||||||
|
|
||||||
let grantor_user = match User::find_by_uuid(&rejecting_user.uuid, &mut conn).await {
|
let grantor_user = match User::find_by_uuid(&headers.user.uuid, &mut conn).await {
|
||||||
Some(user) => user,
|
Some(user) => user,
|
||||||
None => err!("Grantor user not found."),
|
None => err!("Grantor user not found."),
|
||||||
};
|
};
|
||||||
|
@ -579,14 +574,12 @@ async fn reject_emergency_access(emer_id: String, headers: Headers, mut conn: Db
|
||||||
async fn view_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn view_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let requesting_user = headers.user;
|
|
||||||
let host = headers.host;
|
|
||||||
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !is_valid_request(&emergency_access, requesting_user.uuid, EmergencyAccessType::View) {
|
if !is_valid_request(&emergency_access, headers.user.uuid, EmergencyAccessType::View) {
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,7 +589,8 @@ async fn view_emergency_access(emer_id: String, headers: Headers, mut conn: DbCo
|
||||||
|
|
||||||
let mut ciphers_json = Vec::new();
|
let mut ciphers_json = Vec::new();
|
||||||
for c in ciphers {
|
for c in ciphers {
|
||||||
ciphers_json.push(c.to_json(&host, &emergency_access.grantor_uuid, Some(&cipher_sync_data), &mut conn).await);
|
ciphers_json
|
||||||
|
.push(c.to_json(&headers.host, &emergency_access.grantor_uuid, Some(&cipher_sync_data), &mut conn).await);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(json!({
|
Ok(Json(json!({
|
||||||
|
@ -633,7 +627,7 @@ async fn takeover_emergency_access(emer_id: String, headers: Headers, mut conn:
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
struct EmergencyAccessPasswordData {
|
struct EmergencyAccessPasswordData {
|
||||||
NewMasterPasswordHash: String,
|
NewMasterPasswordHash: String,
|
||||||
|
@ -738,40 +732,44 @@ pub async fn emergency_request_timeout_job(pool: DbPool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(mut conn) = pool.get().await {
|
if let Ok(mut conn) = pool.get().await {
|
||||||
let emergency_access_list = EmergencyAccess::find_all_recoveries(&mut conn).await;
|
let emergency_access_list = EmergencyAccess::find_all_recoveries_initiated(&mut conn).await;
|
||||||
|
|
||||||
if emergency_access_list.is_empty() {
|
if emergency_access_list.is_empty() {
|
||||||
debug!("No emergency request timeout to approve");
|
debug!("No emergency request timeout to approve");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let now = Utc::now().naive_utc();
|
||||||
for mut emer in emergency_access_list {
|
for mut emer in emergency_access_list {
|
||||||
if emer.recovery_initiated_at.is_some()
|
// The find_all_recoveries_initiated already checks if the recovery_initiated_at is not null (None)
|
||||||
&& Utc::now().naive_utc()
|
let recovery_allowed_at =
|
||||||
>= emer.recovery_initiated_at.unwrap() + Duration::days(i64::from(emer.wait_time_days))
|
emer.recovery_initiated_at.unwrap() + Duration::days(i64::from(emer.wait_time_days));
|
||||||
{
|
if recovery_allowed_at.le(&now) {
|
||||||
emer.status = EmergencyAccessStatus::RecoveryApproved as i32;
|
// Only update the access status
|
||||||
emer.save(&mut conn).await.expect("Cannot save emergency access on job");
|
// 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, &mut conn)
|
||||||
|
.await
|
||||||
|
.expect("Unable to update emergency access status");
|
||||||
|
|
||||||
if CONFIG.mail_enabled() {
|
if CONFIG.mail_enabled() {
|
||||||
// get grantor user to send Accepted email
|
// get grantor user to send Accepted email
|
||||||
let grantor_user =
|
let grantor_user =
|
||||||
User::find_by_uuid(&emer.grantor_uuid, &mut conn).await.expect("Grantor user not found.");
|
User::find_by_uuid(&emer.grantor_uuid, &mut conn).await.expect("Grantor user not found");
|
||||||
|
|
||||||
// get grantee user to send Accepted email
|
// get grantee user to send Accepted email
|
||||||
let grantee_user =
|
let grantee_user =
|
||||||
User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid."), &mut conn)
|
User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &mut conn)
|
||||||
.await
|
.await
|
||||||
.expect("Grantee user not found.");
|
.expect("Grantee user not found");
|
||||||
|
|
||||||
mail::send_emergency_access_recovery_timed_out(
|
mail::send_emergency_access_recovery_timed_out(
|
||||||
&grantor_user.email,
|
&grantor_user.email,
|
||||||
&grantee_user.name.clone(),
|
&grantee_user.name,
|
||||||
emer.get_type_as_str(),
|
emer.get_type_as_str(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Error on sending email");
|
.expect("Error on sending email");
|
||||||
|
|
||||||
mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name.clone())
|
mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name)
|
||||||
.await
|
.await
|
||||||
.expect("Error on sending email");
|
.expect("Error on sending email");
|
||||||
}
|
}
|
||||||
|
@ -789,38 +787,47 @@ pub async fn emergency_notification_reminder_job(pool: DbPool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(mut conn) = pool.get().await {
|
if let Ok(mut conn) = pool.get().await {
|
||||||
let emergency_access_list = EmergencyAccess::find_all_recoveries(&mut conn).await;
|
let emergency_access_list = EmergencyAccess::find_all_recoveries_initiated(&mut conn).await;
|
||||||
|
|
||||||
if emergency_access_list.is_empty() {
|
if emergency_access_list.is_empty() {
|
||||||
debug!("No emergency request reminder notification to send");
|
debug!("No emergency request reminder notification to send");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let now = Utc::now().naive_utc();
|
||||||
for mut emer in emergency_access_list {
|
for mut emer in emergency_access_list {
|
||||||
if (emer.recovery_initiated_at.is_some()
|
// The find_all_recoveries_initiated already checks if the recovery_initiated_at is not null (None)
|
||||||
&& Utc::now().naive_utc()
|
// Calculate the day before the recovery will become active
|
||||||
>= emer.recovery_initiated_at.unwrap() + Duration::days((i64::from(emer.wait_time_days)) - 1))
|
let final_recovery_reminder_at =
|
||||||
&& (emer.last_notification_at.is_none()
|
emer.recovery_initiated_at.unwrap() + Duration::days(i64::from(emer.wait_time_days - 1));
|
||||||
|| (emer.last_notification_at.is_some()
|
// Calculate if a day has passed since the previous notification, else no notification has been sent before
|
||||||
&& Utc::now().naive_utc() >= emer.last_notification_at.unwrap() + Duration::days(1)))
|
let next_recovery_reminder_at = if let Some(last_notification_at) = emer.last_notification_at {
|
||||||
{
|
last_notification_at + Duration::days(1)
|
||||||
emer.save(&mut conn).await.expect("Cannot save emergency access on job");
|
} 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, &mut conn)
|
||||||
|
.await
|
||||||
|
.expect("Unable to update emergency access notification date");
|
||||||
|
|
||||||
if CONFIG.mail_enabled() {
|
if CONFIG.mail_enabled() {
|
||||||
// get grantor user to send Accepted email
|
// get grantor user to send Accepted email
|
||||||
let grantor_user =
|
let grantor_user =
|
||||||
User::find_by_uuid(&emer.grantor_uuid, &mut conn).await.expect("Grantor user not found.");
|
User::find_by_uuid(&emer.grantor_uuid, &mut conn).await.expect("Grantor user not found");
|
||||||
|
|
||||||
// get grantee user to send Accepted email
|
// get grantee user to send Accepted email
|
||||||
let grantee_user =
|
let grantee_user =
|
||||||
User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid."), &mut conn)
|
User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &mut conn)
|
||||||
.await
|
.await
|
||||||
.expect("Grantee user not found.");
|
.expect("Grantee user not found");
|
||||||
|
|
||||||
mail::send_emergency_access_recovery_reminder(
|
mail::send_emergency_access_recovery_reminder(
|
||||||
&grantor_user.email,
|
&grantor_user.email,
|
||||||
&grantee_user.name.clone(),
|
&grantee_user.name,
|
||||||
emer.get_type_as_str(),
|
emer.get_type_as_str(),
|
||||||
&emer.wait_time_days.to_string(), // TODO(jjlin): This should be the number of days left.
|
"1", // This notification is only triggered one day before the activation
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Error on sending email");
|
.expect("Error on sending email");
|
||||||
|
|
|
@ -721,7 +721,7 @@ async fn send_invite(
|
||||||
}
|
}
|
||||||
|
|
||||||
if !CONFIG.mail_enabled() {
|
if !CONFIG.mail_enabled() {
|
||||||
let invitation = Invitation::new(email.clone());
|
let invitation = Invitation::new(&email);
|
||||||
invitation.save(&mut conn).await?;
|
invitation.save(&mut conn).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -871,7 +871,7 @@ async fn _reinvite_user(org_id: &str, user_org: &str, invited_by_email: &str, co
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
let invitation = Invitation::new(user.email);
|
let invitation = Invitation::new(&user.email);
|
||||||
invitation.save(conn).await?;
|
invitation.save(conn).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -260,7 +260,6 @@ mod tests {
|
||||||
|
|
||||||
use cached::proc_macro::cached;
|
use cached::proc_macro::cached;
|
||||||
#[cached(key = "String", convert = r#"{ domain.to_string() }"#, size = 16, time = 60)]
|
#[cached(key = "String", convert = r#"{ domain.to_string() }"#, size = 16, time = 60)]
|
||||||
#[allow(clippy::unused_async)] // This is needed because cached causes a false-positive here.
|
|
||||||
async fn is_domain_blacklisted(domain: &str) -> bool {
|
async fn is_domain_blacklisted(domain: &str) -> bool {
|
||||||
// First check the blacklist regex if there is a match.
|
// First check the blacklist regex if there is a match.
|
||||||
// This prevents the blocked domain(s) from being leaked via a DNS lookup.
|
// This prevents the blocked domain(s) from being leaked via a DNS lookup.
|
||||||
|
|
12
src/auth.rs
12
src/auth.rs
|
@ -177,17 +177,17 @@ pub struct EmergencyAccessInviteJwtClaims {
|
||||||
pub sub: String,
|
pub sub: String,
|
||||||
|
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub emer_id: Option<String>,
|
pub emer_id: String,
|
||||||
pub grantor_name: Option<String>,
|
pub grantor_name: String,
|
||||||
pub grantor_email: Option<String>,
|
pub grantor_email: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_emergency_access_invite_claims(
|
pub fn generate_emergency_access_invite_claims(
|
||||||
uuid: String,
|
uuid: String,
|
||||||
email: String,
|
email: String,
|
||||||
emer_id: Option<String>,
|
emer_id: String,
|
||||||
grantor_name: Option<String>,
|
grantor_name: String,
|
||||||
grantor_email: Option<String>,
|
grantor_email: String,
|
||||||
) -> EmergencyAccessInviteJwtClaims {
|
) -> EmergencyAccessInviteJwtClaims {
|
||||||
let time_now = Utc::now().naive_utc();
|
let time_now = Utc::now().naive_utc();
|
||||||
let expire_hours = i64::from(CONFIG.invitation_expiration_hours());
|
let expire_hours = i64::from(CONFIG.invitation_expiration_hours());
|
||||||
|
|
|
@ -366,11 +366,11 @@ make_config! {
|
||||||
/// Defaults to once every minute. Set blank to disable this job.
|
/// Defaults to once every minute. Set blank to disable this job.
|
||||||
incomplete_2fa_schedule: String, false, def, "30 * * * * *".to_string();
|
incomplete_2fa_schedule: String, false, def, "30 * * * * *".to_string();
|
||||||
/// Emergency notification reminder schedule |> Cron schedule of the job that sends expiration reminders to emergency access grantors.
|
/// Emergency notification reminder schedule |> Cron schedule of the job that sends expiration reminders to emergency access grantors.
|
||||||
/// Defaults to hourly. Set blank to disable this job.
|
/// Defaults to hourly. (3 minutes after the hour) Set blank to disable this job.
|
||||||
emergency_notification_reminder_schedule: String, false, def, "0 5 * * * *".to_string();
|
emergency_notification_reminder_schedule: String, false, def, "0 3 * * * *".to_string();
|
||||||
/// Emergency request timeout schedule |> Cron schedule of the job that grants emergency access requests that have met the required wait time.
|
/// Emergency request timeout schedule |> Cron schedule of the job that grants emergency access requests that have met the required wait time.
|
||||||
/// Defaults to hourly. Set blank to disable this job.
|
/// Defaults to hourly. (7 minutes after the hour) Set blank to disable this job.
|
||||||
emergency_request_timeout_schedule: String, false, def, "0 5 * * * *".to_string();
|
emergency_request_timeout_schedule: String, false, def, "0 7 * * * *".to_string();
|
||||||
/// Event cleanup schedule |> Cron schedule of the job that cleans old events from the event table.
|
/// Event cleanup schedule |> Cron schedule of the job that cleans old events from the event table.
|
||||||
/// Defaults to daily. Set blank to disable this job.
|
/// Defaults to daily. Set blank to disable this job.
|
||||||
event_cleanup_schedule: String, false, def, "0 10 0 * * *".to_string();
|
event_cleanup_schedule: String, false, def, "0 10 0 * * *".to_string();
|
||||||
|
|
|
@ -125,7 +125,6 @@ macro_rules! generate_connections {
|
||||||
|
|
||||||
impl DbPool {
|
impl DbPool {
|
||||||
// For the given database URL, guess its type, run migrations, create pool, and return it
|
// For the given database URL, guess its type, run migrations, create pool, and return it
|
||||||
#[allow(clippy::diverging_sub_expression)]
|
|
||||||
pub fn from_config() -> Result<Self, Error> {
|
pub fn from_config() -> Result<Self, Error> {
|
||||||
let url = CONFIG.database_url();
|
let url = CONFIG.database_url();
|
||||||
let conn_type = DbConnType::from_url(&url)?;
|
let conn_type = DbConnType::from_url(&url)?;
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use chrono::{NaiveDateTime, Utc};
|
use chrono::{NaiveDateTime, Utc};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::{api::EmptyResult, db::DbConn, error::MapResult};
|
||||||
|
|
||||||
use super::User;
|
use super::User;
|
||||||
|
|
||||||
db_object! {
|
db_object! {
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset)]
|
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||||
#[diesel(table_name = emergency_access)]
|
#[diesel(table_name = emergency_access)]
|
||||||
#[diesel(treat_none_as_null = true)]
|
#[diesel(treat_none_as_null = true)]
|
||||||
#[diesel(primary_key(uuid))]
|
#[diesel(primary_key(uuid))]
|
||||||
|
@ -27,14 +29,14 @@ db_object! {
|
||||||
/// Local methods
|
/// Local methods
|
||||||
|
|
||||||
impl EmergencyAccess {
|
impl EmergencyAccess {
|
||||||
pub fn new(grantor_uuid: String, email: Option<String>, status: i32, atype: i32, wait_time_days: i32) -> Self {
|
pub fn new(grantor_uuid: String, email: String, status: i32, atype: i32, wait_time_days: i32) -> Self {
|
||||||
let now = Utc::now().naive_utc();
|
let now = Utc::now().naive_utc();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
uuid: crate::util::get_uuid(),
|
uuid: crate::util::get_uuid(),
|
||||||
grantor_uuid,
|
grantor_uuid,
|
||||||
grantee_uuid: None,
|
grantee_uuid: None,
|
||||||
email,
|
email: Some(email),
|
||||||
status,
|
status,
|
||||||
atype,
|
atype,
|
||||||
wait_time_days,
|
wait_time_days,
|
||||||
|
@ -54,14 +56,6 @@ impl EmergencyAccess {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_type(&self, access_type: EmergencyAccessType) -> bool {
|
|
||||||
self.atype == access_type as i32
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_status(&self, status: EmergencyAccessStatus) -> bool {
|
|
||||||
self.status == status as i32
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_json(&self) -> Value {
|
pub fn to_json(&self) -> Value {
|
||||||
json!({
|
json!({
|
||||||
"Id": self.uuid,
|
"Id": self.uuid,
|
||||||
|
@ -87,7 +81,6 @@ impl EmergencyAccess {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::manual_map)]
|
|
||||||
pub async fn to_json_grantee_details(&self, conn: &mut DbConn) -> Value {
|
pub async fn to_json_grantee_details(&self, conn: &mut DbConn) -> Value {
|
||||||
let grantee_user = if let Some(grantee_uuid) = self.grantee_uuid.as_deref() {
|
let grantee_user = if let Some(grantee_uuid) = self.grantee_uuid.as_deref() {
|
||||||
Some(User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found."))
|
Some(User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found."))
|
||||||
|
@ -110,7 +103,7 @@ impl EmergencyAccess {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum EmergencyAccessType {
|
pub enum EmergencyAccessType {
|
||||||
View = 0,
|
View = 0,
|
||||||
Takeover = 1,
|
Takeover = 1,
|
||||||
|
@ -126,18 +119,6 @@ impl EmergencyAccessType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<i32> for EmergencyAccessType {
|
|
||||||
fn eq(&self, other: &i32) -> bool {
|
|
||||||
*other == *self as i32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<EmergencyAccessType> for i32 {
|
|
||||||
fn eq(&self, other: &EmergencyAccessType) -> bool {
|
|
||||||
*self == *other as i32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum EmergencyAccessStatus {
|
pub enum EmergencyAccessStatus {
|
||||||
Invited = 0,
|
Invited = 0,
|
||||||
Accepted = 1,
|
Accepted = 1,
|
||||||
|
@ -148,11 +129,6 @@ pub enum EmergencyAccessStatus {
|
||||||
|
|
||||||
// region Database methods
|
// region Database methods
|
||||||
|
|
||||||
use crate::db::DbConn;
|
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
|
||||||
use crate::error::MapResult;
|
|
||||||
|
|
||||||
impl EmergencyAccess {
|
impl EmergencyAccess {
|
||||||
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
||||||
User::update_uuid_revision(&self.grantor_uuid, conn).await;
|
User::update_uuid_revision(&self.grantor_uuid, conn).await;
|
||||||
|
@ -189,6 +165,45 @@ impl EmergencyAccess {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update_access_status_and_save(
|
||||||
|
&mut self,
|
||||||
|
status: i32,
|
||||||
|
date: &NaiveDateTime,
|
||||||
|
conn: &mut DbConn,
|
||||||
|
) -> EmptyResult {
|
||||||
|
// 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;
|
||||||
|
self.status = status;
|
||||||
|
self.updated_at = date.to_owned();
|
||||||
|
|
||||||
|
db_run! {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")
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_last_notification_date_and_save(
|
||||||
|
&mut self,
|
||||||
|
date: &NaiveDateTime,
|
||||||
|
conn: &mut DbConn,
|
||||||
|
) -> EmptyResult {
|
||||||
|
self.last_notification_at = Some(date.to_owned());
|
||||||
|
self.updated_at = date.to_owned();
|
||||||
|
|
||||||
|
db_run! {conn: {
|
||||||
|
crate::util::retry(|| {
|
||||||
|
diesel::update(emergency_access::table.filter(emergency_access::uuid.eq(&self.uuid)))
|
||||||
|
.set((emergency_access::last_notification_at.eq(date), emergency_access::updated_at.eq(date)))
|
||||||
|
.execute(conn)
|
||||||
|
}, 10)
|
||||||
|
.map_res("Error updating emergency access status")
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult {
|
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult {
|
||||||
for ea in Self::find_all_by_grantor_uuid(user_uuid, conn).await {
|
for ea in Self::find_all_by_grantor_uuid(user_uuid, conn).await {
|
||||||
ea.delete(conn).await?;
|
ea.delete(conn).await?;
|
||||||
|
@ -233,10 +248,11 @@ impl EmergencyAccess {
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_all_recoveries(conn: &mut DbConn) -> Vec<Self> {
|
pub async fn find_all_recoveries_initiated(conn: &mut DbConn) -> Vec<Self> {
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
emergency_access::table
|
emergency_access::table
|
||||||
.filter(emergency_access::status.eq(EmergencyAccessStatus::RecoveryInitiated as i32))
|
.filter(emergency_access::status.eq(EmergencyAccessStatus::RecoveryInitiated as i32))
|
||||||
|
.filter(emergency_access::recovery_initiated_at.is_not_null())
|
||||||
.load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db()
|
.load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db()
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
|
@ -364,7 +364,7 @@ impl User {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Invitation {
|
impl Invitation {
|
||||||
pub fn new(email: String) -> Self {
|
pub fn new(email: &str) -> Self {
|
||||||
let email = email.to_lowercase();
|
let email = email.to_lowercase();
|
||||||
Self {
|
Self {
|
||||||
email,
|
email,
|
||||||
|
|
|
@ -168,7 +168,6 @@ impl<S> MapResult<S> for Option<S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
|
||||||
const fn _has_source<T>(e: T) -> Option<T> {
|
const fn _has_source<T>(e: T) -> Option<T> {
|
||||||
Some(e)
|
Some(e)
|
||||||
}
|
}
|
||||||
|
|
18
src/mail.rs
18
src/mail.rs
|
@ -256,16 +256,16 @@ pub async fn send_invite(
|
||||||
pub async fn send_emergency_access_invite(
|
pub async fn send_emergency_access_invite(
|
||||||
address: &str,
|
address: &str,
|
||||||
uuid: &str,
|
uuid: &str,
|
||||||
emer_id: Option<String>,
|
emer_id: &str,
|
||||||
grantor_name: Option<String>,
|
grantor_name: &str,
|
||||||
grantor_email: Option<String>,
|
grantor_email: &str,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
let claims = generate_emergency_access_invite_claims(
|
let claims = generate_emergency_access_invite_claims(
|
||||||
uuid.to_string(),
|
String::from(uuid),
|
||||||
String::from(address),
|
String::from(address),
|
||||||
emer_id.clone(),
|
String::from(emer_id),
|
||||||
grantor_name.clone(),
|
String::from(grantor_name),
|
||||||
grantor_email,
|
String::from(grantor_email),
|
||||||
);
|
);
|
||||||
|
|
||||||
let invite_token = encode_jwt(&claims);
|
let invite_token = encode_jwt(&claims);
|
||||||
|
@ -275,7 +275,7 @@ pub async fn send_emergency_access_invite(
|
||||||
json!({
|
json!({
|
||||||
"url": CONFIG.domain(),
|
"url": CONFIG.domain(),
|
||||||
"img_src": CONFIG._smtp_img_src(),
|
"img_src": CONFIG._smtp_img_src(),
|
||||||
"emer_id": emer_id.unwrap_or_else(|| "_".to_string()),
|
"emer_id": emer_id,
|
||||||
"email": percent_encode(address.as_bytes(), NON_ALPHANUMERIC).to_string(),
|
"email": percent_encode(address.as_bytes(), NON_ALPHANUMERIC).to_string(),
|
||||||
"grantor_name": grantor_name,
|
"grantor_name": grantor_name,
|
||||||
"token": invite_token,
|
"token": invite_token,
|
||||||
|
@ -328,7 +328,7 @@ pub async fn send_emergency_access_recovery_initiated(
|
||||||
address: &str,
|
address: &str,
|
||||||
grantee_name: &str,
|
grantee_name: &str,
|
||||||
atype: &str,
|
atype: &str,
|
||||||
wait_time_days: &str,
|
wait_time_days: &i32,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
let (subject, body_html, body_text) = get_text(
|
let (subject, body_html, body_text) = get_text(
|
||||||
"email/emergency_access_recovery_initiated",
|
"email/emergency_access_recovery_initiated",
|
||||||
|
|
Laden …
In neuem Issue referenzieren