From 423a08e749fd8cf9846867cc771f75789345bb8a Mon Sep 17 00:00:00 2001 From: Stefan Melmuk Date: Sat, 21 Dec 2024 17:46:36 +0100 Subject: [PATCH] add device_id newtype --- src/api/admin.rs | 4 +- src/api/core/accounts.rs | 26 ++++----- src/api/core/organizations.rs | 2 +- src/api/core/sends.rs | 6 +-- src/api/core/two_factor/duo_oidc.rs | 6 +-- src/api/identity.rs | 2 +- src/api/notifications.rs | 32 +++++------ src/api/push.rs | 19 ++++--- src/auth.rs | 4 +- src/db/models/auth_request.rs | 8 +-- src/db/models/device.rs | 74 +++++++++++++++++++++++--- src/db/models/mod.rs | 2 +- src/db/models/two_factor_incomplete.rs | 23 +++++--- 13 files changed, 142 insertions(+), 66 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index 673af545..8c3f178a 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -418,7 +418,7 @@ async fn delete_user(uuid: UserId, token: AdminToken, mut conn: DbConn) -> Empty 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; + nt.send_logout(&user, &DeviceId::empty()).await; if CONFIG.push_enabled() { for device in Device::find_push_devices_by_user(&user.uuid, &mut conn).await { @@ -444,7 +444,7 @@ async fn disable_user(uuid: UserId, _token: AdminToken, mut conn: DbConn, nt: No let save_result = user.save(&mut conn).await; - nt.send_logout(&user, None).await; + nt.send_logout(&user, &DeviceId::empty()).await; save_result } diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index c18179e3..27c29b53 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -374,7 +374,7 @@ async fn post_password(data: Json, headers: Headers, mut conn: D // Prevent logging out the client where the user requested this endpoint from. // If you do logout the user it will causes issues at the client side. // Adding the device uuid will prevent this. - nt.send_logout(&user, Some(headers.device.uuid)).await; + nt.send_logout(&user, &headers.device.uuid).await; save_result } @@ -434,7 +434,7 @@ async fn post_kdf(data: Json, headers: Headers, mut conn: DbConn, user.set_password(&data.new_master_password_hash, Some(data.key), true, None); let save_result = user.save(&mut conn).await; - nt.send_logout(&user, Some(headers.device.uuid)).await; + nt.send_logout(&user, &headers.device.uuid).await; save_result } @@ -646,7 +646,7 @@ async fn post_rotatekey(data: Json, headers: Headers, mut conn: DbConn, // Prevent logging out the client where the user requested this endpoint from. // If you do logout the user it will causes issues at the client side. // Adding the device uuid will prevent this. - nt.send_logout(&user, Some(headers.device.uuid)).await; + nt.send_logout(&user, &headers.device.uuid).await; save_result } @@ -662,7 +662,7 @@ async fn post_sstamp(data: Json, headers: Headers, mut conn: user.reset_security_stamp(); let save_result = user.save(&mut conn).await; - nt.send_logout(&user, None).await; + nt.send_logout(&user, &DeviceId::empty()).await; save_result } @@ -770,7 +770,7 @@ async fn post_email(data: Json, headers: Headers, mut conn: DbC let save_result = user.save(&mut conn).await; - nt.send_logout(&user, None).await; + nt.send_logout(&user, &DeviceId::empty()).await; save_result } @@ -1028,7 +1028,7 @@ async fn get_known_device(device: KnownDevice, mut conn: DbConn) -> JsonResult { struct KnownDevice { email: String, - uuid: String, + uuid: DeviceId, } #[rocket::async_trait] @@ -1051,7 +1051,7 @@ impl<'r> FromRequest<'r> for KnownDevice { }; let uuid = if let Some(uuid) = req.headers().get_one("X-Device-Identifier") { - uuid.to_string() + uuid.to_string().into() } else { return Outcome::Error((Status::BadRequest, "X-Device-Identifier value is required")); }; @@ -1108,7 +1108,7 @@ async fn put_device_token(uuid: &str, data: Json, headers: Headers, m } #[put("/devices/identifier//clear-token")] -async fn put_clear_device_token(uuid: &str, mut conn: DbConn) -> EmptyResult { +async fn put_clear_device_token(uuid: DeviceId, mut conn: DbConn) -> EmptyResult { // This only clears push token // https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109 // https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37 @@ -1117,8 +1117,8 @@ async fn put_clear_device_token(uuid: &str, mut conn: DbConn) -> EmptyResult { return Ok(()); } - if let Some(device) = Device::find_by_uuid(uuid, &mut conn).await { - Device::clear_push_token_by_uuid(uuid, &mut conn).await?; + if let Some(device) = Device::find_by_uuid(&uuid, &mut conn).await { + Device::clear_push_token_by_uuid(&uuid, &mut conn).await?; unregister_push_device(device.push_uuid).await?; } @@ -1127,7 +1127,7 @@ async fn put_clear_device_token(uuid: &str, mut conn: DbConn) -> EmptyResult { // On upstream server, both PUT and POST are declared. Implementing the POST method in case it would be useful somewhere #[post("/devices/identifier//clear-token")] -async fn post_clear_device_token(uuid: &str, conn: DbConn) -> EmptyResult { +async fn post_clear_device_token(uuid: DeviceId, conn: DbConn) -> EmptyResult { put_clear_device_token(uuid, conn).await } @@ -1135,7 +1135,7 @@ async fn post_clear_device_token(uuid: &str, conn: DbConn) -> EmptyResult { #[serde(rename_all = "camelCase")] struct AuthRequestRequest { access_code: String, - device_identifier: String, + device_identifier: DeviceId, email: String, public_key: String, // Not used for now @@ -1215,7 +1215,7 @@ async fn get_auth_request(uuid: &str, headers: Headers, mut conn: DbConn) -> Jso #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] struct AuthResponseRequest { - device_identifier: String, + device_identifier: DeviceId, key: String, master_password_hash: Option, request_approved: bool, diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 15168d94..38206745 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -2910,7 +2910,7 @@ async fn put_reset_password( user.set_password(reset_request.new_master_password_hash.as_str(), Some(reset_request.key), true, None); user.save(&mut conn).await?; - nt.send_logout(&user, None).await; + nt.send_logout(&user, &DeviceId::empty()).await; log_event( EventType::OrganizationUserAdminResetPassword as i32, diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs index cf217e9f..6eb923b6 100644 --- a/src/api/core/sends.rs +++ b/src/api/core/sends.rs @@ -485,7 +485,7 @@ async fn post_access( UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await, - &String::from("00000000-0000-0000-0000-000000000000"), + &DeviceId::empty(), &mut conn, ) .await; @@ -542,7 +542,7 @@ async fn post_access_file( UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await, - &String::from("00000000-0000-0000-0000-000000000000"), + &DeviceId::empty(), &mut conn, ) .await; @@ -635,7 +635,7 @@ pub async fn update_send_from_data( send.save(conn).await?; if ut != UpdateType::None { - nt.send_send_update(ut, send, &send.update_users_revision(conn).await, &headers.device.uuid, conn).await; + nt.send_send_update(ut, send, &send.update_users_revision(conn).await, &headers.device.uuid, conn).await } Ok(()) } diff --git a/src/api/core/two_factor/duo_oidc.rs b/src/api/core/two_factor/duo_oidc.rs index eb7fb329..e90d229f 100644 --- a/src/api/core/two_factor/duo_oidc.rs +++ b/src/api/core/two_factor/duo_oidc.rs @@ -10,7 +10,7 @@ use crate::{ api::{core::two_factor::duo::get_duo_keys_email, EmptyResult}, crypto, db::{ - models::{EventType, TwoFactorDuoContext}, + models::{DeviceId, EventType, TwoFactorDuoContext}, DbConn, DbPool, }, error::Error, @@ -379,7 +379,7 @@ fn make_callback_url(client_name: &str) -> Result { pub async fn get_duo_auth_url( email: &str, client_id: &str, - device_identifier: &String, + device_identifier: &DeviceId, conn: &mut DbConn, ) -> Result { let (ik, sk, _, host) = get_duo_keys_email(email, conn).await?; @@ -417,7 +417,7 @@ pub async fn validate_duo_login( email: &str, two_factor_token: &str, client_id: &str, - device_identifier: &str, + device_identifier: &DeviceId, conn: &mut DbConn, ) -> EmptyResult { // Result supplied to us by clients in the form "|" diff --git a/src/api/identity.rs b/src/api/identity.rs index c1506adb..5cdef879 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -747,7 +747,7 @@ struct ConnectData { #[field(name = uncased("device_identifier"))] #[field(name = uncased("deviceidentifier"))] - device_identifier: Option, + device_identifier: Option, #[field(name = uncased("device_name"))] #[field(name = uncased("devicename"))] device_name: Option, diff --git a/src/api/notifications.rs b/src/api/notifications.rs index 6653799a..6c1e2b49 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, CollectionId, Folder, Send as DbSend, User, UserId}, + models::{Cipher, CollectionId, DeviceId, Folder, Send as DbSend, User, UserId}, DbConn, }, Error, CONFIG, @@ -347,7 +347,7 @@ impl WebSocketUsers { let data = create_update( vec![("UserId".into(), user.uuid.to_string().into()), ("Date".into(), serialize_date(user.updated_at))], ut, - None, + DeviceId::empty(), ); if CONFIG.enable_websocket() { @@ -359,7 +359,7 @@ impl WebSocketUsers { } } - pub async fn send_logout(&self, user: &User, acting_device_uuid: Option) { + pub async fn send_logout(&self, user: &User, acting_device_uuid: &DeviceId) { // Skip any processing if both WebSockets and Push are not active if *NOTIFICATIONS_DISABLED { return; @@ -375,7 +375,7 @@ impl WebSocketUsers { } if CONFIG.push_enabled() { - push_logout(user, acting_device_uuid); + push_logout(user, acting_device_uuid.clone()); } } @@ -383,7 +383,7 @@ impl WebSocketUsers { &self, ut: UpdateType, folder: &Folder, - acting_device_uuid: &String, + acting_device_uuid: &DeviceId, conn: &mut DbConn, ) { // Skip any processing if both WebSockets and Push are not active @@ -397,7 +397,7 @@ impl WebSocketUsers { ("RevisionDate".into(), serialize_date(folder.updated_at)), ], ut, - Some(acting_device_uuid.into()), + acting_device_uuid.clone(), ); if CONFIG.enable_websocket() { @@ -414,7 +414,7 @@ impl WebSocketUsers { ut: UpdateType, cipher: &Cipher, user_uuids: &[UserId], - acting_device_uuid: &String, + acting_device_uuid: &DeviceId, collection_uuids: Option>, conn: &mut DbConn, ) { @@ -444,7 +444,7 @@ impl WebSocketUsers { ("RevisionDate".into(), revision_date), ], ut, - Some(acting_device_uuid.into()), + acting_device_uuid.clone(), ); if CONFIG.enable_websocket() { @@ -463,7 +463,7 @@ impl WebSocketUsers { ut: UpdateType, send: &DbSend, user_uuids: &[UserId], - acting_device_uuid: &String, + acting_device_uuid: &DeviceId, conn: &mut DbConn, ) { // Skip any processing if both WebSockets and Push are not active @@ -479,7 +479,7 @@ impl WebSocketUsers { ("RevisionDate".into(), serialize_date(send.revision_date)), ], ut, - None, + acting_device_uuid.clone(), ); if CONFIG.enable_websocket() { @@ -496,7 +496,7 @@ impl WebSocketUsers { &self, user_uuid: &UserId, auth_request_uuid: &String, - acting_device_uuid: &String, + acting_device_uuid: &DeviceId, conn: &mut DbConn, ) { // Skip any processing if both WebSockets and Push are not active @@ -506,7 +506,7 @@ impl WebSocketUsers { let data = create_update( vec![("Id".into(), auth_request_uuid.clone().into()), ("UserId".into(), user_uuid.to_string().into())], UpdateType::AuthRequest, - Some(acting_device_uuid.to_string()), + acting_device_uuid.clone(), ); if CONFIG.enable_websocket() { self.send_update(user_uuid, &data).await; @@ -521,7 +521,7 @@ impl WebSocketUsers { &self, user_uuid: &UserId, auth_response_uuid: &str, - approving_device_uuid: String, + approving_device_uuid: DeviceId, conn: &mut DbConn, ) { // Skip any processing if both WebSockets and Push are not active @@ -531,7 +531,7 @@ impl WebSocketUsers { let data = create_update( vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.to_string().into())], UpdateType::AuthRequestResponse, - approving_device_uuid.clone().into(), + approving_device_uuid.clone(), ); if CONFIG.enable_websocket() { self.send_update(user_uuid, &data).await; @@ -585,7 +585,7 @@ impl AnonymousWebSocketSubscriptions { ] ] */ -fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_uuid: Option) -> Vec { +fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_uuid: DeviceId) -> Vec { use rmpv::Value as V; let value = V::Array(vec![ @@ -594,7 +594,7 @@ fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_uui V::Nil, "ReceiveMessage".into(), V::Array(vec![V::Map(vec![ - ("ContextId".into(), acting_device_uuid.map(|v| v.into()).unwrap_or_else(|| V::Nil)), + ("ContextId".into(), acting_device_uuid.to_string().into()), ("Type".into(), (ut as i32).into()), ("Payload".into(), payload.into()), ])]), diff --git a/src/api/push.rs b/src/api/push.rs index bf9601a5..25fe60fe 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, UserId}, + db::models::{Cipher, Device, DeviceId, Folder, Send, User, UserId}, http_client::make_http_request, util::format_date, CONFIG, @@ -148,7 +148,7 @@ pub async fn unregister_push_device(push_uuid: Option) -> EmptyResult { pub async fn push_cipher_update( ut: UpdateType, cipher: &Cipher, - acting_device_uuid: &String, + acting_device_uuid: &DeviceId, conn: &mut crate::db::DbConn, ) { // We shouldn't send a push notification on cipher update if the cipher belongs to an organization, this isn't implemented in the upstream server too. @@ -178,8 +178,8 @@ pub async fn push_cipher_update( } } -pub fn push_logout(user: &User, acting_device_uuid: Option) { - let acting_device_uuid: Value = acting_device_uuid.map(|v| v.into()).unwrap_or_else(|| Value::Null); +pub fn push_logout(user: &User, acting_device_uuid: DeviceId) { + let acting_device_uuid: Value = acting_device_uuid.to_string().into(); tokio::task::spawn(send_to_push_relay(json!({ "userId": user.uuid, @@ -211,7 +211,7 @@ pub fn push_user_update(ut: UpdateType, user: &User) { pub async fn push_folder_update( ut: UpdateType, folder: &Folder, - acting_device_uuid: &String, + acting_device_uuid: &DeviceId, conn: &mut crate::db::DbConn, ) { if Device::check_user_has_push_device(&folder.user_uuid, conn).await { @@ -230,7 +230,12 @@ pub async fn push_folder_update( } } -pub async fn push_send_update(ut: UpdateType, send: &Send, acting_device_uuid: &String, conn: &mut crate::db::DbConn) { +pub async fn push_send_update( + ut: UpdateType, + send: &Send, + acting_device_uuid: &DeviceId, + conn: &mut crate::db::DbConn, +) { if let Some(s) = &send.user_uuid { if Device::check_user_has_push_device(s, conn).await { tokio::task::spawn(send_to_push_relay(json!({ @@ -303,7 +308,7 @@ pub async fn push_auth_request(user_uuid: UserId, auth_request_uuid: String, con pub async fn push_auth_response( user_uuid: UserId, auth_request_uuid: String, - approving_device_uuid: String, + approving_device_uuid: DeviceId, conn: &mut crate::db::DbConn, ) { if Device::check_user_has_push_device(&user_uuid, conn).await { diff --git a/src/auth.rs b/src/auth.rs index a7f403fb..b86d461b 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -14,7 +14,7 @@ use std::{ net::IpAddr, }; -use crate::db::models::{AttachmentId, CipherId, CollectionId, MembershipId, OrganizationId, UserId}; +use crate::db::models::{AttachmentId, CipherId, CollectionId, DeviceId, MembershipId, OrganizationId, UserId}; use crate::{error::Error, CONFIG}; const JWT_ALGORITHM: Algorithm = Algorithm::RS256; @@ -172,7 +172,7 @@ pub struct LoginJwtClaims { // user security_stamp pub sstamp: String, // device uuid - pub device: String, + pub device: DeviceId, // [ "api", "offline_access" ] pub scope: Vec, // [ "Application" ] diff --git a/src/db/models/auth_request.rs b/src/db/models/auth_request.rs index 3f3a53a9..eab91d87 100644 --- a/src/db/models/auth_request.rs +++ b/src/db/models/auth_request.rs @@ -1,4 +1,4 @@ -use super::{OrganizationId, UserId}; +use super::{DeviceId, OrganizationId, UserId}; use crate::crypto::ct_eq; use chrono::{NaiveDateTime, Utc}; @@ -12,11 +12,11 @@ db_object! { pub user_uuid: UserId, pub organization_uuid: Option, - pub request_device_identifier: String, + pub request_device_identifier: DeviceId, pub device_type: i32, // https://github.com/bitwarden/server/blob/master/src/Core/Enums/DeviceType.cs pub request_ip: String, - pub response_device_id: Option, + pub response_device_id: Option, pub access_code: String, pub public_key: String, @@ -35,7 +35,7 @@ db_object! { impl AuthRequest { pub fn new( user_uuid: UserId, - request_device_identifier: String, + request_device_identifier: DeviceId, device_type: i32, request_ip: String, access_code: String, diff --git a/src/db/models/device.rs b/src/db/models/device.rs index 634d7c4f..267a3141 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -1,4 +1,10 @@ use chrono::{NaiveDateTime, Utc}; +use rocket::request::FromParam; +use std::{ + borrow::Borrow, + fmt::{Display, Formatter}, + ops::Deref, +}; use super::UserId; use crate::{crypto, CONFIG}; @@ -10,7 +16,7 @@ db_object! { #[diesel(treat_none_as_null = true)] #[diesel(primary_key(uuid, user_uuid))] pub struct Device { - pub uuid: String, + pub uuid: DeviceId, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, @@ -29,7 +35,7 @@ db_object! { /// Local methods impl Device { - pub fn new(uuid: String, user_uuid: UserId, name: String, atype: i32) -> Self { + pub fn new(uuid: DeviceId, user_uuid: UserId, name: String, atype: i32) -> Self { let now = Utc::now().naive_utc(); Self { @@ -159,7 +165,7 @@ impl Device { }} } - pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &UserId, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid_and_user(uuid: &DeviceId, user_uuid: &UserId, conn: &mut DbConn) -> Option { db_run! { conn: { devices::table .filter(devices::uuid.eq(uuid)) @@ -180,7 +186,7 @@ impl Device { }} } - pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid(uuid: &DeviceId, conn: &mut DbConn) -> Option { db_run! { conn: { devices::table .filter(devices::uuid.eq(uuid)) @@ -190,7 +196,7 @@ impl Device { }} } - pub async fn clear_push_token_by_uuid(uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn clear_push_token_by_uuid(uuid: &DeviceId, conn: &mut DbConn) -> EmptyResult { db_run! { conn: { diesel::update(devices::table) .filter(devices::uuid.eq(uuid)) @@ -273,8 +279,8 @@ pub enum DeviceType { LinuxCLI = 25, } -impl fmt::Display for DeviceType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl Display for DeviceType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { DeviceType::Android => write!(f, "Android"), DeviceType::Ios => write!(f, "iOS"), @@ -339,3 +345,57 @@ impl DeviceType { } } } + +#[derive(DieselNewType, FromForm, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct DeviceId(String); + +impl DeviceId { + pub fn empty() -> Self { + Self(String::from("00000000-0000-0000-0000-000000000000")) + } +} + +impl AsRef for DeviceId { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl Deref for DeviceId { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Borrow for DeviceId { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl Display for DeviceId { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for DeviceId { + fn from(raw: String) -> Self { + Self(raw) + } +} + +impl<'r> FromParam<'r> for DeviceId { + 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/db/models/mod.rs b/src/db/models/mod.rs index c59c6f13..9b2105a8 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -20,7 +20,7 @@ pub use self::attachment::{Attachment, AttachmentId}; pub use self::auth_request::AuthRequest; pub use self::cipher::{Cipher, CipherId, RepromptType}; pub use self::collection::{Collection, CollectionCipher, CollectionId, CollectionUser}; -pub use self::device::{Device, DeviceType}; +pub use self::device::{Device, DeviceId, DeviceType}; pub use self::emergency_access::{EmergencyAccess, EmergencyAccessStatus, EmergencyAccessType}; pub use self::event::{Event, EventType}; pub use self::favorite::Favorite; diff --git a/src/db/models/two_factor_incomplete.rs b/src/db/models/two_factor_incomplete.rs index 6c6cacb8..b8dc4ad7 100644 --- a/src/db/models/two_factor_incomplete.rs +++ b/src/db/models/two_factor_incomplete.rs @@ -3,7 +3,10 @@ use chrono::{NaiveDateTime, Utc}; use crate::{ api::EmptyResult, auth::ClientIp, - db::{models::UserId, DbConn}, + db::{ + models::{DeviceId, UserId}, + DbConn, + }, error::MapResult, CONFIG, }; @@ -17,7 +20,7 @@ db_object! { // 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. - pub device_uuid: String, + pub device_uuid: DeviceId, pub device_name: String, pub device_type: i32, pub login_time: NaiveDateTime, @@ -28,7 +31,7 @@ db_object! { impl TwoFactorIncomplete { pub async fn mark_incomplete( user_uuid: &UserId, - device_uuid: &str, + device_uuid: &DeviceId, device_name: &str, device_type: i32, ip: &ClientIp, @@ -61,7 +64,7 @@ impl TwoFactorIncomplete { }} } - pub async fn mark_complete(user_uuid: &UserId, device_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn mark_complete(user_uuid: &UserId, device_uuid: &DeviceId, conn: &mut DbConn) -> EmptyResult { if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() { return Ok(()); } @@ -69,7 +72,11 @@ impl TwoFactorIncomplete { Self::delete_by_user_and_device(user_uuid, device_uuid, conn).await } - pub async fn find_by_user_and_device(user_uuid: &UserId, device_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_user_and_device( + user_uuid: &UserId, + device_uuid: &DeviceId, + conn: &mut DbConn, + ) -> Option { db_run! { conn: { twofactor_incomplete::table .filter(twofactor_incomplete::user_uuid.eq(user_uuid)) @@ -94,7 +101,11 @@ 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: &UserId, device_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_by_user_and_device( + user_uuid: &UserId, + device_uuid: &DeviceId, + conn: &mut DbConn, + ) -> EmptyResult { db_run! { conn: { diesel::delete(twofactor_incomplete::table .filter(twofactor_incomplete::user_uuid.eq(user_uuid))