Skip to content

Commit

Permalink
Merge pull request #2044 from jjlin/emergency-access-cleanup
Browse files Browse the repository at this point in the history
Emergency Access cleanup
  • Loading branch information
dani-garcia authored Oct 19, 2021
2 parents 016fe22 + cee3fd5 commit f94ac6c
Show file tree
Hide file tree
Showing 21 changed files with 101 additions and 101 deletions.
19 changes: 10 additions & 9 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
## To control this on a per-org basis instead, use the "Disable Send" org policy.
# SENDS_ALLOWED=true

## Controls whether users can enable emergency access to their accounts.
## This setting applies globally to all users.
# EMERGENCY_ACCESS_ALLOWED=true

## Job scheduler settings
##
## Job schedules use a cron-like syntax (as parsed by https://crates.io/crates/cron),
Expand All @@ -78,13 +82,13 @@
## Defaults to daily (5 minutes after midnight). Set blank to disable this job.
# TRASH_PURGE_SCHEDULE="0 5 0 * * *"
##
## Cron schedule of the job that sends expiration reminders to emergency request grantors.
## Defaults to hourly (10 minutes after the hour). Set blank to disable this job.
# EMERGENCY_NOTIFICATION_REMINDER_SCHEDULE="0 10 * * * *"
## 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.
# EMERGENCY_NOTIFICATION_REMINDER_SCHEDULE="0 5 * * * *"
##
## Cron schedule of the job that checks for expired (i.e granted by timeout) emergency requests.
## Defaults to hourly (15 minutes after the hour). Set blank to disable this job.
# EMERGENCY_REQUEST_TIMEOUT_SCHEDULE="0 15 * * * *"
## 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.
# EMERGENCY_REQUEST_TIMEOUT_SCHEDULE="0 5 * * * *"

## Enable extended logging, which shows timestamps and targets in the logs
# EXTENDED_LOGGING=true
Expand Down Expand Up @@ -320,9 +324,6 @@
## If sending the email fails the login attempt will fail!!
# REQUIRE_DEVICE_EMAIL=false

## Emergency access enable. Enable or disable the emergency access feature for all users
# EMERGENCY_ACCESS_ALLOWED=false

