From fa2566d14fc745937ce104011475eca9e6c7a6f6 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Mon, 24 Aug 2026 19:38:23 +0200 Subject: [PATCH 01/20] Fix password change with newer web-vault (#7634) --- src/api/core/accounts.rs | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 0cb4d3c0..626f22bb 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -595,29 +595,52 @@ async fn post_keys(data: Json, headers: Headers, conn: DbConn) -> Json #[serde(rename_all = "camelCase")] struct ChangePassData { master_password_hash: String, - new_master_password_hash: String, master_password_hint: Option, - key: String, + authentication_data: Option, + unlock_data: Option, + + // Outdated values, might still be used by older clients + new_master_password_hash: Option, + key: Option, } #[post("/accounts/password", data = "")] async fn post_password(data: Json, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { let data: ChangePassData = data.into_inner(); - let mut user = headers.user; + let user = headers.user; if !user.check_valid_password(&data.master_password_hash) { err!("Invalid password") } - user.password_hint = clean_password_hint(data.master_password_hint.as_ref()); - enforce_password_hint_setting(user.password_hint.as_ref())?; - log_user_event(EventType::UserChangedPassword as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn) .await; + let (new_master_password_hash, new_key) = + if let (Some(unlock_data), Some(authentication_data)) = (data.unlock_data, data.authentication_data) { + if authentication_data.kdf != unlock_data.kdf { + err!("KDF settings must be equal for authentication and unlock") + } + + if user.email != authentication_data.salt || user.email != unlock_data.salt { + err!("Invalid master password salt") + } + + (authentication_data.master_password_authentication_hash, unlock_data.master_key_wrapped_user_key) + } else if let (Some(new_master_password_hash), Some(new_key)) = (data.new_master_password_hash, data.key) { + (new_master_password_hash, new_key) + } else { + err!("Invalid request!") + }; + + let mut user = user; + + user.password_hint = clean_password_hint(data.master_password_hint.as_ref()); + enforce_password_hint_setting(user.password_hint.as_ref())?; + user.set_password( - &data.new_master_password_hash, - Some(data.key), + &new_master_password_hash, + Some(new_key), true, Some(vec![ String::from("post_rotatekey"), From 10e044f563e6224eb0271419f7b7f4140791dc7f Mon Sep 17 00:00:00 2001 From: Matt Van Horn Date: Sat, 29 Aug 2026 08:01:45 -0700 Subject: [PATCH 02/20] chore: remove duplicate "the" in ciphers.rs comment (#7254) `src/api/core/ciphers.rs:170` comment said "similar to the the userDecryptionOptions" -> "similar to the userDecryptionOptions". Comment-only. Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> --- src/api/core/ciphers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 2b51fd0c..3e94ca7c 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -167,7 +167,7 @@ async fn sync(data: SyncData, headers: Headers, client_version: Option Date: Sat, 29 Aug 2026 11:01:51 -0400 Subject: [PATCH 03/20] Ignore reset-password auto-enroll when mail is disabled (#7585) Account recovery requires SMTP. When mail is off, treat the organization reset-password auto-enroll policy as inactive so invite/accept flows are not forced to supply a reset-password key. Fixes #7459 --- src/db/models/org_policy.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs index d501f8b9..2b45cd86 100644 --- a/src/db/models/org_policy.rs +++ b/src/db/models/org_policy.rs @@ -318,6 +318,13 @@ impl OrgPolicy { } pub async fn org_is_reset_password_auto_enroll(org_uuid: &OrganizationId, conn: &DbConn) -> bool { + // Account recovery depends on outbound mail. When SMTP is disabled, treat the + // auto-enroll policy as inactive so invites/registration are not forced to + // supply a reset-password key (see check_reset_password_applicable). + if !CONFIG.mail_enabled() { + return false; + } + match OrgPolicy::find_by_org_and_type(org_uuid, OrgPolicyType::ResetPassword, conn).await { Some(policy) => match serde_json::from_str::(&policy.data) { Ok(opts) => { From 923f5d0b5eb7e223855031e35ba9606372ff5fa3 Mon Sep 17 00:00:00 2001 From: Timshel Date: Sat, 29 Aug 2026 15:02:01 +0000 Subject: [PATCH 04/20] Fix migration for MariaDB 12.2.2 (#7265) Co-authored-by: Timshel --- .../up.sql | 42 +++++++++++++------ playwright/docker-compose.yml | 2 +- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/migrations/mysql/2024-03-13-170000_sso_users_cascade/up.sql b/migrations/mysql/2024-03-13-170000_sso_users_cascade/up.sql index 9e5e46df..8d1eb178 100644 --- a/migrations/mysql/2024-03-13-170000_sso_users_cascade/up.sql +++ b/migrations/mysql/2024-03-13-170000_sso_users_cascade/up.sql @@ -1,15 +1,31 @@ --- Dynamically create DROP FOREIGN KEY --- Some versions of MySQL or MariaDB might fail if the key doesn't exists --- This checks if the key exists, and if so, will drop it. -SET @drop_sso_fk = IF((SELECT true FROM information_schema.TABLE_CONSTRAINTS WHERE - CONSTRAINT_SCHEMA = DATABASE() AND - TABLE_NAME = 'sso_users' AND - CONSTRAINT_NAME = 'sso_users_ibfk_1' AND - CONSTRAINT_TYPE = 'FOREIGN KEY') = true, - 'ALTER TABLE sso_users DROP FOREIGN KEY sso_users_ibfk_1', - 'SELECT 1'); -PREPARE stmt FROM @drop_sso_fk; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; +SELECT if ( + EXISTS( + SELECT CONSTRAINT_NAME FROM information_schema.table_constraints + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'sso_users' + AND CONSTRAINT_TYPE = 'FOREIGN KEY' + AND CONSTRAINT_NAME = 'sso_users_ibfk_1' + ) + ,'ALTER TABLE sso_users DROP FOREIGN KEY `sso_users_ibfk_1`' + ,'SELECT "info: FK sso_users_ibfk_1 does not exist."' +) INTO @drop_stmt; +PREPARE drop_stmt FROM @drop_stmt; +EXECUTE drop_stmt; + +SELECT if ( + EXISTS( + SELECT CONSTRAINT_NAME FROM information_schema.table_constraints + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'sso_users' + AND CONSTRAINT_TYPE = 'FOREIGN KEY' + AND CONSTRAINT_NAME = '1' + ) + ,'ALTER TABLE sso_users DROP FOREIGN KEY `1`' + ,'SELECT "info: FK sso_users 1 does not exist."' +) INTO @drop_stmt; +PREPARE drop_stmt FROM @drop_stmt; +EXECUTE drop_stmt; + +DEALLOCATE PREPARE drop_stmt; ALTER TABLE sso_users ADD FOREIGN KEY(user_uuid) REFERENCES users(uuid) ON UPDATE CASCADE ON DELETE CASCADE; diff --git a/playwright/docker-compose.yml b/playwright/docker-compose.yml index 5dd04ff4..5bfc47a5 100644 --- a/playwright/docker-compose.yml +++ b/playwright/docker-compose.yml @@ -61,7 +61,7 @@ services: Mariadb: profiles: ["playwright"] container_name: playwright_mariadb - image: mariadb:11.2.4 + image: mariadb:12.2.2 env_file: test.env healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] From 2073c03092d328e4b5fd19882ecfbe491dc8b5c7 Mon Sep 17 00:00:00 2001 From: Timshel Date: Sat, 29 Aug 2026 15:02:22 +0000 Subject: [PATCH 05/20] Add SSO_SIGNUPS_ALLOWED (#7272) * Add SSO_SIGNUPS_ALLOWED * Fix regression with domain_allowed in SSO onboarding --------- Co-authored-by: Timshel --- .env.template | 3 +++ src/api/identity.rs | 28 +++++++++++++++++++++++++++- src/config.rs | 13 +++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/.env.template b/.env.template index 9fc29989..5f6f374c 100644 --- a/.env.template +++ b/.env.template @@ -518,6 +518,9 @@ ## Prevent users from logging in directly without going through SSO # SSO_ONLY=false +## Allow SSO flow to create account. You probably want to disable it when using a public provider. +# SSO_SIGNUPS_ALLOWED=true + ## On SSO Signup if a user with a matching email already exists make the association # SSO_SIGNUPS_MATCH_EMAIL=true diff --git a/src/api/identity.rs b/src/api/identity.rs index 23411dc7..2b1ddfb1 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -234,6 +234,24 @@ async fn sso_login( } ) } + Some((user, None)) + if user.private_key.is_none() + && !CONFIG.sso_signups_allowed() + && !CONFIG.is_email_domain_allowed(&user.email) + && !CONFIG.mail_enabled() + && Invitation::find_by_mail(&user.email, conn).await.is_none() => + { + error!( + "Login failure ({}), no invitation with email ({}) was found", + user_infos.identifier, user.email + ); + err_silent!( + "Missing invitation", + ErrorEvent { + event: EventType::UserFailedLogIn + } + ) + } Some((user, None)) if user.private_key.is_some() && !CONFIG.sso_signups_match_email() => { error!( "Login failure ({}), existing non SSO user ({}) with same email ({}) and association is disabled", @@ -281,7 +299,15 @@ async fn sso_login( // Will trigger 2FA flow if needed let (user, mut device, twofactor_token, sso_user) = match user_with_sso { None => { - if !CONFIG.is_email_domain_allowed(&user_infos.email) { + if !CONFIG.is_sso_signup_allowed(&user_infos.email) { + if CONFIG.signups_domains_whitelist().is_empty() { + err!( + "Signups are disabled. You will need an invitation", + ErrorEvent { + event: EventType::UserFailedLogIn + } + ); + } err!( "Email domain not allowed", ErrorEvent { diff --git a/src/config.rs b/src/config.rs index 72b58252..2502dd02 100644 --- a/src/config.rs +++ b/src/config.rs @@ -817,6 +817,8 @@ make_config! { sso_enabled: bool, true, def, false; /// Only SSO login |> Disable Email+Master Password login sso_only: bool, true, def, false; + /// Allow SSO flow to create account |> You probably want to disable it when using a public provider + sso_signups_allowed: bool, true, def, true; /// Allow email association |> Associate existing non-SSO user based on email sso_signups_match_email: bool, true, def, true; /// Allow unknown email verification status |> Allowing this with `SSO_SIGNUPS_MATCH_EMAIL=true` open potential account takeover. @@ -1544,6 +1546,17 @@ impl Config { } } + /// Tests whether SSO signup is allowed for an email address, taking into + /// account the sso_signups_allowed and signups_domains_whitelist settings. + pub fn is_sso_signup_allowed(&self, email: &str) -> bool { + if self.signups_domains_whitelist().is_empty() { + self.sso_signups_allowed() + } else { + // The whitelist setting overrides the signups_allowed setting. + self.is_email_domain_allowed(email) + } + } + // The registration link should be hidden if // - Signup is not allowed and email whitelist is empty unless mail is disabled and invitations are allowed // - The SSO is activated and password login is disabled. From fdc156b247846ca73f6aa3e9c676a6f2f57577cf Mon Sep 17 00:00:00 2001 From: Timshel Date: Sat, 29 Aug 2026 15:02:26 +0000 Subject: [PATCH 06/20] log_event take enum parameter not i32 (#7656) Co-authored-by: Timshel --- src/api/admin.rs | 6 ++--- src/api/core/ciphers.rs | 28 ++++++++------------- src/api/core/events.rs | 6 ++--- src/api/core/organizations.rs | 46 +++++++++++++++++----------------- src/api/core/two_factor/mod.rs | 14 +++-------- 5 files changed, 42 insertions(+), 58 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index 48f36afd..eaa681dd 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -425,7 +425,7 @@ async fn delete_user(user_id: UserId, token: AdminToken, conn: DbConn) -> EmptyR for membership in memberships { log_event( - EventType::OrganizationUserDeleted as i32, + EventType::OrganizationUserDeleted, &membership.uuid, &membership.org_uuid, &ACTING_ADMIN_USER.into(), @@ -446,7 +446,7 @@ async fn delete_sso_user(user_id: UserId, token: AdminToken, conn: DbConn) -> Em for membership in memberships { log_event( - EventType::OrganizationUserUnlinkedSso as i32, + EventType::OrganizationUserUnlinkedSso, &membership.uuid, &membership.org_uuid, &ACTING_ADMIN_USER.into(), @@ -571,7 +571,7 @@ async fn update_membership_type(data: Json, token: AdminToke OrgPolicy::check_user_allowed(&member_to_edit, "modify", &conn).await?; log_event( - EventType::OrganizationUserUpdated as i32, + EventType::OrganizationUserUpdated, &member_to_edit.uuid, &data.org_uuid, &ACTING_ADMIN_USER.into(), diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 3e94ca7c..13021ca3 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -553,16 +553,8 @@ pub async fn update_cipher_from_data( (_, _) => EventType::CipherUpdated, }; - log_event( - event_type as i32, - &cipher.uuid, - org_id, - &headers.user.uuid, - headers.device.atype, - &headers.ip.ip, - conn, - ) - .await; + log_event(event_type, &cipher.uuid, org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn) + .await; } nt.send_cipher_update( ut, @@ -850,7 +842,7 @@ async fn post_collections_update( .await; log_event( - EventType::CipherUpdatedCollections as i32, + EventType::CipherUpdatedCollections, &cipher.uuid, org_uuid, &headers.user.uuid, @@ -930,7 +922,7 @@ async fn post_collections_admin( .await; log_event( - EventType::CipherUpdatedCollections as i32, + EventType::CipherUpdatedCollections, &cipher.uuid, org_uuid, &headers.user.uuid, @@ -1335,7 +1327,7 @@ async fn save_attachment( if let Some(org_id) = &cipher.organization_uuid { log_event( - EventType::CipherAttachmentCreated as i32, + EventType::CipherAttachmentCreated, &cipher.uuid, org_id, &headers.user.uuid, @@ -1696,7 +1688,7 @@ async fn purge_org_vault( nt.send_user_update(UpdateType::SyncVault, &user, headers.device.push_uuid.as_ref(), &conn).await; log_event( - EventType::OrganizationPurgedVault as i32, + EventType::OrganizationPurgedVault, &organization.org_id, &organization.org_id, &user.uuid, @@ -1824,9 +1816,9 @@ async fn delete_cipher_by_uuid( let event_type = if *delete_options == CipherDeleteOptions::SoftSingle || *delete_options == CipherDeleteOptions::SoftMulti { - EventType::CipherSoftDeleted as i32 + EventType::CipherSoftDeleted } else { - EventType::CipherDeleted as i32 + EventType::CipherDeleted }; log_event(event_type, &cipher.uuid, &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn) @@ -1895,7 +1887,7 @@ async fn restore_cipher_by_uuid( if let Some(org_id) = &cipher.organization_uuid { log_event( - EventType::CipherRestored as i32, + EventType::CipherRestored, &cipher.uuid.clone(), org_id, &headers.user.uuid, @@ -1972,7 +1964,7 @@ async fn delete_cipher_attachment_by_id( if let Some(ref org_id) = cipher.organization_uuid { log_event( - EventType::CipherAttachmentDeleted as i32, + EventType::CipherAttachmentDeleted, &cipher.uuid, org_id, &headers.user.uuid, diff --git a/src/api/core/events.rs b/src/api/core/events.rs index 5518fa3c..2c437a36 100644 --- a/src/api/core/events.rs +++ b/src/api/core/events.rs @@ -10,7 +10,7 @@ use crate::{ auth::{AdminHeaders, Headers}, db::{ DbConn, DbPool, - models::{Cipher, CipherId, Event, Membership, MembershipId, OrganizationId, UserId}, + models::{Cipher, CipherId, Event, EventType, Membership, MembershipId, OrganizationId, UserId}, }, util::parse_date, }; @@ -267,7 +267,7 @@ async fn log_user_event_impl( } pub async fn log_event( - event_type: i32, + event_type: EventType, source_uuid: &str, org_id: &OrganizationId, act_user_id: &UserId, @@ -278,7 +278,7 @@ pub async fn log_event( if !CONFIG.org_events_enabled() { return; } - log_event_impl(event_type, source_uuid, org_id, act_user_id, device_type, None, ip, conn).await; + log_event_impl(event_type as i32, source_uuid, org_id, act_user_id, device_type, None, ip, conn).await; } #[expect(clippy::too_many_arguments)] diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 989ca47d..9082297f 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -269,7 +269,7 @@ async fn leave_organization(org_id: OrganizationId, headers: OrgMemberHeaders, c } log_event( - EventType::OrganizationUserLeft as i32, + EventType::OrganizationUserLeft, &membership.uuid, &org_id, &headers.user.uuid, @@ -327,7 +327,7 @@ async fn post_organization( org.save(&conn).await?; log_event( - EventType::OrganizationUpdated as i32, + EventType::OrganizationUpdated, org_id.as_ref(), &org_id, &headers.user.uuid, @@ -514,7 +514,7 @@ async fn post_organization_collections( collection.save(&conn).await?; log_event( - EventType::CollectionCreated as i32, + EventType::CollectionCreated, &collection.uuid, &org_id, &headers.user.uuid, @@ -597,7 +597,7 @@ async fn post_bulk_access_collections( collection.save(&conn).await?; log_event( - EventType::CollectionUpdated as i32, + EventType::CollectionUpdated, &collection.uuid, &org_id, &headers.user.uuid, @@ -674,7 +674,7 @@ async fn post_organization_collection_update( collection.save(&conn).await?; log_event( - EventType::CollectionUpdated as i32, + EventType::CollectionUpdated, &collection.uuid, &org_id, &headers.user.uuid, @@ -723,7 +723,7 @@ async fn delete_organization_collection_impl( err!("Collection not found", "Collection does not exist or does not belong to this organization") }; log_event( - EventType::CollectionDeleted as i32, + EventType::CollectionDeleted, &collection.uuid, org_id, &headers.user.uuid, @@ -1148,7 +1148,7 @@ async fn send_invite( } log_event( - EventType::OrganizationUserInvited as i32, + EventType::OrganizationUserInvited, &new_member.uuid, &org_id, &headers.user.uuid, @@ -1447,7 +1447,7 @@ async fn confirm_invite_impl( OrgPolicy::check_user_allowed(&member_to_confirm, "confirm", conn).await?; log_event( - EventType::OrganizationUserConfirmed as i32, + EventType::OrganizationUserConfirmed, &member_to_confirm.uuid, org_id, &headers.user.uuid, @@ -1637,7 +1637,7 @@ async fn edit_member( } log_event( - EventType::OrganizationUserUpdated as i32, + EventType::OrganizationUserUpdated, &member_to_edit.uuid, &org_id, &headers.user.uuid, @@ -1724,7 +1724,7 @@ async fn delete_member_impl( } log_event( - EventType::OrganizationUserRemoved as i32, + EventType::OrganizationUserRemoved, &member_to_delete.uuid, org_id, &headers.user.uuid, @@ -2144,7 +2144,7 @@ async fn put_policy( } log_event( - EventType::OrganizationUserRemoved as i32, + EventType::OrganizationUserRemoved, &member.uuid, &org_id, &headers.user.uuid, @@ -2170,7 +2170,7 @@ async fn put_policy( policy.save(&conn).await?; log_event( - EventType::PolicyUpdated as i32, + EventType::PolicyUpdated, policy.uuid.as_ref(), &org_id, &headers.user.uuid, @@ -2339,7 +2339,7 @@ async fn revoke_member_impl( member.save(conn).await?; log_event( - EventType::OrganizationUserRevoked as i32, + EventType::OrganizationUserRevoked, &member.uuid, org_id, &headers.user.uuid, @@ -2437,7 +2437,7 @@ async fn restore_member_impl( member.save(conn).await?; log_event( - EventType::OrganizationUserRestored as i32, + EventType::OrganizationUserRestored, &member.uuid, org_id, &headers.user.uuid, @@ -2605,7 +2605,7 @@ async fn post_groups( let group = group_request.to_group(&org_id); log_event( - EventType::GroupCreated as i32, + EventType::GroupCreated, &group.uuid, &org_id, &headers.user.uuid, @@ -2646,7 +2646,7 @@ async fn put_group( GroupUser::delete_all_by_group(&group_id, &org_id, &conn).await?; log_event( - EventType::GroupUpdated as i32, + EventType::GroupUpdated, &updated_group.uuid, &org_id, &headers.user.uuid, @@ -2679,7 +2679,7 @@ async fn add_update_group( user_entry.save(conn).await?; log_event( - EventType::OrganizationUserUpdatedGroups as i32, + EventType::OrganizationUserUpdatedGroups, &assigned_member, &org_id, &headers.user.uuid, @@ -2754,7 +2754,7 @@ async fn delete_group_impl( }; log_event( - EventType::GroupDeleted as i32, + EventType::GroupDeleted, &group.uuid, org_id, &headers.user.uuid, @@ -2865,7 +2865,7 @@ async fn put_group_members( user_entry.save(&conn).await?; log_event( - EventType::OrganizationUserUpdatedGroups as i32, + EventType::OrganizationUserUpdatedGroups, &assigned_member, &org_id, &headers.user.uuid, @@ -2903,7 +2903,7 @@ async fn post_delete_group_member( } log_event( - EventType::OrganizationUserUpdatedGroups as i32, + EventType::OrganizationUserUpdatedGroups, &member_id, &org_id, &headers.user.uuid, @@ -3039,7 +3039,7 @@ async fn recover_account( nt.send_logout(&user, None, &conn).await; log_event( - EventType::OrganizationUserAdminResetPassword as i32, + EventType::OrganizationUserAdminResetPassword, &member_id, &org_id, &headers.user.uuid, @@ -3166,9 +3166,9 @@ async fn put_reset_password_enrollment( membership.save(&conn).await?; let event_type = if membership.reset_password_key.is_some() { - EventType::OrganizationUserResetPasswordEnroll as i32 + EventType::OrganizationUserResetPasswordEnroll } else { - EventType::OrganizationUserResetPasswordWithdraw as i32 + EventType::OrganizationUserResetPasswordWithdraw }; log_event(event_type, &membership.uuid, &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, &conn) diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs index 8869d23d..c95fb297 100644 --- a/src/api/core/two_factor/mod.rs +++ b/src/api/core/two_factor/mod.rs @@ -190,7 +190,7 @@ pub async fn enforce_2fa_policy( member.save(conn).await?; log_event( - EventType::OrganizationUserRevoked as i32, + EventType::OrganizationUserRevoked, &member.uuid, &member.org_uuid, act_user_id, @@ -224,16 +224,8 @@ pub async fn enforce_2fa_policy_for_org( member.revoke(); member.save(conn).await?; - log_event( - EventType::OrganizationUserRevoked as i32, - &member.uuid, - org_id, - act_user_id, - device_type, - ip, - conn, - ) - .await; + log_event(EventType::OrganizationUserRevoked, &member.uuid, org_id, act_user_id, device_type, ip, conn) + .await; } } From 6729e835218edb29b644a99f65a3b76cde9a341d Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Thu, 3 Sep 2026 00:07:00 +0200 Subject: [PATCH 07/20] Misc Updates (#7676) * Misc Updates - Update Rust to v1.98.0 - Update all the crates and adjusted code where needed - Updated JavaScript libraries Removed jquery as this isn't needed anymore, adjusted code where needed - Updated all GitHub Actions - Fixed nightly clippy lint warnings Signed-off-by: BlackDex * Adjust email validation as suggested Signed-off-by: BlackDex --------- Signed-off-by: BlackDex --- .github/workflows/build.yml | 2 +- .github/workflows/hadolint.yml | 6 +- .github/workflows/release.yml | 4 +- .github/workflows/trivy.yml | 2 +- .github/workflows/typos.yml | 2 +- .github/workflows/zizmor.yml | 2 +- .pre-commit-config.yaml | 2 +- Cargo.lock | 579 +- Cargo.toml | 36 +- macros/Cargo.toml | 2 +- rust-toolchain.toml | 2 +- src/api/admin.rs | 4 +- src/api/web.rs | 3 - src/config.rs | 2 +- src/main.rs | 8 +- src/static/scripts/admin_organizations.js | 11 +- src/static/scripts/admin_users.js | 76 +- src/static/scripts/datatables.css | 160 +- src/static/scripts/datatables.js | 27072 ++++++++--------- src/static/scripts/jquery-4.0.0.slim.js | 6856 ----- src/static/templates/admin/organizations.hbs | 3 +- src/static/templates/admin/users.hbs | 7 +- src/util.rs | 5 +- 23 files changed, 13321 insertions(+), 21525 deletions(-) delete mode 100644 src/static/scripts/jquery-4.0.0.slim.js diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 31a04012..f52bd8e4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -113,7 +113,7 @@ jobs: # Enable Rust Caching - name: Rust Caching - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 with: # Use a custom prefix-key to force a fresh start. This is sometimes needed with bigger changes. # Like changing the build host from Ubuntu 20.04 to 22.04 for example. diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml index 3111e20b..a429d10f 100644 --- a/.github/workflows/hadolint.yml +++ b/.github/workflows/hadolint.yml @@ -20,7 +20,7 @@ jobs: steps: # Start Docker Buildx - name: Setup Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 # https://github.com/moby/buildkit/issues/3969 # Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills with: @@ -41,12 +41,12 @@ jobs: # Uses the Docker-based action (hadolint pre-bundled in ghcr.io/hadolint/hadolint:v2.14.0-debian) # so no binary is downloaded at runtime. Pinned by commit SHA for supply-chain safety. - name: Run hadolint on Dockerfile.debian - uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0 + uses: hadolint/hadolint-action@06be81baf89a55ffd0e24b8f04a4185738dd3387 # v3.5.0 with: dockerfile: docker/Dockerfile.debian - name: Run hadolint on Dockerfile.alpine - uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0 + uses: hadolint/hadolint-action@06be81baf89a55ffd0e24b8f04a4185738dd3387 # v3.5.0 with: dockerfile: docker/Dockerfile.alpine # End Test Dockerfiles with hadolint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d15dd88..311891b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,13 +58,13 @@ jobs: steps: - name: Initialize QEMU binfmt support - uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 + uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0 with: platforms: "arm64,arm" # Start Docker Buildx - name: Setup Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 # https://github.com/moby/buildkit/issues/3969 # Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills with: diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 942a99e9..7f41885d 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -50,6 +50,6 @@ jobs: severity: CRITICAL,HIGH - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 779cd6e3..83cd581b 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -23,4 +23,4 @@ jobs: # When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too - name: Spell Check Repo - uses: crate-ci/typos@8a48f81b6c64dcfea44b3633223084c4be58ac5f # v1.49.0 + uses: crate-ci/typos@4d9c206a77c041268485162b8e2579ad7a5cb9a3 # v1.50.0 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index e1de58c3..5e7100b9 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 + uses: zizmorcore/zizmor-action@70fb788f84895a7701f5643d103d587e460b5c99 # v0.6.3 with: # intentionally not scanning the entire repository, # since it contains integration tests. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f9920696..5269c041 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too - repo: https://github.com/crate-ci/typos - rev: 8a48f81b6c64dcfea44b3633223084c4be58ac5f # v1.49.0 + rev: 4d9c206a77c041268485162b8e2579ad7a5cb9a3 # v1.50.0 hooks: - id: typos always_run: true diff --git a/Cargo.lock b/Cargo.lock index b0a36edf..b8335e5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -76,13 +77,13 @@ dependencies = [ [[package]] name = "argon2" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +checksum = "134c52ddac6d63c576bef8168db10c83c49c26444ecbc68060fef078925a901c" dependencies = [ "base64ct", "blake2", - "cpufeatures 0.2.17", + "cpufeatures 0.3.1", "password-hash", ] @@ -311,13 +312,23 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", +] + +[[package]] +name = "asyncband" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a214ba60d6231afd0e805e3c27c45a1626d9debaa5a5061c45a1ea1b2f1ed0" +dependencies = [ + "hashbrown 0.17.1", + "slab", ] [[package]] @@ -349,9 +360,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-config" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b180a3c8b55960db3426d8964b8745e652466a1a49fe1a2eda828046d30b5e4" +checksum = "a767267da9e2c2e189b2f9df8b5657e850ecf5352644734ba130d4a57095cf1b" dependencies = [ "aws-credential-types", "aws-runtime", @@ -417,9 +428,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.105.0" +version = "1.108.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ffd0fbe7873cb548a7aa60f9573c268fff94155397fd4f14dc9f1ecaaab8516" +checksum = "c15301b04372832947916607983b114b3374b9db0be058a00fb7513800de1f05" dependencies = [ "arc-swap", "aws-credential-types", @@ -443,9 +454,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.107.0" +version = "1.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175763eb222a46377df7aa257a3bca980ab3e96703fefc8f4d0b8da6ad2e254c" +checksum = "72cc2c205cb27108183cf1856333f7d584c2ba0f505421b4209ca5828f9ea899" dependencies = [ "arc-swap", "aws-credential-types", @@ -469,9 +480,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.110.0" +version = "1.113.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b14781dfbff48984017d57167b6ea0b6471c6920ec52b44a2677c7feb3c13" +checksum = "68182ecb449f7537db0f4d5d25917789cf41e32074a9fe47b6a0b847fe1d2032" dependencies = [ "arc-swap", "aws-credential-types", @@ -583,9 +594,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.12.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07505b34e8f4b3591a4fa69e9792b52289b95488dbbc68c3c0075b7bedb245e1" +checksum = "b82e438d30e02a825d363bd639a9efaed68a8089d86101054b0081e7e0d3e606" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -608,9 +619,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b98f2e1fd67ec06618f9c291e5e495a468e60519e44c9c1979cd0521f3affdb" +checksum = "954c563ce84507722d2679f07a35d21b9c6466b3872d513020d0281fc8112ac9" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", @@ -648,9 +659,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6dc683efb34b9e755675b37fedbe0103141e5b6df7bdc9eb6967756a8c167d8" +checksum = "fce83ce9abbb198d25bc7131e468d0f9fe1257125e58c39f3f9fc9f5098c9647" dependencies = [ "base64-simd", "bytes", @@ -780,11 +791,11 @@ checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "blake2" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +checksum = "5b5d4d889834ee8ecfc0f8426ad30faf7cdcb10f741a8e6d7224d95325479f6f" dependencies = [ - "digest 0.10.7", + "digest 0.11.3", ] [[package]] @@ -807,9 +818,9 @@ dependencies = [ [[package]] name = "blocking" -version = "1.6.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" dependencies = [ "async-channel 2.5.0", "async-task", @@ -884,27 +895,28 @@ dependencies = [ [[package]] name = "cached" -version = "2.0.2" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0df7748fe2f601e376916ab19e7bfc2c74461b8abe3bce2ce20036ad8de38f" +checksum = "133b6b7d6a828c24d5055ef51e67457002b4017ae5ea3d1b1552ca35a44b1119" dependencies = [ "ahash", + "async-lock", "cached_proc_macro", "cached_proc_macro_types", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "parking_lot", - "thiserror 2.0.19", - "tokio", + "thiserror 2.0.20", "web-time", ] [[package]] name = "cached_proc_macro" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66e734c52502e6cf54dce2ba07108906b04b8fe57f4f5e3ef7d58267b4abf060" +checksum = "da80977bd46ecf98c593b651e393260f852678283989b8b7c4079304fe5e8936" dependencies = [ "darling 0.20.11", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.119", @@ -912,15 +924,15 @@ dependencies = [ [[package]] name = "cached_proc_macro_types" -version = "1.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26cf465651fa6ad902a2d327ba60c3a6bc61c6a2f4ad70d091cf20dfda0074ef" +checksum = "f5813789573ae815c8b4be58c4428e0e7ae05f0227678ba9de332ded585b9159" [[package]] name = "cc" -version = "1.4.0" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" dependencies = [ "find-msvc-tools", "jobserver", @@ -942,12 +954,12 @@ checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "chacha20" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "rand_core 0.10.1", ] @@ -989,9 +1001,9 @@ checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" [[package]] name = "combine" -version = "4.6.7" +version = "4.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" dependencies = [ "bytes", "memchr", @@ -1069,9 +1081,9 @@ dependencies = [ [[package]] name = "cookie" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" dependencies = [ "percent-encoding", "time", @@ -1133,9 +1145,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ "libc", ] @@ -1152,9 +1164,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" dependencies = [ "cfg-if", ] @@ -1432,7 +1444,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -1558,9 +1570,9 @@ dependencies = [ [[package]] name = "diesel" -version = "2.3.11" +version = "2.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54d1f576cd3a3460f212a4615fd12ce1b6303c095b79a44449ffbe627753dc1" +checksum = "715377c6e464cb44bb89bd8487584240516c8d5052bc645d6babc50bb8be46c3" dependencies = [ "bigdecimal", "bitflags 2.13.1", @@ -1658,7 +1670,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -1751,9 +1763,9 @@ dependencies = [ [[package]] name = "either" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] name = "elliptic-curve" @@ -1896,18 +1908,19 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flate2" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -1948,9 +1961,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -1963,9 +1976,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -1973,15 +1986,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -1990,9 +2003,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-lite" @@ -2009,26 +2022,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-timer" @@ -2038,9 +2051,9 @@ checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -2164,7 +2177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" dependencies = [ "codemap", - "indexmap 2.14.0", + "indexmap 2.14.1", "lasso", "once_cell", "phf 0.11.3", @@ -2183,9 +2196,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.15" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" dependencies = [ "atomic-waker", "bytes", @@ -2193,7 +2206,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.5.0", - "indexmap 2.14.0", + "indexmap 2.14.1", "slab", "tokio", "tokio-util", @@ -2213,9 +2226,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "6.4.3" +version = "6.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4633d16a2350341713c379d6d06a4b9e1845329386026a49ce4fd09c2f3b16f6" +checksum = "75c54236f9045c8004a77942bebc52145b4844639db934a5c70fe08617fbe61a" dependencies = [ "derive_builder", "log", @@ -2224,7 +2237,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.19", + "thiserror 2.0.20", "walkdir", ] @@ -2260,6 +2273,11 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "heck" @@ -2269,9 +2287,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" [[package]] name = "hex" @@ -2296,7 +2314,7 @@ dependencies = [ "ipnet", "jni", "rand 0.10.2", - "thiserror 2.0.19", + "thiserror 2.0.20", "tinyvec", "tokio", "tracing", @@ -2317,7 +2335,7 @@ dependencies = [ "prefix-trie", "rand 0.10.2", "ring", - "thiserror 2.0.19", + "thiserror 2.0.20", "tinyvec", "tracing", "url", @@ -2344,7 +2362,7 @@ dependencies = [ "resolv-conf", "smallvec", "system-configuration", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "tracing", ] @@ -2440,9 +2458,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", "futures-core", @@ -2497,9 +2515,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ "atomic-waker", "bytes", @@ -2523,7 +2541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.5.0", - "hyper 1.11.0", + "hyper 1.11.1", "hyper-util", "rustls 0.23.43", "tokio", @@ -2543,7 +2561,7 @@ dependencies = [ "futures-util", "http 1.5.0", "http-body 1.1.0", - "hyper 1.11.0", + "hyper 1.11.1", "ipnet", "libc", "percent-encoding", @@ -2582,9 +2600,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", @@ -2596,9 +2614,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -2609,9 +2627,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -2623,16 +2641,17 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -2643,15 +2662,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", @@ -2702,9 +2721,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -2839,7 +2858,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.19", + "thiserror 2.0.20", "walkdir", "windows-link", ] @@ -2899,9 +2918,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if", "futures-util", @@ -2922,7 +2941,7 @@ dependencies = [ "p256", "p384", "pem", - "rand 0.8.7", + "rand 0.8.8", "rsa", "serde", "serde_json", @@ -3013,9 +3032,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.37.0" +version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" +checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8" dependencies = [ "cc", "pkg-config", @@ -3030,9 +3049,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "litrs" @@ -3051,9 +3070,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" dependencies = [ "value-bag", ] @@ -3078,7 +3097,7 @@ name = "macros" version = "0.1.0" dependencies = [ "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -3102,10 +3121,11 @@ dependencies = [ [[package]] name = "mea" -version = "0.6.5" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31fc7d159de0085ab6dd7ff145a9819442cfd3d098f783263120503c3f3e58b0" +checksum = "c709842c4ce65cb91e2666ad5319dfc1efc3af0d34f02075eddca9000d9f8afb" dependencies = [ + "hashbrown 0.17.1", "slab", ] @@ -3159,9 +3179,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.9" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" dependencies = [ "adler2", "simd-adler32", @@ -3180,9 +3200,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.15" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +checksum = "4293f18e7567a1caf3c584855554377025c65e0aa445344d04171f5ad63d19b9" dependencies = [ "async-lock", "crossbeam-channel", @@ -3301,7 +3321,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.7", + "rand 0.8.8", "smallvec", "zeroize", ] @@ -3320,14 +3340,14 @@ checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" dependencies = [ "num-traits", ] @@ -3344,9 +3364,9 @@ dependencies = [ [[package]] name = "num-modular" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc41a1374056e9672221567958a66c16be12d0e2c1b408761e14d901c237d5e0" +checksum = "bd8e500409e6cd603b03e477c26a6caecdc27ac58979a53e881c75eafc079f44" [[package]] name = "num-order" @@ -3396,7 +3416,7 @@ dependencies = [ "chrono", "getrandom 0.2.17", "http 1.5.0", - "rand 0.8.7", + "rand 0.8.8", "serde", "serde_json", "serde_path_to_error", @@ -3426,9 +3446,9 @@ dependencies = [ [[package]] name = "opendal" -version = "0.58.1" +version = "0.58.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f20562cc7447fcc915fc5c23df305a412ea80a733c9f2fd9e2d267e2815be6d" +checksum = "33dbff14cc9bb085224256d6a81289d2f3202e85b06f408d42534b42162a4231" dependencies = [ "opendal-core", "opendal-service-fs", @@ -3437,11 +3457,12 @@ dependencies = [ [[package]] name = "opendal-core" -version = "0.58.1" +version = "0.58.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec75551ff4cf3e57da98979f6a937aaa9ddb3915bf68cc17d03df733be6646ed" +checksum = "48dbcef97d3eb7591db2c18d5cae95c836bcce07359b98d98dd6f4e861eb77b7" dependencies = [ "anyhow", + "asyncband", "base64 0.23.1", "bytes", "futures", @@ -3449,7 +3470,6 @@ dependencies = [ "jiff", "log", "md-5", - "mea", "percent-encoding", "quick-xml", "reqsign-core", @@ -3463,9 +3483,9 @@ dependencies = [ [[package]] name = "opendal-service-fs" -version = "0.58.1" +version = "0.58.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826c4e17a30643b888fe983897f9a4b23b07066e1d069727a923cc8fb419a702" +checksum = "c7ef1e1c45f3f89282a59073897e0d685e51385fed0aea771714789525cff996" dependencies = [ "bytes", "log", @@ -3477,9 +3497,9 @@ dependencies = [ [[package]] name = "opendal-service-s3" -version = "0.58.1" +version = "0.58.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e80cdf192d7eff05feed747894d64f81905ac4eaf132edf7ea270abdd2d663" +checksum = "c64335f9f24ccb62ac36f1d976342b48611a75ba61979813a4f78a4ebd94de42" dependencies = [ "base64 0.23.1", "bytes", @@ -3513,7 +3533,7 @@ dependencies = [ "oauth2", "p256", "p384", - "rand 0.8.7", + "rand 0.8.8", "rsa", "serde", "serde-value", @@ -3660,13 +3680,12 @@ dependencies = [ [[package]] name = "password-hash" -version = "0.5.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", + "getrandom 0.4.3", + "phc", ] [[package]] @@ -3731,9 +3750,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" +checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf" dependencies = [ "memchr", "ucd-trie", @@ -3741,9 +3760,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" +checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d" dependencies = [ "pest", "pest_generator", @@ -3751,9 +3770,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" +checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a" dependencies = [ "pest", "pest_meta", @@ -3764,13 +3783,24 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" +checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496" dependencies = [ "pest", ] +[[package]] +name = "phc" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892" +dependencies = [ + "base64ct", + "ctutils", + "getrandom 0.4.3", +] + [[package]] name = "phf" version = "0.11.3" @@ -3797,7 +3827,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared 0.11.3", - "rand 0.8.7", + "rand 0.8.8", ] [[package]] @@ -3883,9 +3913,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "polling" @@ -3903,9 +3933,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "portable-atomic-util" @@ -3918,9 +3948,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] @@ -3971,6 +4001,15 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.13+spec-1.1.0", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -4080,9 +4119,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -4174,22 +4213,22 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -4240,9 +4279,9 @@ dependencies = [ [[package]] name = "reqsign-aws-core" -version = "3.0.3" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4af084e1f3cbf3e67e0c972765399bce54ecec804cceba46b39a8331f3c1bff" +checksum = "bac4749b7dfa7bfaccd01eb03e9dc795ed37e3f20d6f0f38e2c67ee85ad6bc86" dependencies = [ "bytes", "form_urlencoded", @@ -4261,9 +4300,9 @@ dependencies = [ [[package]] name = "reqsign-aws-v4" -version = "3.1.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac5b3b7cefa28933792b439186459f77f19f9b6edbeab41b8b187150361a206" +checksum = "ff250f0fd0b913fbd565e405acc553da0f13bde30bfb5403178c9d0313cdc15f" dependencies = [ "bytes", "http 1.5.0", @@ -4276,9 +4315,9 @@ dependencies = [ [[package]] name = "reqsign-core" -version = "3.2.1" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07dd510b1e1b9b241883e483358147fb2ed2d497a7b39b065ba61eb93deceb0" +checksum = "ff052daffb0599681c50f85c59e7236438976efe991ab864edd9f3b235501a0f" dependencies = [ "anyhow", "base64 0.23.1", @@ -4289,6 +4328,7 @@ dependencies = [ "http 1.5.0", "jiff", "log", + "mea", "percent-encoding", "sha1 0.11.0", "sha2 0.11.0", @@ -4297,9 +4337,9 @@ dependencies = [ [[package]] name = "reqsign-file-read-tokio" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "663d9d55abd0df0830ef0ae43708297cc1371cf4e8ca91f3ac813c309cca8c98" +checksum = "b3235df90a6bca681aa47dd86f2393d122a6d77042aa8a7c81e218cd45c5bfc0" dependencies = [ "anyhow", "reqsign-core", @@ -4323,7 +4363,7 @@ dependencies = [ "http 1.5.0", "http-body 1.1.0", "http-body-util", - "hyper 1.11.0", + "hyper 1.11.1", "hyper-rustls", "hyper-util", "js-sys", @@ -4413,14 +4453,14 @@ dependencies = [ "either", "figment", "futures", - "indexmap 2.14.0", + "indexmap 2.14.1", "log", "memchr", "multer", "num_cpus", "parking_lot", "pin-project-lite", - "rand 0.8.7", + "rand 0.8.8", "ref-cast", "rocket_codegen", "rocket_http", @@ -4445,7 +4485,7 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" dependencies = [ "devise", "glob", - "indexmap 2.14.0", + "indexmap 2.14.1", "proc-macro2", "quote", "rocket_http", @@ -4465,7 +4505,7 @@ dependencies = [ "futures", "http 0.2.12", "hyper 0.14.32", - "indexmap 2.14.0", + "indexmap 2.14.1", "log", "memchr", "pear", @@ -4532,17 +4572,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" dependencies = [ "hashbrown 0.16.1", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] name = "rtoolbox" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" +checksum = "9a1efe12a1469752d0e6ff5ebec0b6ef4924cc5c4c71046b0ec730040535819d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4608,7 +4648,7 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.13", + "rustls-webpki 0.103.15", "subtle", "zeroize", ] @@ -4657,7 +4697,7 @@ dependencies = [ "rustls 0.23.43", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.13", + "rustls-webpki 0.103.15", "security-framework", "security-framework-sys", "webpki-root-certs", @@ -4682,9 +4722,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "ring", "rustls-pki-types", @@ -4866,7 +4906,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -4875,7 +4915,6 @@ version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ - "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -4935,16 +4974,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.21.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" dependencies = [ "base64 0.22.1", "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.14.0", + "indexmap 2.14.1", + "jiff", "schemars 0.9.0", "schemars 1.2.2", "serde_core", @@ -4955,9 +4995,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.21.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -4983,7 +5023,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "digest 0.11.3", ] @@ -5005,7 +5045,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "digest 0.11.3", ] @@ -5084,7 +5124,7 @@ checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.19", + "thiserror 2.0.20", "time", ] @@ -5207,11 +5247,11 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svg-hush" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "929223e80cdcec0482207576ea09692dd71b2b559057fc172e292ecec9a97559" +checksum = "e690409a034dc81758d2986dcc0e01ffe4a77c65902a120777f5182dd40b1f7f" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "data-url", "quick-error", "url", @@ -5231,9 +5271,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" dependencies = [ "proc-macro2", "quote", @@ -5323,11 +5363,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl 2.0.19", + "thiserror-impl 2.0.20", ] [[package]] @@ -5343,13 +5383,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -5404,9 +5444,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", @@ -5452,7 +5492,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -5522,7 +5562,7 @@ dependencies = [ "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", - "toml_edit", + "toml_edit 0.22.27", ] [[package]] @@ -5556,13 +5596,22 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.14.1", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -5570,6 +5619,18 @@ dependencies = [ "winnow 0.7.15", ] +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap 2.14.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + [[package]] name = "toml_parser" version = "1.1.3+spec-1.1.0" @@ -5727,7 +5788,7 @@ dependencies = [ "http 1.5.0", "httparse", "log", - "rand 0.8.7", + "rand 0.8.8", "sha1 0.10.7", "thiserror 1.0.69", "url", @@ -5822,9 +5883,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.24.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -5979,9 +6040,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if", "once_cell", @@ -5992,9 +6053,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.76" +version = "0.4.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" dependencies = [ "js-sys", "wasm-bindgen", @@ -6002,9 +6063,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6012,9 +6073,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", @@ -6025,9 +6086,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] @@ -6047,9 +6108,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" dependencies = [ "js-sys", "wasm-bindgen", @@ -6144,9 +6205,9 @@ dependencies = [ [[package]] name = "which" -version = "8.0.5" +version = "8.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3ef584124b911bcc3875c2f1472e80f24361ceb789bd1c62b3e9a3df9ff43c" +checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" dependencies = [ "libc", ] @@ -6276,15 +6337,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -6429,6 +6481,9 @@ name = "winnow" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] [[package]] name = "wit-bindgen" @@ -6438,9 +6493,9 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "x509-parser" @@ -6471,9 +6526,9 @@ dependencies = [ [[package]] name = "xml" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "636f85e5ca6488e96401b61eb7de54f4e44755c988af0f52cf90230c312a1a89" +checksum = "2f45bb2c13fec6a6cb4c0f76a7e94839e110a14ec803ec2940777a94c347bc52" [[package]] name = "xmlparser" @@ -6528,18 +6583,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", @@ -6589,9 +6644,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -6600,9 +6655,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "yoke", "zerofrom", @@ -6611,15 +6666,21 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + [[package]] name = "zmij" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 3e187ff3..7d711fe1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace.package] edition = "2024" -rust-version = "1.95.0" +rust-version = "1.96.1" license = "AGPL-3.0-only" repository = "https://github.com/dani-garcia/vaultwarden" publish = false @@ -66,7 +66,7 @@ syslog = "7.0.0" macros = { path = "./macros" } # Logging -log = "0.4.33" +log = "0.4.34" fern = { version = "0.7.1", features = ["syslog-7", "reopen-1"] } # We need the `log` feature for `tracing` to enable logging for several crates to work, like lettre or webauthn-rs tracing = { version = "0.1.44", features = ["log"] } @@ -90,7 +90,7 @@ rmpv = "1.3.1" # MessagePack library dashmap = "6.2.1" # Async futures -futures = "0.3.33" +futures = "0.3.34" tokio = { version = "1.53.1", features = [ "fs", "io-util", @@ -107,7 +107,7 @@ serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.151" # A safe, extensible ORM and Query builder -diesel = { version = "2.3.11", features = ["chrono", "r2d2", "numeric"] } +diesel = { version = "2.3.12", features = ["chrono", "r2d2", "numeric"] } diesel_migrations = "2.3.2" derive_more = { version = "2.1.1", features = [ @@ -120,7 +120,7 @@ derive_more = { version = "2.1.1", features = [ diesel-derive-newtype = "2.1.3" # SQLite, statically bundled unless the `sqlite_system` feature is enabled -libsqlite3-sys = { version = "0.37.0", optional = true } +libsqlite3-sys = { version = "0.38.2", optional = true } # Crypto-related libraries rand = "0.10.2" @@ -129,7 +129,7 @@ rustls = { version = "0.23.43", features = ["ring", "std"], default-features = f subtle = "2.6.1" # UUID generation -uuid = { version = "1.24.0", features = ["v4"] } +uuid = { version = "1.26.0", features = ["v4"] } # Date and time libraries chrono = { version = "0.4.45", default-features = false, features = ["clock", "serde"] } @@ -180,7 +180,7 @@ percent-encoding = "2.3.2" # URL encoding library used for URL's in the emails email_address = "0.2.9" # HTML Template library -handlebars = { version = "6.4.3", features = ["dir_source"] } +handlebars = { version = "6.4.4", features = ["dir_source"] } # HTTP client (Used for favicons, version check, DUO and HIBP API) reqwest = { version = "0.13.4", default-features = false, features = [ @@ -212,13 +212,13 @@ regex = { version = "1.13.1", default-features = false, features = [ ] } data-url = "0.3.2" bytes = "1.12.1" -svg-hush = "0.9.6" +svg-hush = "0.9.7" # Cache function results (Used for version check and favicon fetching) -cached = { version = "2.0.2", features = ["async"] } +cached = { version = "3.1.1", features = ["async"] } # Used for custom short lived cookie jar during favicon extraction -cookie = "0.18.1" +cookie = "0.18.2" cookie_store = "0.22.1" # Used by U2F, JWT and PostgreSQL @@ -236,7 +236,7 @@ ipnet = "2.12.1" # OIDC for SSO openidconnect = { version = "4.0.1", default-features = false } -moka = { version = "0.12.15", features = ["future"] } +moka = { version = "0.12.16", features = ["future"] } # Check client versions for specific features. semver = "1.0.28" @@ -245,10 +245,10 @@ semver = "1.0.28" # Mainly used for the musl builds, since the default musl malloc is very slow mimalloc = { version = "0.1.52", optional = true, default-features = false, features = ["secure"] } -which = "8.0.5" +which = "8.0.6" # Argon2 library with support for the PHC format -argon2 = "0.5.3" +argon2 = "0.6.0" # Reading a password from the cli for generating the Argon2id ADMIN_TOKEN rpassword = "7.5.4" @@ -257,20 +257,20 @@ rpassword = "7.5.4" grass_compiler = { version = "0.13.4", default-features = false } # File are accessed through Apache OpenDAL -opendal = { version = "0.58.1", default-features = false, features = ["services-fs"] } +opendal = { version = "0.58.2", default-features = false, features = ["services-fs"] } # For retrieving AWS credentials, including temporary SSO credentials -aws-config = { version = "1.10.1", optional = true, default-features = false, features = [ +aws-config = { version = "1.11.0", optional = true, default-features = false, features = [ "behavior-version-latest", "credentials-process", "rt-tokio", "sso", ] } aws-credential-types = { version = "1.3.0", optional = true } -aws-smithy-runtime-api = { version = "1.14.0", optional = true } +aws-smithy-runtime-api = { version = "1.15.0", optional = true } http = { version = "1.5.0", optional = true } -reqsign-aws-v4 = { version = "3.1.0", optional = true } -reqsign-core = { version = "3.2.1", optional = true } +reqsign-aws-v4 = { version = "3.3.0", optional = true } +reqsign-core = { version = "3.3.1", optional = true } # Strip debuginfo from the release builds # The debug symbols are to provide better panic traces diff --git a/macros/Cargo.toml b/macros/Cargo.toml index f059a214..84d17192 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -14,7 +14,7 @@ proc-macro = true [dependencies] quote = "1.0.47" -syn = "3.0.3" +syn = "3.0.4" [lints] workspace = true diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 9c5862a2..9bfb1d94 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.97.1" +channel = "1.98.0" components = [ "rustfmt", "clippy" ] profile = "minimal" diff --git a/src/api/admin.rs b/src/api/admin.rs index eaa681dd..4bdf8e71 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -231,7 +231,7 @@ fn validate_token(token: &str) -> bool { None => false, Some(t) if t.starts_with("$argon2") => { use argon2::password_hash::PasswordVerifier; - match argon2::password_hash::PasswordHash::new(t) { + match argon2::password_hash::phc::PasswordHash::new(t) { Ok(h) => { // NOTE: hash params from `ADMIN_TOKEN` are used instead of what is configured in the `Argon2` instance. argon2::Argon2::default().verify_password(token.trim().as_ref(), &h).is_ok() @@ -647,7 +647,7 @@ use cached::macros::cached; /// Cache this function to prevent API call rate limit. Github only allows 60 requests per hour, and we use 3 here already /// It will cache this function for 600 seconds (10 minutes) which should prevent the exhaustion of the rate limit /// Any cache will be lost if Vaultwarden is restarted -#[cached(ttl = 600, sync_writes = "default")] +#[cached(ttl_secs = 600, sync_writes = "default")] async fn get_release_info(has_http_access: bool) -> (String, String, String) { // If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway. if has_http_access { diff --git a/src/api/web.rs b/src/api/web.rs index a7eca9fc..d6d8d62c 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -301,9 +301,6 @@ pub fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Erro "jdenticon-3.3.0.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon-3.3.0.js"))), "datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))), "datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))), - "jquery-4.0.0.slim.js" => { - Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-4.0.0.slim.js"))) - } _ => err!(format!("Static file not found: {filename}")), } } diff --git a/src/config.rs b/src/config.rs index 2502dd02..87bea195 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1272,7 +1272,7 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> { if !cfg.disable_admin_token { match cfg.admin_token.as_ref() { Some(t) if t.starts_with("$argon2") => { - if let Err(e) = argon2::password_hash::PasswordHash::new(t) { + if let Err(e) = argon2::password_hash::phc::PasswordHash::new(t) { err!(format!("The configured Argon2 PHC in `ADMIN_TOKEN` is invalid: '{e}'")) } } diff --git a/src/main.rs b/src/main.rs index 28645694..437354af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -137,9 +137,7 @@ fn parse_args() { if let Some(command) = pargs.subcommand().unwrap_or_default() { if command == "hash" { - use argon2::{ - Algorithm::Argon2id, Argon2, ParamsBuilder, PasswordHasher, Version::V0x13, password_hash::SaltString, - }; + use argon2::{Algorithm::Argon2id, Argon2, ParamsBuilder, PasswordHasher, Version::V0x13}; let mut argon2_params = ParamsBuilder::new(); let preset: Option = pargs.opt_value_from_str(["-p", "--preset"]).unwrap_or_default(); @@ -172,10 +170,10 @@ fn parse_args() { } let argon2 = Argon2::new(Argon2id, V0x13, argon2_params.build().unwrap()); - let salt = SaltString::encode_b64(&crypto::get_random_bytes::<32>()).unwrap(); + let salt = crypto::get_random_bytes::<32>(); let argon2_timer = tokio::time::Instant::now(); - if let Ok(password_hash) = argon2.hash_password(password.as_bytes(), &salt) { + if let Ok(password_hash) = argon2.hash_password_with_salt(password.as_bytes(), &salt) { println!( "\n\ ADMIN_TOKEN='{password_hash}'\n\n\ diff --git a/src/static/scripts/admin_organizations.js b/src/static/scripts/admin_organizations.js index 33314ad7..0aa57dcd 100644 --- a/src/static/scripts/admin_organizations.js +++ b/src/static/scripts/admin_organizations.js @@ -1,5 +1,5 @@ "use strict"; -/* global jQuery, _post:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ +/* global DataTable, _post:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ function deleteOrganization(event) { event.preventDefault(); @@ -41,8 +41,9 @@ function initActions() { // onLoad events document.addEventListener("DOMContentLoaded", (/*event*/) => { - jQuery("#orgs-table").DataTable({ - "drawCallback": function() { + const columnCount = document.getElementById("orgs-table").querySelectorAll("thead th").length; + new DataTable("#orgs-table", { + "drawCallback": function () { initActions(); }, "stateSave": true, @@ -53,7 +54,7 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => { ], "pageLength": -1, // Default show all "columnDefs": [{ - "targets": [4,5], + "targets": [columnCount - 2, columnCount - 1], // Do not include the last two columns into the search/order features "searchable": false, "orderable": false }] @@ -66,4 +67,4 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => { if (btnReload) { btnReload.addEventListener("click", reload); } -}); \ No newline at end of file +}); diff --git a/src/static/scripts/admin_users.js b/src/static/scripts/admin_users.js index a2a643c3..63ee2d7b 100644 --- a/src/static/scripts/admin_users.js +++ b/src/static/scripts/admin_users.js @@ -1,5 +1,5 @@ "use strict"; -/* global jQuery, _post:readable, _delete:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ +/* global DataTable, _post:readable, _delete:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ function deleteUser(event) { event.preventDefault(); @@ -141,7 +141,7 @@ function inviteUser(event) { ); } -function resendUserInvite (event) { +function resendUserInvite(event) { event.preventDefault(); event.stopPropagation(); const id = event.target.parentNode.dataset.vwUserUuid; @@ -179,37 +179,9 @@ const ORG_TYPES = { }, }; -// Special sort function to sort dates in ISO format -jQuery.extend(jQuery.fn.dataTableExt.oSort, { - "date-iso-pre": function(a) { - let x; - const sortDate = a.replace(/(<([^>]+)>)/gi, "").trim(); - if (sortDate !== "") { - const dtParts = sortDate.split(" "); - const timeParts = (undefined != dtParts[1]) ? dtParts[1].split(":") : ["00", "00", "00"]; - const dateParts = dtParts[0].split("-"); - x = (dateParts[0] + dateParts[1] + dateParts[2] + timeParts[0] + timeParts[1] + ((undefined != timeParts[2]) ? timeParts[2] : 0)) * 1; - if (isNaN(x)) { - x = 0; - } - } else { - x = Infinity; - } - return x; - }, - - "date-iso-asc": function(a, b) { - return a - b; - }, - - "date-iso-desc": function(a, b) { - return b - a; - } -}); - const userOrgTypeDialog = document.getElementById("userOrgTypeDialog"); // Fill the form and title -userOrgTypeDialog.addEventListener("show.bs.modal", function(event) { +userOrgTypeDialog.addEventListener("show.bs.modal", function (event) { // Get shared values const userEmail = event.relatedTarget.parentNode.dataset.vwUserEmail; const userUuid = event.relatedTarget.parentNode.dataset.vwUserUuid; @@ -227,7 +199,7 @@ userOrgTypeDialog.addEventListener("show.bs.modal", function(event) { }, false); // Prevent accidental submission of the form with valid elements after the modal has been hidden. -userOrgTypeDialog.addEventListener("hide.bs.modal", function() { +userOrgTypeDialog.addEventListener("hide.bs.modal", function () { document.getElementById("userOrgTypeDialogOrgName").textContent = ""; document.getElementById("userOrgTypeDialogUserEmail").textContent = ""; document.getElementById("userOrgTypeUserUuid").value = ""; @@ -249,7 +221,7 @@ function updateUserOrgType(event) { function initUserTable() { // Color all the org buttons per type - document.querySelectorAll("button[data-vw-org-type]").forEach(function(e) { + document.querySelectorAll("button[data-vw-org-type]").forEach(function (e) { const orgType = ORG_TYPES[e.dataset.vwOrgType]; e.style.backgroundColor = orgType.bg; if (orgType.font !== undefined) { @@ -285,12 +257,37 @@ function initUserTable() { } } +// Special sort function to sort dates in ISO format and have anything else as 0 +DataTable.ext.type.order["date-iso-pre"] = function (a) { + let x; + const sortDate = a.replace(/(<([^>]+)>)/gi, "").trim(); + if (sortDate !== "") { + const dtParts = sortDate.split(" "); + const timeParts = (undefined != dtParts[1]) ? dtParts[1].split(":") : ["00", "00", "00"]; + const dateParts = dtParts[0].split("-"); + x = (dateParts[0] + dateParts[1] + dateParts[2] + timeParts[0] + timeParts[1] + ((undefined != timeParts[2]) ? timeParts[2] : 0)) * 1; + if (isNaN(x)) { + x = 0; + } + } else { + x = Infinity; + } + return x; +}; + // onLoad events document.addEventListener("DOMContentLoaded", (/*event*/) => { - const size = jQuery("#users-table > thead th").length; - const ssoOffset = size-7; - jQuery("#users-table").DataTable({ - "drawCallback": function() { + DataTable.ext.type.detect.unshift(function (data) { + if (typeof data !== "string") { return null; } + return data.indexOf("data-sort-type=\"date-iso\"") !== -1 + ? "date-iso" + : null; + }); + + const columnCount = document.getElementById("users-table").querySelectorAll("thead th").length; + new DataTable("#users-table", { + "typeDetect": true, + "drawCallback": function () { initUserTable(); }, "stateSave": true, @@ -301,10 +298,7 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => { ], "pageLength": -1, // Default show all "columnDefs": [{ - "targets": [1 + ssoOffset, 2 + ssoOffset], - "type": "date-iso" - }, { - "targets": size-1, + "targets": columnCount - 1, // Do not include the last column into the search/order features "searchable": false, "orderable": false }] diff --git a/src/static/scripts/datatables.css b/src/static/scripts/datatables.css index e518c143..48b7400c 100644 --- a/src/static/scripts/datatables.css +++ b/src/static/scripts/datatables.css @@ -4,25 +4,32 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#bs5/dt-2.3.8 + * https://datatables.net/download/#bs5/dt-3.0.3 * * Included libraries: - * DataTables 2.3.8 + * DataTables 3.0.3 */ +/*! DataTables Bootstrap 5 integration + * © SpryMedia Ltd - datatables.net/license + */ :root { - --dt-row-selected: 13, 110, 253; - --dt-row-selected-text: 255, 255, 255; - --dt-row-selected-link: 228, 228, 228; - --dt-row-stripe: 0, 0, 0; - --dt-row-hover: 0, 0, 0; - --dt-column-ordering: 0, 0, 0; - --dt-header-align-items: center; - --dt-header-vertical-align: middle; - --dt-html-background: white; + --dt_background-selected: 13, 110, 253; + --dt_color-selected: 255, 255, 255; + --dt_link_color-selected: 228, 228, 228; + --dt-row_background: transparent; + --dt-row_background-selected: var(--dt_background-selected); + --dt-row-text_color-selected: var(--dt_color-selected); + --dt-row-link_color-selected: var(--dt_link_color-selected); + --dt-row_background-stripe: 0, 0, 0; + --dt-row_background-hover: 0, 0, 0; + --dt-column-ordering_background: 0, 0, 0; + --dt-header-cell_align-items: center; + --dt-header-cell_vertical-align: middle; + --dt-html_background: white; } :root.dark { - --dt-html-background: rgb(33, 37, 41); + --dt-html_background: rgb(33, 37, 41); } table.dataTable tbody td.dt-control { @@ -44,16 +51,13 @@ table.dataTable tbody tr.dt-hasChild td.dt-control:before { border-bottom: 0px solid transparent; border-right: 5px solid transparent; } -table.dataTable tfoot:empty { - display: none; -} -html.dark table.dataTable td.dt-control:before, +:root.dark table.dataTable td.dt-control:before, :root[data-bs-theme=dark] table.dataTable td.dt-control:before, :root[data-theme=dark] table.dataTable td.dt-control:before { border-left-color: rgba(255, 255, 255, 0.5); } -html.dark table.dataTable tr.dt-hasChild td.dt-control:before, +:root.dark table.dataTable tr.dt-hasChild td.dt-control:before, :root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before, :root[data-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before { border-top-color: rgba(255, 255, 255, 0.5); @@ -84,6 +88,25 @@ div.dt-scroll-body tfoot tr td div.dt-scroll-sizing { overflow: hidden !important; } +/*! DataTables Bootstrap 5 integration + * © SpryMedia Ltd - datatables.net/license + */ +:root { + --dt-order-arrow_color: rgb(51, 51, 51); + --dt-order-arrow_color-current: rgb(51, 51, 51); + --dt-order-arrow-height: 7px; + --dt-order-arrow_opacity: 0.125; + --dt-order-arrow_opacity-current: 0.65; + --dt-order-arrow-width: 8px; + --dt-order-arrow-gap: 1px; + --dt-order-header_outline-hover: 2px solid rgba(0, 0, 0, 0.05); +} +:root.dark, :root[data-bs-theme=dark], :root[data-theme=dark] { + --dt-order-arrow_color: rgb(229, 233, 238); + --dt-order-arrow_color-current: rgb(229, 233, 238); + --dt-order-header_outline-hover: 2px solid rgba(255, 255, 255, 0.05); +} + table.dataTable thead > tr > th:active, table.dataTable thead > tr > td:active { outline: none; @@ -91,20 +114,18 @@ table.dataTable thead > tr > td:active { table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > td.dt-orderable-asc .dt-column-order:before, table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before { - position: absolute; - display: block; - bottom: 50%; - content: "\25B2"; - content: "\25B2"/""; + bottom: calc(50% + var(--dt-order-arrow-gap)); + border-bottom: var(--dt-order-arrow-height) solid var(--dt-order-arrow_color); + border-left: calc(var(--dt-order-arrow-width) / 2) solid transparent; + border-right: calc(var(--dt-order-arrow-width) / 2) solid transparent; } table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, table.dataTable thead > tr > td.dt-orderable-desc .dt-column-order:after, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:after { - position: absolute; - display: block; - top: 50%; - content: "\25BC"; - content: "\25BC"/""; + top: calc(50% + 1px); + border-top: var(--dt-order-arrow-height) solid var(--dt-order-arrow_color); + border-left: calc(var(--dt-order-arrow-width) / 2) solid transparent; + border-right: calc(var(--dt-order-arrow-width) / 2) solid transparent; } table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order, table.dataTable thead > tr > td.dt-orderable-asc .dt-column-order, @@ -112,8 +133,8 @@ table.dataTable thead > tr > td.dt-orderable-desc .dt-column-order, table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order { position: relative; - width: 12px; - height: 20px; + width: var(--dt-order-arrow-width); + align-self: stretch; } table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, table.dataTable thead > tr > td.dt-orderable-asc .dt-column-order:before, @@ -124,10 +145,14 @@ table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:after, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:before, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:after { + position: absolute; + display: block; + content: " "; + height: 0; + width: 0; left: 0; - opacity: 0.125; - line-height: 9px; - font-size: 0.8em; + color: var(--dt-order-arrow_color); + opacity: var(--dt-order-arrow_opacity); } table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > td.dt-orderable-asc, @@ -137,13 +162,18 @@ table.dataTable thead > tr > td.dt-orderable-desc { table.dataTable thead > tr > th.dt-orderable-asc:hover, table.dataTable thead > tr > th.dt-orderable-desc:hover, table.dataTable thead > tr > td.dt-orderable-asc:hover, table.dataTable thead > tr > td.dt-orderable-desc:hover { - outline: 2px solid rgba(0, 0, 0, 0.05); + outline: var(--dt-order-header_outline-hover); outline-offset: -2px; } -table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, -table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before, +table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before { + border-bottom-color: var(--dt-order-arrow_color-current); + opacity: var(--dt-order-arrow_opacity-current); +} +table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:after { - opacity: 0.6; + border-top-color: var(--dt-order-arrow_color-current); + opacity: var(--dt-order-arrow_opacity-current); } table.dataTable thead > tr > th.dt-orderable-none:not(.dt-ordering-asc, .dt-ordering-desc) .dt-column-order:empty, table.dataTable thead > tr > th.sorting_desc_disabled .dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled .dt-column-order:before, table.dataTable thead > tr > td.dt-orderable-none:not(.dt-ordering-asc, .dt-ordering-desc) .dt-column-order:empty, @@ -166,7 +196,7 @@ table.dataTable tfoot > tr > td div.dt-column-header, table.dataTable tfoot > tr > td div.dt-column-footer { display: flex; justify-content: space-between; - align-items: var(--dt-header-align-items); + align-items: var(--dt-header-cell_align-items); gap: 4px; } table.dataTable thead > tr > th div.dt-column-header .dt-column-title, @@ -202,7 +232,14 @@ div.dt-scroll-body > table.dataTable > thead > tr > td { :root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-desc:hover, :root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-asc:hover, :root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-desc:hover { - outline: 2px solid rgba(255, 255, 255, 0.05); + outline: var(--dt-order-header_outline-hover); +} + +/*! DataTables Bootstrap 5 integration + * © SpryMedia Ltd - datatables.net/license + */ +:root { + --dt-processing-circle_background: var(--dt_background-selected); } div.dt-processing { @@ -228,8 +265,7 @@ div.dt-processing > div:last-child > div { width: 13px; height: 13px; border-radius: 50%; - background: rgb(13, 110, 253); - background: rgb(var(--dt-row-selected)); + background: rgb(var(--dt-processing-circle_background)); animation-timing-function: cubic-bezier(0, 1, 1, 0); } div.dt-processing > div:last-child > div:nth-child(1) { @@ -342,7 +378,7 @@ table.dataTable thead td, table.dataTable tfoot th, table.dataTable tfoot td { text-align: left; - vertical-align: var(--dt-header-vertical-align); + vertical-align: var(--dt-header-cell_vertical-align); } table.dataTable thead th.dt-head-left, table.dataTable thead td.dt-head-left, @@ -425,11 +461,16 @@ table.dataTable tbody td.dt-body-nowrap { white-space: nowrap; } -/*! Bootstrap 5 integration for DataTables - * - * ©2020 SpryMedia Ltd, all rights reserved. - * License: MIT datatables.net/license/mit - */ +:root { + --dt_background-selected: 13, 110, 253; +} + +:root[data-bs-theme=dark] { + --dt-row_background-hover: 255, 255, 255; + --dt-row_background-stripe: 255, 255, 255; + --dt-column-ordering_background: 255, 255, 255; +} + table.table.dataTable { clear: both; margin-bottom: 0; @@ -443,31 +484,26 @@ table.table.dataTable > :not(caption) > * > * { background-color: var(--bs-table-bg); } table.table.dataTable > tbody > tr { - background-color: transparent; + background-color: var(--dt-row_background); } table.table.dataTable > tbody > tr.selected > * { - box-shadow: inset 0 0 0 9999px rgb(13, 110, 253); - box-shadow: inset 0 0 0 9999px rgb(var(--dt-row-selected)); - color: rgb(255, 255, 255); - color: rgb(var(--dt-row-selected-text)); + box-shadow: inset 0 0 0 9999px rgb(var(--dt-row_background-selected)); + color: rgb(var(--dt-row-text_color-selected)); } table.table.dataTable > tbody > tr.selected a { - color: rgb(228, 228, 228); - color: rgb(var(--dt-row-selected-link)); + color: rgb(var(--dt-row-link_color-selected)); } table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * { - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row_background-stripe), 0.05); } table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1).selected > * { - box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row_background-selected), 0.95); } table.table.dataTable.table-hover > tbody > tr:hover > * { - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row_background-hover), 0.075); } table.table.dataTable.table-hover > tbody > tr.selected:hover > * { - box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row_background-selected), 0.975); } div.dt-container div.dt-layout-start > *:not(:last-child) { @@ -616,10 +652,4 @@ div.table-responsive > div.dt-container > div.row > div[class^=col-]:last-child padding-right: 0; } -:root[data-bs-theme=dark] { - --dt-row-hover: 255, 255, 255; - --dt-row-stripe: 255, 255, 255; - --dt-column-ordering: 255, 255, 255; -} - diff --git a/src/static/scripts/datatables.js b/src/static/scripts/datatables.js index c9f9ea56..1ae94cd2 100644 --- a/src/static/scripts/datatables.js +++ b/src/static/scripts/datatables.js @@ -4,14196 +4,12792 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#bs5/dt-2.3.8 + * https://datatables.net/download/#bs5/dt-3.0.3 * * Included libraries: - * DataTables 2.3.8 + * DataTables 3.0.3 */ -/*! DataTables 2.3.8 - * © SpryMedia Ltd - datatables.net/license +/*! DataTables 3.0.3 + * Copyright (c) SpryMedia Ltd - datatables.net/license */ -(function( factory ) { - "use strict"; - - if ( typeof define === 'function' && define.amd ) { +(function(factory){ + if (typeof define === 'function' && define.amd) { // AMD - define( ['jquery'], function ( $ ) { - return factory( $, window, document ); - } ); + define([], function () { + return factory(window, document); + }); } - else if ( typeof exports === 'object' ) { + else if (typeof exports === 'object') { // CommonJS - // jQuery's factory checks for a global window - if it isn't present then it - // returns a factory function that expects the window object - var jq = require('jquery'); + var cjsRequires = function (root) { }; if (typeof window === 'undefined') { - module.exports = function (root, $) { - if ( ! root ) { + module.exports = function (root) { + if (! root) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } - if ( ! $ ) { - $ = jq( root ); - } - - return factory( $, root, root.document ); + cjsRequires(root); + return factory(root, root.document); }; } else { - module.exports = factory( jq, window, window.document ); + cjsRequires(window); + module.exports = factory(window, window.document); } } else { // Browser - window.DataTable = factory( jQuery, window, document ); + window.DataTable = factory(window, document); } -}(function( $, window, document ) { - "use strict"; +}(function(window, document) { +'use strict'; - - var DataTable = function ( selector, options ) - { - // Check if called with a window or jQuery object for DOM less applications - // This is for backwards compatibility - if (DataTable.factory(selector, options)) { - return DataTable; - } - - // When creating with `new`, create a new DataTable, returning the API instance - if (this instanceof DataTable) { - return $(selector).DataTable(options); - } - else { - // Argument switching - options = selector; - } - - var _that = this; - var emptyInit = options === undefined; - var len = this.length; - - if ( emptyInit ) { - options = {}; - } - - // Method to get DT API instance from jQuery object - this.api = function () - { - return new _Api( this ); - }; - - this.each(function() { - // For each initialisation we want to give it a clean initialisation - // object that can be bashed around - var o = {}; - var oInit = len > 1 ? // optimisation for single table case - _fnExtend( o, options, true ) : - options; - - - var i=0, iLen; - var sId = this.getAttribute( 'id' ); - var defaults = DataTable.defaults; - var $this = $(this); - - // Sanity check - if ( this.nodeName.toLowerCase() != 'table' ) - { - _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); - return; - } - - // Special case for options - if (oInit.on && oInit.on.options) { - _fnListener($this, 'options', oInit.on.options); - } - - $this.trigger( 'options.dt', oInit ); - - /* Backwards compatibility for the defaults */ - _fnCompatOpts( defaults ); - _fnCompatCols( defaults.column ); - - /* Convert the camel-case defaults to Hungarian */ - _fnCamelToHungarian( defaults, defaults, true ); - _fnCamelToHungarian( defaults.column, defaults.column, true ); - - /* Setting up the initialisation object */ - _fnCamelToHungarian( defaults, $.extend( oInit, _fnEscapeObject($this.data()) ), true ); - - - - /* Check to see if we are re-initialising a table */ - var allSettings = DataTable.settings; - for ( i=0, iLen=allSettings.length ; i'), - fastData: function (row, column, type) { - return _fnGetCellData(oSettings, row, column, type); - } - } ); - oSettings.nTable = this; - oSettings.oInit = oInit; - - allSettings.push( oSettings ); - - // Make a single API instance available for internal handling - oSettings.api = new _Api( oSettings ); - - // Need to add the instance after the instance after the settings object has been added - // to the settings array, so we can self reference the table instance if more than one - oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable(); - - // Backwards compatibility, before we apply all the defaults - _fnCompatOpts( oInit ); - - // If the length menu is given, but the init display length is not, use the length menu - if ( oInit.aLengthMenu && ! oInit.iDisplayLength ) - { - oInit.iDisplayLength = Array.isArray(oInit.aLengthMenu[0]) - ? oInit.aLengthMenu[0][0] - : $.isPlainObject( oInit.aLengthMenu[0] ) - ? oInit.aLengthMenu[0].value - : oInit.aLengthMenu[0]; - } - - // Apply the defaults and init options to make a single init object will all - // options defined from defaults and instance options. - oInit = _fnExtend( $.extend( true, {}, defaults ), oInit ); - - - // Map the initialisation options onto the settings object - _fnMap( oSettings.oFeatures, oInit, [ - "bPaginate", - "bLengthChange", - "bFilter", - "bSort", - "bSortMulti", - "bInfo", - "bProcessing", - "bAutoWidth", - "bSortClasses", - "bServerSide", - "bDeferRender" - ] ); - _fnMap( oSettings, oInit, [ - "ajax", - "fnFormatNumber", - "sServerMethod", - "aaSorting", - "aaSortingFixed", - "aLengthMenu", - "sPaginationType", - "iStateDuration", - "bSortCellsTop", - "iTabIndex", - "sDom", - "fnStateLoadCallback", - "fnStateSaveCallback", - "renderer", - "searchDelay", - "rowId", - "caption", - "layout", - "orderDescReverse", - "orderIndicators", - "orderHandler", - "titleRow", - "typeDetect", - "columnTitleTag", - [ "iCookieDuration", "iStateDuration" ], // backwards compat - [ "oSearch", "oPreviousSearch" ], - [ "aoSearchCols", "aoPreSearchCols" ], - [ "iDisplayLength", "_iDisplayLength" ] - ] ); - _fnMap( oSettings.oScroll, oInit, [ - [ "sScrollX", "sX" ], - [ "sScrollXInner", "sXInner" ], - [ "sScrollY", "sY" ], - [ "bScrollCollapse", "bCollapse" ] - ] ); - _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" ); - - /* Callback functions which are array driven */ - _fnCallbackReg( oSettings, 'aoDrawCallback', oInit.fnDrawCallback ); - _fnCallbackReg( oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams ); - _fnCallbackReg( oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams ); - _fnCallbackReg( oSettings, 'aoStateLoaded', oInit.fnStateLoaded ); - _fnCallbackReg( oSettings, 'aoRowCallback', oInit.fnRowCallback ); - _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow ); - _fnCallbackReg( oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback ); - _fnCallbackReg( oSettings, 'aoFooterCallback', oInit.fnFooterCallback ); - _fnCallbackReg( oSettings, 'aoInitComplete', oInit.fnInitComplete ); - _fnCallbackReg( oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback ); - - oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId ); - - // Add event listeners - if (oInit.on) { - Object.keys(oInit.on).forEach(function (key) { - _fnListener($this, key, oInit.on[key]); - }); - } - - /* Browser support detection */ - _fnBrowserDetect( oSettings ); - - var oClasses = oSettings.oClasses; - - $.extend( oClasses, DataTable.ext.classes, oInit.oClasses ); - $this.addClass( oClasses.table ); - - if (! oSettings.oFeatures.bPaginate) { - oInit.iDisplayStart = 0; - } - - if ( oSettings.iInitDisplayStart === undefined ) - { - /* Display start point, taking into account the save saving */ - oSettings.iInitDisplayStart = oInit.iDisplayStart; - oSettings._iDisplayStart = oInit.iDisplayStart; - } - - var defer = oInit.iDeferLoading; - if ( defer !== null ) - { - oSettings.deferLoading = true; - - var tmp = Array.isArray(defer); - oSettings._iRecordsDisplay = tmp ? defer[0] : defer; - oSettings._iRecordsTotal = tmp ? defer[1] : defer; - } - - /* - * Columns - * See if we should load columns automatically or use defined ones - */ - var columnsInit = []; - var thead = this.getElementsByTagName('thead'); - var initHeaderLayout = _fnDetectHeader( oSettings, thead[0] ); - - // If we don't have a columns array, then generate one with nulls - if ( oInit.aoColumns ) { - columnsInit = oInit.aoColumns; - } - else if ( initHeaderLayout.length ) { - for ( i=0, iLen=initHeaderLayout[0].length ; i').prependTo( $this ); - } - - caption.html( oSettings.caption ); - } - - // Store the caption side, so we can remove the element from the document - // when creating the element - if (caption.length) { - caption[0]._captionSide = caption.css('caption-side'); - oSettings.captionNode = caption[0]; - } - - // Place the colgroup element in the correct location for the HTML structure - if (caption.length) { - oSettings.colgroup.insertAfter(caption); - } - else { - oSettings.colgroup.prependTo(oSettings.nTable); - } - - if ( thead.length === 0 ) { - thead = $('').appendTo($this); - } - oSettings.nTHead = thead[0]; - - var tbody = $this.children('tbody'); - if ( tbody.length === 0 ) { - tbody = $('').insertAfter(thead); - } - oSettings.nTBody = tbody[0]; - - var tfoot = $this.children('tfoot'); - if ( tfoot.length === 0 ) { - // If we are a scrolling table, and no footer has been given, then we need to create - // a tfoot element for the caption element to be appended to - tfoot = $('').appendTo($this); - } - oSettings.nTFoot = tfoot[0]; - - // Copy the data index array - oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); - - // Initialisation complete - table can be drawn - oSettings.bInitialised = true; - - // Language definitions - var oLanguage = oSettings.oLanguage; - $.extend( true, oLanguage, oInit.oLanguage ); - - if ( oLanguage.sUrl ) { - // Get the language definitions from a file - $.ajax( { - dataType: 'json', - url: oLanguage.sUrl, - success: function ( json ) { - _fnCamelToHungarian( defaults.oLanguage, json ); - $.extend( true, oLanguage, json, oSettings.oInit.oLanguage ); - - _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true); - _fnInitialise( oSettings ); - }, - error: function () { - // Error occurred loading language file - _fnLog( oSettings, 0, 'i18n file loading error', 21 ); - - // Continue on as best we can - _fnInitialise( oSettings ); - } - } ); - } - else { - _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true); - _fnInitialise( oSettings ); - } - } ); - _that = null; - return this; - }; - - - - /** - * DataTables extensions - * - * This namespace acts as a collection area for plug-ins that can be used to - * extend DataTables capabilities. Indeed many of the build in methods - * use this method to provide their own capabilities (sorting methods for - * example). - * - * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy - * reasons - * - * @namespace - */ - DataTable.ext = _ext = { - /** - * DataTables build type (expanded by the download builder) - * - * @type string - */ - builder: "bs5/dt-2.3.8", - - /** - * Buttons. For use with the Buttons extension for DataTables. This is - * defined here so other extensions can define buttons regardless of load - * order. It is _not_ used by DataTables core. - * - * @type object - * @default {} - */ - buttons: {}, - - - /** - * ColumnControl buttons and content - * - * @type object - */ - ccContent: {}, - - - /** - * Element class names - * - * @type object - * @default {} - */ - classes: {}, - - - /** - * Error reporting. - * - * How should DataTables report an error. Can take the value 'alert', - * 'throw', 'none' or a function. - * - * @type string|function - * @default alert - */ - errMode: "alert", - - /** HTML entity escaping */ - escape: { - /** When reading data-* attributes for initialisation options */ - attributes: false - }, - - /** - * Legacy so v1 plug-ins don't throw js errors on load - */ - feature: [], - - /** - * Feature plug-ins. - * - * This is an object of callbacks which provide the features for DataTables - * to be initialised via the `layout` option. - */ - features: {}, - - - /** - * Row searching. - * - * This method of searching is complimentary to the default type based - * searching, and a lot more comprehensive as it allows you complete control - * over the searching logic. Each element in this array is a function - * (parameters described below) that is called for every row in the table, - * and your logic decides if it should be included in the searching data set - * or not. - * - * Searching functions have the following input parameters: - * - * 1. `{object}` DataTables settings object: see - * {@link DataTable.models.oSettings} - * 2. `{array|object}` Data for the row to be processed (same as the - * original format that was passed in as the data source, or an array - * from a DOM data source - * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which - * can be useful to retrieve the `TR` element if you need DOM interaction. - * - * And the following return is expected: - * - * * {boolean} Include the row in the searched result set (true) or not - * (false) - * - * Note that as with the main search ability in DataTables, technically this - * is "filtering", since it is subtractive. However, for consistency in - * naming we call it searching here. - * - * @type array - * @default [] - * - * @example - * // The following example shows custom search being applied to the - * // fourth column (i.e. the data[3] index) based on two input values - * // from the end-user, matching the data in a certain range. - * $.fn.dataTable.ext.search.push( - * function( settings, data, dataIndex ) { - * var min = document.getElementById('min').value * 1; - * var max = document.getElementById('max').value * 1; - * var version = data[3] == "-" ? 0 : data[3]*1; - * - * if ( min == "" && max == "" ) { - * return true; - * } - * else if ( min == "" && version < max ) { - * return true; - * } - * else if ( min < version && "" == max ) { - * return true; - * } - * else if ( min < version && version < max ) { - * return true; - * } - * return false; - * } - * ); - */ - search: [], - - - /** - * Selector extensions - * - * The `selector` option can be used to extend the options available for the - * selector modifier options (`selector-modifier` object data type) that - * each of the three built in selector types offer (row, column and cell + - * their plural counterparts). For example the Select extension uses this - * mechanism to provide an option to select only rows, columns and cells - * that have been marked as selected by the end user (`{selected: true}`), - * which can be used in conjunction with the existing built in selector - * options. - * - * Each property is an array to which functions can be pushed. The functions - * take three attributes: - * - * * Settings object for the host table - * * Options object (`selector-modifier` object type) - * * Array of selected item indexes - * - * The return is an array of the resulting item indexes after the custom - * selector has been applied. - * - * @type object - */ - selector: { - cell: [], - column: [], - row: [] - }, - - - /** - * Legacy configuration options. Enable and disable legacy options that - * are available in DataTables. - * - * @type object - */ - legacy: { - /** - * Enable / disable DataTables 1.9 compatible server-side processing - * requests - * - * @type boolean - * @default null - */ - ajax: null - }, - - - /** - * Pagination plug-in methods. - * - * Each entry in this object is a function and defines which buttons should - * be shown by the pagination rendering method that is used for the table: - * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the - * buttons are displayed in the document, while the functions here tell it - * what buttons to display. This is done by returning an array of button - * descriptions (what each button will do). - * - * Pagination types (the four built in options and any additional plug-in - * options defined here) can be used through the `paginationType` - * initialisation parameter. - * - * The functions defined take two parameters: - * - * 1. `{int} page` The current page index - * 2. `{int} pages` The number of pages in the table - * - * Each function is expected to return an array where each element of the - * array can be one of: - * - * * `first` - Jump to first page when activated - * * `last` - Jump to last page when activated - * * `previous` - Show previous page when activated - * * `next` - Show next page when activated - * * `{int}` - Show page of the index given - * * `{array}` - A nested array containing the above elements to add a - * containing 'DIV' element (might be useful for styling). - * - * Note that DataTables v1.9- used this object slightly differently whereby - * an object with two functions would be defined for each plug-in. That - * ability is still supported by DataTables 1.10+ to provide backwards - * compatibility, but this option of use is now decremented and no longer - * documented in DataTables 1.10+. - * - * @type object - * @default {} - * - * @example - * // Show previous, next and current page buttons only - * $.fn.dataTableExt.oPagination.current = function ( page, pages ) { - * return [ 'previous', page, 'next' ]; - * }; - */ - pager: {}, - - - renderer: { - pageButton: {}, - header: {} - }, - - - /** - * Ordering plug-ins - custom data source - * - * The extension options for ordering of data available here is complimentary - * to the default type based ordering that DataTables typically uses. It - * allows much greater control over the data that is being used to - * order a column, but is necessarily therefore more complex. - * - * This type of ordering is useful if you want to do ordering based on data - * live from the DOM (for example the contents of an 'input' element) rather - * than just the static string that DataTables knows of. - * - * The way these plug-ins work is that you create an array of the values you - * wish to be ordering for the column in question and then return that - * array. The data in the array much be in the index order of the rows in - * the table (not the currently ordering order!). Which order data gathering - * function is run here depends on the `dt-init columns.orderDataType` - * parameter that is used for the column (if any). - * - * The functions defined take two parameters: - * - * 1. `{object}` DataTables settings object: see - * {@link DataTable.models.oSettings} - * 2. `{int}` Target column index - * - * Each function is expected to return an array: - * - * * `{array}` Data for the column to be ordering upon - * - * @type array - * - * @example - * // Ordering using `input` node values - * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col ) - * { - * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { - * return $('input', td).val(); - * } ); - * } - */ - order: {}, - - - /** - * Type based plug-ins. - * - * Each column in DataTables has a type assigned to it, either by automatic - * detection or by direct assignment using the `type` option for the column. - * The type of a column will effect how it is ordering and search (plug-ins - * can also make use of the column type if required). - * - * @namespace - */ - type: { - /** - * Automatic column class assignment - */ - className: {}, - - /** - * Type detection functions. - * - * The functions defined in this object are used to automatically detect - * a column's type, making initialisation of DataTables super easy, even - * when complex data is in the table. - * - * The functions defined take two parameters: - * - * 1. `{*}` Data from the column cell to be analysed - * 2. `{settings}` DataTables settings object. This can be used to - * perform context specific type detection - for example detection - * based on language settings such as using a comma for a decimal - * place. Generally speaking the options from the settings will not - * be required - * - * Each function is expected to return: - * - * * `{string|null}` Data type detected, or null if unknown (and thus - * pass it on to the other type detection functions. - * - * @type array - * - * @example - * // Currency type detection plug-in: - * $.fn.dataTable.ext.type.detect.push( - * function ( data, settings ) { - * // Check the numeric part - * if ( ! data.substring(1).match(/[0-9]/) ) { - * return null; - * } - * - * // Check prefixed by currency - * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) { - * return 'currency'; - * } - * return null; - * } - * ); - */ - detect: [], - - /** - * Automatic renderer assignment - */ - render: {}, - - - /** - * Type based search formatting. - * - * The type based searching functions can be used to pre-format the - * data to be search on. For example, it can be used to strip HTML - * tags or to de-format telephone numbers for numeric only searching. - * - * Note that is a search is not defined for a column of a given type, - * no search formatting will be performed. - * - * Pre-processing of searching data plug-ins - When you assign the sType - * for a column (or have it automatically detected for you by DataTables - * or a type detection plug-in), you will typically be using this for - * custom sorting, but it can also be used to provide custom searching - * by allowing you to pre-processing the data and returning the data in - * the format that should be searched upon. This is done by adding - * functions this object with a parameter name which matches the sType - * for that target column. This is the corollary of afnSortData - * for searching data. - * - * The functions defined take a single parameter: - * - * 1. `{*}` Data from the column cell to be prepared for searching - * - * Each function is expected to return: - * - * * `{string|null}` Formatted string that will be used for the searching. - * - * @type object - * @default {} - * - * @example - * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { - * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); - * } - */ - search: {}, - - - /** - * Type based ordering. - * - * The column type tells DataTables what ordering to apply to the table - * when a column is sorted upon. The order for each type that is defined, - * is defined by the functions available in this object. - * - * Each ordering option can be described by three properties added to - * this object: - * - * * `{type}-pre` - Pre-formatting function - * * `{type}-asc` - Ascending order function - * * `{type}-desc` - Descending order function - * - * All three can be used together, only `{type}-pre` or only - * `{type}-asc` and `{type}-desc` together. It is generally recommended - * that only `{type}-pre` is used, as this provides the optimal - * implementation in terms of speed, although the others are provided - * for compatibility with existing JavaScript sort functions. - * - * `{type}-pre`: Functions defined take a single parameter: - * - * 1. `{*}` Data from the column cell to be prepared for ordering - * - * And return: - * - * * `{*}` Data to be sorted upon - * - * `{type}-asc` and `{type}-desc`: Functions are typical JavaScript sort - * functions, taking two parameters: - * - * 1. `{*}` Data to compare to the second parameter - * 2. `{*}` Data to compare to the first parameter - * - * And returning: - * - * * `{*}` Ordering match: <0 if first parameter should be sorted lower - * than the second parameter, ===0 if the two parameters are equal and - * >0 if the first parameter should be sorted height than the second - * parameter. - * - * @type object - * @default {} - * - * @example - * // Numeric ordering of formatted numbers with a pre-formatter - * $.extend( $.fn.dataTable.ext.type.order, { - * "string-pre": function(x) { - * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); - * return parseFloat( a ); - * } - * } ); - * - * @example - * // Case-sensitive string ordering, with no pre-formatting method - * $.extend( $.fn.dataTable.ext.order, { - * "string-case-asc": function(x,y) { - * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - * }, - * "string-case-desc": function(x,y) { - * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); - * } - * } ); - */ - order: {} - }, - - /** - * Unique DataTables instance counter - * - * @type int - * @private - */ - _unique: 0, - - - // - // Depreciated - // The following properties are retained for backwards compatibility only. - // The should not be used in new projects and will be removed in a future - // version - // - - /** - * Version check function. - * @type function - * @depreciated Since 1.10 - */ - fnVersionCheck: DataTable.fnVersionCheck, - - - /** - * Index for what 'this' index API functions should use - * @type int - * @deprecated Since v1.10 - */ - iApiIndex: 0, - - - /** - * Software version - * @type string - * @deprecated Since v1.10 - */ - sVersion: DataTable.version - }; - - - // - // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts - // - $.extend( _ext, { - afnFiltering: _ext.search, - aTypes: _ext.type.detect, - ofnSearch: _ext.type.search, - oSort: _ext.type.order, - afnSortData: _ext.order, - aoFeatures: _ext.feature, - oStdClasses: _ext.classes, - oPagination: _ext.pager - } ); - - - $.extend( DataTable.ext.classes, { - container: 'dt-container', - empty: { - row: 'dt-empty' - }, - info: { - container: 'dt-info' - }, - layout: { - row: 'dt-layout-row', - cell: 'dt-layout-cell', - tableRow: 'dt-layout-table', - tableCell: '', - start: 'dt-layout-start', - end: 'dt-layout-end', - full: 'dt-layout-full' - }, - length: { - container: 'dt-length', - select: 'dt-input' - }, - order: { - canAsc: 'dt-orderable-asc', - canDesc: 'dt-orderable-desc', - isAsc: 'dt-ordering-asc', - isDesc: 'dt-ordering-desc', - none: 'dt-orderable-none', - position: 'sorting_' - }, - processing: { - container: 'dt-processing' - }, - scrolling: { - body: 'dt-scroll-body', - container: 'dt-scroll', - footer: { - self: 'dt-scroll-foot', - inner: 'dt-scroll-footInner' - }, - header: { - self: 'dt-scroll-head', - inner: 'dt-scroll-headInner' - } - }, - search: { - container: 'dt-search', - input: 'dt-input' - }, - table: 'dataTable', - tbody: { - cell: '', - row: '' - }, - thead: { - cell: '', - row: '' - }, - tfoot: { - cell: '', - row: '' - }, - paging: { - active: 'current', - button: 'dt-paging-button', - container: 'dt-paging', - disabled: 'disabled', - nav: '' - } - } ); - - - /* - * It is useful to have variables which are scoped locally so only the - * DataTables functions can access them and they don't leak into global space. - * At the same time these functions are often useful over multiple files in the - * core and API, so we list, or at least document, all variables which are used - * by DataTables as private variables here. This also ensures that there is no - * clashing of variable names and that they can easily referenced for reuse. - */ - - - // Defined else where - // _selector_run - // _selector_opts - // _selector_row_indexes - - var _ext; // DataTable.ext - var _Api; // DataTable.Api - var _api_register; // DataTable.Api.register - var _api_registerPlural; // DataTable.Api.registerPlural - - var _re_dic = {}; - var _re_new_lines = /[\r\n\u2028]/g; - var _re_html = /<([^>]*>)/g; - var _max_str_len = Math.pow(2, 28); - - // This is not strict ISO8601 - Date.parse() is quite lax, although - // implementations differ between browsers. - var _re_date = /^\d{2,4}[./-]\d{1,2}[./-]\d{1,2}([T ]{1}\d{1,2}[:.]\d{2}([.:]\d{2})?)?$/; - - // Escape regular expression special characters - var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); - - // https://en.wikipedia.org/wiki/Foreign_exchange_market - // - \u20BD - Russian ruble. - // - \u20a9 - South Korean Won - // - \u20BA - Turkish Lira - // - \u20B9 - Indian Rupee - // - R - Brazil (R$) and South Africa - // - fr - Swiss Franc - // - kr - Swedish krona, Norwegian krone and Danish krone - // - \u2009 is thin space and \u202F is narrow no-break space, both used in many - // - Ƀ - Bitcoin - // - Ξ - Ethereum - // standards as thousands separators. - var _re_formatted_numeric = /['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi; - - - var _empty = function ( d ) { - return !d || d === true || d === '-' ? true : false; - }; - - - var _intVal = function ( s ) { - var integer = parseInt( s, 10 ); - return !isNaN(integer) && isFinite(s) ? integer : null; - }; - - // Convert from a formatted number with characters other than `.` as the - // decimal place, to a JavaScript number - var _numToDecimal = function ( num, decimalPoint ) { - // Cache created regular expressions for speed as this function is called often - if ( ! _re_dic[ decimalPoint ] ) { - _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' ); - } - return typeof num === 'string' && decimalPoint !== '.' ? - num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) : - num; - }; - - - var _isNumber = function ( d, decimalPoint, formatted, allowEmpty ) { - var type = typeof d; - var strType = type === 'string'; - - if ( type === 'number' || type === 'bigint') { - return true; - } - - // If empty return immediately so there must be a number if it is a - // formatted string (this stops the string "k", or "kr", etc being detected - // as a formatted number for currency - if ( allowEmpty && _empty( d ) ) { - return true; - } - - if ( decimalPoint && strType ) { - d = _numToDecimal( d, decimalPoint ); - } - - if ( formatted && strType ) { - d = d.replace( _re_formatted_numeric, '' ); - } - - return !isNaN( parseFloat(d) ) && isFinite( d ); - }; - - - // A string without HTML in it can be considered to be HTML still - var _isHtml = function ( d ) { - return _empty( d ) || typeof d === 'string'; - }; - - // Is a string a number surrounded by HTML? - var _htmlNumeric = function ( d, decimalPoint, formatted, allowEmpty ) { - if ( allowEmpty && _empty( d ) ) { - return true; - } - - // input and select strings mean that this isn't just a number - if (typeof d === 'string' && d.match(/<(input|select)/i)) { - return null; - } - - var html = _isHtml( d ); - return ! html ? - null : - _isNumber( _stripHtml( d ), decimalPoint, formatted, allowEmpty ) ? - true : - null; - }; - - - var _pluck = function ( a, prop, prop2 ) { - var out = []; - var i=0, iLen=a.length; - - // Could have the test in the loop for slightly smaller code, but speed - // is essential here - if ( prop2 !== undefined ) { - for ( ; i _max_str_len) { - throw new Error('Exceeded max str len'); - } - - var previous; - - input = input.replace(_re_html, replacement || ''); // Complete tags - - // Safety for incomplete script tag - use do / while to ensure that - // we get all instances - do { - previous = input; - input = input.replace(/ diff --git a/src/static/templates/admin/users.hbs b/src/static/templates/admin/users.hbs index 4c91bc0e..b1dfb17d 100644 --- a/src/static/templates/admin/users.hbs +++ b/src/static/templates/admin/users.hbs @@ -14,7 +14,7 @@ Entries Attachments Organizations - Actions + Actions @@ -47,10 +47,10 @@ {{/if}} - {{created_at}} + {{created_at}} - {{last_active}} + {{last_active}} {{cipher_count}} @@ -153,7 +153,6 @@ - diff --git a/src/util.rs b/src/util.rs index 0e8a93e4..6de2d803 100644 --- a/src/util.rs +++ b/src/util.rs @@ -537,10 +537,7 @@ pub fn is_valid_email(email: &str) -> bool { let Ok(email_url) = url::Url::parse(&format!("https://{}", email.domain())) else { return false; }; - if email_url.path().ne("/") || email_url.domain().is_none() || email_url.query().is_some() { - return false; - } - true + email_url.domain().is_some() && email_url.path() == "/" && email_url.query().is_none() } // From a6c3bd6d1826fb527822df4a2655f34fd440d7d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Thu, 3 Sep 2026 21:17:50 +0200 Subject: [PATCH 08/20] Update rust docker version (#7689) --- docker/DockerSettings.yaml | 2 +- docker/Dockerfile.alpine | 8 ++++---- docker/Dockerfile.debian | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/DockerSettings.yaml b/docker/DockerSettings.yaml index 4c5e851b..fdbf40f2 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -5,7 +5,7 @@ vault_image_digest: "sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10 # We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts # https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707" -rust_version: 1.97.1 # Rust version to be used +rust_version: 1.98.0 # Rust version to be used debian_version: trixie # Debian release name to be used alpine_version: "3.24" # Alpine version to be used # For which platforms/architectures will we try to build images diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index 7045138d..491aa9e0 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -32,10 +32,10 @@ FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330 ########################## ALPINE BUILD IMAGES ########################## ## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64 ## And for Alpine we define all build images here, they will only be loaded when actually used -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.97.1 AS build_amd64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.97.1 AS build_arm64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.97.1 AS build_armv7 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.97.1 AS build_armv6 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.98.0 AS build_amd64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.98.0 AS build_arm64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.98.0 AS build_armv7 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.98.0 AS build_armv6 ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 9ab02568..280559e2 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -36,7 +36,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 -FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.97.1-slim-trixie AS build +FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.98.0-slim-trixie AS build # hadolint ignore=DL3067 COPY --from=xx / / ARG TARGETARCH From 32d85d03bb5ec401d1378f4cd60139be1d8db3f4 Mon Sep 17 00:00:00 2001 From: Tom <83423411+tom27052006@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:13:32 +0200 Subject: [PATCH 09/20] Fix organization import failing with missing field groups (#7699) --- src/api/core/organizations.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 9082297f..c0c90426 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -132,7 +132,6 @@ struct FullCollectionData { name: String, groups: Vec, users: Vec, - id: Option, external_id: Option, } @@ -1793,11 +1792,22 @@ async fn bulk_public_keys( use super::ciphers::CipherData; use super::ciphers::update_cipher_from_data; +// The import endpoint only ever uses the name/id/external_id of a collection. +// Bitwarden's own server ignores `groups`/`users` here too, so do not make them +// mandatory: clients are free to leave them out. +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ImportCollectionData { + name: String, + id: Option, + external_id: Option, +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct ImportData { ciphers: Vec, - collections: Vec, + collections: Vec, collection_relationships: Vec, } From 2ffad8775d8712329aab7d00a05a94f64098170b Mon Sep 17 00:00:00 2001 From: Tom <83423411+tom27052006@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:13:40 +0200 Subject: [PATCH 10/20] Add `pm-32413-multi-client-password-management` feature flag (#7677) --- .env.template | 1 + src/config.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/.env.template b/.env.template index 5f6f374c..d22145b8 100644 --- a/.env.template +++ b/.env.template @@ -390,6 +390,7 @@ ## ## The following flags are available: ## - "pm-5594-safari-account-switching": Enable account switching in Safari. (Safari >= 2026.2.0) +## - "pm-32413-multi-client-password-management": Enable changing the master password directly in the client. (Desktop/Extension >= 2026.4.0) ## - "ssh-agent": Enable SSH agent support on Desktop. (Desktop >= 2024.12.0) ## - "ssh-agent-v2": Enable newer SSH agent support. (Desktop >= 2026.2.1) ## - "ssh-key-vault-item": Enable the creation and use of SSH key vault items. (Clients >= 2024.12.0) diff --git a/src/config.rs b/src/config.rs index 87bea195..7e21ecf1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1425,6 +1425,7 @@ pub const SUPPORTED_FEATURE_FLAGS: &[&str] = &[ "desktop-ui-migration-milestone-4", // Auth Team "pm-5594-safari-account-switching", + "pm-32413-multi-client-password-management", // Autofill Team "ssh-agent", "ssh-agent-v2", From 277e1536ebe426296519f8bd8bf99f5f6c1c5c77 Mon Sep 17 00:00:00 2001 From: The CRahn <5043504+crahn@users.noreply.github.com> Date: Tue, 8 Sep 2026 05:13:51 -0500 Subject: [PATCH 11/20] Log IP/username on two-factor email-login credential failures (#7654) The three "Username or password is incorrect" errors in send_email_login() (email.rs) don't log the client IP or submitted identifier, unlike the equivalent wrong-password error in password_login() (identity.rs), which logs both via format!("IP: {}. Username: {username}.", ip.ip). This makes the two code paths inconsistent for the same underlying error, and means log-based tooling that keys on the identity.rs error's "IP: x.x.x.x" pattern can't do the same for this endpoint. Bring email.rs's three call sites in line with identity.rs's existing format. The two email-present branches log IP+Username (the email submitted); the device-identifier-only branch (SSO path, no email in scope) logs IP+Device instead of fabricating a username. Verified: cargo build/test/clippy/fmt all pass with the sqlite feature (matching one leg of this repo's own CI matrix), including the two existing unit tests in this file. --- src/api/core/two_factor/email.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/api/core/two_factor/email.rs b/src/api/core/two_factor/email.rs index 44ba2e7f..3667b871 100644 --- a/src/api/core/two_factor/email.rs +++ b/src/api/core/two_factor/email.rs @@ -63,13 +63,19 @@ async fn send_email_login(data: Json, client_headers: Client let user = if let Some(email) = email { let Some(user) = User::find_by_mail(email, &conn).await else { - err!("Username or password is incorrect. Try again.") + err!( + "Username or password is incorrect. Try again", + format!("IP: {}. Username: {email}.", client_headers.ip.ip) + ) }; if let Some(master_password_hash) = master_password_hash { // Check password if !user.check_valid_password(master_password_hash) { - err!("Username or password is incorrect. Try again.") + err!( + "Username or password is incorrect. Try again", + format!("IP: {}. Username: {email}.", client_headers.ip.ip) + ) } } else if let Some(auth_request_id) = auth_request_id { let Some(auth_request) = AuthRequest::find_by_uuid(auth_request_id, &conn).await else { @@ -96,7 +102,10 @@ async fn send_email_login(data: Json, client_headers: Client }; // SSO login only sends device id, so we get the user by the most recently used device let Some(user) = User::find_by_device_for_email2fa(device_identifier, &conn).await else { - err!("Username or password is incorrect. Try again.") + err!( + "Username or password is incorrect. Try again", + format!("IP: {}. Device: {device_identifier}.", client_headers.ip.ip) + ) }; user From 57fbed1bed2e42b540cb790dd536e02633c4f445 Mon Sep 17 00:00:00 2001 From: Timshel Date: Tue, 8 Sep 2026 10:14:01 +0000 Subject: [PATCH 12/20] Support admin reset 2fa (#7435) * Support admin reset 2fa * Fix recovery email --------- Co-authored-by: Timshel --- playwright/tests/organization.smtp.spec.ts | 7 +- src/api/core/organizations.rs | 89 ++++++++++++------- src/auth.rs | 12 ++- src/config.rs | 2 +- src/db/models/event.rs | 8 +- src/mail.rs | 14 ++- .../email/admin_account_recovery.hbs | 12 +++ ...ml.hbs => admin_account_recovery.html.hbs} | 11 ++- .../templates/email/admin_reset_password.hbs | 4 - 9 files changed, 112 insertions(+), 47 deletions(-) create mode 100644 src/static/templates/email/admin_account_recovery.hbs rename src/static/templates/email/{admin_reset_password.html.hbs => admin_account_recovery.html.hbs} (55%) delete mode 100644 src/static/templates/email/admin_reset_password.hbs diff --git a/playwright/tests/organization.smtp.spec.ts b/playwright/tests/organization.smtp.spec.ts index 6d0eb859..1e97ed5d 100644 --- a/playwright/tests/organization.smtp.spec.ts +++ b/playwright/tests/organization.smtp.spec.ts @@ -127,6 +127,9 @@ test('Organization is visible', async ({ page }) => { }); test('Recover user password', async ({ page }) => { + await logUser(test, page, users.user2, { mailBuffer: mail2Buffer }); + await activateTOTP(test, page, users.user2); + await logUser(test, page, users.user1, { mailBuffer: mail1Buffer }); let newPassword = "TotoNewPassword"; @@ -138,9 +141,10 @@ test('Recover user password', async ({ page }) => { await page.getByRole('menuitem', { name: 'Recover account' }).click(); await page.getByRole('textbox', { name: 'New master password * (required)', exact: true }).fill(newPassword); await page.getByRole('textbox', { name: 'Confirm new master password * (' }).fill(newPassword); + await page.getByRole('checkbox', { name: 'Reset two-step login' }).check(); await page.getByRole('button', { name: 'Save' }).click(); await utils.checkNotification(page, 'Account recovery success'); - await mail2Buffer.expect((m) => m.subject.includes('Master Password Has Been Changed')); + await mail2Buffer.expect((m) => m.subject.includes('Admin account recovery from Test organization')); }); let user2 = { @@ -150,6 +154,7 @@ test('Recover user password', async ({ page }) => { }; await logUser(test, page, user2, { mailBuffer: mail2Buffer, + mail2fa: true, notNewDevice: true, }); }); diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index c0c90426..4f490854 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use num_traits::FromPrimitive; -use rocket::{Route, serde::json::Json}; +use rocket::{Route, http::Status, serde::json::Json}; use serde_json::Value; use crate::{ @@ -17,7 +17,8 @@ use crate::{ models::{ Cipher, CipherId, Collection, CollectionCipher, CollectionGroup, CollectionId, CollectionUser, EventType, Group, GroupId, GroupUser, Invitation, Membership, MembershipId, MembershipStatus, MembershipType, - OrgPolicy, OrgPolicyType, Organization, OrganizationApiKey, OrganizationId, User, UserId, + OrgPolicy, OrgPolicyType, Organization, OrganizationApiKey, OrganizationId, TwoFactor, TwoFactorType, User, + UserId, }, }, mail, @@ -390,7 +391,7 @@ async fn get_org_collections(org_id: OrganizationId, headers: ManagerHeadersLoos } if !headers.membership.has_full_access() { - err_code!("Resource not found.", "User does not have full access", rocket::http::Status::NotFound.code); + err_code!("Resource not found.", "User does not have full access", Status::NotFound.code); } Ok(Json(json!({ @@ -886,11 +887,11 @@ struct OrgIdData { #[get("/ciphers/organization-details?")] async fn get_org_details(data: OrgIdData, headers: ManagerHeadersLoose, conn: DbConn) -> JsonResult { if data.organization_id != headers.membership.org_uuid { - err_code!("Resource not found.", "Organization id's do not match", rocket::http::Status::NotFound.code); + err_code!("Resource not found.", "Organization id's do not match", Status::NotFound.code); } if !headers.membership.has_full_access() { - err_code!("Resource not found.", "User does not have full access", rocket::http::Status::NotFound.code); + err_code!("Resource not found.", "User does not have full access", Status::NotFound.code); } Ok(Json(json!({ @@ -954,7 +955,7 @@ async fn get_members( } if !headers.membership.has_full_access() { - err_code!("Resource not found.", "User does not have full access", rocket::http::Status::NotFound.code); + err_code!("Resource not found.", "User does not have full access", Status::NotFound.code); } let mut users_json = Vec::new(); @@ -2486,7 +2487,7 @@ async fn get_groups_data( || Collection::has_manageable_collection_by_user(&org_id, &headers.membership.user_uuid, &conn).await }; if !allowed { - err_code!("Resource not found.", "User does not have access", rocket::http::Status::NotFound.code); + err_code!("Resource not found.", "User does not have access", Status::NotFound.code); } let groups: Vec = if CONFIG.org_groups_enabled() { @@ -2937,8 +2938,8 @@ struct OrganizationUserResetPasswordEnrollmentRequest { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct OrganizationUserRecoverAccountRequest { - new_master_password_hash: String, - key: String, + new_master_password_hash: Option, + key: Option, #[serde(default)] reset_master_password: bool, @@ -2982,12 +2983,7 @@ async fn put_recover_account( conn: DbConn, nt: Notify<'_>, ) -> EmptyResult { - let req = data.into_inner(); - if req.reset_master_password && !req.reset_two_factor { - recover_account(org_id, member_id, headers, req, conn, nt).await - } else { - err!("Unsupported operation") - } + recover_account(org_id, member_id, headers, data.into_inner(), conn, nt).await } // Deprecated since `v2026.4.2` @@ -3007,7 +3003,7 @@ async fn recover_account( org_id: OrganizationId, member_id: MembershipId, headers: AdminHeaders, - reset_request: OrganizationUserRecoverAccountRequest, + req: OrganizationUserRecoverAccountRequest, conn: DbConn, nt: Notify<'_>, ) -> EmptyResult { @@ -3022,7 +3018,7 @@ async fn recover_account( err!("User to reset isn't member of required organization") }; - let Some(user) = User::find_by_uuid(&member.user_uuid, &conn).await else { + let Some(mut user) = User::find_by_uuid(&member.user_uuid, &conn).await else { err!("User not found") }; @@ -3035,29 +3031,56 @@ async fn recover_account( err!("Organization user must be confirmed for password reset functionality"); } - // Sending email before resetting password to ensure working email configuration and the resulting - // user notification. Also this might add some protection against security flaws and misuse - if let Err(e) = mail::send_admin_reset_password(&user.email, user.display_name(), &org.name).await { + let fallback_2fa_email = if req.reset_two_factor && CONFIG.email_2fa_auto_fallback() { + TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email as i32, &conn).await.is_none() + } else { + false + }; + + // Sending email first ensure working email configuration and the resulting user notification. + // Also this might add some protection against security flaws and misuse + if let Err(e) = mail::send_admin_account_recovery( + &user.email, + user.display_name(), + &org.name, + req.reset_master_password, + req.reset_two_factor, + fallback_2fa_email, + ) + .await + { err!(format!("Error sending user reset password email: {e:#?}")); } - let mut user = user; - user.set_password(reset_request.new_master_password_hash.as_str(), Some(reset_request.key), true, None, &conn) - .await?; + if req.reset_master_password { + if let Some(key) = req.key + && let Some(hash) = req.new_master_password_hash + { + user.set_password(hash.as_str(), Some(key), true, None, &conn).await?; + } else { + err_code!("Unprocessable request", "Missing fields to reset password", Status::UnprocessableEntity.code); + } + } + + if req.reset_two_factor { + TwoFactor::delete_all_by_user(&user.uuid, &conn).await?; + if !fallback_2fa_email || two_factor::email::find_and_activate_email_2fa(&user.uuid, &conn).await.is_err() { + two_factor::enforce_2fa_policy(&user, &headers.user.uuid, headers.device.atype, &headers.ip.ip, &conn) + .await?; + } + } + user.save(&conn).await?; nt.send_logout(&user, None, &conn).await; - log_event( - EventType::OrganizationUserAdminResetPassword, - &member_id, - &org_id, - &headers.user.uuid, - headers.device.atype, - &headers.ip.ip, - &conn, - ) - .await; + if req.reset_master_password { + headers.log_event(EventType::OrganizationUserAdminResetPassword, &member_id, &org_id, &conn).await; + } + + if req.reset_two_factor { + headers.log_event(EventType::OrganizationUserAdminResetTwoFactor, &member_id, &org_id, &conn).await; + } Ok(()) } diff --git a/src/auth.rs b/src/auth.rs index 762088e5..07373389 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -23,14 +23,14 @@ use rocket::{ use crate::{ CONFIG, - api::ApiResult, + api::{ApiResult, core::log_event}, config::PathType, db::{ DbConn, models::{ AttachmentId, CipherId, Collection, CollectionId, Device, DeviceId, DeviceType, EmergencyAccessId, - Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, OrganizationId, SendFileId, - SendId, User, UserId, UserStampException, + EventType, Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, OrganizationId, + SendFileId, SendId, User, UserId, UserStampException, }, }, error::Error, @@ -822,6 +822,12 @@ pub struct AdminHeaders { pub org_id: OrganizationId, } +impl AdminHeaders { + pub async fn log_event(&self, event_type: EventType, source_uuid: &str, org_id: &OrganizationId, conn: &DbConn) { + log_event(event_type, source_uuid, org_id, &self.user.uuid, self.device.atype, &self.ip.ip, conn).await; + } +} + #[rocket::async_trait] impl<'r> FromRequest<'r> for AdminHeaders { type Error = &'static str; diff --git a/src/config.rs b/src/config.rs index 7e21ecf1..37fc3e85 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1745,7 +1745,7 @@ where reg!("email/email_footer"); reg!("email/email_footer_text"); - reg!("email/admin_reset_password", ".html"); + reg!("email/admin_account_recovery", ".html"); reg!("email/change_email_existing", ".html"); reg!("email/change_email_invited", ".html"); reg!("email/change_email", ".html"); diff --git a/src/db/models/event.rs b/src/db/models/event.rs index 86cbf5d0..1f307979 100644 --- a/src/db/models/event.rs +++ b/src/db/models/event.rs @@ -43,7 +43,7 @@ pub struct Event { pub provider_org_uuid: Option, } -// Upstream enum: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Enums/EventType.cs +// Upstream enum: https://github.com/bitwarden/server/blob/v2026.6.2/src/Core/Dirt/Enums/EventType.cs #[derive(Debug, Copy, Clone)] pub enum EventType { // User @@ -108,6 +108,12 @@ pub enum EventType { OrganizationUserRejectedAuthRequest = 1514, OrganizationUserDeleted = 1515, // Both user and organization user data were deleted OrganizationUserLeft = 1516, // User voluntarily left the organization + // OrganizationUserAutomaticallyConfirmed = 1517, + // OrganizationUserSelfRevoked = 1518, // User self-revoked due to declining organization data ownership policy + OrganizationUserAdminResetTwoFactor = 1519, + // OrganizationUserRevoked_TwoFactorNonCompliance = 1520, + // OrganizationUserRevoked_SingleOrganizationNonCompliance = 1521, + // OrganizationUserNotificationBannerActionClicked = 1522, // Organization OrganizationUpdated = 1600, diff --git a/src/mail.rs b/src/mail.rs index a7e5e5ae..b20f2853 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -633,14 +633,24 @@ pub async fn send_test(address: &str) -> EmptyResult { send_email(address, &subject, body_html, body_text).await } -pub async fn send_admin_reset_password(address: &str, user_name: &str, org_name: &str) -> EmptyResult { +pub async fn send_admin_account_recovery( + address: &str, + user_name: &str, + org_name: &str, + reset_password: bool, + reset_2fa: bool, + fallback_2fa_email: bool, +) -> EmptyResult { let (subject, body_html, body_text) = get_text( - "email/admin_reset_password", + "email/admin_account_recovery", json!({ "url": CONFIG.domain(), "img_src": CONFIG._smtp_img_src(), "user_name": user_name, "org_name": org_name, + "reset_password": reset_password, + "reset_2fa": reset_2fa, + "fallback_2fa_email": fallback_2fa_email, }), )?; send_email(address, &subject, body_html, body_text).await diff --git a/src/static/templates/email/admin_account_recovery.hbs b/src/static/templates/email/admin_account_recovery.hbs new file mode 100644 index 00000000..a35a1d05 --- /dev/null +++ b/src/static/templates/email/admin_account_recovery.hbs @@ -0,0 +1,12 @@ +Admin account recovery from {{org_name}} organization + +{{#if reset_password}} +The master password for {{user_name}} has been changed. +{{/if}} +{{#if reset_2fa}} +Your two-step verification providers have been reset.{{#if fallback_2fa_email}} Email two factor has been activated as a fallback.{{/if}} +{{/if}} + +If you did not initiate this request, please reach out to your administrator immediately. + +{{> email/email_footer_text }} diff --git a/src/static/templates/email/admin_reset_password.html.hbs b/src/static/templates/email/admin_account_recovery.html.hbs similarity index 55% rename from src/static/templates/email/admin_reset_password.html.hbs rename to src/static/templates/email/admin_account_recovery.html.hbs index d9749d22..cf8eebed 100644 --- a/src/static/templates/email/admin_reset_password.html.hbs +++ b/src/static/templates/email/admin_account_recovery.html.hbs @@ -1,10 +1,17 @@ -Master Password Has Been Changed +Admin account recovery from {{org_name}} organization {{> email/email_header }}
- The master password for {{user_name}} has been changed by an administrator in your {{org_name}} organization. If you did not initiate this request, please reach out to your administrator immediately. + {{#if reset_password}} + The master password for {{user_name}} has been changed. + {{/if}} + {{#if reset_2fa}} + Your two-step verification providers have been reset.{{#if fallback_2fa_email}} Email two factor has been activated as a fallback.{{/if}} + {{/if}} +
+ If you did not initiate this request, please reach out to your administrator immediately.
diff --git a/src/static/templates/email/admin_reset_password.hbs b/src/static/templates/email/admin_reset_password.hbs deleted file mode 100644 index f70423f1..00000000 --- a/src/static/templates/email/admin_reset_password.hbs +++ /dev/null @@ -1,4 +0,0 @@ -Master Password Has Been Changed - -The master password for {{user_name}} has been changed by an administrator in your {{org_name}} organization. If you did not initiate this request, please reach out to your administrator immediately. -{{> email/email_footer_text }} From f1ff61300844b0664907393c0fe93f5092654784 Mon Sep 17 00:00:00 2001 From: Bryan Date: Tue, 8 Sep 2026 12:14:07 +0200 Subject: [PATCH 13/20] fix(security): revoke 2FA remember tokens when credentials or 2FA change (#7682) --- src/api/core/two_factor/mod.rs | 5 +++-- src/api/identity.rs | 6 ++++++ src/db/models/device.rs | 12 ++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs index c95fb297..0eb6563e 100644 --- a/src/api/core/two_factor/mod.rs +++ b/src/api/core/two_factor/mod.rs @@ -16,8 +16,8 @@ use crate::{ db::{ DbConn, DbPool, models::{ - DeviceType, EventType, Membership, MembershipType, OrgPolicyType, Organization, OrganizationId, TwoFactor, - TwoFactorIncomplete, TwoFactorType, User, UserId, + Device, DeviceType, EventType, Membership, MembershipType, OrgPolicyType, Organization, OrganizationId, + TwoFactor, TwoFactorIncomplete, TwoFactorType, User, UserId, }, }, mail, @@ -151,6 +151,7 @@ async fn disable_twofactor(data: Json, headers: Headers, c if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await { twofactor.delete(&conn).await?; + Device::clear_twofactor_remember_by_user(&user.uuid, &conn).await?; log_user_event(EventType::UserDisabled2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn) .await; } diff --git a/src/api/identity.rs b/src/api/identity.rs index 2b1ddfb1..a2525d9b 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -905,6 +905,12 @@ async fn twofactor_auth( // Remove all twofactors from the user TwoFactor::delete_all_by_user(&user.uuid, conn).await?; + + // No device may keep skipping 2FA once every second factor is gone. + // `device` is cleared in memory too, since saving it later would restore its token. + Device::clear_twofactor_remember_by_user(&user.uuid, conn).await?; + device.delete_twofactor_remember(); + enforce_2fa_policy(user, &user.uuid, device.atype, &ip.ip, conn).await?; log_user_event(EventType::UserRecovered2fa as i32, &user.uuid, device.atype, &ip.ip, conn).await; diff --git a/src/db/models/device.rs b/src/db/models/device.rs index 6c1b686a..cc8f1cec 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -266,10 +266,22 @@ impl Device { let devices = Self::find_by_user(user_uuid, conn).await; for mut device in devices { device.refresh_token = Device::generate_refresh_token(); + device.twofactor_remember = None; device.save(false, conn).await?; } Ok(()) } + + pub async fn clear_twofactor_remember_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult { + conn.run(move |conn| { + diesel::update(devices::table) + .filter(devices::user_uuid.eq(user_uuid)) + .set(devices::twofactor_remember.eq::>(None)) + .execute(conn) + .map_res("Error removing two factor remember tokens") + }) + .await + } } #[derive(Display)] From f1c36b8c1d9b2cdd0f1cf6f1c4062f81c3a70302 Mon Sep 17 00:00:00 2001 From: Bryan Date: Tue, 8 Sep 2026 12:14:16 +0200 Subject: [PATCH 14/20] fix(security): rate limit prelogin and auth request endpoints (#7681) --- src/api/core/accounts.rs | 16 +++++++++++----- src/api/identity.rs | 8 ++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 626f22bb..3ea6eada 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -1340,11 +1340,13 @@ pub struct PreloginData { } #[post("/accounts/prelogin", data = "")] -async fn post_prelogin(data: Json, conn: DbConn) -> Json { - prelogin(data, conn).await +async fn post_prelogin(data: Json, ip: ClientIp, conn: DbConn) -> JsonResult { + prelogin(data, ip, conn).await } -pub async fn prelogin(data: Json, conn: DbConn) -> Json { +pub async fn prelogin(data: Json, ip: ClientIp, conn: DbConn) -> JsonResult { + crate::ratelimit::check_limit_unauthenticated(&ip.ip)?; + let data: PreloginData = data.into_inner(); let (kdf_type, kdf_iter, kdf_mem, kdf_para) = match User::find_by_mail(&data.email, &conn).await { @@ -1352,7 +1354,7 @@ pub async fn prelogin(data: Json, conn: DbConn) -> Json { None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT, None, None), }; - Json(json!({ + Ok(Json(json!({ "kdf": kdf_type, "kdfIterations": kdf_iter, "kdfMemory": kdf_mem, @@ -1364,7 +1366,7 @@ pub async fn prelogin(data: Json, conn: DbConn) -> Json { "parallelism": kdf_para }, "salt": null, - })) + }))) } // https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Auth/Models/Request/Accounts/SecretVerificationRequestModel.cs @@ -1595,6 +1597,8 @@ async fn post_auth_request( conn: DbConn, nt: Notify<'_>, ) -> JsonResult { + crate::ratelimit::check_limit_unauthenticated(&client_headers.ip.ip)?; + let data = data.into_inner(); let Some(user) = User::find_by_mail(&data.email, &conn).await else { @@ -1756,6 +1760,8 @@ async fn get_auth_request_response( client_headers: ClientHeaders, conn: DbConn, ) -> JsonResult { + crate::ratelimit::check_limit_unauthenticated(&client_headers.ip.ip)?; + let Some(auth_request) = AuthRequest::find_by_uuid(&auth_request_id, &conn).await else { err!("AuthRequest doesn't exist", "User not found") }; diff --git a/src/api/identity.rs b/src/api/identity.rs index a2525d9b..7bd12a78 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -1056,13 +1056,13 @@ async fn json_err_twofactor( } #[post("/accounts/prelogin", data = "")] -async fn post_prelogin(data: Json, conn: DbConn) -> Json { - prelogin(data, conn).await +async fn post_prelogin(data: Json, ip: ClientIp, conn: DbConn) -> JsonResult { + prelogin(data, ip, conn).await } #[post("/accounts/prelogin/password", data = "")] -async fn prelogin_password(data: Json, conn: DbConn) -> Json { - prelogin(data, conn).await +async fn prelogin_password(data: Json, ip: ClientIp, conn: DbConn) -> JsonResult { + prelogin(data, ip, conn).await } #[post("/accounts/register", data = "")] From b7667e27bf3500a2446d39446b1a7b10b8b25991 Mon Sep 17 00:00:00 2001 From: niniconi <112842746+niniconi@users.noreply.github.com> Date: Tue, 8 Sep 2026 19:42:58 +0800 Subject: [PATCH 15/20] fix: Correct invalid comment syntax in .dockerignore (#7274) Docker only recognizes `#` as a valid comment indicator in .dockerignore files. Using `//` causes the lines to be incorrectly parsed as glob patterns rather than comments. While this may not cause fatal errors if no matching files exist, it is syntactically invalid and could lead to unexpected behavior. Corrected the syntax to use `#`. --- .dockerignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.dockerignore b/.dockerignore index a9a358a3..d6ac6b9b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,7 @@ -// Ignore everything +# Ignore everything * -// Allow what is needed +# Allow what is needed !.git !docker/healthcheck.sh !docker/start.sh From de7abaaafa5ce6627e43efa52840f6df6f43da23 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Wed, 9 Sep 2026 11:51:23 +0200 Subject: [PATCH 16/20] Update Rust and adjust DockerSettings (#7690) - Update Rust to v1.98.1 which resolves a build issues with strange outcomes - Adjusted the DockerSettings and render_template to extract the `rust_version` from the `rust-toolchain.toml` file. This should prevent mismatches and forgetting to update DockerSettings. - Updated typos in GHA and Pre-Commit - Updated all possible crates including hickory which has several CVE's fixed. Signed-off-by: BlackDex --- .github/workflows/typos.yml | 2 +- .github/workflows/zizmor.yml | 2 +- .pre-commit-config.yaml | 2 +- Cargo.lock | 357 +++++++++++++++++++---------------- Cargo.toml | 19 +- docker/DockerSettings.yaml | 3 +- docker/Dockerfile.alpine | 8 +- docker/Dockerfile.debian | 2 +- docker/render_template | 10 +- macros/Cargo.toml | 2 +- rust-toolchain.toml | 2 +- 11 files changed, 227 insertions(+), 182 deletions(-) diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 83cd581b..00fcbab4 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -23,4 +23,4 @@ jobs: # When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too - name: Spell Check Repo - uses: crate-ci/typos@4d9c206a77c041268485162b8e2579ad7a5cb9a3 # v1.50.0 + uses: crate-ci/typos@d43b6c087ac471e2ea7b8af622ff15f05c0c365b # v1.50.1 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 5e7100b9..31153b07 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@70fb788f84895a7701f5643d103d587e460b5c99 # v0.6.3 + uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4 with: # intentionally not scanning the entire repository, # since it contains integration tests. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5269c041..e8319414 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too - repo: https://github.com/crate-ci/typos - rev: 4d9c206a77c041268485162b8e2579ad7a5cb9a3 # v1.50.0 + rev: d43b6c087ac471e2ea7b8af622ff15f05c0c365b # v1.50.1 hooks: - id: typos always_run: true diff --git a/Cargo.lock b/Cargo.lock index b8335e5b..f9c763d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,9 +151,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8" +checksum = "24a8ec73eb862508b7041723c89386365894b4e7d9f6998bf1b8529e5b0ee254" dependencies = [ "compression-codecs", "compression-core", @@ -318,7 +318,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -360,9 +360,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-config" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a767267da9e2c2e189b2f9df8b5657e850ecf5352644734ba130d4a57095cf1b" +checksum = "b8d7b388a9fc3a6db15a5ec778c38b354eff1364882c94d08e0252f7a47dcaa4" dependencies = [ "aws-credential-types", "aws-runtime", @@ -403,9 +403,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9007227e10b5fed2f3e0a2beff489211e2b5604c400b7a9d5d81ca9d64c24bb" +checksum = "ef47857a1d4488b528f4a5d5715fa7c3300820897824152234d3fa22b1426657" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -428,9 +428,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.108.0" +version = "1.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15301b04372832947916607983b114b3374b9db0be058a00fb7513800de1f05" +checksum = "c3cfe74df5d9ad2fedd691973ad3521ebf4f27a3c68c792556686aedb5519bab" dependencies = [ "arc-swap", "aws-credential-types", @@ -454,9 +454,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.110.0" +version = "1.111.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72cc2c205cb27108183cf1856333f7d584c2ba0f505421b4209ca5828f9ea899" +checksum = "81b0ec31ed6191bd11350aae4b2004198f2db21350cb0a20c57e0a92e55dd161" dependencies = [ "arc-swap", "aws-credential-types", @@ -480,9 +480,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.113.0" +version = "1.114.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68182ecb449f7537db0f4d5d25917789cf41e32074a9fe47b6a0b847fe1d2032" +checksum = "ef45745026107ec30c4ef86bd8ae4b002e7e5f6a86e4225240bdf6b06a0b944a" dependencies = [ "arc-swap", "aws-credential-types", @@ -619,9 +619,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "954c563ce84507722d2679f07a35d21b9c6466b3872d513020d0281fc8112ac9" +checksum = "9c054752dd9e4dc73d0b75748c99ac2d0feafbf2f25c7b0516f03a3534161223" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", @@ -659,9 +659,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.6.2" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce83ce9abbb198d25bc7131e468d0f9fe1257125e58c39f3f9fc9f5098c9647" +checksum = "8f94d16e797ec62cd999fc9d5942b48fa7050c3093ddadff48e4d7528d16fcb9" dependencies = [ "base64-simd", "bytes", @@ -694,9 +694,9 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eec1cd5469f328c782dc3e33d4153cf118a54e33cbb3356d60d16f89883e1f94" +checksum = "209f3a6d82a6e9e5f94abbed94c7a26e1c052341002bf57a5fb5481f625896fc" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -895,9 +895,9 @@ dependencies = [ [[package]] name = "cached" -version = "3.1.1" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133b6b7d6a828c24d5055ef51e67457002b4017ae5ea3d1b1552ca35a44b1119" +checksum = "c5a6cf8262820194a1488ece477f5fb5ba9256ef25229d525470e71d6e9a5835" dependencies = [ "ahash", "async-lock", @@ -911,9 +911,9 @@ dependencies = [ [[package]] name = "cached_proc_macro" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da80977bd46ecf98c593b651e393260f852678283989b8b7c4079304fe5e8936" +checksum = "ece0579b43cf6e927b3370c7d44359c7122e7e52b3ea635bc8484da571442edb" dependencies = [ "darling 0.20.11", "proc-macro-crate", @@ -930,9 +930,9 @@ checksum = "f5813789573ae815c8b4be58c4428e0e7ae05f0227678ba9de332ded585b9159" [[package]] name = "cc" -version = "1.4.4" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" dependencies = [ "find-msvc-tools", "jobserver", @@ -1011,9 +1011,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.38" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" +checksum = "100590da849306918656ffbb22576bbdfc1382b50ad10d1d50ec177d3b205fb2" dependencies = [ "brotli", "compression-core", @@ -1025,9 +1025,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" +checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" [[package]] name = "concurrent-queue" @@ -1134,6 +1134,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1191,27 +1197,27 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.20" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crunchy" @@ -1308,12 +1314,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", + "darling_core 0.24.1", + "darling_macro 0.24.1", ] [[package]] @@ -1346,15 +1352,15 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" dependencies = [ "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] @@ -1381,13 +1387,13 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ - "darling_core 0.23.0", + "darling_core 0.24.1", "quote", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] @@ -1570,9 +1576,9 @@ dependencies = [ [[package]] name = "diesel" -version = "2.3.12" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715377c6e464cb44bb89bd8487584240516c8d5052bc645d6babc50bb8be46c3" +checksum = "e3b934ddbdcb2abb9f9fc9c30bd47bcc5618b615eea1d334cda5fdf8ff9b072a" dependencies = [ "bigdecimal", "bitflags 2.13.1", @@ -1607,9 +1613,9 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.3.9" +version = "2.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1817b7f4279b947fc4cafddec12b0e5f8727141706561ce3ac94a60bddd1cf5" +checksum = "ecbd51fb6c020672543641167efa4e6417ff7ad76849ed556ace3595e72de03a" dependencies = [ "diesel_table_macro_syntax", "dsl_auto_type", @@ -1670,7 +1676,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -1809,11 +1815,17 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.35" +version = "0.8.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a" dependencies = [ "cfg-if", + "core_detect", + "multiversion", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", ] [[package]] @@ -1908,9 +1920,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" [[package]] name = "flate2" @@ -2028,7 +2040,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -2177,7 +2189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" dependencies = [ "codemap", - "indexmap 2.14.1", + "indexmap 2.14.2", "lasso", "once_cell", "phf 0.11.3", @@ -2206,7 +2218,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.5.0", - "indexmap 2.14.1", + "indexmap 2.14.2", "slab", "tokio", "tokio-util", @@ -2299,9 +2311,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hickory-net" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" +checksum = "084e7bd6a377435d568f652153e571b50970d7ccc1d1eeec0519f834632287e1" dependencies = [ "async-trait", "cfg-if", @@ -2323,9 +2335,9 @@ dependencies = [ [[package]] name = "hickory-proto" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" +checksum = "7e2da0694c15b44c6f68a6b05e0233617008c54080e31d6eb848d858a9c5b38d" dependencies = [ "data-encoding", "idna", @@ -2343,9 +2355,9 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" +checksum = "0e4f9f4603319422d482738f3f6fe5aac03157fdbfed1cd85a3ff45adb09072f" dependencies = [ "cfg-if", "futures-util", @@ -2483,9 +2495,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ "typenum", ] @@ -2543,9 +2555,9 @@ dependencies = [ "http 1.5.0", "hyper 1.11.1", "hyper-util", - "rustls 0.23.43", + "rustls 0.23.44", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls 0.26.5", "tower-service", ] @@ -2721,9 +2733,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.1" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -2752,9 +2764,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.1" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" dependencies = [ "serde", ] @@ -2918,9 +2930,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.104" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" dependencies = [ "cfg-if", "futures-util", @@ -2999,12 +3011,12 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "rustls 0.23.43", + "rustls 0.23.44", "rustls-native-certs", "serde", "socket2 0.6.5", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls 0.26.5", "tracing", "url", ] @@ -3097,7 +3109,7 @@ name = "macros" version = "0.1.0" dependencies = [ "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -3189,9 +3201,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", "wasi", @@ -3237,6 +3249,33 @@ dependencies = [ "version_check", ] +[[package]] +name = "multiversion" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c" +dependencies = [ + "multiversion-macros", +] + +[[package]] +name = "multiversion-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83" +dependencies = [ + "proc-macro2", + "quote", + "rustversion", + "syn 3.0.5", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + [[package]] name = "mysqlclient-sys" version = "0.5.2" @@ -3340,7 +3379,7 @@ checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -3446,9 +3485,9 @@ dependencies = [ [[package]] name = "opendal" -version = "0.58.2" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33dbff14cc9bb085224256d6a81289d2f3202e85b06f408d42534b42162a4231" +checksum = "f950151f9587a51a7bed70a15fa0cff464eae96e41ae7499f97067bdafdf43eb" dependencies = [ "opendal-core", "opendal-service-fs", @@ -3457,9 +3496,9 @@ dependencies = [ [[package]] name = "opendal-core" -version = "0.58.2" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dbcef97d3eb7591db2c18d5cae95c836bcce07359b98d98dd6f4e861eb77b7" +checksum = "a43405d217dfdfb543f58847336d3af672897dd1939bb7dcf314b63cf364f1c9" dependencies = [ "anyhow", "asyncband", @@ -3483,9 +3522,9 @@ dependencies = [ [[package]] name = "opendal-service-fs" -version = "0.58.2" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7ef1e1c45f3f89282a59073897e0d685e51385fed0aea771714789525cff996" +checksum = "fb9caf04d6d38713299dd4abac984b95ab16ee20e1ff09160f495c5a64644083" dependencies = [ "bytes", "log", @@ -3497,9 +3536,9 @@ dependencies = [ [[package]] name = "opendal-service-s3" -version = "0.58.2" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64335f9f24ccb62ac36f1d976342b48611a75ba61979813a4f78a4ebd94de42" +checksum = "388b1d39b62535c62803754ebef89808859558697366dbedd0299345887ba461" dependencies = [ "base64 0.23.1", "bytes", @@ -3750,9 +3789,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf" +checksum = "6d45aeb61b4bf818e12d4205f2466f8c4748f85f4fce0146d1c03d69d753f0ad" dependencies = [ "memchr", "ucd-trie", @@ -3760,9 +3799,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d" +checksum = "89cc5a242e25ed4e7704d0be240f2cfbe20a8c27e7e252d94835be93d92dc39f" dependencies = [ "pest", "pest_generator", @@ -3770,9 +3809,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a" +checksum = "7abf21475cc3820fe4b2ca2dc2142902f67a02189f3b5b3a229f4febc01a43e5" dependencies = [ "pest", "pest_meta", @@ -3783,9 +3822,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496" +checksum = "adba4db388f687393c18c51348d44a41d870ca9df71a2c98172ea3035dc6936e" dependencies = [ "pest", ] @@ -3939,9 +3978,9 @@ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "portable-atomic-util" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" dependencies = [ "portable-atomic", ] @@ -4228,7 +4267,7 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -4348,11 +4387,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bytes", "cookie", "cookie_store", @@ -4371,7 +4410,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustls 0.23.43", + "rustls 0.23.44", "rustls-pki-types", "rustls-platform-verifier", "serde", @@ -4379,7 +4418,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls 0.26.5", "tokio-util", "tower", "tower-http", @@ -4453,7 +4492,7 @@ dependencies = [ "either", "figment", "futures", - "indexmap 2.14.1", + "indexmap 2.14.2", "log", "memchr", "multer", @@ -4485,7 +4524,7 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" dependencies = [ "devise", "glob", - "indexmap 2.14.1", + "indexmap 2.14.2", "proc-macro2", "quote", "rocket_http", @@ -4505,7 +4544,7 @@ dependencies = [ "futures", "http 0.2.12", "hyper 0.14.32", - "indexmap 2.14.1", + "indexmap 2.14.2", "log", "memchr", "pear", @@ -4640,9 +4679,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.43" +version = "0.23.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" dependencies = [ "log", "once_cell", @@ -4694,7 +4733,7 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.43", + "rustls 0.23.44", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki 0.103.15", @@ -4906,7 +4945,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -4974,16 +5013,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" +checksum = "935177bb8c0cd8ca1a4e6d1a2ac8988bea69cab4f9d3a31311e012ad27868ea4" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.14.1", + "indexmap 2.14.2", "jiff", "schemars 0.9.0", "schemars 1.2.2", @@ -4995,14 +5034,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" +checksum = "1d607aa01a3cb0ad757d6fd216136910db3c97b102fe686585689615a02dbcdc" dependencies = [ - "darling 0.23.0", + "darling 0.24.1", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] @@ -5142,9 +5181,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.2" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" [[package]] name = "socket2" @@ -5271,9 +5310,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.4" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" dependencies = [ "proc-macro2", "quote", @@ -5389,7 +5428,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -5454,9 +5493,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" dependencies = [ "tinyvec_macros", ] @@ -5492,7 +5531,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -5507,11 +5546,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.4" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" dependencies = [ - "rustls 0.23.43", + "rustls 0.23.44", "tokio", ] @@ -5611,7 +5650,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.14.1", + "indexmap 2.14.2", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -5625,7 +5664,7 @@ version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ - "indexmap 2.14.1", + "indexmap 2.14.2", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "winnow 1.0.4", @@ -5901,9 +5940,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "value-bag" -version = "1.13.2" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068e763e8279de7ab94b6afebded2cb701678af094feb1c12ccb061b4783c1be" +checksum = "2799ffb329a792ecfd902b71306c8a815a6ef1c0470fa9953a6aa4d4cecbe511" [[package]] name = "vaultwarden" @@ -5965,7 +6004,7 @@ dependencies = [ "rocket", "rocket_ws", "rpassword", - "rustls 0.23.43", + "rustls 0.23.44", "semver", "serde", "serde_json", @@ -6040,9 +6079,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" dependencies = [ "cfg-if", "once_cell", @@ -6053,9 +6092,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.77" +version = "0.4.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" dependencies = [ "js-sys", "wasm-bindgen", @@ -6063,9 +6102,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6073,22 +6112,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" dependencies = [ "unicode-ident", ] @@ -6108,9 +6147,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.104" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" dependencies = [ "js-sys", "wasm-bindgen", @@ -6583,18 +6622,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.56" +version = "0.8.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.56" +version = "0.8.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" dependencies = [ "proc-macro2", "quote", @@ -6672,7 +6711,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -6689,27 +6728,27 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zstd" -version = "0.13.3" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +checksum = "bf06bd8162af0734b344780deb55b42a2429ae430870d13fcc12f238e880fe6e" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.4" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +checksum = "ae42c0555055784c70058d19ba8e275528e8a99a706684868ace5da4e716a4ab" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" +version = "2.1.0+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 7d711fe1..cc6dff02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ members = ["macros"] name = "vaultwarden" version = "1.0.0" authors = ["Daniel García "] -readme = "README.md" build = "build.rs" repository.workspace = true edition.workspace = true @@ -107,7 +106,7 @@ serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.151" # A safe, extensible ORM and Query builder -diesel = { version = "2.3.12", features = ["chrono", "r2d2", "numeric"] } +diesel = { version = "2.3.13", features = ["chrono", "r2d2", "numeric"] } diesel_migrations = "2.3.2" derive_more = { version = "2.1.1", features = [ @@ -125,7 +124,7 @@ libsqlite3-sys = { version = "0.38.2", optional = true } # Crypto-related libraries rand = "0.10.2" ring = "0.17.14" -rustls = { version = "0.23.43", features = ["ring", "std"], default-features = false } +rustls = { version = "0.23.44", features = ["ring", "std"], default-features = false } subtle = "2.6.1" # UUID generation @@ -183,7 +182,7 @@ email_address = "0.2.9" handlebars = { version = "6.4.4", features = ["dir_source"] } # HTTP client (Used for favicons, version check, DUO and HIBP API) -reqwest = { version = "0.13.4", default-features = false, features = [ +reqwest = { version = "0.13.5", default-features = false, features = [ # Misc "charset", "cookies", @@ -201,7 +200,7 @@ reqwest = { version = "0.13.4", default-features = false, features = [ "socks", "system-proxy", ] } -hickory-resolver = "0.26.1" +hickory-resolver = "0.26.2" # Favicon extraction libraries html5gum = "0.8.4" @@ -215,7 +214,7 @@ bytes = "1.12.1" svg-hush = "0.9.7" # Cache function results (Used for version check and favicon fetching) -cached = { version = "3.1.1", features = ["async"] } +cached = { version = "4.0.0", features = ["async"] } # Used for custom short lived cookie jar during favicon extraction cookie = "0.18.2" @@ -232,7 +231,7 @@ pastey = "0.2.3" governor = "0.10.4" # CIDR parsing for the trusted proxies of the client IP header -ipnet = "2.12.1" +ipnet = "2.12.2" # OIDC for SSO openidconnect = { version = "4.0.1", default-features = false } @@ -257,17 +256,17 @@ rpassword = "7.5.4" grass_compiler = { version = "0.13.4", default-features = false } # File are accessed through Apache OpenDAL -opendal = { version = "0.58.2", default-features = false, features = ["services-fs"] } +opendal = { version = "0.59.1", default-features = false, features = ["services-fs"] } # For retrieving AWS credentials, including temporary SSO credentials -aws-config = { version = "1.11.0", optional = true, default-features = false, features = [ +aws-config = { version = "1.12.0", optional = true, default-features = false, features = [ "behavior-version-latest", "credentials-process", "rt-tokio", "sso", ] } aws-credential-types = { version = "1.3.0", optional = true } -aws-smithy-runtime-api = { version = "1.15.0", optional = true } +aws-smithy-runtime-api = { version = "1.16.0", optional = true } http = { version = "1.5.0", optional = true } reqsign-aws-v4 = { version = "3.3.0", optional = true } reqsign-core = { version = "3.3.1", optional = true } diff --git a/docker/DockerSettings.yaml b/docker/DockerSettings.yaml index fdbf40f2..6ae6e9a8 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -5,7 +5,8 @@ vault_image_digest: "sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10 # We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts # https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707" -rust_version: 1.98.0 # Rust version to be used +# The `rust_version` variable is extracted from `rust-toolchain.toml` +# rust_version: x.yy.z # Rust version to be used debian_version: trixie # Debian release name to be used alpine_version: "3.24" # Alpine version to be used # For which platforms/architectures will we try to build images diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index 491aa9e0..a91deb07 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -32,10 +32,10 @@ FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330 ########################## ALPINE BUILD IMAGES ########################## ## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64 ## And for Alpine we define all build images here, they will only be loaded when actually used -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.98.0 AS build_amd64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.98.0 AS build_arm64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.98.0 AS build_armv7 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.98.0 AS build_armv6 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.98.1 AS build_amd64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.98.1 AS build_arm64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.98.1 AS build_armv7 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.98.1 AS build_armv6 ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 280559e2..b8490609 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -36,7 +36,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 -FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.98.0-slim-trixie AS build +FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.98.1-slim-trixie AS build # hadolint ignore=DL3067 COPY --from=xx / / ARG TARGETARCH diff --git a/docker/render_template b/docker/render_template index 401e0ad0..84ca8ead 100755 --- a/docker/render_template +++ b/docker/render_template @@ -3,17 +3,23 @@ import os import argparse import json +import tomllib import yaml import jinja2 # Load settings file -with open("DockerSettings.yaml", 'r') as yaml_file: +with open('DockerSettings.yaml', 'r', encoding='utf-8') as yaml_file: yaml_data = yaml.safe_load(yaml_file) +# Extract the rust_version from the rust-toolchain.toml file +script_dir = os.path.dirname(os.path.abspath(__file__)) +with open(os.path.join(script_dir, '..', 'rust-toolchain.toml'), 'rb') as toolchain_file: + yaml_data["rust_version"] = tomllib.load(toolchain_file)["toolchain"]["channel"] + settings_env = jinja2.Environment( loader=jinja2.FileSystemLoader(os.getcwd()), ) -settings_yaml = yaml.safe_load(settings_env.get_template("DockerSettings.yaml").render(yaml_data)) +settings_yaml = yaml.safe_load(settings_env.get_template('DockerSettings.yaml').render(yaml_data)) args_parser = argparse.ArgumentParser() args_parser.add_argument('template_file', help='Jinja2 template file to render.') diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 84d17192..34cb913d 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -14,7 +14,7 @@ proc-macro = true [dependencies] quote = "1.0.47" -syn = "3.0.4" +syn = "3.0.5" [lints] workspace = true diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 9bfb1d94..2be20926 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.98.0" +channel = "1.98.1" components = [ "rustfmt", "clippy" ] profile = "minimal" From 5b51b60f9407bc4e088eb1dcb035a5d395178aab Mon Sep 17 00:00:00 2001 From: Chase Douglas Date: Wed, 9 Sep 2026 05:30:25 -0700 Subject: [PATCH 17/20] Route service clients through shared HTTP setup (#7639) * storage: route OpenDAL through HTTP client OpenDAL 0.58 requires applications to provide an HTTP transport. Its default installer creates a standalone client, bypassing Vaultwarden DNS, redirect, proxy, timeout, and request configuration. Build the client through the internal HTTP interface and inject it into OpenDAL's public reqwest transport. * http: honor block setting on redirects Clients can disable host blocking for administrator-configured private services. DNS resolution honors this setting, but the redirect policy still performs config-backed host checks. Capture the setting in the redirect policy and skip those checks when blocking is disabled. This also avoids re-entering CONFIG when a remote configuration request is redirected during startup. * http: make DNS setup bootstrap-safe Remote configuration can require an HTTP client while CONFIG is still initializing. Building the DNS resolver currently reads CONFIG.dns_prefer_ipv6(), so loading an S3-backed config can deadlock. Build one resolver without consulting CONFIG. Order addresses for each lookup using the merged setting when available, falling back to the environment and then IPv4-first during bootstrap. * aws: use internal HTTP client The AWS SDK connector builds a raw reqwest client, bypassing Vaultwarden TLS, DNS, redirect, proxy, timeout, and request setup. Construct it through the internal HTTP client interface and retain the standard ten-second request deadline. Permit private AWS metadata and service endpoints by disabling non-global IP blocking. Preserve timeout errors when adapting reqwest failures to the AWS SDK so the runtime receives the correct connector error category. * Added comment for the prefer IPv function Signed-off-by: BlackDex --------- Signed-off-by: BlackDex Co-authored-by: BlackDex --- Cargo.lock | 15 ++++++++ Cargo.toml | 2 ++ src/http_client.rs | 87 ++++++++++++++++++++++++++++++++++++++-------- src/storage.rs | 19 ++++++---- 4 files changed, 102 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9c763d1..55a7233b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3520,6 +3520,20 @@ dependencies = [ "web-time", ] +[[package]] +name = "opendal-http-transport-reqwest" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "401999057db611e592f883fcf2cbd6754ff37af587deaadd07b8c1398b2b6b06" +dependencies = [ + "bytes", + "futures", + "http 1.5.0", + "http-body 1.1.0", + "opendal-core", + "reqwest", +] + [[package]] name = "opendal-service-fs" version = "0.59.1" @@ -5989,6 +6003,7 @@ dependencies = [ "num-derive", "num-traits", "opendal", + "opendal-http-transport-reqwest", "openidconnect", "openssl", "pastey 0.2.3", diff --git a/Cargo.toml b/Cargo.toml index cc6dff02..d3a3d5e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ vendored_openssl = ["openssl/vendored"] enable_mimalloc = ["dep:mimalloc"] s3 = [ "opendal/services-s3", + "dep:opendal-http-transport-reqwest", "dep:aws-config", "dep:aws-credential-types", "dep:aws-smithy-runtime-api", @@ -257,6 +258,7 @@ grass_compiler = { version = "0.13.4", default-features = false } # File are accessed through Apache OpenDAL opendal = { version = "0.59.1", default-features = false, features = ["services-fs"] } +opendal-http-transport-reqwest = { version = "0.59.1", default-features = false, features = ["rustls-no-provider"], optional = true } # For retrieving AWS credentials, including temporary SSO credentials aws-config = { version = "1.12.0", optional = true, default-features = false, features = [ diff --git a/src/http_client.rs b/src/http_client.rs index 0831d990..5ef293fc 100644 --- a/src/http_client.rs +++ b/src/http_client.rs @@ -14,7 +14,10 @@ use reqwest::{ }; use url::Host; -use crate::{CONFIG, util::is_global}; +use crate::{ + CONFIG, + util::{get_env_bool, is_global}, +}; pub fn make_http_request(method: reqwest::Method, url: &str) -> Result { static INSTANCE: LazyLock = @@ -36,7 +39,7 @@ pub fn get_reqwest_client_builder(enforce_block: bool) -> ClientBuilder { let mut headers = header::HeaderMap::new(); headers.insert(header::USER_AGENT, header::HeaderValue::from_static("Vaultwarden")); - let redirect_policy = reqwest::redirect::Policy::custom(|attempt| { + let redirect_policy = reqwest::redirect::Policy::custom(move |attempt| { if attempt.previous().len() >= 5 { return attempt.error("Too many redirects"); } @@ -45,7 +48,7 @@ pub fn get_reqwest_client_builder(enforce_block: bool) -> ClientBuilder { return attempt.error("Invalid host"); }; - if let Err(e) = should_block_host(&host) { + if enforce_block && let Err(e) = should_block_host(&host) { return attempt.error(e); } @@ -59,6 +62,14 @@ pub fn get_reqwest_client_builder(enforce_block: bool) -> ClientBuilder { .timeout(Duration::from_secs(10)) } +fn dns_prefer_ipv6() -> bool { + // CONFIG may require DNS to initialize, so avoid forcing it during bootstrap. + match LazyLock::get(&CONFIG) { + Some(config) => config.dns_prefer_ipv6(), + None => get_env_bool("DNS_PREFER_IPV6").unwrap_or(false), + } +} + fn should_block_ip(ip: IpAddr) -> bool { if !CONFIG.http_request_block_non_global_ips() { return false; @@ -258,12 +269,8 @@ impl CustomDnsResolver { fn new() -> Arc { TokioResolver::builder(TokioRuntimeProvider::default()) .and_then(|mut builder| { - // Hickory's default since v0.26 is `Ipv6AndIpv4`, which sorts IPv6 first - // This might cause issues on IPv4 only systems or containers - // Unless someone enabled DNS_PREFER_IPV6, use Ipv4AndIpv6, which returns IPv4 first which was our previous default - if !CONFIG.dns_prefer_ipv6() { - builder.options_mut().ip_strategy = hickory_resolver::config::LookupIpStrategy::Ipv4AndIpv6; - } + // Query both families; the preferred order is applied per lookup below. + builder.options_mut().ip_strategy = hickory_resolver::config::LookupIpStrategy::Ipv4AndIpv6; builder.build() }) .inspect_err(|e| warn!("Error creating Hickory resolver, falling back to default: {e:?}")) @@ -289,6 +296,17 @@ impl CustomDnsResolver { } } +fn sort_addresses(addresses: &mut [SocketAddr], prefer_ipv6: bool) { + // `sort_by_key` orders `false` before `true`. + // When IPv6 is preferred, IPv6 addresses return `false` for `is_ipv4()` and sort first. + // When IPv4 is preferred, IPv4 addresses return `false` for `is_ipv6()` and sort first. + if prefer_ipv6 { + addresses.sort_by_key(SocketAddr::is_ipv4); + } else { + addresses.sort_by_key(SocketAddr::is_ipv6); + } +} + fn pre_resolve(name: &str, enforce_block: bool) -> Result<(), CustomHttpClientError> { let Ok(host) = get_valid_host(name) else { return Err(CustomHttpClientError::Invalid { @@ -320,7 +338,9 @@ impl Resolve for CustomDns { let this = Arc::clone(&self.resolver); Box::pin(async move { let name = name.as_str(); - let results = this.resolve_domain(name, enforce_block).await?; + let mut results = this.resolve_domain(name, enforce_block).await?; + // Recheck after bootstrap so long-lived clients adopt the loaded config. + sort_addresses(&mut results, dns_prefer_ipv6()); if results.is_empty() { warn!("Unable to resolve {name} to any valid IP address"); } @@ -339,10 +359,29 @@ pub(crate) mod aws { }; use reqwest::Client; + use super::get_reqwest_client_builder; + // Adapter that wraps reqwest to be compatible with the AWS SDK #[derive(Debug)] pub(crate) struct AwsReqwestConnector { - pub(crate) client: Client, + client: Client, + } + + impl AwsReqwestConnector { + pub(crate) fn new() -> Self { + let client = get_reqwest_client_builder(false).build().expect("Failed to build AWS HTTP client"); + Self { + client, + } + } + } + + fn connector_error(error: reqwest::Error) -> ConnectorError { + if error.is_timeout() { + ConnectorError::timeout(Box::new(error)) + } else { + ConnectorError::io(Box::new(error)) + } } impl HttpConnector for AwsReqwestConnector { @@ -362,10 +401,10 @@ pub(crate) mod aws { req_builder = req_builder.body(body_bytes.to_vec()); } - let response = req_builder.send().await.map_err(|e| ConnectorError::io(Box::new(e)))?; + let response = req_builder.send().await.map_err(connector_error)?; let status = response.status().into(); - let bytes = response.bytes().await.map_err(|e| ConnectorError::io(Box::new(e)))?; + let bytes = response.bytes().await.map_err(connector_error)?; Ok(HttpResponse::new(status, bytes.into())) }; @@ -391,7 +430,7 @@ pub(crate) mod aws { mod tests { use super::*; use crate::util::is_global_hardcoded; - use std::net::Ipv4Addr; + use std::net::{Ipv4Addr, Ipv6Addr}; use url::Host; // === @@ -404,6 +443,26 @@ mod tests { } } + #[test] + fn dns_setup_does_not_initialize_config() { + assert!(LazyLock::get(&CONFIG).is_none()); + drop(CustomDns::instance(false)); + assert!(LazyLock::get(&CONFIG).is_none()); + } + + #[test] + fn dns_preference_orders_addresses() { + let ipv4 = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0); + let ipv6 = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 0); + let mut addresses = [ipv6, ipv4]; + + sort_addresses(&mut addresses, false); + assert_eq!(addresses, [ipv4, ipv6]); + + sort_addresses(&mut addresses, true); + assert_eq!(addresses, [ipv6, ipv4]); + } + #[test] fn dotted_decimal_loopback_normalizes() { let ip = parse_to_ip("127.0.0.1").unwrap(); diff --git a/src/storage.rs b/src/storage.rs index 689be302..32562a0d 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -77,10 +77,18 @@ pub(crate) fn operator_for_path(path: &str) -> Result = LazyLock::new(|| { + // Storage endpoints are administrator-configured and may be private. + crate::http_client::get_reqwest_client_builder(false).build().expect("Failed to build OpenDAL HTTP client") + }); + pub(super) fn is_uri(path: &str) -> bool { path.starts_with("s3://") } @@ -177,12 +185,7 @@ mod s3 { let chain = DEFAULT_CREDENTIAL_CHAIN .get_or_init(|| { - let reqwest_client = reqwest::Client::builder().build().unwrap(); - let connector = AwsReqwestConnector { - client: reqwest_client, - }; - - let conf = ProviderConfig::default().with_http_client(connector); + let conf = ProviderConfig::default().with_http_client(AwsReqwestConnector::new()); DefaultCredentialsChain::builder().configure(conf).build() }) @@ -236,7 +239,9 @@ mod s3 { builder.credential_provider_chain(ProvideCredentialChain::new().push(OpenDALS3CredentialProvider)); } - Ok(opendal::Operator::new(builder)?) + let http_transport = opendal::HttpTransporter::new(ReqwestTransport::new(HTTP_CLIENT.clone())); + let context = opendal::OperationContext::new().with_http_transport(http_transport); + Ok(opendal::Operator::new(builder)?.with_context(context)) } fn uri_has_option(uri: &opendal::OperatorUri, names: &[&str]) -> bool { From e992cbb4f520c53d50b22fa3cc1f2f77c4a95a81 Mon Sep 17 00:00:00 2001 From: Tom <83423411+tom27052006@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:31:03 +0200 Subject: [PATCH 18/20] Fix iOS registration token response (#7714) * Return registration token as text/plain for Accept: */* * fix register verification response content negotiation --- src/api/identity.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/api/identity.rs b/src/api/identity.rs index 7bd12a78..6808ddde 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -3,7 +3,7 @@ use num_traits::FromPrimitive; use rocket::{ Route, form::{Form, FromForm}, - http::{Cookie, CookieJar, SameSite}, + http::{Accept, Cookie, CookieJar, MediaType, SameSite}, response::Redirect, serde::json::Json, }; @@ -1083,11 +1083,18 @@ enum RegisterVerificationResponse { #[response(status = 204)] NoContent(()), Token(Json), + PlainToken(String), +} + +// Return JSON only when the client explicitly requests it, otherwise return plain text. +fn accepts_json(accept: Option<&Accept>) -> bool { + accept.is_some_and(|accept| accept.preferred().media_type() == &MediaType::JSON) } #[post("/accounts/register/send-verification-email", data = "")] async fn register_verification_email( data: Json, + accept: Option<&Accept>, ip: ClientIp, conn: DbConn, ) -> ApiResult { @@ -1125,7 +1132,11 @@ async fn register_verification_email( } else { // If email verification is not required, return the token directly // the clients will use this token to finish the registration - Ok(RegisterVerificationResponse::Token(Json(token))) + Ok(if accepts_json(accept) { + RegisterVerificationResponse::Token(Json(token)) + } else { + RegisterVerificationResponse::PlainToken(token) + }) } } From 25dfedafd73cdeee47a50c1dfa7a451afe1ae1bd Mon Sep 17 00:00:00 2001 From: Timshel Date: Wed, 9 Sep 2026 14:43:47 +0000 Subject: [PATCH 19/20] Use insert_into when possible (#6437) Co-authored-by: Timshel --- src/api/core/accounts.rs | 2 +- src/db/models/archive.rs | 9 +- src/db/models/attachment.rs | 22 ++--- src/db/models/auth_request.rs | 34 +++----- src/db/models/cipher.rs | 22 ++--- src/db/models/collection.rs | 96 +++++++-------------- src/db/models/device.rs | 9 +- src/db/models/emergency_access.rs | 22 ++--- src/db/models/event.rs | 29 ++++--- src/db/models/folder.rs | 33 +++----- src/db/models/group.rs | 135 ++++++++++-------------------- src/db/models/organization.rs | 68 +++++---------- src/db/models/send.rs | 22 ++--- src/db/models/user.rs | 18 ++-- 14 files changed, 188 insertions(+), 333 deletions(-) diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 3ea6eada..69be1334 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -1611,7 +1611,7 @@ async fn post_auth_request( _ => err!("AuthRequest doesn't exist", "Device verification failed"), }; - let mut auth_request = AuthRequest::new( + let auth_request = AuthRequest::new( user.uuid.clone(), data.device_identifier.clone(), client_headers.device_type, diff --git a/src/db/models/archive.rs b/src/db/models/archive.rs index 83d547f2..2330fac4 100644 --- a/src/db/models/archive.rs +++ b/src/db/models/archive.rs @@ -41,17 +41,20 @@ impl Archive { ) -> EmptyResult { User::update_uuid_revision(user_uuid, conn).await; db_run! { conn: - sqlite, mysql { - diesel::replace_into(archives::table) + mysql { + diesel::insert_into(archives::table) .values(( archives::user_uuid.eq(user_uuid), archives::cipher_uuid.eq(cipher_uuid), archives::archived_at.eq(archived_at), )) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(archives::archived_at.eq(archived_at)) .execute(conn) .map_res("Error saving archive") } - postgresql { + postgresql, sqlite { diesel::insert_into(archives::table) .values(( archives::user_uuid.eq(user_uuid), diff --git a/src/db/models/attachment.rs b/src/db/models/attachment.rs index 244f8c27..0536dde5 100644 --- a/src/db/models/attachment.rs +++ b/src/db/models/attachment.rs @@ -82,24 +82,16 @@ impl Attachment { impl Attachment { pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - sqlite, mysql { - match diesel::replace_into(attachments::table) + mysql { + diesel::insert_into(attachments::table) .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(attachments::table) - .filter(attachments::id.eq(&self.id)) - .set(self) - .execute(conn) - .map_res("Error saving attachment") - } - Err(e) => Err(e.into()), - }.map_res("Error saving attachment") + .map_res("Error saving attachment") } - postgresql { + postgresql, sqlite { diesel::insert_into(attachments::table) .values(self) .on_conflict(attachments::id) diff --git a/src/db/models/auth_request.rs b/src/db/models/auth_request.rs index a3876661..cc4b60fd 100644 --- a/src/db/models/auth_request.rs +++ b/src/db/models/auth_request.rs @@ -82,31 +82,23 @@ impl AuthRequest { } impl AuthRequest { - pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { + pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - sqlite, mysql { - match diesel::replace_into(auth_requests::table) - .values(&*self) - .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(auth_requests::table) - .filter(auth_requests::uuid.eq(&self.uuid)) - .set(&*self) - .execute(conn) - .map_res("Error auth_request") - } - Err(e) => Err(e.into()), - }.map_res("Error auth_request") - } - postgresql { + mysql { diesel::insert_into(auth_requests::table) - .values(&*self) + .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(self) + .execute(conn) + .map_res("Error saving auth_request") + } + postgresql, sqlite { + diesel::insert_into(auth_requests::table) + .values(self) .on_conflict(auth_requests::uuid) .do_update() - .set(&*self) + .set(self) .execute(conn) .map_res("Error saving auth_request") } diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index eed5041d..721d9790 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -440,24 +440,16 @@ impl Cipher { self.updated_at = Utc::now().naive_utc(); db_run! { conn: - sqlite, mysql { - match diesel::replace_into(ciphers::table) + mysql { + diesel::insert_into(ciphers::table) .values(&*self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(&*self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(ciphers::table) - .filter(ciphers::uuid.eq(&self.uuid)) - .set(&*self) - .execute(conn) - .map_res("Error saving cipher") - } - Err(e) => Err(e.into()), - }.map_res("Error saving cipher") + .map_res("Error saving cipher") } - postgresql { + postgresql, sqlite { diesel::insert_into(ciphers::table) .values(&*self) .on_conflict(ciphers::uuid) diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs index 8aec90ea..be108f13 100644 --- a/src/db/models/collection.rs +++ b/src/db/models/collection.rs @@ -168,24 +168,16 @@ impl Collection { self.update_users_revision(conn).await; db_run! { conn: - sqlite, mysql { - match diesel::replace_into(collections::table) + mysql { + diesel::insert_into(collections::table) .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(collections::table) - .filter(collections::uuid.eq(&self.uuid)) - .set(self) - .execute(conn) - .map_res("Error saving collection") - } - Err(e) => Err(e.into()), - }.map_res("Error saving collection") + .map_res("Error saving collection") } - postgresql { + postgresql, sqlite { diesel::insert_into(collections::table) .values(self) .on_conflict(collections::uuid) @@ -728,53 +720,30 @@ impl CollectionUser { ) -> EmptyResult { User::update_uuid_revision(user_uuid, conn).await; + let values = ( + users_collections::user_uuid.eq(user_uuid), + users_collections::collection_uuid.eq(collection_uuid), + users_collections::read_only.eq(read_only), + users_collections::hide_passwords.eq(hide_passwords), + users_collections::manage.eq(manage), + ); + db_run! { conn: - sqlite, mysql { - match diesel::replace_into(users_collections::table) - .values(( - users_collections::user_uuid.eq(user_uuid), - users_collections::collection_uuid.eq(collection_uuid), - users_collections::read_only.eq(read_only), - users_collections::hide_passwords.eq(hide_passwords), - users_collections::manage.eq(manage), - )) - .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(users_collections::table) - .filter(users_collections::user_uuid.eq(user_uuid)) - .filter(users_collections::collection_uuid.eq(collection_uuid)) - .set(( - users_collections::user_uuid.eq(user_uuid), - users_collections::collection_uuid.eq(collection_uuid), - users_collections::read_only.eq(read_only), - users_collections::hide_passwords.eq(hide_passwords), - users_collections::manage.eq(manage), - )) - .execute(conn) - .map_res("Error adding user to collection") - } - Err(e) => Err(e.into()), - }.map_res("Error adding user to collection") - } - postgresql { + mysql { diesel::insert_into(users_collections::table) - .values(( - users_collections::user_uuid.eq(user_uuid), - users_collections::collection_uuid.eq(collection_uuid), - users_collections::read_only.eq(read_only), - users_collections::hide_passwords.eq(hide_passwords), - users_collections::manage.eq(manage), - )) + .values(values) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(values) + .execute(conn) + .map_res("Error adding user to collection") + } + postgresql, sqlite { + diesel::insert_into(users_collections::table) + .values(values) .on_conflict((users_collections::user_uuid, users_collections::collection_uuid)) .do_update() - .set(( - users_collections::read_only.eq(read_only), - users_collections::hide_passwords.eq(hide_passwords), - users_collections::manage.eq(manage), - )) + .set(values) .execute(conn) .map_res("Error adding user to collection") } @@ -909,19 +878,18 @@ impl CollectionCipher { Self::update_users_revision(collection_uuid, conn).await; db_run! { conn: - sqlite, mysql { - // Not checking for ForeignKey Constraints here. - // Table ciphers_collections does not have ForeignKey Constraints which would cause conflicts. - // This table has no constraints pointing to itself, but only to others. - diesel::replace_into(ciphers_collections::table) + mysql { + diesel::insert_into(ciphers_collections::table) .values(( ciphers_collections::cipher_uuid.eq(cipher_uuid), ciphers_collections::collection_uuid.eq(collection_uuid), )) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_nothing() .execute(conn) .map_res("Error adding cipher to collection") } - postgresql { + postgresql, sqlite { diesel::insert_into(ciphers_collections::table) .values(( ciphers_collections::cipher_uuid.eq(cipher_uuid), diff --git a/src/db/models/device.rs b/src/db/models/device.rs index cc8f1cec..5e5f1f97 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -146,15 +146,18 @@ impl Device { } db_run! { conn: - sqlite, mysql { + mysql { crate::util::retry(|| - diesel::replace_into(devices::table) + diesel::insert_into(devices::table) .values(&*self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(&*self) .execute(conn), 10, ).map_res("Error saving device") } - postgresql { + postgresql, sqlite { crate::util::retry(|| diesel::insert_into(devices::table) .values(&*self) diff --git a/src/db/models/emergency_access.rs b/src/db/models/emergency_access.rs index 45fad91f..09783061 100644 --- a/src/db/models/emergency_access.rs +++ b/src/db/models/emergency_access.rs @@ -146,24 +146,16 @@ impl EmergencyAccess { self.updated_at = Utc::now().naive_utc(); db_run! { conn: - sqlite, mysql { - match diesel::replace_into(emergency_access::table) + mysql { + diesel::insert_into(emergency_access::table) .values(&*self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(&*self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(emergency_access::table) - .filter(emergency_access::uuid.eq(&self.uuid)) - .set(&*self) - .execute(conn) - .map_res("Error updating emergency access") - } - Err(e) => Err(e.into()), - }.map_res("Error saving emergency access") + .map_res("Error saving emergency access") } - postgresql { + postgresql, sqlite { diesel::insert_into(emergency_access::table) .values(&*self) .on_conflict(emergency_access::uuid) diff --git a/src/db/models/event.rs b/src/db/models/event.rs index 1f307979..2d9ed8b2 100644 --- a/src/db/models/event.rs +++ b/src/db/models/event.rs @@ -208,20 +208,23 @@ impl Event { /// Basic Queries pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - sqlite, mysql { - diesel::replace_into(event::table) - .values(self) - .execute(conn) - .map_res("Error saving event") - } - postgresql { + mysql { diesel::insert_into(event::table) - .values(self) - .on_conflict(event::uuid) - .do_update() - .set(self) - .execute(conn) - .map_res("Error saving event") + .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(self) + .execute(conn) + .map_res("Error saving event") + } + postgresql, sqlite { + diesel::insert_into(event::table) + .values(self) + .on_conflict(event::uuid) + .do_update() + .set(self) + .execute(conn) + .map_res("Error saving event") } } } diff --git a/src/db/models/folder.rs b/src/db/models/folder.rs index 745608e3..adbe993f 100644 --- a/src/db/models/folder.rs +++ b/src/db/models/folder.rs @@ -77,24 +77,16 @@ impl Folder { self.updated_at = Utc::now().naive_utc(); db_run! { conn: - sqlite, mysql { - match diesel::replace_into(folders::table) + mysql { + diesel::insert_into(folders::table) .values(&*self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(&*self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(folders::table) - .filter(folders::uuid.eq(&self.uuid)) - .set(&*self) - .execute(conn) - .map_res("Error saving folder") - } - Err(e) => Err(e.into()), - }.map_res("Error saving folder") + .map_res("Error saving folder") } - postgresql { + postgresql, sqlite { diesel::insert_into(folders::table) .values(&*self) .on_conflict(folders::uuid) @@ -147,16 +139,15 @@ impl Folder { impl FolderCipher { pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - sqlite, mysql { - // Not checking for ForeignKey Constraints here. - // Table folders_ciphers does not have ForeignKey Constraints which would cause conflicts. - // This table has no constraints pointing to itself, but only to others. - diesel::replace_into(folders_ciphers::table) + mysql { + diesel::insert_into(folders_ciphers::table) .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_nothing() .execute(conn) .map_res("Error adding cipher to folder") } - postgresql { + postgresql, sqlite { diesel::insert_into(folders_ciphers::table) .values(self) .on_conflict((folders_ciphers::cipher_uuid, folders_ciphers::folder_uuid)) diff --git a/src/db/models/group.rs b/src/db/models/group.rs index 37037de6..32e9333f 100644 --- a/src/db/models/group.rs +++ b/src/db/models/group.rs @@ -166,24 +166,16 @@ impl Group { self.revision_date = Utc::now().naive_utc(); db_run! { conn: - sqlite, mysql { - match diesel::replace_into(groups::table) + mysql { + diesel::insert_into(groups::table) .values(&*self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(&*self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(groups::table) - .filter(groups::uuid.eq(&self.uuid)) - .set(&*self) - .execute(conn) - .map_res("Error saving group") - } - Err(e) => Err(e.into()), - }.map_res("Error saving group") + .map_res("Error saving group") } - postgresql { + postgresql, sqlite { diesel::insert_into(groups::table) .values(&*self) .on_conflict(groups::uuid) @@ -326,53 +318,30 @@ impl CollectionGroup { group_user.update_user_revision(conn).await; } + let values = ( + collections_groups::collections_uuid.eq(&self.collections_uuid), + collections_groups::groups_uuid.eq(&self.groups_uuid), + collections_groups::read_only.eq(&self.read_only), + collections_groups::hide_passwords.eq(&self.hide_passwords), + collections_groups::manage.eq(&self.manage), + ); + db_run! { conn: - sqlite, mysql { - match diesel::replace_into(collections_groups::table) - .values(( - collections_groups::collections_uuid.eq(&self.collections_uuid), - collections_groups::groups_uuid.eq(&self.groups_uuid), - collections_groups::read_only.eq(&self.read_only), - collections_groups::hide_passwords.eq(&self.hide_passwords), - collections_groups::manage.eq(&self.manage), - )) - .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(collections_groups::table) - .filter(collections_groups::collections_uuid.eq(&self.collections_uuid)) - .filter(collections_groups::groups_uuid.eq(&self.groups_uuid)) - .set(( - collections_groups::collections_uuid.eq(&self.collections_uuid), - collections_groups::groups_uuid.eq(&self.groups_uuid), - collections_groups::read_only.eq(&self.read_only), - collections_groups::hide_passwords.eq(&self.hide_passwords), - collections_groups::manage.eq(&self.manage), - )) - .execute(conn) - .map_res("Error adding group to collection") - } - Err(e) => Err(e.into()), - }.map_res("Error adding group to collection") - } - postgresql { + mysql { diesel::insert_into(collections_groups::table) - .values(( - collections_groups::collections_uuid.eq(&self.collections_uuid), - collections_groups::groups_uuid.eq(&self.groups_uuid), - collections_groups::read_only.eq(self.read_only), - collections_groups::hide_passwords.eq(self.hide_passwords), - collections_groups::manage.eq(self.manage), - )) + .values(values) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(values) + .execute(conn) + .map_res("Error adding group to collection") + } + postgresql, sqlite { + diesel::insert_into(collections_groups::table) + .values(values) .on_conflict((collections_groups::collections_uuid, collections_groups::groups_uuid)) .do_update() - .set(( - collections_groups::read_only.eq(self.read_only), - collections_groups::hide_passwords.eq(self.hide_passwords), - collections_groups::manage.eq(self.manage), - )) + .set(values) .execute(conn) .map_res("Error adding group to collection") } @@ -497,43 +466,25 @@ impl GroupUser { pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { self.update_user_revision(conn).await; + let values = ( + groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), + groups_users::groups_uuid.eq(&self.groups_uuid), + ); + db_run! { conn: - sqlite, mysql { - match diesel::replace_into(groups_users::table) - .values(( - groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), - groups_users::groups_uuid.eq(&self.groups_uuid), - )) - .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(groups_users::table) - .filter(groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid)) - .filter(groups_users::groups_uuid.eq(&self.groups_uuid)) - .set(( - groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), - groups_users::groups_uuid.eq(&self.groups_uuid), - )) - .execute(conn) - .map_res("Error adding user to group") - } - Err(e) => Err(e.into()), - }.map_res("Error adding user to group") - } - postgresql { + mysql { diesel::insert_into(groups_users::table) - .values(( - groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), - groups_users::groups_uuid.eq(&self.groups_uuid), - )) + .values(values) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_nothing() + .execute(conn) + .map_res("Error adding user to group") + } + postgresql, sqlite { + diesel::insert_into(groups_users::table) + .values(values) .on_conflict((groups_users::users_organizations_uuid, groups_users::groups_uuid)) - .do_update() - .set(( - groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), - groups_users::groups_uuid.eq(&self.groups_uuid), - )) + .do_nothing() .execute(conn) .map_res("Error adding user to group") } diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index bdb69864..29016865 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -353,25 +353,16 @@ impl Organization { } db_run! { conn: - sqlite, mysql { - match diesel::replace_into(organizations::table) + mysql { + diesel::insert_into(organizations::table) .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(organizations::table) - .filter(organizations::uuid.eq(&self.uuid)) - .set(self) - .execute(conn) - .map_res("Error saving organization") - } - Err(e) => Err(e.into()), - }.map_res("Error saving organization") - + .map_res("Error saving organization") } - postgresql { + postgresql, sqlite { diesel::insert_into(organizations::table) .values(self) .on_conflict(organizations::uuid) @@ -753,24 +744,16 @@ impl Membership { User::update_uuid_revision(&self.user_uuid, conn).await; db_run! { conn: - sqlite, mysql { - match diesel::replace_into(users_organizations::table) + mysql { + diesel::insert_into(users_organizations::table) .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(users_organizations::table) - .filter(users_organizations::uuid.eq(&self.uuid)) - .set(self) - .execute(conn) - .map_res("Error adding user to organization") - }, - Err(e) => Err(e.into()), - }.map_res("Error adding user to organization") + .map_res("Error adding user to organization") } - postgresql { + postgresql, sqlite { diesel::insert_into(users_organizations::table) .values(self) .on_conflict(users_organizations::uuid) @@ -1186,25 +1169,16 @@ impl Membership { impl OrganizationApiKey { pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - sqlite, mysql { - match diesel::replace_into(organization_api_key::table) + mysql { + diesel::insert_into(organization_api_key::table) .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(organization_api_key::table) - .filter(organization_api_key::uuid.eq(&self.uuid)) - .set(self) - .execute(conn) - .map_res("Error saving organization") - } - Err(e) => Err(e.into()), - }.map_res("Error saving organization") - + .map_res("Error saving organization") } - postgresql { + postgresql, sqlite { diesel::insert_into(organization_api_key::table) .values(self) .on_conflict((organization_api_key::uuid, organization_api_key::org_uuid)) diff --git a/src/db/models/send.rs b/src/db/models/send.rs index c5bc98c4..d7de7749 100644 --- a/src/db/models/send.rs +++ b/src/db/models/send.rs @@ -202,24 +202,16 @@ impl Send { self.revision_date = Utc::now().naive_utc(); db_run! { conn: - sqlite, mysql { - match diesel::replace_into(sends::table) + mysql { + diesel::insert_into(sends::table) .values(&*self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(&*self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(sends::table) - .filter(sends::uuid.eq(&self.uuid)) - .set(&*self) - .execute(conn) - .map_res("Error saving send") - } - Err(e) => Err(e.into()), - }.map_res("Error saving send") + .map_res("Error saving send") } - postgresql { + postgresql, sqlite { diesel::insert_into(sends::table) .values(&*self) .on_conflict(sends::uuid) diff --git a/src/db/models/user.rs b/src/db/models/user.rs index 93d750d5..81cb8d84 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -463,15 +463,17 @@ impl Invitation { } db_run! { conn: - sqlite, mysql { - // Not checking for ForeignKey Constraints here - // Table invitations does not have any ForeignKey Constraints. - diesel::replace_into(invitations::table) + // Not checking for ForeignKey Constraints here + // Table invitations does not have any ForeignKey Constraints. + mysql { + diesel::insert_into(invitations::table) .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_nothing() .execute(conn) .map_res("Error saving invitation") } - postgresql { + postgresql, sqlite { diesel::insert_into(invitations::table) .values(self) .on_conflict(invitations::email) @@ -528,13 +530,13 @@ pub struct UserId(String); impl SsoUser { pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - sqlite, mysql { - diesel::replace_into(sso_users::table) + mysql { + diesel::insert_into(sso_users::table) .values(self) .execute(conn) .map_res("Error saving SSO user") } - postgresql { + postgresql, sqlite { diesel::insert_into(sso_users::table) .values(self) .execute(conn) From eb212e23fad88e6136723f43e5b73543fa7026d3 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Wed, 9 Sep 2026 18:24:33 +0200 Subject: [PATCH 20/20] Fix archiveDate update (#7722) When `archiveDate` is set to `null` it should unarchive it for that specific user, which is what Bitwarden does. This should fix this by checking and validating if it is `null` Fixes #7581 Signed-off-by: BlackDex --- src/api/core/ciphers.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 13021ca3..50be6732 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -537,11 +537,12 @@ pub async fn update_cipher_from_data( cipher.move_to_folder(data.folder_id, &headers.user.uuid, conn).await?; cipher.set_favorite(data.favorite, &headers.user.uuid, conn).await?; - if let Some(dt_str) = data.archived_date { - match NaiveDateTime::parse_from_str(&dt_str, "%+") { + match data.archived_date { + Some(dt_str) => match NaiveDateTime::parse_from_str(&dt_str, "%+") { Ok(dt) => cipher.set_archived_at(dt, &headers.user.uuid, conn).await?, Err(err) => warn!("Error parsing ArchivedDate '{dt_str}': {err}"), - } + }, + None => cipher.unarchive(&headers.user.uuid, conn).await?, } if ut != UpdateType::None {