2021-03-24 20:15:55 +01:00
|
|
|
use chrono::{NaiveDateTime, Utc};
|
|
|
|
use serde_json::Value;
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
use crate::{api::EmptyResult, db::DbConn, error::MapResult};
|
|
|
|
|
2021-03-24 20:15:55 +01:00
|
|
|
use super::User;
|
|
|
|
|
|
|
|
db_object! {
|
2022-11-26 19:07:28 +01:00
|
|
|
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
2022-05-20 23:39:47 +02:00
|
|
|
#[diesel(table_name = emergency_access)]
|
|
|
|
#[diesel(treat_none_as_null = true)]
|
|
|
|
#[diesel(primary_key(uuid))]
|
2021-03-24 20:15:55 +01:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-11 18:58:25 +02:00
|
|
|
// Local methods
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
impl EmergencyAccess {
|
2022-11-26 19:07:28 +01:00
|
|
|
pub fn new(grantor_uuid: String, email: String, status: i32, atype: i32, wait_time_days: i32) -> Self {
|
2021-09-01 12:54:47 +02:00
|
|
|
let now = Utc::now().naive_utc();
|
|
|
|
|
2021-03-24 20:15:55 +01:00
|
|
|
Self {
|
|
|
|
uuid: crate::util::get_uuid(),
|
|
|
|
grantor_uuid,
|
|
|
|
grantee_uuid: None,
|
2022-11-26 19:07:28 +01:00
|
|
|
email: Some(email),
|
2021-03-24 20:15:55 +01:00
|
|
|
status,
|
|
|
|
atype,
|
|
|
|
wait_time_days,
|
|
|
|
recovery_initiated_at: None,
|
2021-09-01 12:54:47 +02:00
|
|
|
created_at: now,
|
|
|
|
updated_at: now,
|
2021-03-24 20:15:55 +01:00
|
|
|
key_encrypted: None,
|
|
|
|
last_notification_at: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-19 10:27:50 +02:00
|
|
|
pub fn get_type_as_str(&self) -> &'static str {
|
2021-03-24 20:15:55 +01:00
|
|
|
if self.atype == EmergencyAccessType::View as i32 {
|
|
|
|
"View"
|
|
|
|
} else {
|
|
|
|
"Takeover"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn to_json(&self) -> Value {
|
|
|
|
json!({
|
2024-06-23 21:31:02 +02:00
|
|
|
"id": self.uuid,
|
|
|
|
"status": self.status,
|
|
|
|
"type": self.atype,
|
|
|
|
"waitTimeDays": self.wait_time_days,
|
|
|
|
"object": "emergencyAccess",
|
2021-03-24 20:15:55 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn to_json_grantor_details(&self, conn: &mut DbConn) -> Value {
|
2021-11-16 17:07:55 +01:00
|
|
|
let grantor_user = User::find_by_uuid(&self.grantor_uuid, conn).await.expect("Grantor user not found.");
|
2021-10-19 10:27:50 +02:00
|
|
|
|
2021-03-24 20:15:55 +01:00
|
|
|
json!({
|
2024-06-23 21:31:02 +02:00
|
|
|
"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",
|
2021-10-19 10:27:50 +02:00
|
|
|
})
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
|
2024-04-27 22:16:05 +02:00
|
|
|
pub async fn to_json_grantee_details(&self, conn: &mut DbConn) -> Option<Value> {
|
2021-10-19 10:27:50 +02:00
|
|
|
let grantee_user = if let Some(grantee_uuid) = self.grantee_uuid.as_deref() {
|
2024-04-27 22:16:05 +02:00
|
|
|
User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found.")
|
2021-10-19 10:27:50 +02:00
|
|
|
} else if let Some(email) = self.email.as_deref() {
|
2024-04-27 22:16:05 +02:00
|
|
|
match User::find_by_mail(email, conn).await {
|
|
|
|
Some(user) => user,
|
|
|
|
None => {
|
|
|
|
// remove outstanding invitations which should not exist
|
2024-09-23 20:25:32 +02:00
|
|
|
Self::delete_all_by_grantee_email(email, conn).await.ok();
|
2024-04-27 22:16:05 +02:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|
2021-03-24 20:15:55 +01:00
|
|
|
} else {
|
2024-04-27 22:16:05 +02:00
|
|
|
return None;
|
2021-10-19 10:27:50 +02:00
|
|
|
};
|
|
|
|
|
2024-04-27 22:16:05 +02:00
|
|
|
Some(json!({
|
2024-06-23 21:31:02 +02:00
|
|
|
"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",
|
2024-04-27 22:16:05 +02:00
|
|
|
}))
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
#[derive(Copy, Clone)]
|
2021-03-24 20:15:55 +01:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub enum EmergencyAccessStatus {
|
|
|
|
Invited = 0,
|
|
|
|
Accepted = 1,
|
|
|
|
Confirmed = 2,
|
|
|
|
RecoveryInitiated = 3,
|
|
|
|
RecoveryApproved = 4,
|
|
|
|
}
|
|
|
|
|
|
|
|
// region Database methods
|
|
|
|
|
|
|
|
impl EmergencyAccess {
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
2021-11-16 17:07:55 +01:00
|
|
|
User::update_uuid_revision(&self.grantor_uuid, conn).await;
|
2021-03-24 20:15:55 +01:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
pub async fn update_access_status_and_save(
|
|
|
|
&mut self,
|
|
|
|
status: i32,
|
|
|
|
date: &NaiveDateTime,
|
|
|
|
conn: &mut DbConn,
|
|
|
|
) -> EmptyResult {
|
|
|
|
// Update the grantee so that it will refresh it's status.
|
|
|
|
User::update_uuid_revision(self.grantee_uuid.as_ref().expect("Error getting grantee"), conn).await;
|
|
|
|
self.status = status;
|
2024-04-06 13:55:10 +02:00
|
|
|
date.clone_into(&mut self.updated_at);
|
2022-11-26 19:07:28 +01:00
|
|
|
|
|
|
|
db_run! {conn: {
|
|
|
|
crate::util::retry(|| {
|
|
|
|
diesel::update(emergency_access::table.filter(emergency_access::uuid.eq(&self.uuid)))
|
|
|
|
.set((emergency_access::status.eq(status), emergency_access::updated_at.eq(date)))
|
|
|
|
.execute(conn)
|
|
|
|
}, 10)
|
|
|
|
.map_res("Error updating emergency access status")
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn update_last_notification_date_and_save(
|
|
|
|
&mut self,
|
|
|
|
date: &NaiveDateTime,
|
|
|
|
conn: &mut DbConn,
|
|
|
|
) -> EmptyResult {
|
|
|
|
self.last_notification_at = Some(date.to_owned());
|
2024-04-06 13:55:10 +02:00
|
|
|
date.clone_into(&mut self.updated_at);
|
2022-11-26 19:07:28 +01:00
|
|
|
|
|
|
|
db_run! {conn: {
|
|
|
|
crate::util::retry(|| {
|
|
|
|
diesel::update(emergency_access::table.filter(emergency_access::uuid.eq(&self.uuid)))
|
|
|
|
.set((emergency_access::last_notification_at.eq(date), emergency_access::updated_at.eq(date)))
|
|
|
|
.execute(conn)
|
|
|
|
}, 10)
|
|
|
|
.map_res("Error updating emergency access status")
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult {
|
2021-11-16 17:07:55 +01:00
|
|
|
for ea in Self::find_all_by_grantor_uuid(user_uuid, conn).await {
|
|
|
|
ea.delete(conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
2021-11-16 17:07:55 +01:00
|
|
|
for ea in Self::find_all_by_grantee_uuid(user_uuid, conn).await {
|
|
|
|
ea.delete(conn).await?;
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-04-27 22:16:05 +02:00
|
|
|
pub async fn delete_all_by_grantee_email(grantee_email: &str, conn: &mut DbConn) -> EmptyResult {
|
|
|
|
for ea in Self::find_all_invited_by_grantee_email(grantee_email, conn).await {
|
|
|
|
ea.delete(conn).await?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
|
2021-11-16 17:07:55 +01:00
|
|
|
User::update_uuid_revision(&self.grantor_uuid, conn).await;
|
2021-03-24 20:15:55 +01:00
|
|
|
|
|
|
|
db_run! { conn: {
|
|
|
|
diesel::delete(emergency_access::table.filter(emergency_access::uuid.eq(self.uuid)))
|
|
|
|
.execute(conn)
|
2021-10-19 10:27:50 +02:00
|
|
|
.map_res("Error removing user from emergency access")
|
2021-03-24 20:15:55 +01:00
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
pub async fn find_by_grantor_uuid_and_grantee_uuid_or_email(
|
2021-03-24 20:15:55 +01:00
|
|
|
grantor_uuid: &str,
|
|
|
|
grantee_uuid: &str,
|
|
|
|
email: &str,
|
2022-05-20 23:39:47 +02:00
|
|
|
conn: &mut DbConn,
|
2021-03-24 20:15:55 +01:00
|
|
|
) -> 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()
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2022-11-26 19:07:28 +01:00
|
|
|
pub async fn find_all_recoveries_initiated(conn: &mut DbConn) -> Vec<Self> {
|
2021-03-24 20:15:55 +01:00
|
|
|
db_run! { conn: {
|
|
|
|
emergency_access::table
|
|
|
|
.filter(emergency_access::status.eq(EmergencyAccessStatus::RecoveryInitiated as i32))
|
2022-11-26 19:07:28 +01:00
|
|
|
.filter(emergency_access::recovery_initiated_at.is_not_null())
|
2021-03-24 20:15:55 +01:00
|
|
|
.load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db()
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn find_by_uuid_and_grantor_uuid(uuid: &str, grantor_uuid: &str, conn: &mut DbConn) -> Option<Self> {
|
2021-03-24 20:15:55 +01:00
|
|
|
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()
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2024-07-08 23:39:22 +02:00
|
|
|
pub async fn find_by_uuid_and_grantee_uuid(uuid: &str, grantee_uuid: &str, conn: &mut DbConn) -> Option<Self> {
|
|
|
|
db_run! { conn: {
|
|
|
|
emergency_access::table
|
|
|
|
.filter(emergency_access::uuid.eq(uuid))
|
|
|
|
.filter(emergency_access::grantee_uuid.eq(grantee_uuid))
|
|
|
|
.first::<EmergencyAccessDb>(conn)
|
|
|
|
.ok().from_db()
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn find_by_uuid_and_grantee_email(uuid: &str, grantee_email: &str, conn: &mut DbConn) -> Option<Self> {
|
|
|
|
db_run! { conn: {
|
|
|
|
emergency_access::table
|
|
|
|
.filter(emergency_access::uuid.eq(uuid))
|
|
|
|
.filter(emergency_access::email.eq(grantee_email))
|
|
|
|
.first::<EmergencyAccessDb>(conn)
|
|
|
|
.ok().from_db()
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn find_all_by_grantee_uuid(grantee_uuid: &str, conn: &mut DbConn) -> Vec<Self> {
|
2021-03-24 20:15:55 +01:00
|
|
|
db_run! { conn: {
|
|
|
|
emergency_access::table
|
|
|
|
.filter(emergency_access::grantee_uuid.eq(grantee_uuid))
|
|
|
|
.load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db()
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn find_invited_by_grantee_email(grantee_email: &str, conn: &mut DbConn) -> Option<Self> {
|
2021-03-24 20:15:55 +01:00
|
|
|
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()
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2024-04-27 22:16:05 +02:00
|
|
|
pub async fn find_all_invited_by_grantee_email(grantee_email: &str, conn: &mut DbConn) -> Vec<Self> {
|
|
|
|
db_run! { conn: {
|
|
|
|
emergency_access::table
|
|
|
|
.filter(emergency_access::email.eq(grantee_email))
|
|
|
|
.filter(emergency_access::status.eq(EmergencyAccessStatus::Invited as i32))
|
|
|
|
.load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db()
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn find_all_by_grantor_uuid(grantor_uuid: &str, conn: &mut DbConn) -> Vec<Self> {
|
2021-03-24 20:15:55 +01:00
|
|
|
db_run! { conn: {
|
|
|
|
emergency_access::table
|
|
|
|
.filter(emergency_access::grantor_uuid.eq(grantor_uuid))
|
|
|
|
.load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db()
|
|
|
|
}}
|
|
|
|
}
|
2024-04-27 22:16:05 +02:00
|
|
|
|
|
|
|
pub async fn accept_invite(&mut self, grantee_uuid: &str, grantee_email: &str, conn: &mut DbConn) -> EmptyResult {
|
|
|
|
if self.email.is_none() || self.email.as_ref().unwrap() != grantee_email {
|
|
|
|
err!("User email does not match invite.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.status == EmergencyAccessStatus::Accepted as i32 {
|
|
|
|
err!("Emergency contact already accepted.");
|
|
|
|
}
|
|
|
|
|
|
|
|
self.status = EmergencyAccessStatus::Accepted as i32;
|
|
|
|
self.grantee_uuid = Some(String::from(grantee_uuid));
|
|
|
|
self.email = None;
|
|
|
|
self.save(conn).await
|
|
|
|
}
|
2021-03-24 20:15:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// endregion
|