2021-03-24 20:15:55 +01:00
|
|
|
use chrono::{Duration, Utc};
|
2022-11-26 19:07:28 +01:00
|
|
|
use rocket::{serde::json::Json, Route};
|
2021-03-24 20:15:55 +01:00
|
|
|
use serde_json::Value;
|
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
|
|
|
|
2021-03-24 20:15:55 +01:00
|
|
|
use crate::{
|
2022-06-21 17:36:07 +02:00
|
|
|
api::{
|
|
|
|
core::{CipherSyncData, CipherSyncType},
|
|
|
|
EmptyResult, JsonResult, JsonUpcase, NumberOrString,
|
|
|
|
},
|
2021-03-24 20:15:55 +01:00
|
|
|
auth::{decode_emergency_access_invite, Headers},
|
|
|
|
db::{models::*, DbConn, DbPool},
|
|
|
|
mail, CONFIG,
|
|
|
|
};
|
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
|
|
|
|
|
|
|
pub fn routes() -> Vec<Route> {
|
2021-03-24 20:15:55 +01:00
|
|
|
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,
|
|
|
|
]
|
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
|
|
|
}
|
|
|
|
|
2021-03-24 20:15:55 +01:00
|
|
|
// region get
|
|
|
|
|
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
|
|
|
#[get("/emergency-access/trusted")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn get_contacts(headers: Headers, mut conn: DbConn) -> JsonResult {
|
2021-03-24 20:15:55 +01:00
|
|
|
check_emergency_access_allowed()?;
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
let emergency_access_list = EmergencyAccess::find_all_by_grantor_uuid(&headers.user.uuid, &mut conn).await;
|
|
|
|
let mut emergency_access_list_json = Vec::with_capacity(emergency_access_list.len());
|
|
|
|
for ea in emergency_access_list {
|
|
|
|
emergency_access_list_json.push(ea.to_json_grantee_details(&mut conn).await);
|
2022-05-20 23:39:47 +02:00
|
|
|
}
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
Ok(Json(json!({
|
|
|
|
"Data": emergency_access_list_json,
|
|
|
|
"Object": "list",
|
|
|
|
"ContinuationToken": null
|
|
|
|
})))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/emergency-access/granted")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn get_grantees(headers: Headers, mut conn: DbConn) -> JsonResult {
|
2021-03-24 20:15:55 +01:00
|
|
|
check_emergency_access_allowed()?;
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
let emergency_access_list = EmergencyAccess::find_all_by_grantee_uuid(&headers.user.uuid, &mut conn).await;
|
|
|
|
let mut emergency_access_list_json = Vec::with_capacity(emergency_access_list.len());
|
|
|
|
for ea in emergency_access_list {
|
|
|
|
emergency_access_list_json.push(ea.to_json_grantor_details(&mut conn).await);
|
2022-05-20 23:39:47 +02:00
|
|
|
}
|
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
|
|
|
|
|
|
|
Ok(Json(json!({
|
2021-03-24 20:15:55 +01:00
|
|
|
"Data": emergency_access_list_json,
|
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
|
|
|
"Object": "list",
|
|
|
|
"ContinuationToken": null
|
|
|
|
})))
|
|
|
|
}
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
#[get("/emergency-access/<emer_id>")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn get_emergency_access(emer_id: String, mut conn: DbConn) -> JsonResult {
|
2021-03-24 20:15:55 +01:00
|
|
|
check_emergency_access_allowed()?;
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
|
|
|
Some(emergency_access) => Ok(Json(emergency_access.to_json_grantee_details(&mut conn).await)),
|
2021-03-24 20:15:55 +01:00
|
|
|
None => err!("Emergency access not valid."),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
// region put/post
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
#[derive(Deserialize)]
|
2021-03-24 20:15:55 +01:00
|
|
|
#[allow(non_snake_case)]
|
|
|
|
struct EmergencyAccessUpdateData {
|
|
|
|
Type: NumberOrString,
|
|
|
|
WaitTimeDays: i32,
|
|
|
|
KeyEncrypted: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[put("/emergency-access/<emer_id>", data = "<data>")]
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn put_emergency_access(
|
|
|
|
emer_id: String,
|
|
|
|
data: JsonUpcase<EmergencyAccessUpdateData>,
|
|
|
|
conn: DbConn,
|
|
|
|
) -> JsonResult {
|
|
|
|
post_emergency_access(emer_id, data, conn).await
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/emergency-access/<emer_id>", data = "<data>")]
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn post_emergency_access(
|
|
|
|
emer_id: String,
|
|
|
|
data: JsonUpcase<EmergencyAccessUpdateData>,
|
2022-05-20 23:39:47 +02:00
|
|
|
mut conn: DbConn,
|
2021-11-16 17:07:55 +01:00
|
|
|
) -> JsonResult {
|
2021-03-24 20:15:55 +01:00
|
|
|
check_emergency_access_allowed()?;
|
|
|
|
|
|
|
|
let data: EmergencyAccessUpdateData = data.into_inner().data;
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
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;
|
2023-02-01 23:10:09 +01:00
|
|
|
if data.KeyEncrypted.is_some() {
|
|
|
|
emergency_access.key_encrypted = data.KeyEncrypted;
|
|
|
|
}
|
2021-03-24 20:15:55 +01:00
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
emergency_access.save(&mut conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
Ok(Json(emergency_access.to_json()))
|
|
|
|
}
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
// region delete
|
|
|
|
|
|
|
|
#[delete("/emergency-access/<emer_id>")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn delete_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
2021-03-24 20:15:55 +01:00
|
|
|
check_emergency_access_allowed()?;
|
|
|
|
|
|
|
|
let grantor_user = headers.user;
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
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."),
|
|
|
|
};
|
2022-05-20 23:39:47 +02:00
|
|
|
emergency_access.delete(&mut conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/emergency-access/<emer_id>/delete")]
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn post_delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult {
|
|
|
|
delete_emergency_access(emer_id, headers, conn).await
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
// region invite
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
#[derive(Deserialize)]
|
2021-03-24 20:15:55 +01:00
|
|
|
#[allow(non_snake_case)]
|
|
|
|
struct EmergencyAccessInviteData {
|
|
|
|
Email: String,
|
|
|
|
Type: NumberOrString,
|
|
|
|
WaitTimeDays: i32,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/emergency-access/invite", data = "<data>")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
2021-03-24 20:15:55 +01:00
|
|
|
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.")
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let grantee_user = match User::find_by_mail(&email, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
None => {
|
2021-12-31 11:53:21 +01:00
|
|
|
if !CONFIG.invitations_allowed() {
|
2022-11-26 19:07:28 +01:00
|
|
|
err!(format!("Grantee user does not exist: {}", &email))
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if !CONFIG.is_email_domain_allowed(&email) {
|
|
|
|
err!("Email domain not eligible for invitations")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !CONFIG.mail_enabled() {
|
2022-11-26 19:07:28 +01:00
|
|
|
let invitation = Invitation::new(&email);
|
2022-05-20 23:39:47 +02:00
|
|
|
invitation.save(&mut conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut user = User::new(email.clone());
|
2022-05-20 23:39:47 +02:00
|
|
|
user.save(&mut conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
user
|
|
|
|
}
|
|
|
|
Some(user) => user,
|
|
|
|
};
|
|
|
|
|
|
|
|
if EmergencyAccess::find_by_grantor_uuid_and_grantee_uuid_or_email(
|
|
|
|
&grantor_user.uuid,
|
|
|
|
&grantee_user.uuid,
|
|
|
|
&grantee_user.email,
|
2022-05-20 23:39:47 +02:00
|
|
|
&mut conn,
|
2021-03-24 20:15:55 +01:00
|
|
|
)
|
2021-11-16 17:07:55 +01:00
|
|
|
.await
|
2021-03-24 20:15:55 +01:00
|
|
|
.is_some()
|
|
|
|
{
|
2022-11-26 19:07:28 +01:00
|
|
|
err!(format!("Grantee user already invited: {}", &grantee_user.email))
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
let mut new_emergency_access =
|
|
|
|
EmergencyAccess::new(grantor_user.uuid, grantee_user.email, emergency_access_status, new_type, wait_time_days);
|
2022-05-20 23:39:47 +02:00
|
|
|
new_emergency_access.save(&mut conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
if CONFIG.mail_enabled() {
|
|
|
|
mail::send_emergency_access_invite(
|
2022-11-26 19:07:28 +01:00
|
|
|
&new_emergency_access.email.expect("Grantee email does not exists"),
|
2021-03-24 20:15:55 +01:00
|
|
|
&grantee_user.uuid,
|
2022-11-26 19:07:28 +01:00
|
|
|
&new_emergency_access.uuid,
|
|
|
|
&grantor_user.name,
|
|
|
|
&grantor_user.email,
|
2022-07-06 23:57:37 +02:00
|
|
|
)
|
|
|
|
.await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
} else {
|
|
|
|
// Automatically mark user as accepted if no email invites
|
2022-05-20 23:39:47 +02:00
|
|
|
match User::find_by_mail(&email, &mut conn).await {
|
2022-11-26 19:07:28 +01:00
|
|
|
Some(user) => match accept_invite_process(user.uuid, &mut new_emergency_access, &email, &mut conn).await {
|
|
|
|
Ok(v) => v,
|
|
|
|
Err(e) => err!(e.to_string()),
|
|
|
|
},
|
2021-03-24 20:15:55 +01:00
|
|
|
None => err!("Grantee user not found."),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/emergency-access/<emer_id>/reinvite")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn resend_invite(emer_id: String, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
2021-03-24 20:15:55 +01:00
|
|
|
check_emergency_access_allowed()?;
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
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."),
|
|
|
|
};
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let grantee_user = match User::find_by_mail(&email, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(user) => user,
|
2021-09-01 12:54:47 +02:00
|
|
|
None => err!("Grantee user not found."),
|
2021-03-24 20:15:55 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
let grantor_user = headers.user;
|
|
|
|
|
|
|
|
if CONFIG.mail_enabled() {
|
|
|
|
mail::send_emergency_access_invite(
|
|
|
|
&email,
|
|
|
|
&grantor_user.uuid,
|
2022-11-26 19:07:28 +01:00
|
|
|
&emergency_access.uuid,
|
|
|
|
&grantor_user.name,
|
|
|
|
&grantor_user.email,
|
2022-07-06 23:57:37 +02:00
|
|
|
)
|
|
|
|
.await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
} else {
|
2022-05-20 23:39:47 +02:00
|
|
|
if Invitation::find_by_mail(&email, &mut conn).await.is_none() {
|
2022-11-26 19:07:28 +01:00
|
|
|
let invitation = Invitation::new(&email);
|
2022-05-20 23:39:47 +02:00
|
|
|
invitation.save(&mut conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Automatically mark user as accepted if no email invites
|
2022-11-26 19:07:28 +01:00
|
|
|
match accept_invite_process(grantee_user.uuid, &mut emergency_access, &email, &mut conn).await {
|
2022-08-20 16:42:36 +02:00
|
|
|
Ok(v) => v,
|
2021-03-24 20:15:55 +01:00
|
|
|
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>")]
|
2022-11-26 19:07:28 +01:00
|
|
|
async fn accept_invite(
|
|
|
|
emer_id: String,
|
|
|
|
data: JsonUpcase<AcceptData>,
|
|
|
|
headers: Headers,
|
|
|
|
mut conn: DbConn,
|
|
|
|
) -> EmptyResult {
|
2021-03-24 20:15:55 +01:00
|
|
|
check_emergency_access_allowed()?;
|
|
|
|
|
|
|
|
let data: AcceptData = data.into_inner().data;
|
|
|
|
let token = &data.Token;
|
|
|
|
let claims = decode_emergency_access_invite(token)?;
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
// This can happen if the user who received the invite used a different email to signup.
|
|
|
|
// Since we do not know if this is intented, we error out here and do nothing with the invite.
|
|
|
|
if claims.email != headers.user.email {
|
|
|
|
err!("Claim email does not match current users email")
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let grantee_user = match User::find_by_mail(&claims.email, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(user) => {
|
2022-05-20 23:39:47 +02:00
|
|
|
Invitation::take(&claims.email, &mut conn).await;
|
2021-03-24 20:15:55 +01:00
|
|
|
user
|
|
|
|
}
|
|
|
|
None => err!("Invited user not found"),
|
|
|
|
};
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(emer) => emer,
|
|
|
|
None => err!("Emergency access not valid."),
|
|
|
|
};
|
|
|
|
|
|
|
|
// get grantor user to send Accepted email
|
2022-05-20 23:39:47 +02:00
|
|
|
let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(user) => user,
|
|
|
|
None => err!("Grantor user not found."),
|
|
|
|
};
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
if emer_id == claims.emer_id
|
|
|
|
&& grantor_user.name == claims.grantor_name
|
|
|
|
&& grantor_user.email == claims.grantor_email
|
2021-03-24 20:15:55 +01:00
|
|
|
{
|
2022-11-26 19:07:28 +01:00
|
|
|
match accept_invite_process(grantee_user.uuid, &mut emergency_access, &grantee_user.email, &mut conn).await {
|
2022-08-20 16:42:36 +02:00
|
|
|
Ok(v) => v,
|
2021-03-24 20:15:55 +01:00
|
|
|
Err(e) => err!(e.to_string()),
|
|
|
|
}
|
|
|
|
|
|
|
|
if CONFIG.mail_enabled() {
|
2022-07-06 23:57:37 +02:00
|
|
|
mail::send_emergency_access_invite_accepted(&grantor_user.email, &grantee_user.email).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
err!("Emergency access invitation error.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn accept_invite_process(
|
|
|
|
grantee_uuid: String,
|
2022-11-26 19:07:28 +01:00
|
|
|
emergency_access: &mut EmergencyAccess,
|
|
|
|
grantee_email: &str,
|
2022-05-20 23:39:47 +02:00
|
|
|
conn: &mut DbConn,
|
2021-11-16 17:07:55 +01:00
|
|
|
) -> EmptyResult {
|
2022-11-26 19:07:28 +01:00
|
|
|
if emergency_access.email.is_none() || emergency_access.email.as_ref().unwrap() != grantee_email {
|
2021-03-24 20:15:55 +01:00
|
|
|
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;
|
2021-11-16 17:07:55 +01:00
|
|
|
emergency_access.save(conn).await
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
struct ConfirmData {
|
|
|
|
Key: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/emergency-access/<emer_id>/confirm", data = "<data>")]
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn confirm_emergency_access(
|
2021-03-24 20:15:55 +01:00
|
|
|
emer_id: String,
|
|
|
|
data: JsonUpcase<ConfirmData>,
|
|
|
|
headers: Headers,
|
2022-05-20 23:39:47 +02:00
|
|
|
mut conn: DbConn,
|
2021-03-24 20:15:55 +01:00
|
|
|
) -> JsonResult {
|
|
|
|
check_emergency_access_allowed()?;
|
|
|
|
|
|
|
|
let confirming_user = headers.user;
|
|
|
|
let data: ConfirmData = data.into_inner().data;
|
|
|
|
let key = data.Key;
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
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.")
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let grantor_user = match User::find_by_uuid(&confirming_user.uuid, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(user) => user,
|
|
|
|
None => err!("Grantor user not found."),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() {
|
2022-05-20 23:39:47 +02:00
|
|
|
let grantee_user = match User::find_by_uuid(grantee_uuid, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
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;
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
emergency_access.save(&mut conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
if CONFIG.mail_enabled() {
|
2022-07-06 23:57:37 +02:00
|
|
|
mail::send_emergency_access_invite_confirmed(&grantee_user.email, &grantor_user.name).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
Ok(Json(emergency_access.to_json()))
|
|
|
|
} else {
|
|
|
|
err!("Grantee user not found.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
// region access emergency access
|
|
|
|
|
|
|
|
#[post("/emergency-access/<emer_id>/initiate")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn initiate_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
2021-03-24 20:15:55 +01:00
|
|
|
check_emergency_access_allowed()?;
|
|
|
|
|
|
|
|
let initiating_user = headers.user;
|
2022-05-20 23:39:47 +02:00
|
|
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(emer) => emer,
|
|
|
|
None => err!("Emergency access not valid."),
|
|
|
|
};
|
|
|
|
|
|
|
|
if emergency_access.status != EmergencyAccessStatus::Confirmed as i32
|
2022-11-26 19:07:28 +01:00
|
|
|
|| emergency_access.grantee_uuid != Some(initiating_user.uuid)
|
2021-03-24 20:15:55 +01:00
|
|
|
{
|
|
|
|
err!("Emergency access not valid.")
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
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);
|
2022-05-20 23:39:47 +02:00
|
|
|
emergency_access.save(&mut conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
if CONFIG.mail_enabled() {
|
|
|
|
mail::send_emergency_access_recovery_initiated(
|
|
|
|
&grantor_user.email,
|
|
|
|
&initiating_user.name,
|
2021-10-19 01:27:50 -07:00
|
|
|
emergency_access.get_type_as_str(),
|
2022-11-26 19:07:28 +01:00
|
|
|
&emergency_access.wait_time_days,
|
2022-07-06 23:57:37 +02:00
|
|
|
)
|
|
|
|
.await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
Ok(Json(emergency_access.to_json()))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/emergency-access/<emer_id>/approve")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn approve_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
2021-03-24 20:15:55 +01:00
|
|
|
check_emergency_access_allowed()?;
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(emer) => emer,
|
|
|
|
None => err!("Emergency access not valid."),
|
|
|
|
};
|
|
|
|
|
|
|
|
if emergency_access.status != EmergencyAccessStatus::RecoveryInitiated as i32
|
2022-11-26 19:07:28 +01:00
|
|
|
|| emergency_access.grantor_uuid != headers.user.uuid
|
2021-03-24 20:15:55 +01:00
|
|
|
{
|
|
|
|
err!("Emergency access not valid.")
|
|
|
|
}
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
let grantor_user = match User::find_by_uuid(&headers.user.uuid, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(user) => user,
|
|
|
|
None => err!("Grantor user not found."),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() {
|
2022-05-20 23:39:47 +02:00
|
|
|
let grantee_user = match User::find_by_uuid(grantee_uuid, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(user) => user,
|
|
|
|
None => err!("Grantee user not found."),
|
|
|
|
};
|
|
|
|
|
|
|
|
emergency_access.status = EmergencyAccessStatus::RecoveryApproved as i32;
|
2022-05-20 23:39:47 +02:00
|
|
|
emergency_access.save(&mut conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
if CONFIG.mail_enabled() {
|
2022-07-06 23:57:37 +02:00
|
|
|
mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
Ok(Json(emergency_access.to_json()))
|
|
|
|
} else {
|
|
|
|
err!("Grantee user not found.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/emergency-access/<emer_id>/reject")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn reject_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
2021-03-24 20:15:55 +01:00
|
|
|
check_emergency_access_allowed()?;
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(emer) => emer,
|
|
|
|
None => err!("Emergency access not valid."),
|
|
|
|
};
|
|
|
|
|
|
|
|
if (emergency_access.status != EmergencyAccessStatus::RecoveryInitiated as i32
|
|
|
|
&& emergency_access.status != EmergencyAccessStatus::RecoveryApproved as i32)
|
2022-11-26 19:07:28 +01:00
|
|
|
|| emergency_access.grantor_uuid != headers.user.uuid
|
2021-03-24 20:15:55 +01:00
|
|
|
{
|
|
|
|
err!("Emergency access not valid.")
|
|
|
|
}
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
let grantor_user = match User::find_by_uuid(&headers.user.uuid, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(user) => user,
|
|
|
|
None => err!("Grantor user not found."),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() {
|
2022-05-20 23:39:47 +02:00
|
|
|
let grantee_user = match User::find_by_uuid(grantee_uuid, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(user) => user,
|
|
|
|
None => err!("Grantee user not found."),
|
|
|
|
};
|
|
|
|
|
|
|
|
emergency_access.status = EmergencyAccessStatus::Confirmed as i32;
|
2022-05-20 23:39:47 +02:00
|
|
|
emergency_access.save(&mut conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
if CONFIG.mail_enabled() {
|
2022-07-06 23:57:37 +02:00
|
|
|
mail::send_emergency_access_recovery_rejected(&grantee_user.email, &grantor_user.name).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
Ok(Json(emergency_access.to_json()))
|
|
|
|
} else {
|
|
|
|
err!("Grantee user not found.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
// region action
|
|
|
|
|
|
|
|
#[post("/emergency-access/<emer_id>/view")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn view_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
2021-03-24 20:15:55 +01:00
|
|
|
check_emergency_access_allowed()?;
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(emer) => emer,
|
|
|
|
None => err!("Emergency access not valid."),
|
|
|
|
};
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
if !is_valid_request(&emergency_access, headers.user.uuid, EmergencyAccessType::View) {
|
2021-03-24 20:15:55 +01:00
|
|
|
err!("Emergency access not valid.")
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let ciphers = Cipher::find_owned_by_user(&emergency_access.grantor_uuid, &mut conn).await;
|
2023-01-11 20:23:53 +01:00
|
|
|
let cipher_sync_data = CipherSyncData::new(&emergency_access.grantor_uuid, CipherSyncType::User, &mut conn).await;
|
2022-05-04 21:13:05 +02:00
|
|
|
|
2023-01-11 20:23:53 +01:00
|
|
|
let mut ciphers_json = Vec::with_capacity(ciphers.len());
|
2022-05-20 23:39:47 +02:00
|
|
|
for c in ciphers {
|
2022-11-26 19:07:28 +01:00
|
|
|
ciphers_json
|
|
|
|
.push(c.to_json(&headers.host, &emergency_access.grantor_uuid, Some(&cipher_sync_data), &mut conn).await);
|
2022-05-20 23:39:47 +02:00
|
|
|
}
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
Ok(Json(json!({
|
|
|
|
"Ciphers": ciphers_json,
|
|
|
|
"KeyEncrypted": &emergency_access.key_encrypted,
|
|
|
|
"Object": "emergencyAccessView",
|
|
|
|
})))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/emergency-access/<emer_id>/takeover")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn takeover_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
2021-03-24 20:15:55 +01:00
|
|
|
check_emergency_access_allowed()?;
|
|
|
|
|
|
|
|
let requesting_user = headers.user;
|
2022-05-20 23:39:47 +02:00
|
|
|
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
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.")
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(user) => user,
|
|
|
|
None => err!("Grantor user not found."),
|
|
|
|
};
|
|
|
|
|
2023-01-31 21:26:23 -05:00
|
|
|
let mut result = json!({
|
|
|
|
"Kdf": grantor_user.client_kdf_type,
|
|
|
|
"KdfIterations": grantor_user.client_kdf_iter,
|
|
|
|
"KeyEncrypted": &emergency_access.key_encrypted,
|
|
|
|
"Object": "emergencyAccessTakeover",
|
|
|
|
});
|
|
|
|
|
|
|
|
if grantor_user.client_kdf_type == UserKdfType::Argon2id as i32 {
|
|
|
|
result["KdfMemory"] =
|
|
|
|
Value::Number(grantor_user.client_kdf_memory.expect("Argon2 memory parameter is required.").into());
|
|
|
|
result["KdfParallelism"] = Value::Number(
|
|
|
|
grantor_user.client_kdf_parallelism.expect("Argon2 parallelism parameter is required.").into(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Json(result))
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
#[derive(Deserialize)]
|
2021-03-24 20:15:55 +01:00
|
|
|
#[allow(non_snake_case)]
|
|
|
|
struct EmergencyAccessPasswordData {
|
|
|
|
NewMasterPasswordHash: String,
|
|
|
|
Key: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/emergency-access/<emer_id>/password", data = "<data>")]
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn password_emergency_access(
|
2021-03-24 20:15:55 +01:00
|
|
|
emer_id: String,
|
|
|
|
data: JsonUpcase<EmergencyAccessPasswordData>,
|
|
|
|
headers: Headers,
|
2022-05-20 23:39:47 +02:00
|
|
|
mut conn: DbConn,
|
2021-03-24 20:15:55 +01:00
|
|
|
) -> EmptyResult {
|
|
|
|
check_emergency_access_allowed()?;
|
|
|
|
|
|
|
|
let data: EmergencyAccessPasswordData = data.into_inner().data;
|
|
|
|
let new_master_password_hash = &data.NewMasterPasswordHash;
|
2023-01-14 10:16:03 +01:00
|
|
|
//let key = &data.Key;
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
let requesting_user = headers.user;
|
2022-05-20 23:39:47 +02:00
|
|
|
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
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.")
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let mut grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(user) => user,
|
|
|
|
None => err!("Grantor user not found."),
|
|
|
|
};
|
|
|
|
|
|
|
|
// change grantor_user password
|
2023-01-14 10:16:03 +01:00
|
|
|
grantor_user.set_password(new_master_password_hash, Some(data.Key), true, None);
|
2022-05-20 23:39:47 +02:00
|
|
|
grantor_user.save(&mut conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
// Disable TwoFactor providers since they will otherwise block logins
|
2022-05-20 23:39:47 +02:00
|
|
|
TwoFactor::delete_all_by_user(&grantor_user.uuid, &mut conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
// Remove grantor from all organisations unless Owner
|
2022-05-20 23:39:47 +02:00
|
|
|
for user_org in UserOrganization::find_any_state_by_user(&grantor_user.uuid, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
if user_org.atype != UserOrgType::Owner as i32 {
|
2022-05-20 23:39:47 +02:00
|
|
|
user_org.delete(&mut conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
#[get("/emergency-access/<emer_id>/policies")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn policies_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
2021-03-24 20:15:55 +01:00
|
|
|
let requesting_user = headers.user;
|
2022-05-20 23:39:47 +02:00
|
|
|
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
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.")
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await {
|
2021-03-24 20:15:55 +01:00
|
|
|
Some(user) => user,
|
|
|
|
None => err!("Grantor user not found."),
|
|
|
|
};
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
let policies = OrgPolicy::find_confirmed_by_user(&grantor_user.uuid, &mut conn);
|
2021-11-16 17:07:55 +01:00
|
|
|
let policies_json: Vec<Value> = policies.await.iter().map(OrgPolicy::to_json).collect();
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
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(())
|
|
|
|
}
|
|
|
|
|
2021-11-07 18:53:39 +01:00
|
|
|
pub async fn emergency_request_timeout_job(pool: DbPool) {
|
2021-03-24 20:15:55 +01:00
|
|
|
debug!("Start emergency_request_timeout_job");
|
|
|
|
if !CONFIG.emergency_access_allowed() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
if let Ok(mut conn) = pool.get().await {
|
2022-11-26 19:07:28 +01:00
|
|
|
let emergency_access_list = EmergencyAccess::find_all_recoveries_initiated(&mut conn).await;
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
if emergency_access_list.is_empty() {
|
|
|
|
debug!("No emergency request timeout to approve");
|
|
|
|
}
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
let now = Utc::now().naive_utc();
|
2021-03-24 20:15:55 +01:00
|
|
|
for mut emer in emergency_access_list {
|
2022-11-26 19:07:28 +01:00
|
|
|
// The find_all_recoveries_initiated already checks if the recovery_initiated_at is not null (None)
|
|
|
|
let recovery_allowed_at =
|
|
|
|
emer.recovery_initiated_at.unwrap() + Duration::days(i64::from(emer.wait_time_days));
|
|
|
|
if recovery_allowed_at.le(&now) {
|
|
|
|
// Only update the access status
|
|
|
|
// Updating the whole record could cause issues when the emergency_notification_reminder_job is also active
|
|
|
|
emer.update_access_status_and_save(EmergencyAccessStatus::RecoveryApproved as i32, &now, &mut conn)
|
|
|
|
.await
|
|
|
|
.expect("Unable to update emergency access status");
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
if CONFIG.mail_enabled() {
|
|
|
|
// get grantor user to send Accepted email
|
2021-11-16 17:07:55 +01:00
|
|
|
let grantor_user =
|
2022-11-26 19:07:28 +01:00
|
|
|
User::find_by_uuid(&emer.grantor_uuid, &mut conn).await.expect("Grantor user not found");
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
// get grantee user to send Accepted email
|
|
|
|
let grantee_user =
|
2022-11-26 19:07:28 +01:00
|
|
|
User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &mut conn)
|
2021-11-16 17:07:55 +01:00
|
|
|
.await
|
2022-11-26 19:07:28 +01:00
|
|
|
.expect("Grantee user not found");
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
mail::send_emergency_access_recovery_timed_out(
|
|
|
|
&grantor_user.email,
|
2022-11-26 19:07:28 +01:00
|
|
|
&grantee_user.name,
|
2021-10-19 01:27:50 -07:00
|
|
|
emer.get_type_as_str(),
|
2021-03-24 20:15:55 +01:00
|
|
|
)
|
2022-07-06 23:57:37 +02:00
|
|
|
.await
|
2021-03-24 20:15:55 +01:00
|
|
|
.expect("Error on sending email");
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name)
|
2022-07-06 23:57:37 +02:00
|
|
|
.await
|
2021-03-24 20:15:55 +01:00
|
|
|
.expect("Error on sending email");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
error!("Failed to get DB connection while searching emergency request timed out")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-07 18:53:39 +01:00
|
|
|
pub async fn emergency_notification_reminder_job(pool: DbPool) {
|
2021-03-24 20:15:55 +01:00
|
|
|
debug!("Start emergency_notification_reminder_job");
|
|
|
|
if !CONFIG.emergency_access_allowed() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
if let Ok(mut conn) = pool.get().await {
|
2022-11-26 19:07:28 +01:00
|
|
|
let emergency_access_list = EmergencyAccess::find_all_recoveries_initiated(&mut conn).await;
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
if emergency_access_list.is_empty() {
|
|
|
|
debug!("No emergency request reminder notification to send");
|
|
|
|
}
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
let now = Utc::now().naive_utc();
|
2021-03-24 20:15:55 +01:00
|
|
|
for mut emer in emergency_access_list {
|
2022-11-26 19:07:28 +01:00
|
|
|
// The find_all_recoveries_initiated already checks if the recovery_initiated_at is not null (None)
|
|
|
|
// Calculate the day before the recovery will become active
|
|
|
|
let final_recovery_reminder_at =
|
|
|
|
emer.recovery_initiated_at.unwrap() + Duration::days(i64::from(emer.wait_time_days - 1));
|
|
|
|
// Calculate if a day has passed since the previous notification, else no notification has been sent before
|
|
|
|
let next_recovery_reminder_at = if let Some(last_notification_at) = emer.last_notification_at {
|
|
|
|
last_notification_at + Duration::days(1)
|
|
|
|
} else {
|
|
|
|
now
|
|
|
|
};
|
|
|
|
if final_recovery_reminder_at.le(&now) && next_recovery_reminder_at.le(&now) {
|
|
|
|
// Only update the last notification date
|
|
|
|
// Updating the whole record could cause issues when the emergency_request_timeout_job is also active
|
|
|
|
emer.update_last_notification_date_and_save(&now, &mut conn)
|
|
|
|
.await
|
|
|
|
.expect("Unable to update emergency access notification date");
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
if CONFIG.mail_enabled() {
|
|
|
|
// get grantor user to send Accepted email
|
2021-11-16 17:07:55 +01:00
|
|
|
let grantor_user =
|
2022-11-26 19:07:28 +01:00
|
|
|
User::find_by_uuid(&emer.grantor_uuid, &mut conn).await.expect("Grantor user not found");
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
// get grantee user to send Accepted email
|
|
|
|
let grantee_user =
|
2022-11-26 19:07:28 +01:00
|
|
|
User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &mut conn)
|
2021-11-16 17:07:55 +01:00
|
|
|
.await
|
2022-11-26 19:07:28 +01:00
|
|
|
.expect("Grantee user not found");
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
mail::send_emergency_access_recovery_reminder(
|
|
|
|
&grantor_user.email,
|
2022-11-26 19:07:28 +01:00
|
|
|
&grantee_user.name,
|
2021-10-19 01:27:50 -07:00
|
|
|
emer.get_type_as_str(),
|
2022-11-26 19:07:28 +01:00
|
|
|
"1", // This notification is only triggered one day before the activation
|
2021-03-24 20:15:55 +01:00
|
|
|
)
|
2022-07-06 23:57:37 +02:00
|
|
|
.await
|
2021-03-24 20:15:55 +01:00
|
|
|
.expect("Error on sending email");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
error!("Failed to get DB connection while searching emergency notification reminder")
|
|
|
|
}
|
|
|
|
}
|