From 16afe108985740cfdf6fa5d82f5fabd61322b082 Mon Sep 17 00:00:00 2001 From: Stefan Melmuk Date: Sat, 21 Dec 2024 11:41:24 +0100 Subject: [PATCH] introduce user_id newtype --- src/api/admin.rs | 34 ++++---- src/api/core/accounts.rs | 12 +-- src/api/core/ciphers.rs | 6 +- src/api/core/emergency_access.rs | 2 +- src/api/core/events.rs | 14 ++-- src/api/core/organizations.rs | 28 +++++-- src/api/core/sends.rs | 2 +- src/api/core/two_factor/authenticator.rs | 8 +- src/api/core/two_factor/duo.rs | 6 +- src/api/core/two_factor/email.rs | 8 +- src/api/core/two_factor/protected_actions.rs | 4 +- src/api/core/two_factor/webauthn.rs | 14 ++-- src/api/identity.rs | 13 +-- src/api/notifications.rs | 55 ++++++------ src/api/push.rs | 10 +-- src/auth.rs | 4 +- src/db/models/attachment.rs | 8 +- src/db/models/auth_request.rs | 10 +-- src/db/models/cipher.rs | 47 ++++++----- src/db/models/collection.rs | 28 +++---- src/db/models/device.rs | 17 ++-- src/db/models/emergency_access.rs | 33 ++++---- src/db/models/event.rs | 4 +- src/db/models/favorite.rs | 12 +-- src/db/models/folder.rs | 14 ++-- src/db/models/group.rs | 8 +- src/db/models/mod.rs | 2 +- src/db/models/org_policy.rs | 14 ++-- src/db/models/organization.rs | 36 ++++---- src/db/models/send.rs | 14 ++-- src/db/models/two_factor.rs | 11 +-- src/db/models/two_factor_incomplete.rs | 20 +++-- src/db/models/user.rs | 88 ++++++++++++++++---- src/mail.rs | 22 ++--- 34 files changed, 351 insertions(+), 257 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index 4cb5ffc0..673af545 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -280,7 +280,7 @@ struct InviteData { email: String, } -async fn get_user_or_404(uuid: &str, conn: &mut DbConn) -> ApiResult { +async fn get_user_or_404(uuid: &UserId, conn: &mut DbConn) -> ApiResult { if let Some(user) = User::find_by_uuid(uuid, conn).await { Ok(user) } else { @@ -382,8 +382,8 @@ async fn get_user_by_mail_json(mail: &str, _token: AdminToken, mut conn: DbConn) } #[get("/users/")] -async fn get_user_json(uuid: &str, _token: AdminToken, mut conn: DbConn) -> JsonResult { - let u = get_user_or_404(uuid, &mut conn).await?; +async fn get_user_json(uuid: UserId, _token: AdminToken, mut conn: DbConn) -> JsonResult { + let u = get_user_or_404(&uuid, &mut conn).await?; let mut usr = u.to_json(&mut conn).await; usr["userEnabled"] = json!(u.enabled); usr["createdAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); @@ -391,11 +391,11 @@ async fn get_user_json(uuid: &str, _token: AdminToken, mut conn: DbConn) -> Json } #[post("/users//delete")] -async fn delete_user(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyResult { - let user = get_user_or_404(uuid, &mut conn).await?; +async fn delete_user(uuid: UserId, token: AdminToken, mut conn: DbConn) -> EmptyResult { + let user = get_user_or_404(&uuid, &mut conn).await?; // Get the membership records before deleting the actual user - let memberships = Membership::find_any_state_by_user(uuid, &mut conn).await; + let memberships = Membership::find_any_state_by_user(&uuid, &mut conn).await; let res = user.delete(&mut conn).await; for membership in memberships { @@ -415,8 +415,8 @@ async fn delete_user(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyRe } #[post("/users//deauth")] -async fn deauth_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { - let mut user = get_user_or_404(uuid, &mut conn).await?; +async fn deauth_user(uuid: UserId, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { + let mut user = get_user_or_404(&uuid, &mut conn).await?; nt.send_logout(&user, None).await; @@ -436,8 +436,8 @@ async fn deauth_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Notif } #[post("/users//disable")] -async fn disable_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { - let mut user = get_user_or_404(uuid, &mut conn).await?; +async fn disable_user(uuid: UserId, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { + let mut user = get_user_or_404(&uuid, &mut conn).await?; Device::delete_all_by_user(&user.uuid, &mut conn).await?; user.reset_security_stamp(); user.enabled = false; @@ -450,16 +450,16 @@ async fn disable_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Noti } #[post("/users//enable")] -async fn enable_user(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult { - let mut user = get_user_or_404(uuid, &mut conn).await?; +async fn enable_user(uuid: UserId, _token: AdminToken, mut conn: DbConn) -> EmptyResult { + let mut user = get_user_or_404(&uuid, &mut conn).await?; user.enabled = true; user.save(&mut conn).await } #[post("/users//remove-2fa")] -async fn remove_2fa(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyResult { - let mut user = get_user_or_404(uuid, &mut conn).await?; +async fn remove_2fa(uuid: UserId, token: AdminToken, mut conn: DbConn) -> EmptyResult { + let mut user = get_user_or_404(&uuid, &mut conn).await?; TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?; two_factor::enforce_2fa_policy(&user, ACTING_ADMIN_USER, 14, &token.ip.ip, &mut conn).await?; user.totp_recover = None; @@ -467,8 +467,8 @@ async fn remove_2fa(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyRes } #[post("/users//invite/resend")] -async fn resend_user_invite(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult { - if let Some(user) = User::find_by_uuid(uuid, &mut conn).await { +async fn resend_user_invite(uuid: UserId, _token: AdminToken, mut conn: DbConn) -> EmptyResult { + if let Some(user) = User::find_by_uuid(&uuid, &mut conn).await { //TODO: replace this with user.status check when it will be available (PR#3397) if !user.password_hash.is_empty() { err_code!("User already accepted invitation", Status::BadRequest.code); @@ -487,7 +487,7 @@ async fn resend_user_invite(uuid: &str, _token: AdminToken, mut conn: DbConn) -> #[derive(Debug, Deserialize)] struct MembershipTypeData { user_type: NumberOrString, - user_uuid: String, + user_uuid: UserId, org_uuid: OrganizationId, } diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 26986c64..38dc657b 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -306,8 +306,8 @@ async fn put_avatar(data: Json, headers: Headers, mut conn: DbConn) } #[get("/users//public-key")] -async fn get_public_keys(uuid: &str, _headers: Headers, mut conn: DbConn) -> JsonResult { - let user = match User::find_by_uuid(uuid, &mut conn).await { +async fn get_public_keys(uuid: UserId, _headers: Headers, mut conn: DbConn) -> JsonResult { + let user = match User::find_by_uuid(&uuid, &mut conn).await { Some(user) if user.public_key.is_some() => user, Some(_) => err_code!("User has no public_key", Status::NotFound.code), None => err_code!("User doesn't exist", Status::NotFound.code), @@ -793,7 +793,7 @@ async fn post_verify_email(headers: Headers) -> EmptyResult { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct VerifyEmailTokenData { - user_id: String, + user_id: UserId, token: String, } @@ -808,7 +808,7 @@ async fn post_verify_email_token(data: Json, mut conn: DbC let Ok(claims) = decode_verify_email(&data.token) else { err!("Invalid claim") }; - if claims.sub != user.uuid { + if claims.sub != *user.uuid { err!("Invalid claim"); } user.verified_at = Some(Utc::now().naive_utc()); @@ -850,7 +850,7 @@ async fn post_delete_recover(data: Json, mut conn: DbConn) -> #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct DeleteRecoverTokenData { - user_id: String, + user_id: UserId, token: String, } @@ -866,7 +866,7 @@ async fn post_delete_recover_token(data: Json, mut conn: err!("User doesn't exist") }; - if claims.sub != user.uuid { + if claims.sub != *user.uuid { err!("Invalid claim"); } user.delete(&mut conn).await diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 8558fef7..21dbf08c 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -771,7 +771,7 @@ async fn post_collections_update( let posted_collections = HashSet::::from_iter(data.collection_ids); let current_collections = - HashSet::::from_iter(cipher.get_collections(headers.user.uuid.clone(), &mut conn).await); + HashSet::::from_iter(cipher.get_collections(headers.user.uuid.to_string(), &mut conn).await); for collection in posted_collections.symmetric_difference(¤t_collections) { match Collection::find_by_uuid_and_org(collection, cipher.organization_uuid.as_ref().unwrap(), &mut conn).await @@ -848,7 +848,7 @@ async fn post_collections_admin( let posted_collections = HashSet::::from_iter(data.collection_ids); let current_collections = - HashSet::::from_iter(cipher.get_admin_collections(headers.user.uuid.clone(), &mut conn).await); + HashSet::::from_iter(cipher.get_admin_collections(headers.user.uuid.to_string(), &mut conn).await); for collection in posted_collections.symmetric_difference(¤t_collections) { match Collection::find_by_uuid_and_org(collection, cipher.organization_uuid.as_ref().unwrap(), &mut conn).await @@ -1848,7 +1848,7 @@ pub enum CipherSyncType { } impl CipherSyncData { - pub async fn new(user_uuid: &str, sync_type: CipherSyncType, conn: &mut DbConn) -> Self { + pub async fn new(user_uuid: &UserId, sync_type: CipherSyncType, conn: &mut DbConn) -> Self { let cipher_folders: HashMap; let cipher_favorites: HashSet; match sync_type { diff --git a/src/api/core/emergency_access.rs b/src/api/core/emergency_access.rs index 6d3d881d..cc93f23c 100644 --- a/src/api/core/emergency_access.rs +++ b/src/api/core/emergency_access.rs @@ -701,7 +701,7 @@ async fn policies_emergency_access(emer_id: &str, headers: Headers, mut conn: Db fn is_valid_request( emergency_access: &EmergencyAccess, - requesting_user_uuid: &str, + requesting_user_uuid: &UserId, requested_access_type: EmergencyAccessType, ) -> bool { emergency_access.grantee_uuid.is_some() diff --git a/src/api/core/events.rs b/src/api/core/events.rs index 9bd1c806..a49dc785 100644 --- a/src/api/core/events.rs +++ b/src/api/core/events.rs @@ -8,7 +8,7 @@ use crate::{ api::{EmptyResult, JsonResult}, auth::{AdminHeaders, Headers}, db::{ - models::{Cipher, Event, Membership, MembershipId, OrganizationId}, + models::{Cipher, Event, Membership, MembershipId, OrganizationId, UserId}, DbConn, DbPool, }, util::parse_date, @@ -218,7 +218,7 @@ async fn post_events_collect(data: Json>, headers: Headers, Ok(()) } -pub async fn log_user_event(event_type: i32, user_uuid: &str, device_type: i32, ip: &IpAddr, conn: &mut DbConn) { +pub async fn log_user_event(event_type: i32, user_uuid: &UserId, device_type: i32, ip: &IpAddr, conn: &mut DbConn) { if !CONFIG.org_events_enabled() { return; } @@ -227,7 +227,7 @@ pub async fn log_user_event(event_type: i32, user_uuid: &str, device_type: i32, async fn _log_user_event( event_type: i32, - user_uuid: &str, + user_uuid: &UserId, device_type: i32, event_date: Option, ip: &IpAddr, @@ -238,8 +238,8 @@ async fn _log_user_event( // Upstream saves the event also without any org_uuid. let mut event = Event::new(event_type, event_date); - event.user_uuid = Some(String::from(user_uuid)); - event.act_user_uuid = Some(String::from(user_uuid)); + event.user_uuid = Some(user_uuid.clone()); + event.act_user_uuid = Some(user_uuid.to_string()); event.device_type = Some(device_type); event.ip_address = Some(ip.to_string()); events.push(event); @@ -247,9 +247,9 @@ async fn _log_user_event( // For each org a user is a member of store these events per org for org_uuid in orgs { let mut event = Event::new(event_type, event_date); - event.user_uuid = Some(String::from(user_uuid)); + event.user_uuid = Some(user_uuid.clone()); event.org_uuid = Some(org_uuid); - event.act_user_uuid = Some(String::from(user_uuid)); + event.act_user_uuid = Some(user_uuid.to_string()); event.device_type = Some(device_type); event.ip_address = Some(ip.to_string()); events.push(event); diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index ffefcd76..d9a8e644 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -358,7 +358,7 @@ async fn get_org_collections_details( let users: Vec = coll_users .iter() .filter(|collection_user| collection_user.collection_uuid == col.uuid) - .map(|collection_user| SelectionReadOnly::to_collection_user_details_read_only(collection_user).to_json()) + .map(|collection_user| UserSelection::to_collection_user_details_read_only(collection_user).to_json()) .collect(); // get the group details for the given collection @@ -667,7 +667,7 @@ async fn get_org_collection_detail( .await .iter() .map(|collection_user| { - SelectionReadOnly::to_collection_user_details_read_only(collection_user).to_json() + UserSelection::to_collection_user_details_read_only(collection_user).to_json() }) .collect(); @@ -761,7 +761,7 @@ async fn get_org_details(data: OrgIdData, headers: Headers, mut conn: DbConn) -> }))) } -async fn _get_org_details(org_id: &OrganizationId, host: &str, user_uuid: &str, conn: &mut DbConn) -> Value { +async fn _get_org_details(org_id: &OrganizationId, host: &str, user_uuid: &UserId, conn: &mut DbConn) -> Value { let ciphers = Cipher::find_by_org(org_id, conn).await; let cipher_sync_data = CipherSyncData::new(user_uuid, CipherSyncType::Organization, conn).await; @@ -2384,16 +2384,30 @@ impl SelectionReadOnly { CollectionGroup::new(self.id.clone(), groups_uuid, self.read_only, self.hide_passwords) } - pub fn to_collection_group_details_read_only(collection_group: &CollectionGroup) -> SelectionReadOnly { - SelectionReadOnly { + pub fn to_collection_group_details_read_only(collection_group: &CollectionGroup) -> Self { + Self { id: collection_group.groups_uuid.clone(), read_only: collection_group.read_only, hide_passwords: collection_group.hide_passwords, } } - pub fn to_collection_user_details_read_only(collection_user: &CollectionUser) -> SelectionReadOnly { - SelectionReadOnly { + pub fn to_json(&self) -> Value { + json!(self) + } +} + +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +struct UserSelection { + id: UserId, + read_only: bool, + hide_passwords: bool, +} + +impl UserSelection { + pub fn to_collection_user_details_read_only(collection_user: &CollectionUser) -> Self { + Self { id: collection_user.user_uuid.clone(), read_only: collection_user.read_only, hide_passwords: collection_user.hide_passwords, diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs index b98ecf70..cf217e9f 100644 --- a/src/api/core/sends.rs +++ b/src/api/core/sends.rs @@ -106,7 +106,7 @@ async fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, c Ok(()) } -fn create_send(data: SendData, user_uuid: String) -> ApiResult { +fn create_send(data: SendData, user_uuid: UserId) -> ApiResult { let data_val = if data.r#type == SendType::Text as i32 { data.text } else if data.r#type == SendType::File as i32 { diff --git a/src/api/core/two_factor/authenticator.rs b/src/api/core/two_factor/authenticator.rs index 64cc6486..5861bee2 100644 --- a/src/api/core/two_factor/authenticator.rs +++ b/src/api/core/two_factor/authenticator.rs @@ -7,7 +7,7 @@ use crate::{ auth::{ClientIp, Headers}, crypto, db::{ - models::{EventType, TwoFactor, TwoFactorType}, + models::{EventType, TwoFactor, TwoFactorType, UserId}, DbConn, }, util::NumberOrString, @@ -95,7 +95,7 @@ async fn activate_authenticator_put(data: Json, headers } pub async fn validate_totp_code_str( - user_uuid: &str, + user_uuid: &UserId, totp_code: &str, secret: &str, ip: &ClientIp, @@ -109,7 +109,7 @@ pub async fn validate_totp_code_str( } pub async fn validate_totp_code( - user_uuid: &str, + user_uuid: &UserId, totp_code: &str, secret: &str, ip: &ClientIp, @@ -124,7 +124,7 @@ pub async fn validate_totp_code( let mut twofactor = match TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::Authenticator as i32, conn).await { Some(tf) => tf, - _ => TwoFactor::new(user_uuid.to_string(), TwoFactorType::Authenticator, secret.to_string()), + _ => TwoFactor::new(user_uuid.clone(), TwoFactorType::Authenticator, secret.to_string()), }; // The amount of steps back and forward in time diff --git a/src/api/core/two_factor/duo.rs b/src/api/core/two_factor/duo.rs index 76421043..5567f41e 100644 --- a/src/api/core/two_factor/duo.rs +++ b/src/api/core/two_factor/duo.rs @@ -11,7 +11,7 @@ use crate::{ auth::Headers, crypto, db::{ - models::{EventType, TwoFactor, TwoFactorType, User}, + models::{EventType, TwoFactor, TwoFactorType, User, UserId}, DbConn, }, error::MapResult, @@ -228,11 +228,11 @@ const AUTH_PREFIX: &str = "AUTH"; const DUO_PREFIX: &str = "TX"; const APP_PREFIX: &str = "APP"; -async fn get_user_duo_data(uuid: &str, conn: &mut DbConn) -> DuoStatus { +async fn get_user_duo_data(user_uuid: &UserId, conn: &mut DbConn) -> DuoStatus { let type_ = TwoFactorType::Duo as i32; // If the user doesn't have an entry, disabled - let Some(twofactor) = TwoFactor::find_by_user_and_type(uuid, type_, conn).await else { + let Some(twofactor) = TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await else { return DuoStatus::Disabled(DuoData::global().is_some()); }; diff --git a/src/api/core/two_factor/email.rs b/src/api/core/two_factor/email.rs index 09f2f3b7..eabfbcf5 100644 --- a/src/api/core/two_factor/email.rs +++ b/src/api/core/two_factor/email.rs @@ -10,7 +10,7 @@ use crate::{ auth::Headers, crypto, db::{ - models::{EventType, TwoFactor, TwoFactorType, User}, + models::{EventType, TwoFactor, TwoFactorType, User, UserId}, DbConn, }, error::{Error, MapResult}, @@ -59,7 +59,7 @@ async fn send_email_login(data: Json, mut conn: DbConn) -> E } /// Generate the token, save the data for later verification and send email to user -pub async fn send_token(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { +pub async fn send_token(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult { let type_ = TwoFactorType::Email as i32; let mut twofactor = TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await.map_res("Two factor not found")?; @@ -198,7 +198,7 @@ async fn email(data: Json, headers: Headers, mut conn: DbConn) -> Jso } /// Validate the email code when used as TwoFactor token mechanism -pub async fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: &mut DbConn) -> EmptyResult { +pub async fn validate_email_code_str(user_uuid: &UserId, token: &str, data: &str, conn: &mut DbConn) -> EmptyResult { let mut email_data = EmailTokenData::from_json(data)?; let mut twofactor = TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::Email as i32, conn) .await @@ -327,7 +327,7 @@ pub fn obscure_email(email: &str) -> String { format!("{}@{}", new_name, &domain) } -pub async fn find_and_activate_email_2fa(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { +pub async fn find_and_activate_email_2fa(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult { if let Some(user) = User::find_by_uuid(user_uuid, conn).await { activate_email_2fa(&user, conn).await } else { diff --git a/src/api/core/two_factor/protected_actions.rs b/src/api/core/two_factor/protected_actions.rs index 1a1d59c8..775fef73 100644 --- a/src/api/core/two_factor/protected_actions.rs +++ b/src/api/core/two_factor/protected_actions.rs @@ -6,7 +6,7 @@ use crate::{ auth::Headers, crypto, db::{ - models::{TwoFactor, TwoFactorType}, + models::{TwoFactor, TwoFactorType, UserId}, DbConn, }, error::{Error, MapResult}, @@ -104,7 +104,7 @@ async fn verify_otp(data: Json, headers: Headers, mut con pub async fn validate_protected_action_otp( otp: &str, - user_uuid: &str, + user_uuid: &UserId, delete_if_valid: bool, conn: &mut DbConn, ) -> EmptyResult { diff --git a/src/api/core/two_factor/webauthn.rs b/src/api/core/two_factor/webauthn.rs index 9ee83d38..f7a04dd7 100644 --- a/src/api/core/two_factor/webauthn.rs +++ b/src/api/core/two_factor/webauthn.rs @@ -11,7 +11,7 @@ use crate::{ }, auth::Headers, db::{ - models::{EventType, TwoFactor, TwoFactorType}, + models::{EventType, TwoFactor, TwoFactorType, UserId}, DbConn, }, error::Error, @@ -148,7 +148,7 @@ async fn generate_webauthn_challenge(data: Json, headers: Hea )?; let type_ = TwoFactorType::WebauthnRegisterChallenge; - TwoFactor::new(user.uuid, type_, serde_json::to_string(&state)?).save(&mut conn).await?; + TwoFactor::new(user.uuid.clone(), type_, serde_json::to_string(&state)?).save(&mut conn).await?; let mut challenge_value = serde_json::to_value(challenge.public_key)?; challenge_value["status"] = "ok".into(); @@ -352,7 +352,7 @@ async fn delete_webauthn(data: Json, headers: Headers, mut conn: } pub async fn get_webauthn_registrations( - user_uuid: &str, + user_uuid: &UserId, conn: &mut DbConn, ) -> Result<(bool, Vec), Error> { let type_ = TwoFactorType::Webauthn as i32; @@ -362,7 +362,7 @@ pub async fn get_webauthn_registrations( } } -pub async fn generate_webauthn_login(user_uuid: &str, conn: &mut DbConn) -> JsonResult { +pub async fn generate_webauthn_login(user_uuid: &UserId, conn: &mut DbConn) -> JsonResult { // Load saved credentials let creds: Vec = get_webauthn_registrations(user_uuid, conn).await?.1.into_iter().map(|r| r.credential).collect(); @@ -376,7 +376,7 @@ pub async fn generate_webauthn_login(user_uuid: &str, conn: &mut DbConn) -> Json let (response, state) = WebauthnConfig::load().generate_challenge_authenticate_options(creds, Some(ext))?; // Save the challenge state for later validation - TwoFactor::new(user_uuid.into(), TwoFactorType::WebauthnLoginChallenge, serde_json::to_string(&state)?) + TwoFactor::new(user_uuid.clone(), TwoFactorType::WebauthnLoginChallenge, serde_json::to_string(&state)?) .save(conn) .await?; @@ -384,7 +384,7 @@ pub async fn generate_webauthn_login(user_uuid: &str, conn: &mut DbConn) -> Json Ok(Json(serde_json::to_value(response.public_key)?)) } -pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &mut DbConn) -> EmptyResult { +pub async fn validate_webauthn_login(user_uuid: &UserId, response: &str, conn: &mut DbConn) -> EmptyResult { let type_ = TwoFactorType::WebauthnLoginChallenge as i32; let state = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await { Some(tf) => { @@ -413,7 +413,7 @@ pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &mut if ®.credential.cred_id == cred_id { reg.credential.counter = auth_data.counter; - TwoFactor::new(user_uuid.to_string(), TwoFactorType::Webauthn, serde_json::to_string(®istrations)?) + TwoFactor::new(user_uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(®istrations)?) .save(conn) .await?; return Ok(()); diff --git a/src/api/identity.rs b/src/api/identity.rs index a32433f0..c1506adb 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -31,7 +31,7 @@ pub fn routes() -> Vec { async fn login(data: Form, client_header: ClientHeaders, mut conn: DbConn) -> JsonResult { let data: ConnectData = data.into_inner(); - let mut user_uuid: Option = None; + let mut user_uuid: Option = None; let login_result = match data.grant_type.as_ref() { "refresh_token" => { @@ -141,7 +141,7 @@ struct MasterPasswordPolicy { async fn _password_login( data: ConnectData, - user_uuid: &mut Option, + user_uuid: &mut Option, conn: &mut DbConn, ip: &ClientIp, ) -> JsonResult { @@ -359,7 +359,7 @@ async fn _password_login( async fn _api_key_login( data: ConnectData, - user_uuid: &mut Option, + user_uuid: &mut Option, conn: &mut DbConn, ip: &ClientIp, ) -> JsonResult { @@ -376,7 +376,7 @@ async fn _api_key_login( async fn _user_api_key_login( data: ConnectData, - user_uuid: &mut Option, + user_uuid: &mut Option, conn: &mut DbConn, ip: &ClientIp, ) -> JsonResult { @@ -385,7 +385,8 @@ async fn _user_api_key_login( let Some(client_user_uuid) = client_id.strip_prefix("user.") else { err!("Malformed client_id", format!("IP: {}.", ip.ip)) }; - let Some(user) = User::find_by_uuid(client_user_uuid, conn).await else { + let client_user_uuid: UserId = client_user_uuid.to_string().into(); + let Some(user) = User::find_by_uuid(&client_user_uuid, conn).await else { err!("Invalid client_id", format!("IP: {}.", ip.ip)) }; @@ -615,7 +616,7 @@ fn _selected_data(tf: Option) -> ApiResult { async fn _json_err_twofactor( providers: &[i32], - user_uuid: &str, + user_uuid: &UserId, data: &ConnectData, conn: &mut DbConn, ) -> ApiResult { diff --git a/src/api/notifications.rs b/src/api/notifications.rs index dc650039..16749521 100644 --- a/src/api/notifications.rs +++ b/src/api/notifications.rs @@ -10,7 +10,7 @@ use rocket_ws::{Message, WebSocket}; use crate::{ auth::{ClientIp, WsAccessTokenHeader}, db::{ - models::{Cipher, Folder, Send as DbSend, User}, + models::{Cipher, Folder, Send as DbSend, User, UserId}, DbConn, }, Error, CONFIG, @@ -53,13 +53,13 @@ struct WsAccessToken { struct WSEntryMapGuard { users: Arc, - user_uuid: String, + user_uuid: UserId, entry_uuid: uuid::Uuid, addr: IpAddr, } impl WSEntryMapGuard { - fn new(users: Arc, user_uuid: String, entry_uuid: uuid::Uuid, addr: IpAddr) -> Self { + fn new(users: Arc, user_uuid: UserId, entry_uuid: uuid::Uuid, addr: IpAddr) -> Self { Self { users, user_uuid, @@ -72,7 +72,7 @@ impl WSEntryMapGuard { impl Drop for WSEntryMapGuard { fn drop(&mut self) { info!("Closing WS connection from {}", self.addr); - if let Some(mut entry) = self.users.map.get_mut(&self.user_uuid) { + if let Some(mut entry) = self.users.map.get_mut(&self.user_uuid.to_string()) { entry.retain(|(uuid, _)| uuid != &self.entry_uuid); } } @@ -129,7 +129,7 @@ fn websockets_hub<'r>( // Add a channel to send messages to this client to the map let entry_uuid = uuid::Uuid::new_v4(); let (tx, rx) = tokio::sync::mpsc::channel::(100); - users.map.entry(claims.sub.clone()).or_default().push((entry_uuid, tx)); + users.map.entry(claims.sub.to_string()).or_default().push((entry_uuid, tx)); // Once the guard goes out of scope, the connection will have been closed and the entry will be deleted from the map (rx, WSEntryMapGuard::new(users, claims.sub, entry_uuid, addr)) @@ -328,8 +328,8 @@ pub struct WebSocketUsers { } impl WebSocketUsers { - async fn send_update(&self, user_uuid: &str, data: &[u8]) { - if let Some(user) = self.map.get(user_uuid).map(|v| v.clone()) { + async fn send_update(&self, user_uuid: &UserId, data: &[u8]) { + if let Some(user) = self.map.get(user_uuid.as_ref()).map(|v| v.clone()) { for (_, sender) in user.iter() { if let Err(e) = sender.send(Message::binary(data)).await { error!("Error sending WS update {e}"); @@ -345,7 +345,7 @@ impl WebSocketUsers { return; } let data = create_update( - vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))], + vec![("UserId".into(), user.uuid.to_string().into()), ("Date".into(), serialize_date(user.updated_at))], ut, None, ); @@ -365,7 +365,7 @@ impl WebSocketUsers { return; } let data = create_update( - vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))], + vec![("UserId".into(), user.uuid.to_string().into()), ("Date".into(), serialize_date(user.updated_at))], UpdateType::LogOut, acting_device_uuid.clone(), ); @@ -393,7 +393,7 @@ impl WebSocketUsers { let data = create_update( vec![ ("Id".into(), folder.uuid.clone().into()), - ("UserId".into(), folder.user_uuid.clone().into()), + ("UserId".into(), folder.user_uuid.to_string().into()), ("RevisionDate".into(), serialize_date(folder.updated_at)), ], ut, @@ -413,7 +413,7 @@ impl WebSocketUsers { &self, ut: UpdateType, cipher: &Cipher, - user_uuids: &[String], + user_uuids: &[UserId], acting_device_uuid: &String, collection_uuids: Option>, conn: &mut DbConn, @@ -432,7 +432,7 @@ impl WebSocketUsers { serialize_date(Utc::now().naive_utc()), ) } else { - (convert_option(cipher.user_uuid.clone()), Value::Nil, serialize_date(cipher.updated_at)) + (convert_option(cipher.user_uuid.as_deref()), Value::Nil, serialize_date(cipher.updated_at)) }; let data = create_update( @@ -462,7 +462,7 @@ impl WebSocketUsers { &self, ut: UpdateType, send: &DbSend, - user_uuids: &[String], + user_uuids: &[UserId], acting_device_uuid: &String, conn: &mut DbConn, ) { @@ -470,7 +470,7 @@ impl WebSocketUsers { if *NOTIFICATIONS_DISABLED { return; } - let user_uuid = convert_option(send.user_uuid.clone()); + let user_uuid = convert_option(send.user_uuid.as_deref()); let data = create_update( vec![ @@ -494,7 +494,7 @@ impl WebSocketUsers { pub async fn send_auth_request( &self, - user_uuid: &String, + user_uuid: &UserId, auth_request_uuid: &String, acting_device_uuid: &String, conn: &mut DbConn, @@ -504,7 +504,7 @@ impl WebSocketUsers { return; } let data = create_update( - vec![("Id".into(), auth_request_uuid.clone().into()), ("UserId".into(), user_uuid.clone().into())], + vec![("Id".into(), auth_request_uuid.clone().into()), ("UserId".into(), user_uuid.to_string().into())], UpdateType::AuthRequest, Some(acting_device_uuid.to_string()), ); @@ -513,13 +513,13 @@ impl WebSocketUsers { } if CONFIG.push_enabled() { - push_auth_request(user_uuid.to_string(), auth_request_uuid.to_string(), conn).await; + push_auth_request(user_uuid.clone(), auth_request_uuid.to_string(), conn).await; } } pub async fn send_auth_response( &self, - user_uuid: &String, + user_uuid: &UserId, auth_response_uuid: &str, approving_device_uuid: String, conn: &mut DbConn, @@ -529,17 +529,16 @@ impl WebSocketUsers { return; } let data = create_update( - vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.clone().into())], + vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.to_string().into())], UpdateType::AuthRequestResponse, approving_device_uuid.clone().into(), ); if CONFIG.enable_websocket() { - self.send_update(auth_response_uuid, &data).await; + self.send_update(user_uuid, &data).await; } if CONFIG.push_enabled() { - push_auth_response(user_uuid.to_string(), auth_response_uuid.to_string(), approving_device_uuid, conn) - .await; + push_auth_response(user_uuid.clone(), auth_response_uuid.to_string(), approving_device_uuid, conn).await; } } } @@ -558,16 +557,16 @@ impl AnonymousWebSocketSubscriptions { } } - pub async fn send_auth_response(&self, user_uuid: &String, auth_response_uuid: &str) { + pub async fn send_auth_response(&self, user_uuid: &UserId, auth_response_uuid: &str) { if !CONFIG.enable_websocket() { return; } let data = create_anonymous_update( - vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.clone().into())], + vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.to_string().into())], UpdateType::AuthRequestResponse, - user_uuid.to_string(), + user_uuid.clone(), ); - self.send_update(auth_response_uuid, &data).await; + self.send_update(user_uuid, &data).await; } } @@ -604,7 +603,7 @@ fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_uui serialize(value) } -fn create_anonymous_update(payload: Vec<(Value, Value)>, ut: UpdateType, user_id: String) -> Vec { +fn create_anonymous_update(payload: Vec<(Value, Value)>, ut: UpdateType, user_id: UserId) -> Vec { use rmpv::Value as V; let value = V::Array(vec![ @@ -615,7 +614,7 @@ fn create_anonymous_update(payload: Vec<(Value, Value)>, ut: UpdateType, user_id V::Array(vec![V::Map(vec![ ("Type".into(), (ut as i32).into()), ("Payload".into(), payload.into()), - ("UserId".into(), user_id.into()), + ("UserId".into(), user_id.to_string().into()), ])]), ]); diff --git a/src/api/push.rs b/src/api/push.rs index 7396a68d..bf9601a5 100644 --- a/src/api/push.rs +++ b/src/api/push.rs @@ -7,7 +7,7 @@ use tokio::sync::RwLock; use crate::{ api::{ApiResult, EmptyResult, UpdateType}, - db::models::{Cipher, Device, Folder, Send, User}, + db::models::{Cipher, Device, Folder, Send, User, UserId}, http_client::make_http_request, util::format_date, CONFIG, @@ -284,8 +284,8 @@ async fn send_to_push_relay(notification_data: Value) { }; } -pub async fn push_auth_request(user_uuid: String, auth_request_uuid: String, conn: &mut crate::db::DbConn) { - if Device::check_user_has_push_device(user_uuid.as_str(), conn).await { +pub async fn push_auth_request(user_uuid: UserId, auth_request_uuid: String, conn: &mut crate::db::DbConn) { + if Device::check_user_has_push_device(&user_uuid, conn).await { tokio::task::spawn(send_to_push_relay(json!({ "userId": user_uuid, "organizationId": (), @@ -301,12 +301,12 @@ pub async fn push_auth_request(user_uuid: String, auth_request_uuid: String, con } pub async fn push_auth_response( - user_uuid: String, + user_uuid: UserId, auth_request_uuid: String, approving_device_uuid: String, conn: &mut crate::db::DbConn, ) { - if Device::check_user_has_push_device(user_uuid.as_str(), conn).await { + if Device::check_user_has_push_device(&user_uuid, conn).await { tokio::task::spawn(send_to_push_relay(json!({ "userId": user_uuid, "organizationId": (), diff --git a/src/auth.rs b/src/auth.rs index e09d6161..3f92b127 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -14,7 +14,7 @@ use std::{ net::IpAddr, }; -use crate::db::models::{MembershipId, OrganizationId}; +use crate::db::models::{MembershipId, OrganizationId, UserId}; use crate::{error::Error, CONFIG}; const JWT_ALGORITHM: Algorithm = Algorithm::RS256; @@ -151,7 +151,7 @@ pub struct LoginJwtClaims { // Issuer pub iss: String, // Subject - pub sub: String, + pub sub: UserId, pub premium: bool, pub name: String, diff --git a/src/db/models/attachment.rs b/src/db/models/attachment.rs index 266fd192..443704ac 100644 --- a/src/db/models/attachment.rs +++ b/src/db/models/attachment.rs @@ -3,7 +3,7 @@ use std::io::ErrorKind; use bigdecimal::{BigDecimal, ToPrimitive}; use serde_json::Value; -use super::OrganizationId; +use super::{OrganizationId, UserId}; use crate::CONFIG; db_object! { @@ -145,7 +145,7 @@ impl Attachment { }} } - pub async fn size_by_user(user_uuid: &str, conn: &mut DbConn) -> i64 { + pub async fn size_by_user(user_uuid: &UserId, conn: &mut DbConn) -> i64 { db_run! { conn: { let result: Option = attachments::table .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) @@ -162,7 +162,7 @@ impl Attachment { }} } - pub async fn count_by_user(user_uuid: &str, conn: &mut DbConn) -> i64 { + pub async fn count_by_user(user_uuid: &UserId, conn: &mut DbConn) -> i64 { db_run! { conn: { attachments::table .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) @@ -205,7 +205,7 @@ impl Attachment { // There is no filtering done here if the user actually has access! // It is used to speed up the sync process, and the matching is done in a different part. pub async fn find_all_by_user_and_orgs( - user_uuid: &str, + user_uuid: &UserId, org_uuids: &Vec, conn: &mut DbConn, ) -> Vec { diff --git a/src/db/models/auth_request.rs b/src/db/models/auth_request.rs index ed931edd..3f3a53a9 100644 --- a/src/db/models/auth_request.rs +++ b/src/db/models/auth_request.rs @@ -1,4 +1,4 @@ -use super::OrganizationId; +use super::{OrganizationId, UserId}; use crate::crypto::ct_eq; use chrono::{NaiveDateTime, Utc}; @@ -9,7 +9,7 @@ db_object! { #[diesel(primary_key(uuid))] pub struct AuthRequest { pub uuid: String, - pub user_uuid: String, + pub user_uuid: UserId, pub organization_uuid: Option, pub request_device_identifier: String, @@ -34,7 +34,7 @@ db_object! { impl AuthRequest { pub fn new( - user_uuid: String, + user_uuid: UserId, request_device_identifier: String, device_type: i32, request_ip: String, @@ -112,7 +112,7 @@ impl AuthRequest { }} } - pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &UserId, conn: &mut DbConn) -> Option { db_run! {conn: { auth_requests::table .filter(auth_requests::uuid.eq(uuid)) @@ -123,7 +123,7 @@ impl AuthRequest { }} } - pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! {conn: { auth_requests::table .filter(auth_requests::user_uuid.eq(user_uuid)) diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index 76cd0a77..db75d2bf 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -5,7 +5,7 @@ use serde_json::Value; use super::{ Attachment, CollectionCipher, Favorite, FolderCipher, Group, Membership, MembershipStatus, MembershipType, - OrganizationId, User, + OrganizationId, User, UserId, }; use crate::api::core::{CipherData, CipherSyncData, CipherSyncType}; @@ -22,7 +22,7 @@ db_object! { pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, - pub user_uuid: Option, + pub user_uuid: Option, pub organization_uuid: Option, pub key: Option, @@ -136,7 +136,7 @@ impl Cipher { pub async fn to_json( &self, host: &str, - user_uuid: &str, + user_uuid: &UserId, cipher_sync_data: Option<&CipherSyncData>, sync_type: CipherSyncType, conn: &mut DbConn, @@ -357,7 +357,7 @@ impl Cipher { json_object } - pub async fn update_users_revision(&self, conn: &mut DbConn) -> Vec { + pub async fn update_users_revision(&self, conn: &mut DbConn) -> Vec { let mut user_uuids = Vec::new(); match self.user_uuid { Some(ref user_uuid) => { @@ -443,7 +443,7 @@ impl Cipher { Ok(()) } - pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult { for cipher in Self::find_owned_by_user(user_uuid, conn).await { cipher.delete(conn).await?; } @@ -461,7 +461,12 @@ impl Cipher { } } - pub async fn move_to_folder(&self, folder_uuid: Option, user_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn move_to_folder( + &self, + folder_uuid: Option, + user_uuid: &UserId, + conn: &mut DbConn, + ) -> EmptyResult { User::update_uuid_revision(user_uuid, conn).await; match (self.get_folder_uuid(user_uuid, conn).await, folder_uuid) { @@ -489,14 +494,14 @@ impl Cipher { } /// Returns whether this cipher is directly owned by the user. - pub fn is_owned_by_user(&self, user_uuid: &str) -> bool { + pub fn is_owned_by_user(&self, user_uuid: &UserId) -> bool { self.user_uuid.is_some() && self.user_uuid.as_ref().unwrap() == user_uuid } /// Returns whether this cipher is owned by an org in which the user has full access. async fn is_in_full_access_org( &self, - user_uuid: &str, + user_uuid: &UserId, cipher_sync_data: Option<&CipherSyncData>, conn: &mut DbConn, ) -> bool { @@ -515,7 +520,7 @@ impl Cipher { /// Returns whether this cipher is owned by an group in which the user has full access. async fn is_in_full_access_group( &self, - user_uuid: &str, + user_uuid: &UserId, cipher_sync_data: Option<&CipherSyncData>, conn: &mut DbConn, ) -> bool { @@ -539,7 +544,7 @@ impl Cipher { /// the access restrictions. pub async fn get_access_restrictions( &self, - user_uuid: &str, + user_uuid: &UserId, cipher_sync_data: Option<&CipherSyncData>, conn: &mut DbConn, ) -> Option<(bool, bool)> { @@ -599,7 +604,7 @@ impl Cipher { Some((read_only, hide_passwords)) } - async fn get_user_collections_access_flags(&self, user_uuid: &str, conn: &mut DbConn) -> Vec<(bool, bool)> { + async fn get_user_collections_access_flags(&self, user_uuid: &UserId, conn: &mut DbConn) -> Vec<(bool, bool)> { db_run! {conn: { // Check whether this cipher is in any collections accessible to the // user. If so, retrieve the access flags for each collection. @@ -616,7 +621,7 @@ impl Cipher { }} } - async fn get_group_collections_access_flags(&self, user_uuid: &str, conn: &mut DbConn) -> Vec<(bool, bool)> { + async fn get_group_collections_access_flags(&self, user_uuid: &UserId, conn: &mut DbConn) -> Vec<(bool, bool)> { if !CONFIG.org_groups_enabled() { return Vec::new(); } @@ -642,31 +647,31 @@ impl Cipher { }} } - pub async fn is_write_accessible_to_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool { + pub async fn is_write_accessible_to_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool { match self.get_access_restrictions(user_uuid, None, conn).await { Some((read_only, _hide_passwords)) => !read_only, None => false, } } - pub async fn is_accessible_to_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool { + pub async fn is_accessible_to_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool { self.get_access_restrictions(user_uuid, None, conn).await.is_some() } // Returns whether this cipher is a favorite of the specified user. - pub async fn is_favorite(&self, user_uuid: &str, conn: &mut DbConn) -> bool { + pub async fn is_favorite(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool { Favorite::is_favorite(&self.uuid, user_uuid, conn).await } // Sets whether this cipher is a favorite of the specified user. - pub async fn set_favorite(&self, favorite: Option, user_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn set_favorite(&self, favorite: Option, user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult { match favorite { None => Ok(()), // No change requested. Some(status) => Favorite::set_favorite(status, &self.uuid, user_uuid, conn).await, } } - pub async fn get_folder_uuid(&self, user_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn get_folder_uuid(&self, user_uuid: &UserId, conn: &mut DbConn) -> Option { db_run! {conn: { folders_ciphers::table .inner_join(folders::table) @@ -711,7 +716,7 @@ impl Cipher { // true, then the non-interesting ciphers will not be returned. As a // result, those ciphers will not appear in "My Vault" for the org // owner/admin, but they can still be accessed via the org vault view. - pub async fn find_by_user(user_uuid: &str, visible_only: bool, conn: &mut DbConn) -> Vec { + pub async fn find_by_user(user_uuid: &UserId, visible_only: bool, conn: &mut DbConn) -> Vec { if CONFIG.org_groups_enabled() { db_run! {conn: { let mut query = ciphers::table @@ -793,12 +798,12 @@ impl Cipher { } // Find all ciphers visible to the specified user. - pub async fn find_by_user_visible(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_user_visible(user_uuid: &UserId, conn: &mut DbConn) -> Vec { Self::find_by_user(user_uuid, true, conn).await } // Find all ciphers directly owned by the specified user. - pub async fn find_owned_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_owned_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! {conn: { ciphers::table .filter( @@ -809,7 +814,7 @@ impl Cipher { }} } - pub async fn count_owned_by_user(user_uuid: &str, conn: &mut DbConn) -> i64 { + pub async fn count_owned_by_user(user_uuid: &UserId, conn: &mut DbConn) -> i64 { db_run! {conn: { ciphers::table .filter(ciphers::user_uuid.eq(user_uuid)) diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs index 055ecb2c..9313585d 100644 --- a/src/db/models/collection.rs +++ b/src/db/models/collection.rs @@ -1,6 +1,6 @@ use serde_json::Value; -use super::{CollectionGroup, GroupUser, Membership, MembershipStatus, MembershipType, OrganizationId, User}; +use super::{CollectionGroup, GroupUser, Membership, MembershipStatus, MembershipType, OrganizationId, User, UserId}; use crate::CONFIG; db_object! { @@ -18,7 +18,7 @@ db_object! { #[diesel(table_name = users_collections)] #[diesel(primary_key(user_uuid, collection_uuid))] pub struct CollectionUser { - pub user_uuid: String, + pub user_uuid: UserId, pub collection_uuid: String, pub read_only: bool, pub hide_passwords: bool, @@ -74,7 +74,7 @@ impl Collection { pub async fn to_json_details( &self, - user_uuid: &str, + user_uuid: &UserId, cipher_sync_data: Option<&crate::api::core::CipherSyncData>, conn: &mut DbConn, ) -> Value { @@ -208,7 +208,7 @@ impl Collection { }} } - pub async fn find_by_user_uuid(user_uuid: String, conn: &mut DbConn) -> Vec { + pub async fn find_by_user_uuid(user_uuid: UserId, conn: &mut DbConn) -> Vec { if CONFIG.org_groups_enabled() { db_run! { conn: { collections::table @@ -281,7 +281,7 @@ impl Collection { pub async fn find_by_organization_and_user_uuid( org_uuid: &OrganizationId, - user_uuid: &str, + user_uuid: &UserId, conn: &mut DbConn, ) -> Vec { Self::find_by_user_uuid(user_uuid.to_owned(), conn) @@ -324,7 +324,7 @@ impl Collection { }} } - pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: String, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: UserId, conn: &mut DbConn) -> Option { if CONFIG.org_groups_enabled() { db_run! { conn: { collections::table @@ -391,7 +391,7 @@ impl Collection { } } - pub async fn is_writable_by_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool { + pub async fn is_writable_by_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool { let user_uuid = user_uuid.to_string(); if CONFIG.org_groups_enabled() { db_run! { conn: { @@ -453,7 +453,7 @@ impl Collection { } } - pub async fn hide_passwords_for_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool { + pub async fn hide_passwords_for_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool { let user_uuid = user_uuid.to_string(); db_run! { conn: { collections::table @@ -504,7 +504,7 @@ impl Collection { impl CollectionUser { pub async fn find_by_organization_and_user_uuid( org_uuid: &OrganizationId, - user_uuid: &str, + user_uuid: &UserId, conn: &mut DbConn, ) -> Vec { db_run! { conn: { @@ -533,7 +533,7 @@ impl CollectionUser { } pub async fn save( - user_uuid: &str, + user_uuid: &UserId, collection_uuid: &str, read_only: bool, hide_passwords: bool, @@ -632,7 +632,7 @@ impl CollectionUser { pub async fn find_by_collection_and_user( collection_uuid: &str, - user_uuid: &str, + user_uuid: &UserId, conn: &mut DbConn, ) -> Option { db_run! { conn: { @@ -646,7 +646,7 @@ impl CollectionUser { }} } - pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { users_collections::table .filter(users_collections::user_uuid.eq(user_uuid)) @@ -670,7 +670,7 @@ impl CollectionUser { } pub async fn delete_all_by_user_and_org( - user_uuid: &str, + user_uuid: &UserId, org_uuid: &OrganizationId, conn: &mut DbConn, ) -> EmptyResult { @@ -689,7 +689,7 @@ impl CollectionUser { }} } - pub async fn has_access_to_collection_by_user(col_id: &str, user_uuid: &str, conn: &mut DbConn) -> bool { + pub async fn has_access_to_collection_by_user(col_id: &str, user_uuid: &UserId, conn: &mut DbConn) -> bool { Self::find_by_collection_and_user(col_id, user_uuid, conn).await.is_some() } } diff --git a/src/db/models/device.rs b/src/db/models/device.rs index efb3380d..634d7c4f 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -1,5 +1,6 @@ use chrono::{NaiveDateTime, Utc}; +use super::UserId; use crate::{crypto, CONFIG}; use core::fmt; @@ -13,7 +14,7 @@ db_object! { pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, - pub user_uuid: String, + pub user_uuid: UserId, pub name: String, pub atype: i32, // https://github.com/bitwarden/server/blob/dcc199bcce4aa2d5621f6fab80f1b49d8b143418/src/Core/Enums/DeviceType.cs @@ -28,7 +29,7 @@ db_object! { /// Local methods impl Device { - pub fn new(uuid: String, user_uuid: String, name: String, atype: i32) -> Self { + pub fn new(uuid: String, user_uuid: UserId, name: String, atype: i32) -> Self { let now = Utc::now().naive_utc(); Self { @@ -150,7 +151,7 @@ impl Device { } } - pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult { db_run! { conn: { diesel::delete(devices::table.filter(devices::user_uuid.eq(user_uuid))) .execute(conn) @@ -158,7 +159,7 @@ impl Device { }} } - pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &UserId, conn: &mut DbConn) -> Option { db_run! { conn: { devices::table .filter(devices::uuid.eq(uuid)) @@ -169,7 +170,7 @@ impl Device { }} } - pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { devices::table .filter(devices::user_uuid.eq(user_uuid)) @@ -208,7 +209,7 @@ impl Device { }} } - pub async fn find_latest_active_by_user(user_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_latest_active_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Option { db_run! { conn: { devices::table .filter(devices::user_uuid.eq(user_uuid)) @@ -219,7 +220,7 @@ impl Device { }} } - pub async fn find_push_devices_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_push_devices_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { devices::table .filter(devices::user_uuid.eq(user_uuid)) @@ -230,7 +231,7 @@ impl Device { }} } - pub async fn check_user_has_push_device(user_uuid: &str, conn: &mut DbConn) -> bool { + pub async fn check_user_has_push_device(user_uuid: &UserId, conn: &mut DbConn) -> bool { db_run! { conn: { devices::table .filter(devices::user_uuid.eq(user_uuid)) diff --git a/src/db/models/emergency_access.rs b/src/db/models/emergency_access.rs index f4f3b9a9..429092ea 100644 --- a/src/db/models/emergency_access.rs +++ b/src/db/models/emergency_access.rs @@ -3,7 +3,7 @@ use serde_json::Value; use crate::{api::EmptyResult, db::DbConn, error::MapResult}; -use super::User; +use super::{User, UserId}; db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)] @@ -12,8 +12,8 @@ db_object! { #[diesel(primary_key(uuid))] pub struct EmergencyAccess { pub uuid: String, - pub grantor_uuid: String, - pub grantee_uuid: Option, + pub grantor_uuid: UserId, + pub grantee_uuid: Option, pub email: Option, pub key_encrypted: Option, pub atype: i32, //EmergencyAccessType @@ -29,7 +29,7 @@ db_object! { // Local methods impl EmergencyAccess { - pub fn new(grantor_uuid: String, email: String, status: i32, atype: i32, wait_time_days: i32) -> Self { + pub fn new(grantor_uuid: UserId, email: String, status: i32, atype: i32, wait_time_days: i32) -> Self { let now = Utc::now().naive_utc(); Self { @@ -82,7 +82,7 @@ impl EmergencyAccess { } pub async fn to_json_grantee_details(&self, conn: &mut DbConn) -> Option { - let grantee_user = if let Some(grantee_uuid) = self.grantee_uuid.as_deref() { + let grantee_user = if let Some(grantee_uuid) = &self.grantee_uuid { User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found.") } else if let Some(email) = self.email.as_deref() { match User::find_by_mail(email, conn).await { @@ -211,7 +211,7 @@ impl EmergencyAccess { }} } - pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult { for ea in Self::find_all_by_grantor_uuid(user_uuid, conn).await { ea.delete(conn).await?; } @@ -239,8 +239,8 @@ impl EmergencyAccess { } pub async fn find_by_grantor_uuid_and_grantee_uuid_or_email( - grantor_uuid: &str, - grantee_uuid: &str, + grantor_uuid: &UserId, + grantee_uuid: &UserId, email: &str, conn: &mut DbConn, ) -> Option { @@ -262,7 +262,7 @@ impl EmergencyAccess { }} } - pub async fn find_by_uuid_and_grantor_uuid(uuid: &str, grantor_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid_and_grantor_uuid(uuid: &str, grantor_uuid: &UserId, conn: &mut DbConn) -> Option { db_run! { conn: { emergency_access::table .filter(emergency_access::uuid.eq(uuid)) @@ -272,7 +272,7 @@ impl EmergencyAccess { }} } - pub async fn find_by_uuid_and_grantee_uuid(uuid: &str, grantee_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid_and_grantee_uuid(uuid: &str, grantee_uuid: &UserId, conn: &mut DbConn) -> Option { db_run! { conn: { emergency_access::table .filter(emergency_access::uuid.eq(uuid)) @@ -292,7 +292,7 @@ impl EmergencyAccess { }} } - pub async fn find_all_by_grantee_uuid(grantee_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_all_by_grantee_uuid(grantee_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { emergency_access::table .filter(emergency_access::grantee_uuid.eq(grantee_uuid)) @@ -319,7 +319,7 @@ impl EmergencyAccess { }} } - pub async fn find_all_by_grantor_uuid(grantor_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_all_by_grantor_uuid(grantor_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { emergency_access::table .filter(emergency_access::grantor_uuid.eq(grantor_uuid)) @@ -327,7 +327,12 @@ impl EmergencyAccess { }} } - pub async fn accept_invite(&mut self, grantee_uuid: &str, grantee_email: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn accept_invite( + &mut self, + grantee_uuid: &UserId, + 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."); } @@ -337,7 +342,7 @@ impl EmergencyAccess { } self.status = EmergencyAccessStatus::Accepted as i32; - self.grantee_uuid = Some(String::from(grantee_uuid)); + self.grantee_uuid = Some(grantee_uuid.clone()); self.email = None; self.save(conn).await } diff --git a/src/db/models/event.rs b/src/db/models/event.rs index 3a225803..f52190ec 100644 --- a/src/db/models/event.rs +++ b/src/db/models/event.rs @@ -1,7 +1,7 @@ use crate::db::DbConn; use serde_json::Value; -use super::OrganizationId; +use super::{OrganizationId, UserId}; use crate::{api::EmptyResult, error::MapResult, CONFIG}; use chrono::{NaiveDateTime, TimeDelta, Utc}; @@ -18,7 +18,7 @@ db_object! { pub struct Event { pub uuid: String, pub event_type: i32, // EventType - pub user_uuid: Option, + pub user_uuid: Option, pub org_uuid: Option, pub cipher_uuid: Option, pub collection_uuid: Option, diff --git a/src/db/models/favorite.rs b/src/db/models/favorite.rs index a301f597..14f89eaf 100644 --- a/src/db/models/favorite.rs +++ b/src/db/models/favorite.rs @@ -1,11 +1,11 @@ -use super::User; +use super::{User, UserId}; db_object! { #[derive(Identifiable, Queryable, Insertable)] #[diesel(table_name = favorites)] #[diesel(primary_key(user_uuid, cipher_uuid))] pub struct Favorite { - pub user_uuid: String, + pub user_uuid: UserId, pub cipher_uuid: String, } } @@ -17,7 +17,7 @@ use crate::error::MapResult; impl Favorite { // Returns whether the specified cipher is a favorite of the specified user. - pub async fn is_favorite(cipher_uuid: &str, user_uuid: &str, conn: &mut DbConn) -> bool { + pub async fn is_favorite(cipher_uuid: &str, user_uuid: &UserId, conn: &mut DbConn) -> bool { db_run! { conn: { let query = favorites::table .filter(favorites::cipher_uuid.eq(cipher_uuid)) @@ -29,7 +29,7 @@ impl Favorite { } // Sets whether the specified cipher is a favorite of the specified user. - pub async fn set_favorite(favorite: bool, cipher_uuid: &str, user_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn set_favorite(favorite: bool, cipher_uuid: &str, user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult { let (old, new) = (Self::is_favorite(cipher_uuid, user_uuid, conn).await, favorite); match (old, new) { (false, true) => { @@ -71,7 +71,7 @@ impl Favorite { } // Delete all favorite entries associated with the specified user. - pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult { db_run! { conn: { diesel::delete(favorites::table.filter(favorites::user_uuid.eq(user_uuid))) .execute(conn) @@ -81,7 +81,7 @@ impl Favorite { /// Return a vec with (cipher_uuid) this will only contain favorite flagged ciphers /// This is used during a full sync so we only need one query for all favorite cipher matches. - pub async fn get_all_cipher_uuid_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn get_all_cipher_uuid_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { favorites::table .filter(favorites::user_uuid.eq(user_uuid)) diff --git a/src/db/models/folder.rs b/src/db/models/folder.rs index 45460720..9666e159 100644 --- a/src/db/models/folder.rs +++ b/src/db/models/folder.rs @@ -1,7 +1,7 @@ use chrono::{NaiveDateTime, Utc}; use serde_json::Value; -use super::User; +use super::{User, UserId}; db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)] @@ -11,7 +11,7 @@ db_object! { pub uuid: String, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, - pub user_uuid: String, + pub user_uuid: UserId, pub name: String, } @@ -26,7 +26,7 @@ db_object! { /// Local methods impl Folder { - pub fn new(user_uuid: String, name: String) -> Self { + pub fn new(user_uuid: UserId, name: String) -> Self { let now = Utc::now().naive_utc(); Self { @@ -113,14 +113,14 @@ impl Folder { }} } - pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult { for folder in Self::find_by_user(user_uuid, conn).await { folder.delete(conn).await?; } Ok(()) } - pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &UserId, conn: &mut DbConn) -> Option { db_run! { conn: { folders::table .filter(folders::uuid.eq(uuid)) @@ -131,7 +131,7 @@ impl Folder { }} } - pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { folders::table .filter(folders::user_uuid.eq(user_uuid)) @@ -216,7 +216,7 @@ impl FolderCipher { /// Return a vec with (cipher_uuid, folder_uuid) /// This is used during a full sync so we only need one query for all folder matches. - pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<(String, String)> { + pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<(String, String)> { db_run! { conn: { folders_ciphers::table .inner_join(folders::table) diff --git a/src/db/models/group.rs b/src/db/models/group.rs index c0a31761..0a6cca10 100644 --- a/src/db/models/group.rs +++ b/src/db/models/group.rs @@ -1,4 +1,4 @@ -use super::{Membership, MembershipId, OrganizationId, User}; +use super::{Membership, MembershipId, OrganizationId, User, UserId}; use crate::api::EmptyResult; use crate::db::DbConn; use crate::error::MapResult; @@ -222,7 +222,7 @@ impl Group { }} } //Returns all organizations the user has full access to - pub async fn get_orgs_by_user_with_full_access(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn get_orgs_by_user_with_full_access(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { groups_users::table .inner_join(users_organizations::table.on( @@ -240,7 +240,7 @@ impl Group { }} } - pub async fn is_in_full_access_group(user_uuid: &str, org_uuid: &OrganizationId, conn: &mut DbConn) -> bool { + pub async fn is_in_full_access_group(user_uuid: &UserId, org_uuid: &OrganizationId, conn: &mut DbConn) -> bool { db_run! { conn: { groups::table .inner_join(groups_users::table.on( @@ -353,7 +353,7 @@ impl CollectionGroup { }} } - pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { collections_groups::table .inner_join(groups_users::table.on( diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index fe61e42f..7042b294 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -34,4 +34,4 @@ pub use self::send::{Send, SendType}; pub use self::two_factor::{TwoFactor, TwoFactorType}; pub use self::two_factor_duo_context::TwoFactorDuoContext; pub use self::two_factor_incomplete::TwoFactorIncomplete; -pub use self::user::{Invitation, User, UserKdfType, UserStampException}; +pub use self::user::{Invitation, User, UserId, UserKdfType, UserStampException}; diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs index c63fa16d..78cf9973 100644 --- a/src/db/models/org_policy.rs +++ b/src/db/models/org_policy.rs @@ -5,7 +5,7 @@ use crate::api::EmptyResult; use crate::db::DbConn; use crate::error::MapResult; -use super::{Membership, MembershipId, MembershipStatus, MembershipType, OrganizationId, TwoFactor}; +use super::{Membership, MembershipId, MembershipStatus, MembershipType, OrganizationId, TwoFactor, UserId}; db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)] @@ -152,7 +152,7 @@ impl OrgPolicy { }} } - pub async fn find_confirmed_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_confirmed_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { org_policies::table .inner_join( @@ -194,7 +194,7 @@ impl OrgPolicy { } pub async fn find_accepted_and_confirmed_by_user_and_active_policy( - user_uuid: &str, + user_uuid: &UserId, policy_type: OrgPolicyType, conn: &mut DbConn, ) -> Vec { @@ -221,7 +221,7 @@ impl OrgPolicy { } pub async fn find_confirmed_by_user_and_active_policy( - user_uuid: &str, + user_uuid: &UserId, policy_type: OrgPolicyType, conn: &mut DbConn, ) -> Vec { @@ -248,7 +248,7 @@ impl OrgPolicy { /// and the user is not an owner or admin of that org. This is only useful for checking /// applicability of policy types that have these particular semantics. pub async fn is_applicable_to_user( - user_uuid: &str, + user_uuid: &UserId, policy_type: OrgPolicyType, exclude_org_uuid: Option<&OrganizationId>, conn: &mut DbConn, @@ -271,7 +271,7 @@ impl OrgPolicy { } pub async fn is_user_allowed( - user_uuid: &str, + user_uuid: &UserId, org_uuid: &OrganizationId, exclude_current_org: bool, conn: &mut DbConn, @@ -316,7 +316,7 @@ impl OrgPolicy { /// Returns true if the user belongs to an org that has enabled the `DisableHideEmail` /// option of the `Send Options` policy, and the user is not an owner or admin of that org. - pub async fn is_hide_email_disabled(user_uuid: &str, conn: &mut DbConn) -> bool { + pub async fn is_hide_email_disabled(user_uuid: &UserId, conn: &mut DbConn) -> bool { for policy in OrgPolicy::find_confirmed_by_user_and_active_policy(user_uuid, OrgPolicyType::SendOptions, conn).await { diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 7a3657ed..db991fe1 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -10,7 +10,7 @@ use std::{ ops::Deref, }; -use super::{CollectionUser, Group, GroupUser, OrgPolicy, OrgPolicyType, TwoFactor, User}; +use super::{CollectionUser, Group, GroupUser, OrgPolicy, OrgPolicyType, TwoFactor, User, UserId}; use crate::db::models::{Collection, CollectionGroup}; use crate::CONFIG; @@ -31,7 +31,7 @@ db_object! { #[diesel(primary_key(uuid))] pub struct Membership { pub uuid: MembershipId, - pub user_uuid: String, + pub user_uuid: UserId, pub org_uuid: OrganizationId, pub access_all: bool, @@ -204,7 +204,7 @@ impl Organization { static ACTIVATE_REVOKE_DIFF: i32 = 128; impl Membership { - pub fn new(user_uuid: String, org_uuid: OrganizationId) -> Self { + pub fn new(user_uuid: UserId, org_uuid: OrganizationId) -> Self { Self { uuid: MembershipId(crate::util::get_uuid()), @@ -666,7 +666,7 @@ impl Membership { Ok(()) } - pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult { for member in Self::find_any_state_by_user(user_uuid, conn).await { member.delete(conn).await?; } @@ -722,7 +722,7 @@ impl Membership { }} } - pub async fn find_confirmed_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_confirmed_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { users_organizations::table .filter(users_organizations::user_uuid.eq(user_uuid)) @@ -732,7 +732,7 @@ impl Membership { }} } - pub async fn find_invited_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_invited_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { users_organizations::table .filter(users_organizations::user_uuid.eq(user_uuid)) @@ -742,7 +742,7 @@ impl Membership { }} } - pub async fn find_any_state_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_any_state_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { users_organizations::table .filter(users_organizations::user_uuid.eq(user_uuid)) @@ -751,7 +751,7 @@ impl Membership { }} } - pub async fn count_accepted_and_confirmed_by_user(user_uuid: &str, conn: &mut DbConn) -> i64 { + pub async fn count_accepted_and_confirmed_by_user(user_uuid: &UserId, conn: &mut DbConn) -> i64 { db_run! { conn: { users_organizations::table .filter(users_organizations::user_uuid.eq(user_uuid)) @@ -822,7 +822,11 @@ impl Membership { }} } - pub async fn find_by_user_and_org(user_uuid: &str, org_uuid: &OrganizationId, conn: &mut DbConn) -> Option { + pub async fn find_by_user_and_org( + user_uuid: &UserId, + org_uuid: &OrganizationId, + conn: &mut DbConn, + ) -> Option { db_run! { conn: { users_organizations::table .filter(users_organizations::user_uuid.eq(user_uuid)) @@ -833,7 +837,7 @@ impl Membership { } pub async fn find_confirmed_by_user_and_org( - user_uuid: &str, + user_uuid: &UserId, org_uuid: &OrganizationId, conn: &mut DbConn, ) -> Option { @@ -849,7 +853,7 @@ impl Membership { }} } - pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { users_organizations::table .filter(users_organizations::user_uuid.eq(user_uuid)) @@ -858,7 +862,7 @@ impl Membership { }} } - pub async fn get_orgs_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn get_orgs_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { users_organizations::table .filter(users_organizations::user_uuid.eq(user_uuid)) @@ -868,7 +872,11 @@ impl Membership { }} } - pub async fn find_by_user_and_policy(user_uuid: &str, policy_type: OrgPolicyType, conn: &mut DbConn) -> Vec { + pub async fn find_by_user_and_policy( + user_uuid: &UserId, + policy_type: OrgPolicyType, + conn: &mut DbConn, + ) -> Vec { db_run! { conn: { users_organizations::table .inner_join( @@ -940,7 +948,7 @@ impl Membership { }} } - pub async fn user_has_ge_admin_access_to_cipher(user_uuid: &str, cipher_uuid: &str, conn: &mut DbConn) -> bool { + pub async fn user_has_ge_admin_access_to_cipher(user_uuid: &UserId, cipher_uuid: &str, conn: &mut DbConn) -> bool { db_run! { conn: { users_organizations::table .inner_join(ciphers::table.on(ciphers::uuid.eq(cipher_uuid).and(ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable())))) diff --git a/src/db/models/send.rs b/src/db/models/send.rs index 9e49ed5f..8cb27367 100644 --- a/src/db/models/send.rs +++ b/src/db/models/send.rs @@ -3,7 +3,7 @@ use serde_json::Value; use crate::util::LowerCase; -use super::{OrganizationId, User}; +use super::{OrganizationId, User, UserId}; db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)] @@ -13,7 +13,7 @@ db_object! { pub struct Send { pub uuid: String, - pub user_uuid: Option, + pub user_uuid: Option, pub organization_uuid: Option, pub name: String, @@ -242,7 +242,7 @@ impl Send { } } - pub async fn update_users_revision(&self, conn: &mut DbConn) -> Vec { + pub async fn update_users_revision(&self, conn: &mut DbConn) -> Vec { let mut user_uuids = Vec::new(); match &self.user_uuid { Some(user_uuid) => { @@ -256,7 +256,7 @@ impl Send { user_uuids } - pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult { for send in Self::find_by_user(user_uuid, conn).await { send.delete(conn).await?; } @@ -289,7 +289,7 @@ impl Send { }} } - pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &UserId, conn: &mut DbConn) -> Option { db_run! {conn: { sends::table .filter(sends::uuid.eq(uuid)) @@ -300,7 +300,7 @@ impl Send { }} } - pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! {conn: { sends::table .filter(sends::user_uuid.eq(user_uuid)) @@ -308,7 +308,7 @@ impl Send { }} } - pub async fn size_by_user(user_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn size_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Option { let sends = Self::find_by_user(user_uuid, conn).await; #[derive(serde::Deserialize)] diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs index 9155c518..0df69869 100644 --- a/src/db/models/two_factor.rs +++ b/src/db/models/two_factor.rs @@ -1,5 +1,6 @@ use serde_json::Value; +use super::UserId; use crate::{api::EmptyResult, db::DbConn, error::MapResult}; db_object! { @@ -8,7 +9,7 @@ db_object! { #[diesel(primary_key(uuid))] pub struct TwoFactor { pub uuid: String, - pub user_uuid: String, + pub user_uuid: UserId, pub atype: i32, pub enabled: bool, pub data: String, @@ -41,7 +42,7 @@ pub enum TwoFactorType { /// Local methods impl TwoFactor { - pub fn new(user_uuid: String, atype: TwoFactorType, data: String) -> Self { + pub fn new(user_uuid: UserId, atype: TwoFactorType, data: String) -> Self { Self { uuid: crate::util::get_uuid(), user_uuid, @@ -118,7 +119,7 @@ impl TwoFactor { }} } - pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec { db_run! { conn: { twofactor::table .filter(twofactor::user_uuid.eq(user_uuid)) @@ -129,7 +130,7 @@ impl TwoFactor { }} } - pub async fn find_by_user_and_type(user_uuid: &str, atype: i32, conn: &mut DbConn) -> Option { + pub async fn find_by_user_and_type(user_uuid: &UserId, atype: i32, conn: &mut DbConn) -> Option { db_run! { conn: { twofactor::table .filter(twofactor::user_uuid.eq(user_uuid)) @@ -140,7 +141,7 @@ impl TwoFactor { }} } - pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult { db_run! { conn: { diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid))) .execute(conn) diff --git a/src/db/models/two_factor_incomplete.rs b/src/db/models/two_factor_incomplete.rs index 12813eb5..6c6cacb8 100644 --- a/src/db/models/two_factor_incomplete.rs +++ b/src/db/models/two_factor_incomplete.rs @@ -1,13 +1,19 @@ use chrono::{NaiveDateTime, Utc}; -use crate::{api::EmptyResult, auth::ClientIp, db::DbConn, error::MapResult, CONFIG}; +use crate::{ + api::EmptyResult, + auth::ClientIp, + db::{models::UserId, DbConn}, + error::MapResult, + CONFIG, +}; db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[diesel(table_name = twofactor_incomplete)] #[diesel(primary_key(user_uuid, device_uuid))] pub struct TwoFactorIncomplete { - pub user_uuid: String, + pub user_uuid: UserId, // This device UUID is simply what's claimed by the device. It doesn't // necessarily correspond to any UUID in the devices table, since a device // must complete 2FA login before being added into the devices table. @@ -21,7 +27,7 @@ db_object! { impl TwoFactorIncomplete { pub async fn mark_incomplete( - user_uuid: &str, + user_uuid: &UserId, device_uuid: &str, device_name: &str, device_type: i32, @@ -55,7 +61,7 @@ impl TwoFactorIncomplete { }} } - pub async fn mark_complete(user_uuid: &str, device_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn mark_complete(user_uuid: &UserId, device_uuid: &str, conn: &mut DbConn) -> EmptyResult { if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() { return Ok(()); } @@ -63,7 +69,7 @@ impl TwoFactorIncomplete { Self::delete_by_user_and_device(user_uuid, device_uuid, conn).await } - pub async fn find_by_user_and_device(user_uuid: &str, device_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_user_and_device(user_uuid: &UserId, device_uuid: &str, conn: &mut DbConn) -> Option { db_run! { conn: { twofactor_incomplete::table .filter(twofactor_incomplete::user_uuid.eq(user_uuid)) @@ -88,7 +94,7 @@ impl TwoFactorIncomplete { Self::delete_by_user_and_device(&self.user_uuid, &self.device_uuid, conn).await } - pub async fn delete_by_user_and_device(user_uuid: &str, device_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_by_user_and_device(user_uuid: &UserId, device_uuid: &str, conn: &mut DbConn) -> EmptyResult { db_run! { conn: { diesel::delete(twofactor_incomplete::table .filter(twofactor_incomplete::user_uuid.eq(user_uuid)) @@ -98,7 +104,7 @@ impl TwoFactorIncomplete { }} } - pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult { db_run! { conn: { diesel::delete(twofactor_incomplete::table.filter(twofactor_incomplete::user_uuid.eq(user_uuid))) .execute(conn) diff --git a/src/db/models/user.rs b/src/db/models/user.rs index 981a4605..99b38955 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -1,9 +1,23 @@ -use crate::util::{format_date, get_uuid, retry}; use chrono::{NaiveDateTime, TimeDelta, Utc}; +use rocket::request::FromParam; use serde_json::Value; +use std::{ + borrow::Borrow, + fmt::{Display, Formatter}, + ops::Deref, +}; -use crate::crypto; -use crate::CONFIG; +use super::{ + Cipher, Device, EmergencyAccess, Favorite, Folder, Membership, MembershipType, TwoFactor, TwoFactorIncomplete, +}; +use crate::{ + api::EmptyResult, + crypto, + db::DbConn, + error::MapResult, + util::{format_date, get_uuid, retry}, + CONFIG, +}; db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)] @@ -11,7 +25,7 @@ db_object! { #[diesel(treat_none_as_null = true)] #[diesel(primary_key(uuid))] pub struct User { - pub uuid: String, + pub uuid: UserId, pub enabled: bool, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, @@ -91,7 +105,7 @@ impl User { let email = email.to_lowercase(); Self { - uuid: get_uuid(), + uuid: UserId(get_uuid()), enabled: true, created_at: now, updated_at: now, @@ -214,14 +228,6 @@ impl User { } } -use super::{ - Cipher, Device, EmergencyAccess, Favorite, Folder, Membership, MembershipType, Send, TwoFactor, TwoFactorIncomplete, -}; -use crate::db::DbConn; - -use crate::api::EmptyResult; -use crate::error::MapResult; - /// Database methods impl User { pub async fn to_json(&self, conn: &mut DbConn) -> Value { @@ -311,7 +317,7 @@ impl User { } } - Send::delete_all_by_user(&self.uuid, conn).await?; + super::Send::delete_all_by_user(&self.uuid, conn).await?; EmergencyAccess::delete_all_by_user(&self.uuid, conn).await?; EmergencyAccess::delete_all_by_grantee_email(&self.email, conn).await?; Membership::delete_all_by_user(&self.uuid, conn).await?; @@ -330,7 +336,7 @@ impl User { }} } - pub async fn update_uuid_revision(uuid: &str, conn: &mut DbConn) { + pub async fn update_uuid_revision(uuid: &UserId, conn: &mut DbConn) { if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await { warn!("Failed to update revision for {}: {:#?}", uuid, e); } @@ -355,7 +361,7 @@ impl User { Self::_update_revision(&self.uuid, &self.updated_at, conn).await } - async fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &mut DbConn) -> EmptyResult { + async fn _update_revision(uuid: &UserId, date: &NaiveDateTime, conn: &mut DbConn) -> EmptyResult { db_run! {conn: { retry(|| { diesel::update(users::table.filter(users::uuid.eq(uuid))) @@ -377,7 +383,7 @@ impl User { }} } - pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid(uuid: &UserId, conn: &mut DbConn) -> Option { db_run! {conn: { users::table.filter(users::uuid.eq(uuid)).first::(conn).ok().from_db() }} @@ -456,3 +462,51 @@ impl Invitation { } } } + +#[derive(DieselNewType, FromForm, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct UserId(String); + +impl AsRef for UserId { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl Deref for UserId { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Borrow for UserId { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl Display for UserId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for UserId { + fn from(raw: String) -> Self { + Self(raw) + } +} + +impl<'r> FromParam<'r> for UserId { + type Error = (); + + #[inline(always)] + fn from_param(param: &'r str) -> Result { + if param.chars().all(|c| matches!(c, 'a'..='z' | 'A'..='Z' |'0'..='9' | '-')) { + Ok(Self(param.to_string())) + } else { + Err(()) + } + } +} diff --git a/src/mail.rs b/src/mail.rs index 126adace..476968fd 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -17,7 +17,7 @@ use crate::{ encode_jwt, generate_delete_claims, generate_emergency_access_invite_claims, generate_invite_claims, generate_verify_email_claims, }, - db::models::{Device, DeviceType, MembershipId, OrganizationId, User}, + db::models::{Device, DeviceType, MembershipId, OrganizationId, User, UserId}, error::Error, CONFIG, }; @@ -166,8 +166,8 @@ pub async fn send_password_hint(address: &str, hint: Option) -> EmptyRes send_email(address, &subject, body_html, body_text).await } -pub async fn send_delete_account(address: &str, uuid: &str) -> EmptyResult { - let claims = generate_delete_claims(uuid.to_string()); +pub async fn send_delete_account(address: &str, user_id: &UserId) -> EmptyResult { + let claims = generate_delete_claims(user_id.to_string()); let delete_token = encode_jwt(&claims); let (subject, body_html, body_text) = get_text( @@ -175,7 +175,7 @@ pub async fn send_delete_account(address: &str, uuid: &str) -> EmptyResult { json!({ "url": CONFIG.domain(), "img_src": CONFIG._smtp_img_src(), - "user_id": uuid, + "user_id": user_id, "email": percent_encode(address.as_bytes(), NON_ALPHANUMERIC).to_string(), "token": delete_token, }), @@ -184,8 +184,8 @@ pub async fn send_delete_account(address: &str, uuid: &str) -> EmptyResult { send_email(address, &subject, body_html, body_text).await } -pub async fn send_verify_email(address: &str, uuid: &str) -> EmptyResult { - let claims = generate_verify_email_claims(uuid.to_string()); +pub async fn send_verify_email(address: &str, user_id: &UserId) -> EmptyResult { + let claims = generate_verify_email_claims(user_id.to_string()); let verify_email_token = encode_jwt(&claims); let (subject, body_html, body_text) = get_text( @@ -193,7 +193,7 @@ pub async fn send_verify_email(address: &str, uuid: &str) -> EmptyResult { json!({ "url": CONFIG.domain(), "img_src": CONFIG._smtp_img_src(), - "user_id": uuid, + "user_id": user_id, "email": percent_encode(address.as_bytes(), NON_ALPHANUMERIC).to_string(), "token": verify_email_token, }), @@ -214,8 +214,8 @@ pub async fn send_welcome(address: &str) -> EmptyResult { send_email(address, &subject, body_html, body_text).await } -pub async fn send_welcome_must_verify(address: &str, uuid: &str) -> EmptyResult { - let claims = generate_verify_email_claims(uuid.to_string()); +pub async fn send_welcome_must_verify(address: &str, user_id: &UserId) -> EmptyResult { + let claims = generate_verify_email_claims(user_id.to_string()); let verify_email_token = encode_jwt(&claims); let (subject, body_html, body_text) = get_text( @@ -223,7 +223,7 @@ pub async fn send_welcome_must_verify(address: &str, uuid: &str) -> EmptyResult json!({ "url": CONFIG.domain(), "img_src": CONFIG._smtp_img_src(), - "user_id": uuid, + "user_id": user_id, "token": verify_email_token, }), )?; @@ -265,7 +265,7 @@ pub async fn send_invite( invited_by_email: Option, ) -> EmptyResult { let claims = generate_invite_claims( - user.uuid.clone(), + user.uuid.to_string(), user.email.clone(), org_id.clone(), member_id.clone(),