37 changed files with 1586 additions and 14 deletions
			
			
		@ -0,0 +1 @@ | 
				
			|||
DROP TABLE emergency_access; | 
				
			|||
@ -0,0 +1,14 @@ | 
				
			|||
CREATE TABLE emergency_access ( | 
				
			|||
  uuid                      CHAR(36)     NOT NULL PRIMARY KEY, | 
				
			|||
  grantor_uuid              CHAR(36)     REFERENCES users (uuid), | 
				
			|||
  grantee_uuid              CHAR(36)     REFERENCES users (uuid), | 
				
			|||
  email                     VARCHAR(255), | 
				
			|||
  key_encrypted             TEXT, | 
				
			|||
  atype                     INTEGER  NOT NULL, | 
				
			|||
  status                    INTEGER  NOT NULL, | 
				
			|||
  wait_time_days            INTEGER  NOT NULL, | 
				
			|||
  recovery_initiated_at     DATETIME, | 
				
			|||
  last_notification_at      DATETIME, | 
				
			|||
  updated_at                DATETIME NOT NULL, | 
				
			|||
  created_at                DATETIME NOT NULL | 
				
			|||
); | 
				
			|||
@ -0,0 +1 @@ | 
				
			|||
DROP TABLE emergency_access; | 
				
			|||
@ -0,0 +1,14 @@ | 
				
			|||
CREATE TABLE emergency_access ( | 
				
			|||
  uuid                      CHAR(36)     NOT NULL PRIMARY KEY, | 
				
			|||
  grantor_uuid              CHAR(36)     REFERENCES users (uuid), | 
				
			|||
  grantee_uuid              CHAR(36)     REFERENCES users (uuid), | 
				
			|||
  email                     VARCHAR(255), | 
				
			|||
  key_encrypted             TEXT, | 
				
			|||
  atype                     INTEGER  NOT NULL, | 
				
			|||
  status                    INTEGER  NOT NULL, | 
				
			|||
  wait_time_days            INTEGER  NOT NULL, | 
				
			|||
  recovery_initiated_at     TIMESTAMP, | 
				
			|||
  last_notification_at      TIMESTAMP, | 
				
			|||
  updated_at                TIMESTAMP NOT NULL, | 
				
			|||
  created_at                TIMESTAMP NOT NULL | 
				
			|||
); | 
				
			|||
@ -0,0 +1 @@ | 
				
			|||
DROP TABLE emergency_access; | 
				
			|||
@ -0,0 +1,14 @@ | 
				
			|||
CREATE TABLE emergency_access ( | 
				
			|||
  uuid                      TEXT     NOT NULL PRIMARY KEY, | 
				
			|||
  grantor_uuid              TEXT     REFERENCES users (uuid), | 
				
			|||
  grantee_uuid              TEXT     REFERENCES users (uuid), | 
				
			|||
  email                     TEXT, | 
				
			|||
  key_encrypted             TEXT, | 
				
			|||
  atype                     INTEGER  NOT NULL, | 
				
			|||
  status                    INTEGER  NOT NULL, | 
				
			|||
  wait_time_days            INTEGER  NOT NULL, | 
				
			|||
  recovery_initiated_at     DATETIME, | 
				
			|||
  last_notification_at      DATETIME, | 
				
			|||
  updated_at                DATETIME NOT NULL, | 
				
			|||
  created_at                DATETIME NOT NULL | 
				
			|||
); | 
				
			|||
@ -1,24 +1,805 @@ | 
				
			|||
use chrono::{Duration, Utc}; | 
				
			|||
use rocket::Route; | 
				
			|||
use rocket_contrib::json::Json; | 
				
			|||
use serde_json::Value; | 
				
			|||
use std::borrow::Borrow; | 
				
			|||
 | 
				
			|||
use crate::{api::JsonResult, auth::Headers, db::DbConn}; | 
				
			|||