## HIBP Api Key
## HaveIBeenPwned API Key, request it here: https://haveibeenpwned.com/API/Key
# HIBP_API_KEY=
Expand Down
7 changes: 3 additions & 4 deletions src/api/core/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,9 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
user
} else if CONFIG.is_signup_allowed(&email) {
// check if it's invited by emergency contact
if EmergencyAccess::find_invited_by_grantee_email(&data.Email, &conn).is_some() {
user
} else {
err!("Account with this email already exists")
match EmergencyAccess::find_invited_by_grantee_email(&data.Email, &conn) {
Some(_) => user,
_ => err!("Account with this email already exists"),
}
} else {
err!("Registration not allowed or user already exists")
Expand Down
8 changes: 4 additions & 4 deletions src/api/core/emergency_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbConn) ->
mail::send_emergency_access_recovery_initiated(
&grantor_user.email,
&initiating_user.name,
emergency_access.get_atype_as_str(),
emergency_access.get_type_as_str(),
&emergency_access.wait_time_days.clone().to_string(),
)?;
}
Expand Down Expand Up @@ -743,7 +743,7 @@ pub fn emergency_request_timeout_job(pool: DbPool) {
mail::send_emergency_access_recovery_timed_out(
&grantor_user.email,
&grantee_user.name.clone(),
emer.get_atype_as_str(),
emer.get_type_as_str(),
)
.expect("Error on sending email");

Expand Down Expand Up @@ -792,8 +792,8 @@ pub fn emergency_notification_reminder_job(pool: DbPool) {
mail::send_emergency_access_recovery_reminder(
&grantor_user.email,
&grantee_user.name.clone(),
emer.get_atype_as_str(),
&emer.wait_time_days.to_string(),
emer.get_type_as_str(),
&emer.wait_time_days.to_string(), // TODO(jjlin): This should be the number of days left.
)
.expect("Error on sending email");
}
Expand Down
10 changes: 5 additions & 5 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,12 +333,12 @@ make_config! {
/// Trash purge schedule |> Cron schedule of the job that checks for trashed items to delete permanently.
/// Defaults to daily. Set blank to disable this job.
trash_purge_schedule: String, false, def, "0 5 0 * * *".to_string();
/// Emergency notification reminder schedule |> Cron schedule of the job that sends expiration reminders to emergency request 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.
emergency_notification_reminder_schedule: String, false, def, "0 10 * * * *".to_string();
/// Emergency request timeout schedule |> Cron schedule of the job that checks for expired (i.e granted by timeout) emergency requests.
emergency_notification_reminder_schedule: String, false, def, "0 5 * * * *".to_string();
/// 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.
emergency_request_timeout_schedule: String, false, def, "0 15 * * * *".to_string();
emergency_request_timeout_schedule: String, false, def, "0 5 * * * *".to_string();
},

/// General settings
Expand Down Expand Up @@ -391,7 +391,7 @@ make_config! {
org_creation_users: String, true, def, "".to_string();
/// Allow invitations |> Controls whether users can be invited by organization admins, even when signups are otherwise disabled
invitations_allowed: bool, true, def, true;
/// Allow emergency access |> Controls whether users can enable emergency access to their accounts
/// Allow emergency access |> Controls whether users can enable emergency access to their accounts. This setting applies globally to all users.
emergency_access_allowed: bool, true, def, true;
/// Password iterations |> Number of server-side passwords hashing iterations.
/// The changes only apply when a user changes their password. Not recommended to lower the value
Expand Down
82 changes: 37 additions & 45 deletions src/db/models/emergency_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,22 @@ impl EmergencyAccess {
}
}

pub fn get_atype_as_str(&self) -> &'static str {
pub fn get_type_as_str(&self) -> &'static str {
if self.atype == EmergencyAccessType::View as i32 {
"View"
} else {
"Takeover"
}
}

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 {
json!({
"Id": self.uuid,
Expand All @@ -66,55 +74,40 @@ impl EmergencyAccess {
}

pub fn to_json_grantor_details(&self, conn: &DbConn) -> Value {
// find grantor
let grantor_user = User::find_by_uuid(&self.grantor_uuid, conn).unwrap();
let grantor_user = User::find_by_uuid(&self.grantor_uuid, conn).expect("Grantor user not found.");

json!({
"Id": self.uuid,
"Id": self.uuid,
"Status": self.status,
"Type": self.atype,
"WaitTimeDays": self.wait_time_days,
"GrantorId": grantor_user.uuid,
"Email": grantor_user.email,
"Name": grantor_user.name,
"Object": "emergencyAccessGrantorDetails",})
"Object": "emergencyAccessGrantorDetails",
})
}

#[allow(clippy::manual_map)]
pub fn to_json_grantee_details(&self, conn: &DbConn) -> Value {
if self.grantee_uuid.is_some() {
let grantee_user =
User::find_by_uuid(&self.grantee_uuid.clone().unwrap(), conn).expect("Grantee user not found.");

json!({
"Id": self.uuid,
"Status": self.status,
"Type": self.atype,
"WaitTimeDays": self.wait_time_days,
"GranteeId": grantee_user.uuid,
"Email": grantee_user.email,
"Name": grantee_user.name,
"Object": "emergencyAccessGranteeDetails",})
} else if self.email.is_some() {
let grantee_user = User::find_by_mail(&self.email.clone().unwrap(), conn).expect("Grantee user not found.");
json!({
"Id": self.uuid,
"Status": self.status,
"Type": self.atype,
"WaitTimeDays": self.wait_time_days,
"GranteeId": grantee_user.uuid,
"Email": grantee_user.email,
"Name": grantee_user.name,
"Object": "emergencyAccessGranteeDetails",})
let grantee_user = if let Some(grantee_uuid) = self.grantee_uuid.as_deref() {
Some(User::find_by_uuid(grantee_uuid, conn).expect("Grantee user not found."))
} else if let Some(email) = self.email.as_deref() {
Some(User::find_by_mail(email, conn).expect("Grantee user not found."))
} else {
json!({
"Id": self.uuid,
"Status": self.status,
"Type": self.atype,
"WaitTimeDays": self.wait_time_days,
"GranteeId": "",
"Email": "",
"Name": "",
"Object": "emergencyAccessGranteeDetails",})
}
None
};

json!({
"Id": self.uuid,
"Status": self.status,
"Type": self.atype,
"WaitTimeDays": self.wait_time_days,
"GranteeId": grantee_user.as_ref().map_or("", |u| &u.uuid),
"Email": grantee_user.as_ref().map_or("", |u| &u.email),
"Name": grantee_user.as_ref().map_or("", |u| &u.name),
"Object": "emergencyAccessGranteeDetails",
})
}
}

Expand Down Expand Up @@ -198,11 +191,11 @@ impl EmergencyAccess {
}

pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
for user_org in Self::find_all_by_grantor_uuid(user_uuid, conn) {
user_org.delete(conn)?;
for ea in Self::find_all_by_grantor_uuid(user_uuid, conn) {
ea.delete(conn)?;
}
for user_org in Self::find_all_by_grantee_uuid(user_uuid, conn) {
user_org.delete(conn)?;
for ea in Self::find_all_by_grantee_uuid(user_uuid, conn) {
ea.delete(conn)?;
}
Ok(())
}
Expand All @@ -213,7 +206,7 @@ impl EmergencyAccess {
db_run! { conn: {
diesel::delete(emergency_access::table.filter(emergency_access::uuid.eq(self.uuid)))
.execute(conn)
.map_res("Error removing user from organization")
.map_res("Error removing user from emergency access")
}}
}

Expand Down Expand Up @@ -246,7 +239,6 @@ impl EmergencyAccess {
emergency_access::table
.filter(emergency_access::status.eq(EmergencyAccessStatus::RecoveryInitiated as i32))
.load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db()

}}
}

