Browse Source

Sends: store auth_type

pull/7363/head
Timshel 5 days ago
parent
commit
d6e0a74f57
  1. 12
      Cargo.lock
  2. 1
      Cargo.toml
  3. 3
      migrations/mysql/2026-06-18-120000_add_sends_emails/up.sql
  4. 3
      migrations/postgresql/2026-06-18-120000_add_sends_emails/up.sql
  5. 6
      migrations/sqlite/2026-06-18-120000_add_sends_emails/up.sql
  6. 38
      src/api/core/sends.rs
  7. 2
      src/db/models/mod.rs
  8. 18
      src/db/models/send.rs
  9. 1
      src/db/schema.rs

12
Cargo.lock

@ -4961,6 +4961,17 @@ dependencies = [
"serde", "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]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.9" version = "0.6.9"
@ -5991,6 +6002,7 @@ dependencies = [
"semver", "semver",
"serde", "serde",
"serde_json", "serde_json",
"serde_repr",
"subtle", "subtle",
"svg-hush", "svg-hush",
"syslog", "syslog",

1
Cargo.toml

@ -105,6 +105,7 @@ tokio-util = { version = "0.7.19", features = ["compat"] }
# A generic serialization/deserialization framework # A generic serialization/deserialization framework
serde = { version = "1.0.229", features = ["derive"] } serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.151" serde_json = "1.0.151"
serde_repr = "0.1.21"
# A safe, extensible ORM and Query builder # A safe, extensible ORM and Query builder
diesel = { version = "2.3.13", features = ["chrono", "r2d2", "numeric"] } diesel = { version = "2.3.13", features = ["chrono", "r2d2", "numeric"] }

3
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; ALTER TABLE sends ADD COLUMN emails TEXT;
CREATE TABLE sends_otp ( CREATE TABLE sends_otp (

3
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; ALTER TABLE sends ADD COLUMN emails TEXT;
CREATE TABLE sends_otp ( CREATE TABLE sends_otp (

6
migrations/sqlite/2026-06-18-120000_add_sends_emails/up.sql

@ -8,6 +8,7 @@ CREATE TABLE sends (
notes TEXT, notes TEXT,
atype INTEGER NOT NULL, atype INTEGER NOT NULL,
auth_type INTEGER NOT NULL,
data TEXT NOT NULL, data TEXT NOT NULL,
akey TEXT NOT NULL, akey TEXT NOT NULL,
password_hash BLOB, password_hash BLOB,
@ -30,11 +31,12 @@ CREATE TABLE sends (
INSERT INTO sends( INSERT INTO sends(
uuid, user_uuid, name, notes, atype, data, akey, password_hash, password_salt, password_iter, 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, 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, ) 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, max_access_count, access_count, creation_date, revision_date, expiration_date, deletion_date,
disabled, 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; FROM sends_old WHERE user_uuid IS NOT NULL;
DROP TABLE sends_old; DROP TABLE sends_old;

38
src/api/core/sends.rs

@ -16,7 +16,7 @@ use crate::{
config::PathType, config::PathType,
db::{ db::{
DbConn, DbPool, 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}, util::{NumberOrString, save_temp_file},
}; };
@ -69,7 +69,8 @@ pub async fn purge_sends(pool: DbPool) {
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct SendData { pub struct SendData {
r#type: i32, r#type: SendType,
auth_type: SendAuthType,
pub key: String, pub key: String,
password: Option<String>, password: Option<String>,
max_access_count: Option<NumberOrString>, max_access_count: Option<NumberOrString>,
@ -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<Send> { fn create_send(data: SendData, user_id: UserId) -> ApiResult<Send> {
let data_val = if data.r#type == SendType::Text as i32 { let data_val = match data.r#type {
data.text SendType::Text => data.text,
} else if data.r#type == SendType::File as i32 { SendType::File => data.file,
data.file
} else {
err!("Invalid Send type")
}; };
let data_str = if let Some(mut d) = data_val { let data_str = if let Some(mut d) = data_val {
@ -159,6 +157,7 @@ fn create_send(data: SendData, user_id: UserId) -> ApiResult<Send> {
let mut send = Send::new( let mut send = Send::new(
data.r#type, data.r#type,
data.auth_type,
user_id, user_id,
data.name, data.name,
data.notes, data.notes,
@ -205,7 +204,7 @@ async fn post_send(data: Json<SendData>, headers: Headers, conn: DbConn, nt: Not
let data: SendData = data.into_inner(); let data: SendData = data.into_inner();
enforce_disable_hide_email_policy(&data, &headers, &conn).await?; 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") err!("File sends should use /api/sends/file/v2")
} }
@ -235,7 +234,7 @@ async fn post_send_file_v2(data: Json<SendData>, headers: Headers, conn: DbConn)
let data = data.into_inner(); 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"); err!("Send content is not a file");
} }
@ -502,7 +501,7 @@ async fn update_send_from_data(
err!("Send is not owned by user") 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") 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, // 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 // 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 { let data_str = if let Some(mut d) = data.text {
d.as_object_mut().and_then(|d| d.remove("response")); d.as_object_mut().and_then(|d| d.remove("response"));
serde_json::to_string(&d)? serde_json::to_string(&d)?
@ -535,12 +534,25 @@ async fn update_send_from_data(
send.expiration_date = data.expiration_date.map(|d| d.naive_utc()); send.expiration_date = data.expiration_date.map(|d| d.naive_utc());
send.hide_email = data.hide_email.unwrap_or(false); send.hide_email = data.hide_email.unwrap_or(false);
send.disabled = data.disabled; send.disabled = data.disabled;
send.emails = data.emails.map(|e| e.trim().to_lowercase()).filter(|e| !e.is_empty());
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 // Only change the value if it's present
if let Some(password) = data.password { if let Some(password) = data.password {
send.set_password(Some(&password)); send.set_password(Some(&password));
} }
}
SendAuthType::None => {
send.emails = None;
send.set_password(None);
}
}
send.save(conn).await?; send.save(conn).await?;
if ut != UpdateType::None { if ut != UpdateType::None {

2
src/db/models/mod.rs

@ -34,7 +34,7 @@ pub use self::organization::{
Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, Organization, OrganizationApiKey, Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, Organization, OrganizationApiKey,
OrganizationId, 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::sso_auth::{OIDCAuthenticatedUser, OIDCCodeResponseError, SsoAuth};
pub use self::two_factor::{TwoFactor, TwoFactorType}; pub use self::two_factor::{TwoFactor, TwoFactorType};
pub use self::two_factor_duo_context::TwoFactorDuoContext; pub use self::two_factor_duo_context::TwoFactorDuoContext;

18
src/db/models/send.rs

@ -6,6 +6,7 @@ use derive_more::{AsRef, Deref, Display, From};
use diesel::prelude::*; use diesel::prelude::*;
use macros::{IdFromParam, UuidFromParam}; use macros::{IdFromParam, UuidFromParam};
use serde_json::Value; use serde_json::Value;
use serde_repr::Deserialize_repr;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
@ -35,6 +36,7 @@ pub struct Send {
pub notes: Option<String>, pub notes: Option<String>,
pub atype: i32, pub atype: i32,
pub auth_type: i32,
pub data: String, pub data: String,
pub akey: String, pub akey: String,
pub password_hash: Option<Vec<u8>>, pub password_hash: Option<Vec<u8>>,
@ -54,14 +56,16 @@ pub struct Send {
pub hide_email: bool, 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 { pub enum SendType {
Text = 0, Text = 0,
File = 1, File = 1,
} }
enum SendAuthType { #[derive(Copy, Clone, Deserialize_repr, Eq, PartialEq, num_derive::FromPrimitive)]
#[allow(dead_code)] #[repr(u8)]
pub enum SendAuthType {
// Send requires email OTP verification // Send requires email OTP verification
Email = 0, Email = 0,
// Send requires a password // Send requires a password
@ -73,7 +77,8 @@ enum SendAuthType {
impl Send { impl Send {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
atype: i32, atype: SendType,
auth_type: SendAuthType,
user_uuid: UserId, user_uuid: UserId,
name: String, name: String,
notes: Option<String>, notes: Option<String>,
@ -93,7 +98,8 @@ impl Send {
user_uuid, user_uuid,
name, name,
notes, notes,
atype, atype: atype as i32,
auth_type: auth_type as i32,
data, data,
akey, akey,
password_hash: None, password_hash: None,
@ -175,7 +181,7 @@ impl Send {
"maxAccessCount": self.max_access_count, "maxAccessCount": self.max_access_count,
"accessCount": self.access_count, "accessCount": self.access_count,
"password": self.password_hash.as_deref().map(|h| BASE64URL_NOPAD.encode(h)), "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, "disabled": self.disabled,
"hideEmail": self.hide_email, "hideEmail": self.hide_email,
"emails": self.emails, "emails": self.emails,

1
src/db/schema.rs

@ -137,6 +137,7 @@ table! {
name -> Text, name -> Text,
notes -> Nullable<Text>, notes -> Nullable<Text>,
atype -> Integer, atype -> Integer,
auth_type -> Integer,
data -> Text, data -> Text,
akey -> Text, akey -> Text,
password_hash -> Nullable<Binary>, password_hash -> Nullable<Binary>,

Loading…
Cancel
Save