@ -1,6 +1,5 @@
use chrono ::{ Duration , Utc } ;
use rocket ::serde ::json ::Json ;
use rocket ::Route ;
use rocket ::{ serde ::json ::Json , Route } ;
use serde_json ::Value ;
use crate ::{
@ -41,9 +40,10 @@ pub fn routes() -> Vec<Route> {
async fn get_contacts ( headers : Headers , mut conn : DbConn ) -> JsonResult {
check_emergency_access_allowed ( ) ? ;
let mut emergency_access_list_json = Vec ::new ( ) ;
for e in EmergencyAccess ::find_all_by_grantor_uuid ( & headers . user . uuid , & mut conn ) . await {
emergency_access_list_json . push ( e . to_json_grantee_details ( & mut conn ) . await ) ;
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 ) ;
}
Ok ( Json ( json ! ( {
@ -57,9 +57,10 @@ async fn get_contacts(headers: Headers, mut conn: DbConn) -> JsonResult {
async fn get_grantees ( headers : Headers , mut conn : DbConn ) -> JsonResult {
check_emergency_access_allowed ( ) ? ;
let mut emergency_access_list_json = Vec ::new ( ) ;
for e in EmergencyAccess ::find_all_by_grantee_uuid ( & headers . user . uuid , & mut conn ) . await {
emergency_access_list_json . push ( e . to_json_grantor_details ( & mut conn ) . await ) ;
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 ) ;
}
Ok ( Json ( json ! ( {
@ -83,7 +84,7 @@ async fn get_emergency_access(emer_id: String, mut conn: DbConn) -> JsonResult {
// region put/post
#[ derive(Deserialize , Debug )]
#[ derive(Deserialize )]
#[ allow(non_snake_case) ]
struct EmergencyAccessUpdateData {
Type : NumberOrString ,
@ -160,7 +161,7 @@ async fn post_delete_emergency_access(emer_id: String, headers: Headers, conn: D
// region invite
#[ derive(Deserialize , Debug )]
#[ derive(Deserialize )]
#[ allow(non_snake_case) ]
struct EmergencyAccessInviteData {
Email : String ,
@ -193,7 +194,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
let grantee_user = match User ::find_by_mail ( & email , & mut conn ) . await {
None = > {
if ! CONFIG . invitations_allowed ( ) {
err ! ( format! ( "Grantee user does not exist: {}" , email ) )
err ! ( format! ( "Grantee user does not exist: {}" , & email ) )
}
if ! CONFIG . is_email_domain_allowed ( & email ) {
@ -201,7 +202,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
}
if ! CONFIG . mail_enabled ( ) {
let invitation = Invitation ::new ( email . clone ( ) ) ;
let invitation = Invitation ::new ( & email ) ;
invitation . save ( & mut conn ) . await ? ;
}
@ -221,36 +222,29 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
. await
. is_some ( )
{
err ! ( format! ( "Grantee user already invited: {}" , email ) )
err ! ( format! ( "Grantee user already invited: {}" , & grantee_user . email ) )
}
let mut new_emergency_access = EmergencyAccess ::new (
grantor_user . uuid . clone ( ) ,
Some ( grantee_user . email . clone ( ) ) ,
emergency_access_status ,
new_type ,
wait_time_days ,
) ;
let mut new_emergency_access =
EmergencyAccess ::new ( grantor_user . uuid , grantee_user . email , emergency_access_status , new_type , wait_time_days ) ;
new_emergency_access . save ( & mut conn ) . await ? ;
if CONFIG . mail_enabled ( ) {
mail ::send_emergency_access_invite (
& grantee_user. email ,
& new_emergency_access . email . expect ( "Grantee email does not exists" ) ,
& grantee_user . uuid ,
Some ( new_emergency_access . uuid ) ,
Some ( grantor_user . name . clone ( ) ) ,
Some ( grantor_user . email ) ,
& new_emergency_access . uuid ,
& grantor_user . name ,
& grantor_user . email ,
)
. await ? ;
} else {
// Automatically mark user as accepted if no email invites
match User ::find_by_mail ( & email , & mut conn ) . await {
Some ( user ) = > {
match accept_invite_process ( user . uuid , new_emergency_access . uuid , Some ( email ) , & mut conn ) . await {
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 ( ) ) ,
}
}
} ,
None = > err ! ( "Grantee user not found." ) ,
}
}
@ -262,7 +256,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
async fn resend_invite ( emer_id : String , headers : Headers , mut conn : DbConn ) -> EmptyResult {
check_emergency_access_allowed ( ) ? ;
let emergency_access = match EmergencyAccess ::find_by_uuid ( & emer_id , & mut conn ) . await {
let mut emergency_access = match EmergencyAccess ::find_by_uuid ( & emer_id , & mut conn ) . await {
Some ( emer ) = > emer ,
None = > err ! ( "Emergency access not valid." ) ,
} ;
@ -291,19 +285,19 @@ async fn resend_invite(emer_id: String, headers: Headers, mut conn: DbConn) -> E
mail ::send_emergency_access_invite (
& email ,
& grantor_user . uuid ,
Some ( emergency_access . uuid ) ,
Some ( grantor_user . name . clone ( ) ) ,
Some ( grantor_user . email ) ,
& emergency_access . uuid ,
& grantor_user . name ,
& grantor_user . email ,
)
. await ? ;
} else {
if Invitation ::find_by_mail ( & email , & mut conn ) . await . is_none ( ) {
let invitation = Invitation ::new ( email ) ;
let invitation = Invitation ::new ( & email ) ;
invitation . save ( & mut conn ) . await ? ;
}
// Automatically mark user as accepted if no email invites
match accept_invite_process ( grantee_user . uuid , emergency_access . uuid , emergency_access . email , & mut conn ) . await {
match accept_invite_process ( grantee_user . uuid , & mut emergency_access , & email , & mut conn ) . await {
Ok ( v ) = > v ,
Err ( e ) = > err ! ( e . to_string ( ) ) ,
}
@ -319,13 +313,24 @@ struct AcceptData {
}
#[ post( " /emergency-access/<emer_id>/accept " , data = " <data> " ) ]
async fn accept_invite ( emer_id : String , data : JsonUpcase < AcceptData > , mut conn : DbConn ) -> EmptyResult {
async fn accept_invite (
emer_id : String ,
data : JsonUpcase < AcceptData > ,
headers : Headers ,
mut conn : DbConn ,
) -> EmptyResult {
check_emergency_access_allowed ( ) ? ;
let data : AcceptData = data . into_inner ( ) . data ;
let token = & data . Token ;
let claims = decode_emergency_access_invite ( token ) ? ;
// This can happen if the user who received the invite used a different email to signup.
// Since we do not know if this is intented, we error out here and do nothing with the invite.
if claims . email ! = headers . user . email {
err ! ( "Claim email does not match current users email" )
}
let grantee_user = match User ::find_by_mail ( & claims . email , & mut conn ) . await {
Some ( user ) = > {
Invitation ::take ( & claims . email , & mut conn ) . await ;
@ -334,7 +339,7 @@ async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, mut conn:
None = > err ! ( "Invited user not found" ) ,
} ;
let emergency_access = match EmergencyAccess ::find_by_uuid ( & emer_id , & mut conn ) . await {
let mut emergency_access = match EmergencyAccess ::find_by_uuid ( & emer_id , & mut conn ) . await {
Some ( emer ) = > emer ,
None = > err ! ( "Emergency access not valid." ) ,
} ;
@ -345,13 +350,11 @@ async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, mut conn:
None = > err ! ( "Grantor user not found." ) ,
} ;
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 ( ) ) , & mut conn )
. await
if emer_id = = claims . emer_id
& & grantor_user . name = = claims . grantor_name
& & grantor_user . email = = claims . grantor_email
{
match accept_invite_process ( grantee_user . uuid , & mut emergency_access , & grantee_user . email , & mut conn ) . await {
Ok ( v ) = > v ,
Err ( e ) = > err ! ( e . to_string ( ) ) ,
}
@ -368,17 +371,11 @@ async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, mut conn:
async fn accept_invite_process (
grantee_uuid : String ,
emer _id: String ,
email: Option < String > ,
emer gency_access: & mut EmergencyAccess ,
grantee_email: & str ,
conn : & mut DbConn ,
) -> EmptyResult {
let mut emergency_access = match EmergencyAccess ::find_by_uuid ( & emer_id , conn ) . await {
Some ( emer ) = > emer ,
None = > err ! ( "Emergency access not valid." ) ,
} ;
let emer_email = emergency_access . email ;
if emer_email . is_none ( ) | | emer_email ! = email {
if emergency_access . email . is_none ( ) | | emergency_access . email . as_ref ( ) . unwrap ( ) ! = grantee_email {
err ! ( "User email does not match invite." ) ;
}
@ -463,7 +460,7 @@ async fn initiate_emergency_access(emer_id: String, headers: Headers, mut conn:
} ;
if emergency_access . status ! = EmergencyAccessStatus ::Confirmed as i32
| | emergency_access . grantee_uuid ! = Some ( initiating_user . uuid .clone ( ) )
| | emergency_access . grantee_uuid ! = Some ( initiating_user . uuid )
{
err ! ( "Emergency access not valid." )
}
@ -485,7 +482,7 @@ async fn initiate_emergency_access(emer_id: String, headers: Headers, mut conn:
& grantor_user . email ,
& initiating_user . name ,
emergency_access . get_type_as_str ( ) ,
& emergency_access . wait_time_days .clone ( ) . to_string ( ) ,
& emergency_access . wait_time_days ,
)
. await ? ;
}
@ -496,19 +493,18 @@ async fn initiate_emergency_access(emer_id: String, headers: Headers, mut conn:
async fn approve_emergency_access ( emer_id : String , headers : Headers , mut conn : DbConn ) -> JsonResult {
check_emergency_access_allowed ( ) ? ;
let approving_user = headers . user ;
let mut emergency_access = match EmergencyAccess ::find_by_uuid ( & emer_id , & mut conn ) . await {
Some ( emer ) = > emer ,
None = > err ! ( "Emergency access not valid." ) ,
} ;
if emergency_access . status ! = EmergencyAccessStatus ::RecoveryInitiated as i32
| | emergency_access . grantor_uuid ! = approving_ user. uuid
| | emergency_access . grantor_uuid ! = headers. user. uuid
{
err ! ( "Emergency access not valid." )
}
let grantor_user = match User ::find_by_uuid ( & approving_ user. uuid , & mut conn ) . await {
let grantor_user = match User ::find_by_uuid ( & headers. user. uuid , & mut conn ) . await {
Some ( user ) = > user ,
None = > err ! ( "Grantor user not found." ) ,
} ;
@ -535,7 +531,6 @@ async fn approve_emergency_access(emer_id: String, headers: Headers, mut conn: D
async fn reject_emergency_access ( emer_id : String , headers : Headers , mut conn : DbConn ) -> JsonResult {
check_emergency_access_allowed ( ) ? ;
let rejecting_user = headers . user ;
let mut emergency_access = match EmergencyAccess ::find_by_uuid ( & emer_id , & mut conn ) . await {
Some ( emer ) = > emer ,
None = > err ! ( "Emergency access not valid." ) ,
@ -543,12 +538,12 @@ async fn reject_emergency_access(emer_id: String, headers: Headers, mut conn: Db
if ( emergency_access . status ! = EmergencyAccessStatus ::RecoveryInitiated as i32
& & emergency_access . status ! = EmergencyAccessStatus ::RecoveryApproved as i32 )
| | emergency_access . grantor_uuid ! = rejecting_ user. uuid
| | emergency_access . grantor_uuid ! = headers. user. uuid
{
err ! ( "Emergency access not valid." )
}
let grantor_user = match User ::find_by_uuid ( & rejecting_ user. uuid , & mut conn ) . await {
let grantor_user = match User ::find_by_uuid ( & headers. user. uuid , & mut conn ) . await {
Some ( user ) = > user ,
None = > err ! ( "Grantor user not found." ) ,
} ;
@ -579,14 +574,12 @@ async fn reject_emergency_access(emer_id: String, headers: Headers, mut conn: Db
async fn view_emergency_access ( emer_id : String , headers : Headers , mut conn : DbConn ) -> JsonResult {
check_emergency_access_allowed ( ) ? ;
let requesting_user = headers . user ;
let host = headers . host ;
let emergency_access = match EmergencyAccess ::find_by_uuid ( & emer_id , & mut conn ) . await {
Some ( emer ) = > emer ,
None = > err ! ( "Emergency access not valid." ) ,
} ;
if ! is_valid_request ( & emergency_access , requesting_ user. uuid , EmergencyAccessType ::View ) {
if ! is_valid_request ( & emergency_access , headers. user. uuid , EmergencyAccessType ::View ) {
err ! ( "Emergency access not valid." )
}
@ -596,7 +589,8 @@ async fn view_emergency_access(emer_id: String, headers: Headers, mut conn: DbCo
let mut ciphers_json = Vec ::new ( ) ;
for c in ciphers {
ciphers_json . push ( c . to_json ( & host , & emergency_access . grantor_uuid , Some ( & cipher_sync_data ) , & mut conn ) . await ) ;
ciphers_json
. push ( c . to_json ( & headers . host , & emergency_access . grantor_uuid , Some ( & cipher_sync_data ) , & mut conn ) . await ) ;
}
Ok ( Json ( json ! ( {
@ -633,7 +627,7 @@ async fn takeover_emergency_access(emer_id: String, headers: Headers, mut conn:
} ) ) )
}
#[ derive(Deserialize , Debug )]
#[ derive(Deserialize )]
#[ allow(non_snake_case) ]
struct EmergencyAccessPasswordData {
NewMasterPasswordHash : String ,
@ -738,40 +732,44 @@ pub async fn emergency_request_timeout_job(pool: DbPool) {
}
if let Ok ( mut conn ) = pool . get ( ) . await {
let emergency_access_list = EmergencyAccess ::find_all_recoveries ( & mut conn ) . await ;
let emergency_access_list = EmergencyAccess ::find_all_recoveries _initiated ( & mut conn ) . await ;
if emergency_access_list . is_empty ( ) {
debug ! ( "No emergency request timeout to approve" ) ;
}
let now = Utc ::now ( ) . naive_utc ( ) ;
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 ( i64 ::from ( emer . wait_time_days ) )
{
emer . status = EmergencyAccessStatus ::RecoveryApproved as i32 ;
emer . save ( & mut conn ) . await . expect ( "Cannot save emergency access on job" ) ;
// 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" ) ;
if CONFIG . mail_enabled ( ) {
// get grantor user to send Accepted email
let grantor_user =
User ::find_by_uuid ( & emer . grantor_uuid , & mut conn ) . await . expect ( "Grantor user not found . ") ;
User ::find_by_uuid ( & emer . grantor_uuid , & mut conn ) . await . expect ( "Grantor user not found ") ;
// get grantee user to send Accepted email
let grantee_user =
User ::find_by_uuid ( & emer . grantee_uuid . clone ( ) . expect ( "Grantee user invalid . ") , & mut conn )
User ::find_by_uuid ( & emer . grantee_uuid . clone ( ) . expect ( "Grantee user invalid ") , & mut conn )
. await
. expect ( "Grantee user not found . ") ;
. expect ( "Grantee user not found ") ;
mail ::send_emergency_access_recovery_timed_out (
& grantor_user . email ,
& grantee_user . name .clone ( ) ,
& grantee_user . name ,
emer . get_type_as_str ( ) ,
)
. await
. expect ( "Error on sending email" ) ;
mail ::send_emergency_access_recovery_approved ( & grantee_user . email , & grantor_user . name .clone ( ) )
mail ::send_emergency_access_recovery_approved ( & grantee_user . email , & grantor_user . name )
. await
. expect ( "Error on sending email" ) ;
}
@ -789,38 +787,47 @@ pub async fn emergency_notification_reminder_job(pool: DbPool) {
}
if let Ok ( mut conn ) = pool . get ( ) . await {
let emergency_access_list = EmergencyAccess ::find_all_recoveries ( & mut conn ) . await ;
let emergency_access_list = EmergencyAccess ::find_all_recoveries _initiated ( & mut conn ) . await ;
if emergency_access_list . is_empty ( ) {
debug ! ( "No emergency request reminder notification to send" ) ;
}
let now = Utc ::now ( ) . naive_utc ( ) ;
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 ( ( i64 ::from ( emer . wait_time_days ) ) - 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 ( & mut conn ) . await . expect ( "Cannot save emergency access on job" ) ;
// 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" ) ;
if CONFIG . mail_enabled ( ) {
// get grantor user to send Accepted email
let grantor_user =
User ::find_by_uuid ( & emer . grantor_uuid , & mut conn ) . await . expect ( "Grantor user not found." ) ;
User ::find_by_uuid ( & emer . grantor_uuid , & mut conn ) . await . expect ( "Grantor user not found ") ;
// get grantee user to send Accepted email
let grantee_user =
User ::find_by_uuid ( & emer . grantee_uuid . clone ( ) . expect ( "Grantee user invalid . ") , & mut conn )
User ::find_by_uuid ( & emer . grantee_uuid . clone ( ) . expect ( "Grantee user invalid ") , & mut conn )
. await
. expect ( "Grantee user not found . ") ;
. expect ( "Grantee user not found ") ;
mail ::send_emergency_access_recovery_reminder (
& grantor_user . email ,
& grantee_user . name .clone ( ) ,
& grantee_user . name ,
emer . get_type_as_str ( ) ,
& emer . wait_time_days . to_string ( ) , // TODO(jjlin): This should be the number of days left.
"1" , // This notification is only triggered one day before the activation
)
. await
. expect ( "Error on sending email" ) ;