Expand Down
4 changes: 2 additions & 2 deletions src/mail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,15 +329,15 @@ pub fn send_emergency_access_recovery_reminder(
address: &str,
grantee_name: &str,
atype: &str,
wait_time_days: &str,
days_left: &str,
) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/emergency_access_recovery_reminder",
json!({
"url": CONFIG.domain(),
"grantee_name": grantee_name,
"atype": atype,
"wait_time_days": wait_time_days,
"days_left": days_left,
}),
)?;

Expand Down
9 changes: 9 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,12 +345,17 @@ fn schedule_jobs(pool: db::DbPool) {
}));
}

// Grant emergency access requests that have met the required wait time.
// This job should run before the emergency access reminders job to avoid
// sending reminders for requests that are about to be granted anyway.
if !CONFIG.emergency_request_timeout_schedule().is_empty() {
sched.add(Job::new(CONFIG.emergency_request_timeout_schedule().parse().unwrap(), || {
api::emergency_request_timeout_job(pool.clone());
}));
}

// Send reminders to emergency access grantors that there are pending
// emergency access requests.
if !CONFIG.emergency_notification_reminder_schedule().is_empty() {
sched.add(Job::new(CONFIG.emergency_notification_reminder_schedule().parse().unwrap(), || {
api::emergency_notification_reminder_job(pool.clone());
Expand All @@ -362,6 +367,10 @@ fn schedule_jobs(pool: db::DbPool) {
// interval of 30 seconds should be sufficient. Users who want to
// schedule jobs to run more frequently for some reason can reduce
// the poll interval accordingly.
//
// Note that the scheduler checks jobs in the order in which they
// were added, so if two jobs are both eligible to run at a given
// tick, the one that was added earlier will run first.
loop {
sched.tick();
thread::sleep(Duration::from_millis(CONFIG.job_poll_interval_ms()));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Emergency contact {{{grantee_email}}} accepted
Emergency access contact {{{grantee_email}}} accepted
<!---------------->
This email is to notify you that {{grantee_email}} has accepted your invitation to become an emergency access contact.

To confirm this user, Log into {{url}} the Bitwarden web vault, go to settings and confirm the user.
To confirm this user, log into the web vault ({{url}}), go to settings and confirm the user.

If you do not wish to confirm this user, you can also remove them on the same page.
{{> email/email_footer_text }}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Emergency contact {{{grantee_email}}} accepted
Emergency access contact {{{grantee_email}}} accepted
<!---------------->
{{> email/email_header }}
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
Expand All @@ -9,7 +9,7 @@ Emergency contact {{{grantee_email}}} accepted
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
To confirm this user, <a href="{{url}}/">log into</a> the vaultwarden web vault, go to settings and confirm the user.
To confirm this user, log into the <a href="{{url}}/">web vault</a>, go to settings and confirm the user.
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
Expand All @@ -18,4 +18,4 @@ Emergency contact {{{grantee_email}}} accepted
</td>
</tr>
</table>
{{> email/email_footer }}
{{> email/email_footer }}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Emergency contact for {{{grantor_name}}} confirmed
Emergency access contact for {{{grantor_name}}} confirmed
<!---------------->
This email is to notify you that you have been confirmed as an emergency access contact for *{{grantor_name}}* was confirmed.
This email is to notify you that you have been confirmed as an emergency access contact for *{{grantor_name}}*.

You can now initiate emergency access requests from the web vault. Log in {{url}}.
You can now initiate emergency access requests from the web vault ({{url}}).
{{> email/email_footer_text }}
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
Emergency contact for {{{grantor_name}}} confirmed
Emergency access contact for {{{grantor_name}}} confirmed
<!---------------->
{{> email/email_header }}
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
This email is to notify you that you have been confirmed as an emergency access contact for <b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">{{grantor_name}}</b> was confirmed.
This email is to notify you that you have been confirmed as an emergency access contact for <b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">{{grantor_name}}</b>.
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none;" valign="top">
You can now initiate emergency access requests from the web vault. <br>
<a href="{{url}}/">Log in</a>
You can now initiate emergency access requests from the <a href="{{url}}/">web vault</a>.
</td>
</tr>
</table>
{{> email/email_footer }}
{{> email/email_footer }}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Emergency contact request for {{{grantor_name}}} approved
Emergency access request for {{{grantor_name}}} approved
<!---------------->
{{grantor_name}} has approved your emergency request. You may now login {{url}} on the web vault and access their account.
{{grantor_name}} has approved your emergency access request. You may now login on the web vault ({{url}}) and access their account.
{{> email/email_footer_text }}
Loading

0 comments on commit f94ac6c

Please sign in to comment.