use crate::{ | 
				
			|||
    api::{EmptyResult, JsonResult, JsonUpcase, NumberOrString}, | 
				
			|||
    auth::{decode_emergency_access_invite, Headers}, | 
				
			|||
    db::{models::*, DbConn, DbPool}, | 
				
			|||
    mail, CONFIG, | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
pub fn routes() -> Vec<Route> { | 
				
			|||
    routes![get_contacts,] | 
				
			|||
    routes![ | 
				
			|||
        get_contacts, | 
				
			|||
        get_grantees, | 
				
			|||
        get_emergency_access, | 
				
			|||
        put_emergency_access, | 
				
			|||
        delete_emergency_access, | 
				
			|||
        post_delete_emergency_access, | 
				
			|||
        send_invite, | 
				
			|||
        resend_invite, | 
				
			|||
        accept_invite, | 
				
			|||
        confirm_emergency_access, | 
				
			|||
        initiate_emergency_access, | 
				
			|||
        approve_emergency_access, | 
				
			|||
        reject_emergency_access, | 
				
			|||
        takeover_emergency_access, | 
				
			|||
        password_emergency_access, | 
				
			|||
        view_emergency_access, | 
				
			|||
        policies_emergency_access, | 
				
			|||
    ] | 
				
			|||
} | 
				
			|||
 | 
				
			|||
/// This endpoint is expected to return at least something.
 | 
				
			|||
/// If we return an error message that will trigger error toasts for the user.
 | 
				
			|||
/// To prevent this we just return an empty json result with no Data.
 | 
				
			|||
/// When this feature is going to be implemented it also needs to return this empty Data
 | 
				
			|||
/// instead of throwing an error/4XX unless it really is an error.
 | 
				
			|||
// region get
 | 
				
			|||
 | 
				
			|||
#[get("/emergency-access/trusted")] | 
				
			|||
fn get_contacts(_headers: Headers, _conn: DbConn) -> JsonResult { | 
				
			|||
    debug!("Emergency access is not supported."); | 
				
			|||
fn get_contacts(headers: Headers, conn: DbConn) -> JsonResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    let emergency_access_list = EmergencyAccess::find_all_by_grantor_uuid(&headers.user.uuid, &conn); | 
				
			|||
 | 
				
			|||
    let emergency_access_list_json: Vec<Value> = | 
				
			|||
        emergency_access_list.iter().map(|e| e.to_json_grantee_details(&conn)).collect(); | 
				
			|||
 | 
				
			|||
    Ok(Json(json!({ | 
				
			|||
      "Data": emergency_access_list_json, | 
				
			|||
      "Object": "list", | 
				
			|||
      "ContinuationToken": null | 
				
			|||
    }))) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[get("/emergency-access/granted")] | 
				
			|||
fn get_grantees(headers: Headers, conn: DbConn) -> JsonResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    let emergency_access_list = EmergencyAccess::find_all_by_grantee_uuid(&headers.user.uuid, &conn); | 
				
			|||
 | 
				
			|||
    let emergency_access_list_json: Vec<Value> = | 
				
			|||
        emergency_access_list.iter().map(|e| e.to_json_grantor_details(&conn)).collect(); | 
				
			|||
 | 
				
			|||
    Ok(Json(json!({ | 
				
			|||
      "Data": emergency_access_list_json, | 
				
			|||
      "Object": "list", | 
				
			|||
      "ContinuationToken": null | 
				
			|||
    }))) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[get("/emergency-access/<emer_id>")] | 
				
			|||
fn get_emergency_access(emer_id: String, conn: DbConn) -> JsonResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | 
				
			|||
        Some(emergency_access) => Ok(Json(emergency_access.to_json_grantee_details(&conn))), | 
				
			|||
        None => err!("Emergency access not valid."), | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// endregion
 | 
				
			|||
 | 
				
			|||
// region put/post
 | 
				
			|||
 | 
				
			|||
#[derive(Deserialize, Debug)] | 
				
			|||
#[allow(non_snake_case)] | 
				
			|||
struct EmergencyAccessUpdateData { | 
				
			|||
    Type: NumberOrString, | 
				
			|||
    WaitTimeDays: i32, | 
				
			|||
    KeyEncrypted: Option<String>, | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[put("/emergency-access/<emer_id>", data = "<data>")] | 
				
			|||
fn put_emergency_access(emer_id: String, data: JsonUpcase<EmergencyAccessUpdateData>, conn: DbConn) -> JsonResult { | 
				
			|||
    post_emergency_access(emer_id, data, conn) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[post("/emergency-access/<emer_id>", data = "<data>")] | 
				
			|||
fn post_emergency_access(emer_id: String, data: JsonUpcase<EmergencyAccessUpdateData>, conn: DbConn) -> JsonResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    let data: EmergencyAccessUpdateData = data.into_inner().data; | 
				
			|||
 | 
				
			|||
    let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | 
				
			|||
        Some(emergency_access) => emergency_access, | 
				
			|||
        None => err!("Emergency access not valid."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    let new_type = match EmergencyAccessType::from_str(&data.Type.into_string()) { | 
				
			|||
        Some(new_type) => new_type as i32, | 
				
			|||
        None => err!("Invalid emergency access type."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    emergency_access.atype = new_type; | 
				
			|||
    emergency_access.wait_time_days = data.WaitTimeDays; | 
				
			|||
    emergency_access.key_encrypted = data.KeyEncrypted; | 
				
			|||
 | 
				
			|||
    emergency_access.save(&conn)?; | 
				
			|||
    Ok(Json(emergency_access.to_json())) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// endregion
 | 
				
			|||
 | 
				
			|||
// region delete
 | 
				
			|||
 | 
				
			|||
#[delete("/emergency-access/<emer_id>")] | 
				
			|||
fn delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    let grantor_user = headers.user; | 
				
			|||
 | 
				
			|||
    let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | 
				
			|||
        Some(emer) => { | 
				
			|||
            if emer.grantor_uuid != grantor_user.uuid && emer.grantee_uuid != Some(grantor_user.uuid) { | 
				
			|||
                err!("Emergency access not valid.") | 
				
			|||
            } | 
				
			|||
            emer | 
				
			|||
        } | 
				
			|||
        None => err!("Emergency access not valid."), | 
				
			|||
    }; | 
				
			|||
    emergency_access.delete(&conn)?; | 
				
			|||
    Ok(()) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[post("/emergency-access/<emer_id>/delete")] | 
				
			|||
fn post_delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | 
				
			|||
    delete_emergency_access(emer_id, headers, conn) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// endregion
 | 
				
			|||
 | 
				
			|||
// region invite
 | 
				
			|||
 | 
				
			|||
#[derive(Deserialize, Debug)] | 
				
			|||
#[allow(non_snake_case)] | 
				
			|||
struct EmergencyAccessInviteData { | 
				
			|||
    Email: String, | 
				
			|||
    Type: NumberOrString, | 
				
			|||
    WaitTimeDays: i32, | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[post("/emergency-access/invite", data = "<data>")] | 
				
			|||
fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, conn: DbConn) -> EmptyResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    let data: EmergencyAccessInviteData = data.into_inner().data; | 
				
			|||
    let email = data.Email.to_lowercase(); | 
				
			|||
    let wait_time_days = data.WaitTimeDays; | 
				
			|||
 | 
				
			|||
    let emergency_access_status = EmergencyAccessStatus::Invited as i32; | 
				
			|||
 | 
				
			|||
    let new_type = match EmergencyAccessType::from_str(&data.Type.into_string()) { | 
				
			|||
        Some(new_type) => new_type as i32, | 
				
			|||
        None => err!("Invalid emergency access type."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    let grantor_user = headers.user; | 
				
			|||
 | 
				
			|||
    // avoid setting yourself as emergency contact
 | 
				
			|||
    if email == grantor_user.email { | 
				
			|||
        err!("You can not set yourself as an emergency contact.") | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    let grantee_user = match User::find_by_mail(&email, &conn) { | 
				
			|||
        None => { | 
				
			|||
            if !CONFIG.signups_allowed() { | 
				
			|||
                err!(format!("Grantee user does not exist: {}", email)) | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            if !CONFIG.is_email_domain_allowed(&email) { | 
				
			|||
                err!("Email domain not eligible for invitations") | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            if !CONFIG.mail_enabled() { | 
				
			|||
                let invitation = Invitation::new(email.clone()); | 
				
			|||
                invitation.save(&conn)?; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            let mut user = User::new(email.clone()); | 
				
			|||
            user.save(&conn)?; | 
				
			|||
            user | 
				
			|||
        } | 
				
			|||
        Some(user) => user, | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    if EmergencyAccess::find_by_grantor_uuid_and_grantee_uuid_or_email( | 
				
			|||
        &grantor_user.uuid, | 
				
			|||
        &grantee_user.uuid, | 
				
			|||
        &grantee_user.email, | 
				
			|||
        &conn, | 
				
			|||
    ) | 
				
			|||
    .is_some() | 
				
			|||
    { | 
				
			|||
        err!(format!("Grantee user already invited: {}", email)) | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    let mut new_emergency_access = EmergencyAccess::new( | 
				
			|||
        grantor_user.uuid.clone(), | 
				
			|||
        Some(grantee_user.email.clone()), | 
				
			|||
        emergency_access_status, | 
				
			|||
        new_type, | 
				
			|||
        wait_time_days, | 
				
			|||
    ); | 
				
			|||
    new_emergency_access.save(&conn)?; | 
				
			|||
 | 
				
			|||
    if CONFIG.mail_enabled() { | 
				
			|||
        mail::send_emergency_access_invite( | 
				
			|||
            &grantee_user.email, | 
				
			|||
            &grantee_user.uuid, | 
				
			|||
            Some(new_emergency_access.uuid), | 
				
			|||
            Some(grantor_user.name.clone()), | 
				
			|||
            Some(grantor_user.email), | 
				
			|||
        )?; | 
				
			|||
    } else { | 
				
			|||
        // Automatically mark user as accepted if no email invites
 | 
				
			|||
        match User::find_by_mail(&email, &conn) { | 
				
			|||
            Some(user) => { | 
				
			|||
                match accept_invite_process(user.uuid, new_emergency_access.uuid, Some(email), conn.borrow()) { | 
				
			|||
                    Ok(v) => (v), | 
				
			|||
                    Err(e) => err!(e.to_string()), | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
            None => err!("Grantee user not found."), | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    Ok(()) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[post("/emergency-access/<emer_id>/reinvite")] | 
				
			|||
fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | 
				
			|||
        Some(emer) => emer, | 
				
			|||
        None => err!("Emergency access not valid."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    if emergency_access.grantor_uuid != headers.user.uuid { | 
				
			|||
        err!("Emergency access not valid."); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    if emergency_access.status != EmergencyAccessStatus::Invited as i32 { | 
				
			|||
        err!("The grantee user is already accepted or confirmed to the organization"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    let email = match emergency_access.email.clone() { | 
				
			|||
        Some(email) => email, | 
				
			|||
        None => err!("Email not valid."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    let grantee_user = match User::find_by_mail(&email, &conn) { | 
				
			|||
        Some(user) => user, | 
				
			|||
        None => err!("Grantee user not found."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    let grantor_user = headers.user; | 
				
			|||
 | 
				
			|||
    if CONFIG.mail_enabled() { | 
				
			|||
        mail::send_emergency_access_invite( | 
				
			|||
            &email, | 
				
			|||
            &grantor_user.uuid, | 
				
			|||
            Some(emergency_access.uuid), | 
				
			|||
            Some(grantor_user.name.clone()), | 
				
			|||
            Some(grantor_user.email), | 
				
			|||
        )?; | 
				
			|||
    } else { | 
				
			|||
        if Invitation::find_by_mail(&email, &conn).is_none() { | 
				
			|||
            let invitation = Invitation::new(email); | 
				
			|||
            invitation.save(&conn)?; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        // Automatically mark user as accepted if no email invites
 | 
				
			|||
        match accept_invite_process(grantee_user.uuid, emergency_access.uuid, emergency_access.email, conn.borrow()) { | 
				
			|||
            Ok(v) => (v), | 
				
			|||
            Err(e) => err!(e.to_string()), | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    Ok(()) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[derive(Deserialize)] | 
				
			|||
#[allow(non_snake_case)] | 
				
			|||
struct AcceptData { | 
				
			|||
    Token: String, | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[post("/emergency-access/<emer_id>/accept", data = "<data>")] | 
				
			|||
fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, conn: DbConn) -> EmptyResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    let data: AcceptData = data.into_inner().data; | 
				
			|||
    let token = &data.Token; | 
				
			|||
    let claims = decode_emergency_access_invite(token)?; | 
				
			|||
 | 
				
			|||
    let grantee_user = match User::find_by_mail(&claims.email, &conn) { | 
				
			|||
        Some(user) => { | 
				
			|||
            Invitation::take(&claims.email, &conn); | 
				
			|||
            user | 
				
			|||
        } | 
				
			|||
        None => err!("Invited user not found"), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | 
				
			|||
        Some(emer) => emer, | 
				
			|||
        None => err!("Emergency access not valid."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    // get grantor user to send Accepted email
 | 
				
			|||
    let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | 
				
			|||
        Some(user) => user, | 
				
			|||
        None => err!("Grantor user not found."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    if (claims.emer_id.is_some() && emer_id == claims.emer_id.unwrap()) | 
				
			|||
        && (claims.grantor_name.is_some() && grantor_user.name == claims.grantor_name.unwrap()) | 
				
			|||
        && (claims.grantor_email.is_some() && grantor_user.email == claims.grantor_email.unwrap()) | 
				
			|||
    { | 
				
			|||
        match accept_invite_process(grantee_user.uuid.clone(), emer_id, Some(grantee_user.email.clone()), &conn) { | 
				
			|||
            Ok(v) => (v), | 
				
			|||
            Err(e) => err!(e.to_string()), | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        if CONFIG.mail_enabled() { | 
				
			|||
            mail::send_emergency_access_invite_accepted(&grantor_user.email, &grantee_user.email)?; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        Ok(()) | 
				
			|||
    } else { | 
				
			|||
        err!("Emergency access invitation error.") | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
fn accept_invite_process(grantee_uuid: String, emer_id: String, email: Option<String>, conn: &DbConn) -> EmptyResult { | 
				
			|||
    let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, conn) { | 
				
			|||
        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."); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    if emergency_access.status == EmergencyAccessStatus::Accepted as i32 { | 
				
			|||
        err!("Emergency contact already accepted."); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    emergency_access.status = EmergencyAccessStatus::Accepted as i32; | 
				
			|||
    emergency_access.grantee_uuid = Some(grantee_uuid); | 
				
			|||
    emergency_access.email = None; | 
				
			|||
    emergency_access.save(conn) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[derive(Deserialize)] | 
				
			|||
#[allow(non_snake_case)] | 
				
			|||
struct ConfirmData { | 
				
			|||
    Key: String, | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[post("/emergency-access/<emer_id>/confirm", data = "<data>")] | 
				
			|||
fn confirm_emergency_access( | 
				
			|||
    emer_id: String, | 
				
			|||
    data: JsonUpcase<ConfirmData>, | 
				
			|||
    headers: Headers, | 
				
			|||
    conn: DbConn, | 
				
			|||
) -> JsonResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    let confirming_user = headers.user; | 
				
			|||
    let data: ConfirmData = data.into_inner().data; | 
				
			|||
    let key = data.Key; | 
				
			|||
 | 
				
			|||
    let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | 
				
			|||
        Some(emer) => emer, | 
				
			|||
        None => err!("Emergency access not valid."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    if emergency_access.status != EmergencyAccessStatus::Accepted as i32 | 
				
			|||
        || emergency_access.grantor_uuid != confirming_user.uuid | 
				
			|||
    { | 
				
			|||
        err!("Emergency access not valid.") | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    let grantor_user = match User::find_by_uuid(&confirming_user.uuid, &conn) { | 
				
			|||
        Some(user) => user, | 
				
			|||
        None => err!("Grantor user not found."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() { | 
				
			|||
        let grantee_user = match User::find_by_uuid(grantee_uuid, &conn) { | 
				
			|||
            Some(user) => user, | 
				
			|||
            None => err!("Grantee user not found."), | 
				
			|||
        }; | 
				
			|||
 | 
				
			|||
        emergency_access.status = EmergencyAccessStatus::Confirmed as i32; | 
				
			|||
        emergency_access.key_encrypted = Some(key); | 
				
			|||
        emergency_access.email = None; | 
				
			|||
 | 
				
			|||
        emergency_access.save(&conn)?; | 
				
			|||
 | 
				
			|||
        if CONFIG.mail_enabled() { | 
				
			|||
            mail::send_emergency_access_invite_confirmed(&grantee_user.email, &grantor_user.name)?; | 
				
			|||
        } | 
				
			|||
        Ok(Json(emergency_access.to_json())) | 
				
			|||
    } else { | 
				
			|||
        err!("Grantee user not found.") | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// endregion
 | 
				
			|||
 | 
				
			|||
// region access emergency access
 | 
				
			|||
 | 
				
			|||
#[post("/emergency-access/<emer_id>/initiate")] | 
				
			|||
fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    let initiating_user = headers.user; | 
				
			|||
    let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | 
				
			|||
        Some(emer) => emer, | 
				
			|||
        None => err!("Emergency access not valid."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    if emergency_access.status != EmergencyAccessStatus::Confirmed as i32 | 
				
			|||
        || emergency_access.grantee_uuid != Some(initiating_user.uuid.clone()) | 
				
			|||
    { | 
				
			|||
        err!("Emergency access not valid.") | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | 
				
			|||
        Some(user) => user, | 
				
			|||
        None => err!("Grantor user not found."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    let now = Utc::now().naive_utc(); | 
				
			|||
    emergency_access.status = EmergencyAccessStatus::RecoveryInitiated as i32; | 
				
			|||
    emergency_access.updated_at = now; | 
				
			|||
    emergency_access.recovery_initiated_at = Some(now); | 
				
			|||
    emergency_access.last_notification_at = Some(now); | 
				
			|||
    emergency_access.save(&conn)?; | 
				
			|||
 | 
				
			|||
    if CONFIG.mail_enabled() { | 
				
			|||
        mail::send_emergency_access_recovery_initiated( | 
				
			|||
            &grantor_user.email, | 
				
			|||
            &initiating_user.name, | 
				
			|||
            emergency_access.get_atype_as_str(), | 
				
			|||
            &emergency_access.wait_time_days.clone().to_string(), | 
				
			|||
        )?; | 
				
			|||
    } | 
				
			|||
    Ok(Json(emergency_access.to_json())) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[post("/emergency-access/<emer_id>/approve")] | 
				
			|||
fn approve_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    let approving_user = headers.user; | 
				
			|||
    let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | 
				
			|||
        Some(emer) => emer, | 
				
			|||
        None => err!("Emergency access not valid."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    if emergency_access.status != EmergencyAccessStatus::RecoveryInitiated as i32 | 
				
			|||
        || emergency_access.grantor_uuid != approving_user.uuid | 
				
			|||
    { | 
				
			|||
        err!("Emergency access not valid.") | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    let grantor_user = match User::find_by_uuid(&approving_user.uuid, &conn) { | 
				
			|||
        Some(user) => user, | 
				
			|||
        None => err!("Grantor user not found."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() { | 
				
			|||
        let grantee_user = match User::find_by_uuid(grantee_uuid, &conn) { | 
				
			|||
            Some(user) => user, | 
				
			|||
            None => err!("Grantee user not found."), | 
				
			|||
        }; | 
				
			|||
 | 
				
			|||
        emergency_access.status = EmergencyAccessStatus::RecoveryApproved as i32; | 
				
			|||
        emergency_access.save(&conn)?; | 
				
			|||
 | 
				
			|||
        if CONFIG.mail_enabled() { | 
				
			|||
            mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name)?; | 
				
			|||
        } | 
				
			|||
        Ok(Json(emergency_access.to_json())) | 
				
			|||
    } else { | 
				
			|||
        err!("Grantee user not found.") | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[post("/emergency-access/<emer_id>/reject")] | 
				
			|||
fn reject_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    let rejecting_user = headers.user; | 
				
			|||
    let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | 
				
			|||
        Some(emer) => emer, | 
				
			|||
        None => err!("Emergency access not valid."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    if (emergency_access.status != EmergencyAccessStatus::RecoveryInitiated as i32 | 
				
			|||
        && emergency_access.status != EmergencyAccessStatus::RecoveryApproved as i32) | 
				
			|||
        || emergency_access.grantor_uuid != rejecting_user.uuid | 
				
			|||
    { | 
				
			|||
        err!("Emergency access not valid.") | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    let grantor_user = match User::find_by_uuid(&rejecting_user.uuid, &conn) { | 
				
			|||
        Some(user) => user, | 
				
			|||
        None => err!("Grantor user not found."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() { | 
				
			|||
        let grantee_user = match User::find_by_uuid(grantee_uuid, &conn) { | 
				
			|||
            Some(user) => user, | 
				
			|||
            None => err!("Grantee user not found."), | 
				
			|||
        }; | 
				
			|||
 | 
				
			|||
        emergency_access.status = EmergencyAccessStatus::Confirmed as i32; | 
				
			|||
        emergency_access.key_encrypted = None; | 
				
			|||
        emergency_access.save(&conn)?; | 
				
			|||
 | 
				
			|||
        if CONFIG.mail_enabled() { | 
				
			|||
            mail::send_emergency_access_recovery_rejected(&grantee_user.email, &grantor_user.name)?; | 
				
			|||
        } | 
				
			|||
        Ok(Json(emergency_access.to_json())) | 
				
			|||
    } else { | 
				
			|||
        err!("Grantee user not found.") | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// endregion
 | 
				
			|||
 | 
				
			|||
// region action
 | 
				
			|||
 | 
				
			|||
#[post("/emergency-access/<emer_id>/view")] | 
				
			|||
fn view_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    let requesting_user = headers.user; | 
				
			|||
    let host = headers.host; | 
				
			|||
    let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | 
				
			|||
        Some(emer) => emer, | 
				
			|||
        None => err!("Emergency access not valid."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    if !is_valid_request(&emergency_access, requesting_user.uuid, EmergencyAccessType::View) { | 
				
			|||
        err!("Emergency access not valid.") | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    let ciphers = Cipher::find_owned_by_user(&emergency_access.grantor_uuid, &conn); | 
				
			|||
 | 
				
			|||
    let ciphers_json: Vec<Value> = | 
				
			|||
        ciphers.iter().map(|c| c.to_json(&host, &emergency_access.grantor_uuid, &conn)).collect(); | 
				
			|||
 | 
				
			|||
    Ok(Json(json!({ | 
				
			|||
      "Data": [], | 
				
			|||
      "Ciphers": ciphers_json, | 
				
			|||
      "KeyEncrypted": &emergency_access.key_encrypted, | 
				
			|||
      "Object": "emergencyAccessView", | 
				
			|||
    }))) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[post("/emergency-access/<emer_id>/takeover")] | 
				
			|||
fn takeover_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    let requesting_user = headers.user; | 
				
			|||
    let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | 
				
			|||
        Some(emer) => emer, | 
				
			|||
        None => err!("Emergency access not valid."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    if !is_valid_request(&emergency_access, requesting_user.uuid, EmergencyAccessType::Takeover) { | 
				
			|||
        err!("Emergency access not valid.") | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | 
				
			|||
        Some(user) => user, | 
				
			|||
        None => err!("Grantor user not found."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    Ok(Json(json!({ | 
				
			|||
      "Kdf": grantor_user.client_kdf_type, | 
				
			|||
      "KdfIterations": grantor_user.client_kdf_iter, | 
				
			|||
      "KeyEncrypted": &emergency_access.key_encrypted, | 
				
			|||
      "Object": "emergencyAccessTakeover", | 
				
			|||
    }))) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[derive(Deserialize, Debug)] | 
				
			|||
#[allow(non_snake_case)] | 
				
			|||
struct EmergencyAccessPasswordData { | 
				
			|||
    NewMasterPasswordHash: String, | 
				
			|||
    Key: String, | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[post("/emergency-access/<emer_id>/password", data = "<data>")] | 
				
			|||
fn password_emergency_access( | 
				
			|||
    emer_id: String, | 
				
			|||
    data: JsonUpcase<EmergencyAccessPasswordData>, | 
				
			|||
    headers: Headers, | 
				
			|||
    conn: DbConn, | 
				
			|||
) -> EmptyResult { | 
				
			|||
    check_emergency_access_allowed()?; | 
				
			|||
 | 
				
			|||
    let data: EmergencyAccessPasswordData = data.into_inner().data; | 
				
			|||
    let new_master_password_hash = &data.NewMasterPasswordHash; | 
				
			|||
    let key = data.Key; | 
				
			|||
 | 
				
			|||
    let requesting_user = headers.user; | 
				
			|||
    let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | 
				
			|||
        Some(emer) => emer, | 
				
			|||
        None => err!("Emergency access not valid."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    if !is_valid_request(&emergency_access, requesting_user.uuid, EmergencyAccessType::Takeover) { | 
				
			|||
        err!("Emergency access not valid.") | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    let mut grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | 
				
			|||
        Some(user) => user, | 
				
			|||
        None => err!("Grantor user not found."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    // change grantor_user password
 | 
				
			|||
    grantor_user.set_password(new_master_password_hash, None); | 
				
			|||
    grantor_user.akey = key; | 
				
			|||
    grantor_user.save(&conn)?; | 
				
			|||
 | 
				
			|||
    // Disable TwoFactor providers since they will otherwise block logins
 | 
				
			|||
    TwoFactor::delete_all_by_user(&grantor_user.uuid, &conn)?; | 
				
			|||
 | 
				
			|||
    // Removing owner, check that there are at least another owner
 | 
				
			|||
    let user_org_grantor = UserOrganization::find_any_state_by_user(&grantor_user.uuid, &conn); | 
				
			|||
 | 
				
			|||
    // Remove grantor from all organisations unless Owner
 | 
				
			|||
    for user_org in user_org_grantor { | 
				
			|||
        if user_org.atype != UserOrgType::Owner as i32 { | 
				
			|||
            user_org.delete(&conn)?; | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
    Ok(()) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// endregion
 | 
				
			|||
 | 
				
			|||
#[get("/emergency-access/<emer_id>/policies")] | 
				
			|||
fn policies_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | 
				
			|||
    let requesting_user = headers.user; | 
				
			|||
    let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | 
				
			|||
        Some(emer) => emer, | 
				
			|||
        None => err!("Emergency access not valid."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    if !is_valid_request(&emergency_access, requesting_user.uuid, EmergencyAccessType::Takeover) { | 
				
			|||
        err!("Emergency access not valid.") | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | 
				
			|||
        Some(user) => user, | 
				
			|||
        None => err!("Grantor user not found."), | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    let policies = OrgPolicy::find_by_user(&grantor_user.uuid, &conn); | 
				
			|||
    let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect(); | 
				
			|||
 | 
				
			|||
    Ok(Json(json!({ | 
				
			|||
        "Data": policies_json, | 
				
			|||
        "Object": "list", | 
				
			|||
        "ContinuationToken": null | 
				
			|||
    }))) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
fn is_valid_request( | 
				
			|||
    emergency_access: &EmergencyAccess, | 
				
			|||
    requesting_user_uuid: String, | 
				
			|||
    requested_access_type: EmergencyAccessType, | 
				
			|||
) -> bool { | 
				
			|||
    emergency_access.grantee_uuid == Some(requesting_user_uuid) | 
				
			|||
        && emergency_access.status == EmergencyAccessStatus::RecoveryApproved as i32 | 
				
			|||
        && emergency_access.atype == requested_access_type as i32 | 
				
			|||
} | 
				
			|||
 | 
				
			|||
fn check_emergency_access_allowed() -> EmptyResult { | 
				
			|||
    if !CONFIG.emergency_access_allowed() { | 
				
			|||
        err!("Emergency access is not allowed.") | 
				
			|||
    } | 
				
			|||
    Ok(()) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
pub fn emergency_request_timeout_job(pool: DbPool) { | 
				
			|||
    debug!("Start emergency_request_timeout_job"); | 
				
			|||
    if !CONFIG.emergency_access_allowed() { | 
				
			|||
        return; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    if let Ok(conn) = pool.get() { | 
				
			|||
        let emergency_access_list = EmergencyAccess::find_all_recoveries(&conn); | 
				
			|||
 | 
				
			|||
        if emergency_access_list.is_empty() { | 
				
			|||
            debug!("No emergency request timeout to approve"); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        for mut emer in emergency_access_list { | 
				
			|||
            if emer.recovery_initiated_at.is_some() | 
				
			|||
                && Utc::now().naive_utc() | 
				
			|||
                    >= emer.recovery_initiated_at.unwrap() + Duration::days(emer.wait_time_days as i64) | 
				
			|||
            { | 
				
			|||
                emer.status = EmergencyAccessStatus::RecoveryApproved as i32; | 
				
			|||
                emer.save(&conn).expect("Cannot save emergency access on job"); | 
				
			|||
 | 
				
			|||
                if CONFIG.mail_enabled() { | 
				
			|||
                    // get grantor user to send Accepted email
 | 
				
			|||
                    let grantor_user = User::find_by_uuid(&emer.grantor_uuid, &conn).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) | 
				
			|||
                            .expect("Grantee user not found."); | 
				
			|||
 | 
				
			|||
                    mail::send_emergency_access_recovery_timed_out( | 
				
			|||
                        &grantor_user.email, | 
				
			|||
                        &grantee_user.name.clone(), | 
				
			|||
                        emer.get_atype_as_str(), | 
				
			|||
                    ) | 
				
			|||
                    .expect("Error on sending email"); | 
				
			|||
 | 
				
			|||
                    mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name.clone()) | 
				
			|||
                        .expect("Error on sending email"); | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
    } else { | 
				
			|||
        error!("Failed to get DB connection while searching emergency request timed out") | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
pub fn emergency_notification_reminder_job(pool: DbPool) { | 
				
			|||
    debug!("Start emergency_notification_reminder_job"); | 
				
			|||
    if !CONFIG.emergency_access_allowed() { | 
				
			|||
        return; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    if let Ok(conn) = pool.get() { | 
				
			|||
        let emergency_access_list = EmergencyAccess::find_all_recoveries(&conn); | 
				
			|||
 | 
				
			|||
        if emergency_access_list.is_empty() { | 
				
			|||
            debug!("No emergency request reminder notification to send"); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        for mut emer in emergency_access_list { | 
				
			|||
            if (emer.recovery_initiated_at.is_some() | 
				
			|||
                && Utc::now().naive_utc() | 
				
			|||
                    >= emer.recovery_initiated_at.unwrap() + Duration::days((emer.wait_time_days as i64) - 1)) | 
				
			|||
                && (emer.last_notification_at.is_none() | 
				
			|||
                    || (emer.last_notification_at.is_some() | 
				
			|||
                        && Utc::now().naive_utc() >= emer.last_notification_at.unwrap() + Duration::days(1))) | 
				
			|||
            { | 
				
			|||
                emer.save(&conn).expect("Cannot save emergency access on job"); | 
				
			|||
 | 
				
			|||
                if CONFIG.mail_enabled() { | 
				
			|||
                    // get grantor user to send Accepted email
 | 
				
			|||
                    let grantor_user = User::find_by_uuid(&emer.grantor_uuid, &conn).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) | 
				
			|||
                            .expect("Grantee user not found."); | 
				
			|||
 | 
				
			|||
                    mail::send_emergency_access_recovery_reminder( | 
				
			|||
                        &grantor_user.email, | 
				
			|||
                        &grantee_user.name.clone(), | 
				
			|||
                        emer.get_atype_as_str(), | 
				
			|||
                        &emer.wait_time_days.to_string(), | 
				
			|||
                    ) | 
				
			|||
                    .expect("Error on sending email"); | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
    } else { | 
				
			|||
        error!("Failed to get DB connection while searching emergency notification reminder") | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
@ -0,0 +1,290 @@ | 
				
			|||
use chrono::{NaiveDateTime, Utc}; | 
				
			|||
use serde_json::Value; | 
				
			|||
 | 
				
			|||
use super::User; | 
				
			|||
 | 
				
			|||
db_object! { | 
				
			|||
    #[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)] | 
				
			|||
    #[table_name = "emergency_access"] | 
				
			|||
    #[changeset_options(treat_none_as_null="true")] | 
				
			|||
    #[belongs_to(User, foreign_key = "grantor_uuid")] | 
				
			|||
    #[primary_key(uuid)] | 
				
			|||
    pub struct EmergencyAccess { | 
				
			|||
        pub uuid: String, | 
				
			|||
        pub grantor_uuid: String, | 
				
			|||
        pub grantee_uuid: Option<String>, | 
				
			|||
        pub email: Option<String>, | 
				
			|||
        pub key_encrypted: Option<String>, | 
				
			|||
        pub atype: i32, //EmergencyAccessType
 | 
				
			|||
        pub status: i32, //EmergencyAccessStatus
 | 
				
			|||
        pub wait_time_days: i32, | 
				
			|||
        pub recovery_initiated_at: Option<NaiveDateTime>, | 
				
			|||
        pub last_notification_at: Option<NaiveDateTime>, | 
				
			|||
        pub updated_at: NaiveDateTime, | 
				
			|||
        pub created_at: NaiveDateTime, | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
/// Local methods
 | 
				
			|||
 | 
				
			|||
impl EmergencyAccess { | 
				
			|||
    pub fn new(grantor_uuid: String, email: Option<String>, status: i32, atype: i32, wait_time_days: i32) -> Self { | 
				
			|||
        let now = Utc::now().naive_utc(); | 
				
			|||
 | 
				
			|||
        Self { | 
				
			|||
            uuid: crate::util::get_uuid(), | 
				
			|||
            grantor_uuid, | 
				
			|||
            grantee_uuid: None, | 
				
			|||
            email, | 
				
			|||
            status, | 
				
			|||
            atype, | 
				
			|||
            wait_time_days, | 
				
			|||
            recovery_initiated_at: None, | 
				
			|||
            created_at: now, | 
				
			|||
            updated_at: now, | 
				
			|||
            key_encrypted: None, | 
				
			|||
            last_notification_at: None, | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    pub fn get_atype_as_str(&self) -> &'static str { | 
				
			|||
        if self.atype == EmergencyAccessType::View as i32 { | 
				
			|||
            "View" | 
				
			|||
        } else { | 
				
			|||
            "Takeover" | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    pub fn to_json(&self) -> Value { | 
				
			|||
        json!({ | 
				
			|||
            "Id": self.uuid, | 
				
			|||
            "Status": self.status, | 
				
			|||
            "Type": self.atype, | 
				
			|||
            "WaitTimeDays": self.wait_time_days, | 
				
			|||
            "Object": "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(); | 
				
			|||
        json!({ | 
				
			|||
             "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",}) | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    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",}) | 
				
			|||
        } else { | 
				
			|||
            json!({ | 
				
			|||
                "Id": self.uuid, | 
				
			|||
                "Status": self.status, | 
				
			|||
                "Type": self.atype, | 
				
			|||
                "WaitTimeDays": self.wait_time_days, | 
				
			|||
                "GranteeId": "", | 
				
			|||
                "Email": "", | 
				
			|||
                "Name": "", | 
				
			|||
                "Object": "emergencyAccessGranteeDetails",}) | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#[derive(Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)] | 
				
			|||
pub enum EmergencyAccessType { | 
				
			|||
    View = 0, | 
				
			|||
    Takeover = 1, | 
				
			|||
} | 
				
			|||
 | 
				
			|||
impl EmergencyAccessType { | 
				
			|||
    pub fn from_str(s: &str) -> Option<Self> { | 
				
			|||
        match s { | 
				
			|||
            "0" | "View" => Some(EmergencyAccessType::View), | 
				
			|||
            "1" | "Takeover" => Some(EmergencyAccessType::Takeover), | 
				
			|||
            _ => None, | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
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 { | 
				
			|||
    Invited = 0, | 
				
			|||
    Accepted = 1, | 
				
			|||
    Confirmed = 2, | 
				
			|||
    RecoveryInitiated = 3, | 
				
			|||
    RecoveryApproved = 4, | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// region Database methods
 | 
				
			|||
 | 
				
			|||
use crate::db::DbConn; | 
				
			|||
 | 
				
			|||
use crate::api::EmptyResult; | 
				
			|||
use crate::error::MapResult; | 
				
			|||
 | 
				
			|||
impl EmergencyAccess { | 
				
			|||
    pub fn save(&mut self, conn: &DbConn) -> EmptyResult { | 
				
			|||
        User::update_uuid_revision(&self.grantor_uuid, conn); | 
				
			|||
        self.updated_at = Utc::now().naive_utc(); | 
				
			|||
 | 
				
			|||
        db_run! { conn: | 
				
			|||
            sqlite, mysql { | 
				
			|||
                match diesel::replace_into(emergency_access::table) | 
				
			|||
                    .values(EmergencyAccessDb::to_db(self)) | 
				
			|||
                    .execute(conn) | 
				
			|||
                { | 
				
			|||
                    Ok(_) => Ok(()), | 
				
			|||
                    // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
 | 
				
			|||
                    Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { | 
				
			|||
                        diesel::update(emergency_access::table) | 
				
			|||
                            .filter(emergency_access::uuid.eq(&self.uuid)) | 
				
			|||
                            .set(EmergencyAccessDb::to_db(self)) | 
				
			|||
                            .execute(conn) | 
				
			|||
                            .map_res("Error updating emergency access") | 
				
			|||
                    } | 
				
			|||
                    Err(e) => Err(e.into()), | 
				
			|||
                }.map_res("Error saving emergency access") | 
				
			|||
            } | 
				
			|||
            postgresql { | 
				
			|||
                let value = EmergencyAccessDb::to_db(self); | 
				
			|||
                diesel::insert_into(emergency_access::table) | 
				
			|||
                    .values(&value) | 
				
			|||
                    .on_conflict(emergency_access::uuid) | 
				
			|||
                    .do_update() | 
				
			|||
                    .set(&value) | 
				
			|||
                    .execute(conn) | 
				
			|||
                    .map_res("Error saving emergency access") | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    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 user_org in Self::find_all_by_grantee_uuid(user_uuid, conn) { | 
				
			|||
            user_org.delete(conn)?; | 
				
			|||
        } | 
				
			|||
        Ok(()) | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    pub fn delete(self, conn: &DbConn) -> EmptyResult { | 
				
			|||
        User::update_uuid_revision(&self.grantor_uuid, conn); | 
				
			|||
 | 
				
			|||
        db_run! { conn: { | 
				
			|||
            diesel::delete(emergency_access::table.filter(emergency_access::uuid.eq(self.uuid))) | 
				
			|||
                .execute(conn) | 
				
			|||
                .map_res("Error removing user from organization") | 
				
			|||
        }} | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | 
				
			|||
        db_run! { conn: { | 
				
			|||
            emergency_access::table | 
				
			|||
                .filter(emergency_access::uuid.eq(uuid)) | 
				
			|||
                .first::<EmergencyAccessDb>(conn) | 
				
			|||
                .ok().from_db() | 
				
			|||
        }} | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    pub fn find_by_grantor_uuid_and_grantee_uuid_or_email( | 
				
			|||
        grantor_uuid: &str, | 
				
			|||
        grantee_uuid: &str, | 
				
			|||
        email: &str, | 
				
			|||
        conn: &DbConn, | 
				
			|||
    ) -> Option<Self> { | 
				
			|||
        db_run! { conn: { | 
				
			|||
            emergency_access::table | 
				
			|||
                .filter(emergency_access::grantor_uuid.eq(grantor_uuid)) | 
				
			|||
                .filter(emergency_access::grantee_uuid.eq(grantee_uuid).or(emergency_access::email.eq(email))) | 
				
			|||
                .first::<EmergencyAccessDb>(conn) | 
				
			|||
                .ok().from_db() | 
				
			|||
        }} | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    pub fn find_all_recoveries(conn: &DbConn) -> Vec<Self> { | 
				
			|||
        db_run! { conn: { | 
				
			|||
            emergency_access::table | 
				
			|||
                .filter(emergency_access::status.eq(EmergencyAccessStatus::RecoveryInitiated as i32)) | 
				
			|||
                .load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db() | 
				
			|||
 | 
				
			|||
        }} | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    pub fn find_by_uuid_and_grantor_uuid(uuid: &str, grantor_uuid: &str, conn: &DbConn) -> Option<Self> { | 
				
			|||
        db_run! { conn: { | 
				
			|||
            emergency_access::table | 
				
			|||
                .filter(emergency_access::uuid.eq(uuid)) | 
				
			|||
                .filter(emergency_access::grantor_uuid.eq(grantor_uuid)) | 
				
			|||
                .first::<EmergencyAccessDb>(conn) | 
				
			|||
                .ok().from_db() | 
				
			|||
        }} | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    pub fn find_all_by_grantee_uuid(grantee_uuid: &str, conn: &DbConn) -> Vec<Self> { | 
				
			|||
        db_run! { conn: { | 
				
			|||
            emergency_access::table | 
				
			|||
                .filter(emergency_access::grantee_uuid.eq(grantee_uuid)) | 
				
			|||
                .load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db() | 
				
			|||
        }} | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    pub fn find_invited_by_grantee_email(grantee_email: &str, conn: &DbConn) -> Option<Self> { | 
				
			|||
        db_run! { conn: { | 
				
			|||
            emergency_access::table | 
				
			|||
                .filter(emergency_access::email.eq(grantee_email)) | 
				
			|||
                .filter(emergency_access::status.eq(EmergencyAccessStatus::Invited as i32)) | 
				
			|||
                .first::<EmergencyAccessDb>(conn) | 
				
			|||
                .ok().from_db() | 
				
			|||
        }} | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    pub fn find_all_by_grantor_uuid(grantor_uuid: &str, conn: &DbConn) -> Vec<Self> { | 
				
			|||
        db_run! { conn: { | 
				
			|||
            emergency_access::table | 
				
			|||
                .filter(emergency_access::grantor_uuid.eq(grantor_uuid)) | 
				
			|||
                .load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db() | 
				
			|||
        }} | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// endregion
 | 
				
			|||
@ -0,0 +1,8 @@ | 
				
			|||
Emergency 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. | 
				
			|||
 | 
				
			|||
If you do not wish to confirm this user, you can also remove them on the same page. | 
				
			|||
{{> email/email_footer_text }} | 
				
			|||
@ -0,0 +1,21 @@ | 
				
			|||
Emergency 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;"> | 
				
			|||
    <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 {{grantee_email}} has accepted your invitation to become an emergency access contact. | 
				
			|||
        </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" 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. | 
				
			|||
        </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"> | 
				
			|||
            If you do not wish to confirm this user, you can also remove them on the same page. | 
				
			|||
        </td> | 
				
			|||
    </tr> | 
				
			|||
</table> | 
				
			|||
{{> email/email_footer }} | 
				
			|||
@ -0,0 +1,6 @@ | 
				
			|||
Emergency 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. | 
				
			|||
 | 
				
			|||
You can now initiate emergency access requests from the web vault. Log in {{url}}. | 
				
			|||
{{> email/email_footer_text }} | 
				
			|||
@ -0,0 +1,17 @@ | 
				
			|||
Emergency 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. | 
				
			|||
       </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> | 
				
			|||
       </td> | 
				
			|||
    </tr> | 
				
			|||
 </table> | 
				
			|||
{{> email/email_footer }} | 
				
			|||
@ -0,0 +1,4 @@ | 
				
			|||
Emergency contact 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. | 
				
			|||
{{> email/email_footer_text }} | 
				
			|||
@ -0,0 +1,11 @@ | 
				
			|||
Emergency contact for {{{grantor_name}}} approved | 
				
			|||
<!----------------> | 
				
			|||
{{> 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"> | 
				
			|||
           <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> has approved your emergency request. You may now <a href="{{url}}/">login</a> on the web vault and access their account. | 
				
			|||
       </td> | 
				
			|||
    </tr> | 
				
			|||
 </table> | 
				
			|||
{{> email/email_footer }} | 
				
			|||
@ -0,0 +1,6 @@ | 
				
			|||
Emergency access request by {{{grantee_name}}} initiated | 
				
			|||
<!----------------> | 
				
			|||
{{grantee_name}} has initiated an emergency request to *{{atype}}* your account. You may login on the web vault and manually approve or reject this request. | 
				
			|||
 | 
				
			|||
If you do nothing, the request will automatically be approved after {{wait_time_days}} day(s). | 
				
			|||
{{> email/email_footer_text }} | 
				
			|||
@ -0,0 +1,16 @@ | 
				
			|||
Emergency access request by {{{grantee_name}}} initiated | 
				
			|||
<!----------------> | 
				
			|||
{{> 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"> | 
				
			|||
           <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;">{{grantee_name}}</b> has initiated an emergency request to <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;">{{atype}}</b> your account. You may login on the web vault and manually approve or reject this request. | 
				
			|||
       </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"> | 
				
			|||
           If you do nothing, the request will automatically be approved after <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;">{{wait_time_days}}</b> day(s). | 
				
			|||
       </td> | 
				
			|||
    </tr> | 
				
			|||
 </table> | 
				
			|||
{{> email/email_footer }} | 
				
			|||
@ -0,0 +1,4 @@ | 
				
			|||
Emergency access request to {{{grantor_name}}} rejected | 
				
			|||
<!----------------> | 
				
			|||
{{grantor_name}} has rejected your emergency request. | 
				
			|||
{{> email/email_footer_text }} | 
				
			|||
@ -0,0 +1,11 @@ | 
				
			|||
Emergency access request to {{{grantor_name}}} rejected | 
				
			|||
<!----------------> | 
				
			|||
{{> 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"> | 
				
			|||
           <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>  has rejected your emergency request. | 
				
			|||
       </td> | 
				
			|||
    </tr> | 
				
			|||
 </table> | 
				
			|||
{{> email/email_footer }} | 
				
			|||
@ -0,0 +1,6 @@ | 
				
			|||
Emergency access request by {{{grantee_name}}} is pending | 
				
			|||
<!----------------> | 
				
			|||
{{grantee_name}} has a pending emergency request to *{{atype}}* your account. You may login on the web vault and manually approve or reject this request. | 
				
			|||
 | 
				
			|||
If you do nothing, the request will automatically be approved after {{wait_time_days}} day(s). | 
				
			|||
{{> email/email_footer_text }} | 
				
			|||
@ -0,0 +1,16 @@ | 
				
			|||
Emergency access request by {{{grantee_name}}} is pending | 
				
			|||
<!----------------> | 
				
			|||
{{> 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"> | 
				
			|||
           <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;">{{grantee_name}}</b> has a pending emergency request to <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;">{{atype}}</b> your account. You may login on the web vault and manually approve or reject this request. | 
				
			|||
       </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"> | 
				
			|||
           If you do nothing, the request will automatically be approved after <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;">{{wait_time_days}}</b> day(s). | 
				
			|||
       </td> | 
				
			|||
    </tr> | 
				
			|||
 </table> | 
				
			|||
{{> email/email_footer }} | 
				
			|||
@ -0,0 +1,4 @@ | 
				
			|||
Emergency access request by {{{grantee_name}}} granted | 
				
			|||
<!----------------> | 
				
			|||
{{grantee_name}} has been granted emergency request to *{{atype}}* your account. You may login on the web vault and manually revoke this request. | 
				
			|||
{{> email/email_footer_text }} | 
				
			|||
@ -0,0 +1,11 @@ | 
				
			|||
Emergency access request by {{{grantee_name}}} granted | 
				
			|||
<!----------------> | 
				
			|||
{{> 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"> | 
				
			|||
           <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;">{{grantee_name}}</b> has been granted emergency request to <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;">{{atype}}</b> your account. You may login on the web vault and manually revoke this request. | 
				
			|||
       </td> | 
				
			|||
    </tr> | 
				
			|||
 </table> | 
				
			|||
{{> email/email_footer }} | 
				
			|||
@ -0,0 +1,8 @@ | 
				
			|||
Emergency access for {{{grantor_name}}} | 
				
			|||
<!----------------> | 
				
			|||
You have been invited to become an emergency contact for {{grantor_name}}. To accept this invite, click the following link: | 
				
			|||
 | 
				
			|||
Click here to join: {{url}}/#/accept-emergency/?id={{emer_id}}&name={{grantor_name}}&email={{email}}&token={{token}} | 
				
			|||
 | 
				
			|||
If you do not wish to become an emergency contact for {{grantor_name}}, you can safely ignore this email. | 
				
			|||
{{> email/email_footer_text }} | 
				
			|||
@ -0,0 +1,24 @@ | 
				
			|||
Emergency access for {{{grantor_name}}} | 
				
			|||
<!----------------> | 
				
			|||
{{> 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; text-align: center;" valign="top" align="center"> | 
				
			|||
          You have been invited to become an emergency 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" 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; text-align: center;" valign="top" align="center"> | 
				
			|||
          <a href="{{url}}/#/accept-emergency/?id={{emer_id}}&name={{grantor_name}}&email={{email}}&token={{token}}" | 
				
			|||
             clicktracking=off target="_blank" style="color: #ffffff; text-decoration: none; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background-color: #3c8dbc; border-color: #3c8dbc; border-style: solid; border-width: 10px 20px; margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> | 
				
			|||
          Become emergency contact | 
				
			|||
          </a> | 
				
			|||
       </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; text-align: center;" valign="top" align="center"> | 
				
			|||
           If you do not wish to become an emergency contact for {{grantor_name}}, you can safely ignore this email. | 
				
			|||
       </td> | 
				
			|||
    </tr> | 
				
			|||
 </table> | 
				
			|||
{{> email/email_footer }} | 
				
			|||
					Loading…
					
					
				
		Reference in new issue