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
|
|
|
use chrono::{Duration, NaiveDateTime, Utc};
|
2018-10-10 20:40:39 +02:00
|
|
|
use serde_json::Value;
|
2018-02-10 01:00:55 +01:00
|
|
|
|
2018-12-07 02:05:45 +01:00
|
|
|
use crate::crypto;
|
|
|
|
use crate::CONFIG;
|
2018-02-10 01:00:55 +01:00
|
|
|
|
2020-08-18 17:15:44 +02:00
|
|
|
db_object! {
|
2021-03-13 22:04:04 +01:00
|
|
|
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
2022-05-20 23:39:47 +02:00
|
|
|
#[diesel(table_name = users)]
|
|
|
|
#[diesel(treat_none_as_null = true)]
|
|
|
|
#[diesel(primary_key(uuid))]
|
2020-08-18 17:15:44 +02:00
|
|
|
pub struct User {
|
|
|
|
pub uuid: String,
|
2020-11-30 23:12:56 +01:00
|
|
|
pub enabled: bool,
|
2020-08-18 17:15:44 +02:00
|
|
|
pub created_at: NaiveDateTime,
|
|
|
|
pub updated_at: NaiveDateTime,
|
|
|
|
pub verified_at: Option<NaiveDateTime>,
|
|
|
|
pub last_verifying_at: Option<NaiveDateTime>,
|
|
|
|
pub login_verify_count: i32,
|
|
|
|
|
|
|
|
pub email: String,
|
|
|
|
pub email_new: Option<String>,
|
|
|
|
pub email_new_token: Option<String>,
|
|
|
|
pub name: String,
|
|
|
|
|
|
|
|
pub password_hash: Vec<u8>,
|
|
|
|
pub salt: Vec<u8>,
|
|
|
|
pub password_iterations: i32,
|
|
|
|
pub password_hint: Option<String>,
|
|
|
|
|
|
|
|
pub akey: String,
|
|
|
|
pub private_key: Option<String>,
|
|
|
|
pub public_key: Option<String>,
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
#[diesel(column_name = "totp_secret")] // Note, this is only added to the UserDb structs, not to User
|
2020-08-18 17:15:44 +02:00
|
|
|
_totp_secret: Option<String>,
|
|
|
|
pub totp_recover: Option<String>,
|
|
|
|
|
|
|
|
pub security_stamp: String,
|
2020-12-14 19:58:23 +01:00
|
|
|
pub stamp_exception: Option<String>,
|
2020-08-18 17:15:44 +02:00
|
|
|
|
|
|
|
pub equivalent_domains: String,
|
|
|
|
pub excluded_globals: String,
|
|
|
|
|
|
|
|
pub client_kdf_type: i32,
|
|
|
|
pub client_kdf_iter: i32,
|
2023-02-01 03:26:23 +01:00
|
|
|
pub client_kdf_memory: Option<i32>,
|
|
|
|
pub client_kdf_parallelism: Option<i32>,
|
2020-08-18 17:15:44 +02:00
|
|
|
|
2022-01-19 11:51:26 +01:00
|
|
|
pub api_key: Option<String>,
|
2023-01-11 21:45:11 +01:00
|
|
|
|
|
|
|
pub avatar_color: Option<String>,
|
2022-01-19 11:51:26 +01:00
|
|
|
}
|
2020-08-18 17:15:44 +02:00
|
|
|
|
2021-03-13 22:04:04 +01:00
|
|
|
#[derive(Identifiable, Queryable, Insertable)]
|
2022-05-20 23:39:47 +02:00
|
|
|
#[diesel(table_name = invitations)]
|
|
|
|
#[diesel(primary_key(email))]
|
2020-08-18 17:15:44 +02:00
|
|
|
pub struct Invitation {
|
|
|
|
pub email: String,
|
|
|
|
}
|
2018-02-10 01:00:55 +01:00
|
|
|
}
|
|
|
|
|
2023-02-01 03:26:23 +01:00
|
|
|
pub enum UserKdfType {
|
|
|
|
Pbkdf2 = 0,
|
|
|
|
Argon2id = 1,
|
|
|
|
}
|
|
|
|
|
2019-04-13 00:01:52 +02:00
|
|
|
enum UserStatus {
|
|
|
|
Enabled = 0,
|
|
|
|
Invited = 1,
|
|
|
|
_Disabled = 2,
|
|
|
|
}
|
|
|
|
|
2020-12-14 19:58:23 +01:00
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct UserStampException {
|
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 routes: Vec<String>,
|
2021-03-31 22:18:35 +02:00
|
|
|
pub security_stamp: String,
|
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 expire: i64,
|
2020-12-14 19:58:23 +01:00
|
|
|
}
|
|
|
|
|
2018-02-10 01:00:55 +01:00
|
|
|
/// Local methods
|
|
|
|
impl User {
|
2023-02-01 03:26:23 +01:00
|
|
|
pub const CLIENT_KDF_TYPE_DEFAULT: i32 = UserKdfType::Pbkdf2 as i32;
|
2023-01-24 13:06:31 +01:00
|
|
|
pub const CLIENT_KDF_ITER_DEFAULT: i32 = 600_000;
|
2018-09-19 17:30:14 +02:00
|
|
|
|
2021-09-09 13:50:18 +02:00
|
|
|
pub fn new(email: String) -> Self {
|
2018-02-10 01:00:55 +01:00
|
|
|
let now = Utc::now().naive_utc();
|
2021-09-09 13:50:18 +02:00
|
|
|
let email = email.to_lowercase();
|
2018-02-10 01:00:55 +01:00
|
|
|
|
2018-02-15 00:40:34 +01:00
|
|
|
Self {
|
2018-12-07 14:32:40 +01:00
|
|
|
uuid: crate::util::get_uuid(),
|
2020-11-30 23:12:56 +01:00
|
|
|
enabled: true,
|
2018-02-10 01:00:55 +01:00
|
|
|
created_at: now,
|
|
|
|
updated_at: now,
|
2019-11-25 06:28:49 +01:00
|
|
|
verified_at: None,
|
|
|
|
last_verifying_at: None,
|
|
|
|
login_verify_count: 0,
|
2018-02-10 01:00:55 +01:00
|
|
|
name: email.clone(),
|
|
|
|
email,
|
2019-05-20 21:12:41 +02:00
|
|
|
akey: String::new(),
|
2019-11-25 06:28:49 +01:00
|
|
|
email_new: None,
|
|
|
|
email_new_token: None,
|
2018-02-10 01:00:55 +01:00
|
|
|
|
2018-09-11 15:25:12 +02:00
|
|
|
password_hash: Vec::new(),
|
2022-11-13 10:03:04 +01:00
|
|
|
salt: crypto::get_random_bytes::<64>().to_vec(),
|
2019-01-25 18:23:51 +01:00
|
|
|
password_iterations: CONFIG.password_iterations(),
|
2018-02-10 01:00:55 +01:00
|
|
|
|
2018-12-07 14:32:40 +01:00
|
|
|
security_stamp: crate::util::get_uuid(),
|
2020-12-14 19:58:23 +01:00
|
|
|
stamp_exception: None,
|
2018-02-10 01:00:55 +01:00
|
|
|
|
|
|
|
password_hint: None,
|
|
|
|
private_key: None,
|
|
|
|
public_key: None,
|
2018-12-30 23:34:31 +01:00
|
|
|
|
2018-07-12 21:46:50 +02:00
|
|
|
_totp_secret: None,
|
2018-02-10 01:00:55 +01:00
|
|
|
totp_recover: None,
|
2018-02-15 00:40:34 +01:00
|
|
|
|
|
|
|
equivalent_domains: "[]".to_string(),
|
|
|
|
excluded_globals: "[]".to_string(),
|
2018-12-30 23:34:31 +01:00
|
|
|
|
2018-09-19 17:30:14 +02:00
|
|
|
client_kdf_type: Self::CLIENT_KDF_TYPE_DEFAULT,
|
|
|
|
client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT,
|
2023-02-01 03:26:23 +01:00
|
|
|
client_kdf_memory: None,
|
|
|
|
client_kdf_parallelism: None,
|
2022-01-19 11:51:26 +01:00
|
|
|
|
|
|
|
api_key: None,
|
2023-01-11 21:45:11 +01:00
|
|
|
|
|
|
|
avatar_color: None,
|
2018-02-10 01:00:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn check_valid_password(&self, password: &str) -> bool {
|
2018-12-30 23:34:31 +01:00
|
|
|
crypto::verify_password_hash(
|
|
|
|
password.as_bytes(),
|
|
|
|
&self.salt,
|
|
|
|
&self.password_hash,
|
|
|
|
self.password_iterations as u32,
|
|
|
|
)
|
2018-02-10 01:00:55 +01:00
|
|
|
}
|
|
|
|
|
2018-02-15 19:05:57 +01:00
|
|
|
pub fn check_valid_recovery_code(&self, recovery_code: &str) -> bool {
|
|
|
|
if let Some(ref totp_recover) = self.totp_recover {
|
2019-02-11 23:45:55 +01:00
|
|
|
crate::crypto::ct_eq(recovery_code, totp_recover.to_lowercase())
|
2018-02-15 19:05:57 +01:00
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-19 11:51:26 +01:00
|
|
|
pub fn check_valid_api_key(&self, key: &str) -> bool {
|
|
|
|
matches!(self.api_key, Some(ref api_key) if crate::crypto::ct_eq(api_key, key))
|
|
|
|
}
|
|
|
|
|
2020-12-14 19:58:23 +01:00
|
|
|
/// Set the password hash generated
|
|
|
|
/// And resets the security_stamp. Based upon the allow_next_route the security_stamp will be different.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `password` - A str which contains a hashed version of the users master password.
|
2023-01-14 10:16:03 +01:00
|
|
|
/// * `new_key` - A String which contains the new aKey value of the users master password.
|
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
|
|
|
/// * `allow_next_route` - A Option<Vec<String>> with the function names of the next allowed (rocket) routes.
|
|
|
|
/// These routes are able to use the previous stamp id for the next 2 minutes.
|
|
|
|
/// After these 2 minutes this stamp will expire.
|
2020-12-14 19:58:23 +01:00
|
|
|
///
|
2023-01-14 10:16:03 +01:00
|
|
|
pub fn set_password(
|
|
|
|
&mut self,
|
|
|
|
password: &str,
|
|
|
|
new_key: Option<String>,
|
|
|
|
reset_security_stamp: bool,
|
|
|
|
allow_next_route: Option<Vec<String>>,
|
|
|
|
) {
|
2018-12-30 23:34:31 +01:00
|
|
|
self.password_hash = crypto::hash_password(password.as_bytes(), &self.salt, self.password_iterations as u32);
|
2020-12-14 19:58:23 +01:00
|
|
|
|
|
|
|
if let Some(route) = allow_next_route {
|
|
|
|
self.set_stamp_exception(route);
|
|
|
|
}
|
|
|
|
|
2023-01-14 10:16:03 +01:00
|
|
|
if let Some(new_key) = new_key {
|
|
|
|
self.akey = new_key;
|
|
|
|
}
|
|
|
|
|
2023-01-24 13:06:31 +01:00
|
|
|
if reset_security_stamp {
|
|
|
|
self.reset_security_stamp()
|
|
|
|
}
|
2018-02-10 01:00:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn reset_security_stamp(&mut self) {
|
2018-12-07 14:32:40 +01:00
|
|
|
self.security_stamp = crate::util::get_uuid();
|
2018-02-10 01:00:55 +01:00
|
|
|
}
|
2020-12-14 19:58:23 +01:00
|
|
|
|
|
|
|
/// Set the stamp_exception to only allow a subsequent request matching a specific route using the current security-stamp.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
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
|
|
|
/// * `route_exception` - A Vec<String> with the function names of the next allowed (rocket) routes.
|
|
|
|
/// These routes are able to use the previous stamp id for the next 2 minutes.
|
|
|
|
/// After these 2 minutes this stamp will expire.
|
2020-12-14 19:58:23 +01: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
|
|
|
pub fn set_stamp_exception(&mut self, route_exception: Vec<String>) {
|
2020-12-14 19:58:23 +01:00
|
|
|
let stamp_exception = UserStampException {
|
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
|
|
|
routes: route_exception,
|
2022-07-10 16:39:38 +02:00
|
|
|
security_stamp: self.security_stamp.clone(),
|
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
|
|
|
expire: (Utc::now().naive_utc() + Duration::minutes(2)).timestamp(),
|
2020-12-14 19:58:23 +01:00
|
|
|
};
|
|
|
|
self.stamp_exception = Some(serde_json::to_string(&stamp_exception).unwrap_or_default());
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Resets the stamp_exception to prevent re-use of the previous security-stamp
|
|
|
|
pub fn reset_stamp_exception(&mut self) {
|
|
|
|
self.stamp_exception = None;
|
|
|
|
}
|
2018-04-24 22:01:55 +02:00
|
|
|
}
|
|
|
|
|
2021-10-25 10:36:05 +02:00
|
|
|
use super::{
|
|
|
|
Cipher, Device, EmergencyAccess, Favorite, Folder, Send, TwoFactor, TwoFactorIncomplete, UserOrgType,
|
|
|
|
UserOrganization,
|
|
|
|
};
|
2018-12-30 23:34:31 +01:00
|
|
|
use crate::db::DbConn;
|
2018-04-24 22:01:55 +02:00
|
|
|
|
2018-12-19 21:52:53 +01:00
|
|
|
use crate::api::EmptyResult;
|
|
|
|
use crate::error::MapResult;
|
|
|
|
|
2018-04-24 22:01:55 +02:00
|
|
|
/// Database methods
|
|
|
|
impl User {
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn to_json(&self, conn: &mut DbConn) -> Value {
|
|
|
|
let mut orgs_json = Vec::new();
|
|
|
|
for c in UserOrganization::find_confirmed_by_user(&self.uuid, conn).await {
|
|
|
|
orgs_json.push(c.to_json(conn).await);
|
|
|
|
}
|
2021-11-16 17:07:55 +01:00
|
|
|
|
|
|
|
let twofactor_enabled = !TwoFactor::find_by_user(&self.uuid, conn).await.is_empty();
|
2018-07-12 21:46:50 +02:00
|
|
|
|
2019-04-13 00:01:52 +02:00
|
|
|
// TODO: Might want to save the status field in the DB
|
|
|
|
let status = if self.password_hash.is_empty() {
|
|
|
|
UserStatus::Invited
|
|
|
|
} else {
|
|
|
|
UserStatus::Enabled
|
|
|
|
};
|
|
|
|
|
2018-02-10 01:00:55 +01:00
|
|
|
json!({
|
2019-04-13 00:01:52 +02:00
|
|
|
"_Status": status as i32,
|
2018-02-10 01:00:55 +01:00
|
|
|
"Id": self.uuid,
|
|
|
|
"Name": self.name,
|
|
|
|
"Email": self.email,
|
2019-11-25 06:28:49 +01:00
|
|
|
"EmailVerified": !CONFIG.mail_enabled() || self.verified_at.is_some(),
|
2018-02-10 01:00:55 +01:00
|
|
|
"Premium": true,
|
|
|
|
"MasterPasswordHint": self.password_hint,
|
|
|
|
"Culture": "en-US",
|
2018-07-12 21:46:50 +02:00
|
|
|
"TwoFactorEnabled": twofactor_enabled,
|
2019-05-20 21:12:41 +02:00
|
|
|
"Key": self.akey,
|
2018-02-10 01:00:55 +01:00
|
|
|
"PrivateKey": self.private_key,
|
|
|
|
"SecurityStamp": self.security_stamp,
|
2018-04-24 22:01:55 +02:00
|
|
|
"Organizations": orgs_json,
|
2021-08-21 10:36:08 +02:00
|
|
|
"Providers": [],
|
|
|
|
"ProviderOrganizations": [],
|
|
|
|
"ForcePasswordReset": false,
|
2023-01-11 21:45:11 +01:00
|
|
|
"AvatarColor": self.avatar_color,
|
2021-08-21 10:36:08 +02:00
|
|
|
"Object": "profile",
|
2018-02-10 01:00:55 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
2019-09-12 22:12:22 +02:00
|
|
|
if self.email.trim().is_empty() {
|
|
|
|
err!("User email can't be empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
self.updated_at = Utc::now().naive_utc();
|
|
|
|
|
2020-08-18 17:15:44 +02:00
|
|
|
db_run! {conn:
|
|
|
|
sqlite, mysql {
|
2020-09-22 12:13:02 +02:00
|
|
|
match diesel::replace_into(users::table)
|
|
|
|
.values(UserDb::to_db(self))
|
2020-08-18 17:15:44 +02:00
|
|
|
.execute(conn)
|
2020-09-22 12:13:02 +02:00
|
|
|
{
|
|
|
|
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(users::table)
|
|
|
|
.filter(users::uuid.eq(&self.uuid))
|
|
|
|
.set(UserDb::to_db(self))
|
|
|
|
.execute(conn)
|
|
|
|
.map_res("Error saving user")
|
|
|
|
}
|
|
|
|
Err(e) => Err(e.into()),
|
|
|
|
}.map_res("Error saving user")
|
2020-08-18 17:15:44 +02:00
|
|
|
}
|
|
|
|
postgresql {
|
|
|
|
let value = UserDb::to_db(self);
|
|
|
|
diesel::insert_into(users::table) // Insert or update
|
|
|
|
.values(&value)
|
|
|
|
.on_conflict(users::uuid)
|
|
|
|
.do_update()
|
|
|
|
.set(&value)
|
|
|
|
.execute(conn)
|
|
|
|
.map_res("Error saving user")
|
|
|
|
}
|
2019-01-22 17:26:17 +01:00
|
|
|
}
|
2018-02-10 01:00:55 +01:00
|
|
|
}
|
|
|
|
|
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
|
|
|
for user_org in UserOrganization::find_confirmed_by_user(&self.uuid, conn).await {
|
2022-08-20 16:42:36 +02:00
|
|
|
if user_org.atype == UserOrgType::Owner
|
|
|
|
&& UserOrganization::count_confirmed_by_org_and_type(&user_org.org_uuid, UserOrgType::Owner, conn).await
|
|
|
|
<= 1
|
|
|
|
{
|
|
|
|
err!("Can't delete last owner")
|
2018-10-12 16:20:10 +02:00
|
|
|
}
|
2018-02-15 19:05:57 +01:00
|
|
|
}
|
2018-10-12 16:20:10 +02:00
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
Send::delete_all_by_user(&self.uuid, conn).await?;
|
|
|
|
EmergencyAccess::delete_all_by_user(&self.uuid, conn).await?;
|
|
|
|
UserOrganization::delete_all_by_user(&self.uuid, conn).await?;
|
|
|
|
Cipher::delete_all_by_user(&self.uuid, conn).await?;
|
|
|
|
Favorite::delete_all_by_user(&self.uuid, conn).await?;
|
|
|
|
Folder::delete_all_by_user(&self.uuid, conn).await?;
|
|
|
|
Device::delete_all_by_user(&self.uuid, conn).await?;
|
|
|
|
TwoFactor::delete_all_by_user(&self.uuid, conn).await?;
|
|
|
|
TwoFactorIncomplete::delete_all_by_user(&self.uuid, conn).await?;
|
|
|
|
Invitation::take(&self.email, conn).await; // Delete invitation if any
|
2020-08-18 17:15:44 +02:00
|
|
|
|
|
|
|
db_run! {conn: {
|
|
|
|
diesel::delete(users::table.filter(users::uuid.eq(self.uuid)))
|
|
|
|
.execute(conn)
|
|
|
|
.map_res("Error deleting user")
|
|
|
|
}}
|
2018-02-15 19:05:57 +01:00
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn update_uuid_revision(uuid: &str, conn: &mut DbConn) {
|
2021-11-16 17:07:55 +01:00
|
|
|
if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await {
|
2019-02-08 18:45:07 +01:00
|
|
|
warn!("Failed to update revision for {}: {:#?}", uuid, e);
|
|
|
|
}
|
2018-08-21 13:20:55 +02:00
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn update_all_revisions(conn: &mut DbConn) -> EmptyResult {
|
2019-03-07 21:08:33 +01:00
|
|
|
let updated_at = Utc::now().naive_utc();
|
|
|
|
|
2020-08-18 17:15:44 +02:00
|
|
|
db_run! {conn: {
|
|
|
|
crate::util::retry(|| {
|
2019-03-07 21:08:33 +01:00
|
|
|
diesel::update(users::table)
|
|
|
|
.set(users::updated_at.eq(updated_at))
|
2020-08-18 17:15:44 +02:00
|
|
|
.execute(conn)
|
|
|
|
}, 10)
|
|
|
|
.map_res("Error updating revision date for all users")
|
|
|
|
}}
|
2019-03-07 21:08:33 +01:00
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn update_revision(&mut self, conn: &mut DbConn) -> EmptyResult {
|
2018-08-21 11:36:04 +02:00
|
|
|
self.updated_at = Utc::now().naive_utc();
|
2019-02-08 18:45:07 +01:00
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
Self::_update_revision(&self.uuid, &self.updated_at, conn).await
|
2019-02-08 18:45:07 +01:00
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &mut DbConn) -> EmptyResult {
|
2020-08-18 17:15:44 +02:00
|
|
|
db_run! {conn: {
|
|
|
|
crate::util::retry(|| {
|
2019-02-08 18:45:07 +01:00
|
|
|
diesel::update(users::table.filter(users::uuid.eq(uuid)))
|
|
|
|
.set(users::updated_at.eq(date))
|
2020-08-18 17:15:44 +02:00
|
|
|
.execute(conn)
|
|
|
|
}, 10)
|
|
|
|
.map_res("Error updating user revision")
|
|
|
|
}}
|
2018-08-13 11:58:39 +02:00
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn find_by_mail(mail: &str, conn: &mut DbConn) -> Option<Self> {
|
2018-02-10 01:00:55 +01:00
|
|
|
let lower_mail = mail.to_lowercase();
|
2020-08-18 17:15:44 +02:00
|
|
|
db_run! {conn: {
|
|
|
|
users::table
|
|
|
|
.filter(users::email.eq(lower_mail))
|
|
|
|
.first::<UserDb>(conn)
|
|
|
|
.ok()
|
|
|
|
.from_db()
|
|
|
|
}}
|
2018-02-10 01:00:55 +01:00
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> {
|
2020-08-18 17:15:44 +02:00
|
|
|
db_run! {conn: {
|
|
|
|
users::table.filter(users::uuid.eq(uuid)).first::<UserDb>(conn).ok().from_db()
|
|
|
|
}}
|
2018-02-10 01:00:55 +01:00
|
|
|
}
|
2018-10-12 16:20:10 +02:00
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn get_all(conn: &mut DbConn) -> Vec<Self> {
|
2020-08-18 17:15:44 +02:00
|
|
|
db_run! {conn: {
|
|
|
|
users::table.load::<UserDb>(conn).expect("Error loading users").from_db()
|
|
|
|
}}
|
2018-10-12 16:20:10 +02:00
|
|
|
}
|
2020-11-30 22:00:51 +01:00
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn last_active(&self, conn: &mut DbConn) -> Option<NaiveDateTime> {
|
2021-11-16 17:07:55 +01:00
|
|
|
match Device::find_latest_active_by_user(&self.uuid, conn).await {
|
2020-11-30 22:00:51 +01:00
|
|
|
Some(device) => Some(device.updated_at),
|
2021-03-31 22:18:35 +02:00
|
|
|
None => None,
|
2020-12-14 19:58:23 +01:00
|
|
|
}
|
2020-11-30 22:00:51 +01:00
|
|
|
}
|
2018-02-10 01:00:55 +01:00
|
|
|
}
|
2018-09-10 15:51:40 +02:00
|
|
|
|
|
|
|
impl Invitation {
|
2022-11-26 19:07:28 +01:00
|
|
|
pub fn new(email: &str) -> Self {
|
2021-09-09 13:50:18 +02:00
|
|
|
let email = email.to_lowercase();
|
2021-04-06 22:54:42 +02:00
|
|
|
Self {
|
|
|
|
email,
|
|
|
|
}
|
2018-09-10 15:51:40 +02:00
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult {
|
2019-09-12 22:12:22 +02:00
|
|
|
if self.email.trim().is_empty() {
|
|
|
|
err!("Invitation email can't be empty")
|
|
|
|
}
|
|
|
|
|
2020-08-18 17:15:44 +02:00
|
|
|
db_run! {conn:
|
|
|
|
sqlite, mysql {
|
2020-09-22 12:13:02 +02:00
|
|
|
// Not checking for ForeignKey Constraints here
|
|
|
|
// Table invitations does not have any ForeignKey Constraints.
|
2020-08-18 17:15:44 +02:00
|
|
|
diesel::replace_into(invitations::table)
|
|
|
|
.values(InvitationDb::to_db(self))
|
|
|
|
.execute(conn)
|
2020-09-22 12:13:02 +02:00
|
|
|
.map_res("Error saving invitation")
|
2020-08-18 17:15:44 +02:00
|
|
|
}
|
|
|
|
postgresql {
|
|
|
|
diesel::insert_into(invitations::table)
|
|
|
|
.values(InvitationDb::to_db(self))
|
|
|
|
.on_conflict(invitations::email)
|
|
|
|
.do_nothing()
|
|
|
|
.execute(conn)
|
2020-09-22 12:13:02 +02:00
|
|
|
.map_res("Error saving invitation")
|
2020-08-18 17:15:44 +02:00
|
|
|
}
|
2019-01-22 17:26:17 +01:00
|
|
|
}
|
2018-09-10 15:51:40 +02:00
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
|
2020-08-18 17:15:44 +02:00
|
|
|
db_run! {conn: {
|
|
|
|
diesel::delete(invitations::table.filter(invitations::email.eq(self.email)))
|
|
|
|
.execute(conn)
|
|
|
|
.map_res("Error deleting invitation")
|
|
|
|
}}
|
2018-09-10 15:51:40 +02:00
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn find_by_mail(mail: &str, conn: &mut DbConn) -> Option<Self> {
|
2018-09-10 15:51:40 +02:00
|
|
|
let lower_mail = mail.to_lowercase();
|
2020-08-18 17:15:44 +02:00
|
|
|
db_run! {conn: {
|
|
|
|
invitations::table
|
|
|
|
.filter(invitations::email.eq(lower_mail))
|
|
|
|
.first::<InvitationDb>(conn)
|
|
|
|
.ok()
|
|
|
|
.from_db()
|
|
|
|
}}
|
2018-09-10 15:51:40 +02:00
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
pub async fn take(mail: &str, conn: &mut DbConn) -> bool {
|
2021-11-16 17:07:55 +01:00
|
|
|
match Self::find_by_mail(mail, conn).await {
|
|
|
|
Some(invitation) => invitation.delete(conn).await.is_ok(),
|
2020-02-16 21:28:50 +01:00
|
|
|
None => false,
|
|
|
|
}
|
2018-09-10 15:51:40 +02:00
|
|
|
}
|
2018-12-30 23:34:31 +01:00
|
|
|
}
|