From d6e0a74f57c9831931c476b738228278ed64a737 Mon Sep 17 00:00:00 2001 From: Timshel Date: Fri, 16 Oct 2026 13:29:10 +0200 Subject: [PATCH] Sends: store auth_type --- Cargo.lock | 12 +++++ Cargo.toml | 1 + .../2026-06-18-120000_add_sends_emails/up.sql | 3 ++ .../2026-06-18-120000_add_sends_emails/up.sql | 3 ++ .../2026-06-18-120000_add_sends_emails/up.sql | 6 ++- src/api/core/sends.rs | 44 ++++++++++++------- src/db/models/mod.rs | 2 +- src/db/models/send.rs | 18 +++++--- src/db/schema.rs | 1 + 9 files changed, 65 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c9d9237..3f9b8ee0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4961,6 +4961,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -5991,6 +6002,7 @@ dependencies = [ "semver", "serde", "serde_json", + "serde_repr", "subtle", "svg-hush", "syslog", diff --git a/Cargo.toml b/Cargo.toml index 764fbd53..963e0117 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,6 +105,7 @@ tokio-util = { version = "0.7.19", features = ["compat"] } # A generic serialization/deserialization framework serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.151" +serde_repr = "0.1.21" # A safe, extensible ORM and Query builder diesel = { version = "2.3.13", features = ["chrono", "r2d2", "numeric"] } diff --git a/migrations/mysql/2026-06-18-120000_add_sends_emails/up.sql b/migrations/mysql/2026-06-18-120000_add_sends_emails/up.sql index 5b988ffd..324a7b4f 100644 --- a/migrations/mysql/2026-06-18-120000_add_sends_emails/up.sql +++ b/migrations/mysql/2026-06-18-120000_add_sends_emails/up.sql @@ -1,3 +1,6 @@ +ALTER TABLE sends ADD COLUMN auth_type INTEGER NOT NULL DEFAULT 2; +UPDATE sends SET auth_type = 1 WHERE password_hash is NOT NULL; + ALTER TABLE sends ADD COLUMN emails TEXT; CREATE TABLE sends_otp ( diff --git a/migrations/postgresql/2026-06-18-120000_add_sends_emails/up.sql b/migrations/postgresql/2026-06-18-120000_add_sends_emails/up.sql index 9f7a0921..94022002 100644 --- a/migrations/postgresql/2026-06-18-120000_add_sends_emails/up.sql +++ b/migrations/postgresql/2026-06-18-120000_add_sends_emails/up.sql @@ -1,3 +1,6 @@ +ALTER TABLE sends ADD COLUMN auth_type INTEGER NOT NULL DEFAULT 2; +UPDATE sends SET auth_type = 1 WHERE password_hash is NOT NULL; + ALTER TABLE sends ADD COLUMN emails TEXT; CREATE TABLE sends_otp ( diff --git a/migrations/sqlite/2026-06-18-120000_add_sends_emails/up.sql b/migrations/sqlite/2026-06-18-120000_add_sends_emails/up.sql index 28b6daab..582c8378 100644 --- a/migrations/sqlite/2026-06-18-120000_add_sends_emails/up.sql +++ b/migrations/sqlite/2026-06-18-120000_add_sends_emails/up.sql @@ -8,6 +8,7 @@ CREATE TABLE sends ( notes TEXT, atype INTEGER NOT NULL, + auth_type INTEGER NOT NULL, data TEXT NOT NULL, akey TEXT NOT NULL, password_hash BLOB, @@ -30,11 +31,12 @@ CREATE TABLE sends ( INSERT INTO sends( uuid, user_uuid, name, notes, atype, data, akey, password_hash, password_salt, password_iter, max_access_count, access_count, creation_date, revision_date, expiration_date, deletion_date, - disabled, hide_email + disabled, hide_email, auth_type ) SELECT uuid, user_uuid, name, notes, atype, data, akey, password_hash, password_salt, password_iter, max_access_count, access_count, creation_date, revision_date, expiration_date, deletion_date, disabled, - CASE WHEN hide_email IS NOT NULL THEN hide_email ELSE false END + CASE WHEN hide_email IS NOT NULL THEN hide_email ELSE false END, + CASE WHEN password_hash IS NOT NULL THEN 1 ELSE 2 END FROM sends_old WHERE user_uuid IS NOT NULL; DROP TABLE sends_old; diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs index 0b45f319..b5f9047b 100644 --- a/src/api/core/sends.rs +++ b/src/api/core/sends.rs @@ -16,7 +16,7 @@ use crate::{ config::PathType, db::{ DbConn, DbPool, - models::{Device, OrgPolicy, OrgPolicyType, Send, SendFileId, SendId, SendType, UserId}, + models::{Device, OrgPolicy, OrgPolicyType, Send, SendAuthType, SendFileId, SendId, SendType, UserId}, }, util::{NumberOrString, save_temp_file}, }; @@ -69,7 +69,8 @@ pub async fn purge_sends(pool: DbPool) { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct SendData { - r#type: i32, + r#type: SendType, + auth_type: SendAuthType, pub key: String, password: Option, max_access_count: Option, @@ -127,12 +128,9 @@ async fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, c } fn create_send(data: SendData, user_id: UserId) -> ApiResult { - let data_val = if data.r#type == SendType::Text as i32 { - data.text - } else if data.r#type == SendType::File as i32 { - data.file - } else { - err!("Invalid Send type") + let data_val = match data.r#type { + SendType::Text => data.text, + SendType::File => data.file, }; let data_str = if let Some(mut d) = data_val { @@ -159,6 +157,7 @@ fn create_send(data: SendData, user_id: UserId) -> ApiResult { let mut send = Send::new( data.r#type, + data.auth_type, user_id, data.name, data.notes, @@ -205,7 +204,7 @@ async fn post_send(data: Json, headers: Headers, conn: DbConn, nt: Not let data: SendData = data.into_inner(); enforce_disable_hide_email_policy(&data, &headers, &conn).await?; - if data.r#type == SendType::File as i32 { + if data.r#type == SendType::File { err!("File sends should use /api/sends/file/v2") } @@ -235,7 +234,7 @@ async fn post_send_file_v2(data: Json, headers: Headers, conn: DbConn) let data = data.into_inner(); - if data.r#type != SendType::File as i32 { + if data.r#type != SendType::File { err!("Send content is not a file"); } @@ -502,7 +501,7 @@ async fn update_send_from_data( err!("Send is not owned by user") } - if send.atype != data.r#type { + if send.atype != data.r#type as i32 { err!("Sends can't change type") } @@ -514,7 +513,7 @@ async fn update_send_from_data( // When updating a file Send, we receive nulls in the File field, as it's immutable, // so we only need to update the data field in the Text case - if data.r#type == SendType::Text as i32 { + if data.r#type == SendType::Text { let data_str = if let Some(mut d) = data.text { d.as_object_mut().and_then(|d| d.remove("response")); serde_json::to_string(&d)? @@ -535,11 +534,24 @@ async fn update_send_from_data( send.expiration_date = data.expiration_date.map(|d| d.naive_utc()); send.hide_email = data.hide_email.unwrap_or(false); send.disabled = data.disabled; - send.emails = data.emails.map(|e| e.trim().to_lowercase()).filter(|e| !e.is_empty()); - // Only change the value if it's present - if let Some(password) = data.password { - send.set_password(Some(&password)); + send.auth_type = data.auth_type as i32; + match data.auth_type { + SendAuthType::Email => { + send.emails = data.emails.map(|e| e.trim().to_lowercase()).filter(|e| !e.is_empty()); + send.set_password(None); + } + SendAuthType::Password => { + send.emails = None; + // Only change the value if it's present + if let Some(password) = data.password { + send.set_password(Some(&password)); + } + } + SendAuthType::None => { + send.emails = None; + send.set_password(None); + } } send.save(conn).await?; diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index a7a99c28..b726a326 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -34,7 +34,7 @@ pub use self::organization::{ Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, Organization, OrganizationApiKey, OrganizationId, }; -pub use self::send::{Send, SendFileId, SendId, SendOTP, SendType}; +pub use self::send::{Send, SendAuthType, SendFileId, SendId, SendOTP, SendType}; pub use self::sso_auth::{OIDCAuthenticatedUser, OIDCCodeResponseError, SsoAuth}; pub use self::two_factor::{TwoFactor, TwoFactorType}; pub use self::two_factor_duo_context::TwoFactorDuoContext; diff --git a/src/db/models/send.rs b/src/db/models/send.rs index 5c1677d0..d055bf12 100644 --- a/src/db/models/send.rs +++ b/src/db/models/send.rs @@ -6,6 +6,7 @@ use derive_more::{AsRef, Deref, Display, From}; use diesel::prelude::*; use macros::{IdFromParam, UuidFromParam}; use serde_json::Value; +use serde_repr::Deserialize_repr; use uuid::Uuid; use crate::{ @@ -35,6 +36,7 @@ pub struct Send { pub notes: Option, pub atype: i32, + pub auth_type: i32, pub data: String, pub akey: String, pub password_hash: Option>, @@ -54,14 +56,16 @@ pub struct Send { pub hide_email: bool, } -#[derive(Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)] +#[derive(Copy, Clone, Deserialize_repr, PartialEq, Eq, num_derive::FromPrimitive)] +#[repr(u8)] pub enum SendType { Text = 0, File = 1, } -enum SendAuthType { - #[allow(dead_code)] +#[derive(Copy, Clone, Deserialize_repr, Eq, PartialEq, num_derive::FromPrimitive)] +#[repr(u8)] +pub enum SendAuthType { // Send requires email OTP verification Email = 0, // Send requires a password @@ -73,7 +77,8 @@ enum SendAuthType { impl Send { #[allow(clippy::too_many_arguments)] pub fn new( - atype: i32, + atype: SendType, + auth_type: SendAuthType, user_uuid: UserId, name: String, notes: Option, @@ -93,7 +98,8 @@ impl Send { user_uuid, name, notes, - atype, + atype: atype as i32, + auth_type: auth_type as i32, data, akey, password_hash: None, @@ -175,7 +181,7 @@ impl Send { "maxAccessCount": self.max_access_count, "accessCount": self.access_count, "password": self.password_hash.as_deref().map(|h| BASE64URL_NOPAD.encode(h)), - "authType": if self.password_hash.is_some() { SendAuthType::Password } else if self.emails.is_some() { SendAuthType::Email } else { SendAuthType::None } as i32, + "authType": self.auth_type, "disabled": self.disabled, "hideEmail": self.hide_email, "emails": self.emails, diff --git a/src/db/schema.rs b/src/db/schema.rs index d45e96ff..3bdc9d83 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -137,6 +137,7 @@ table! { name -> Text, notes -> Nullable, atype -> Integer, + auth_type -> Integer, data -> Text, akey -> Text, password_hash -> Nullable,