From bcf627930e95b01113ad88bee82a5fe29b91cebd Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Fri, 25 Jul 2025 18:17:55 +0200 Subject: [PATCH 1/6] Adjust issue template to hopefully show better to search for closed and open issues (#6096) --- .github/ISSUE_TEMPLATE/bug_report.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 515bfaff..7d382d7a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -20,17 +20,17 @@ body: See here [how to enable the admin page](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page). > [!IMPORTANT] - > :bangbang: Search for existing **Open _AND_ Closed** [Issues](https://github.com/dani-garcia/vaultwarden/issues?q=is%3Aissue%20) **_AND_** [Discussions](https://github.com/dani-garcia/vaultwarden/discussions?discussions_q=) regarding your topic before posting! + > ## :bangbang: Search for existing **Closed _AND_ Open** [Issues](https://github.com/dani-garcia/vaultwarden/issues?q=is%3Aissue%20) **_AND_** [Discussions](https://github.com/dani-garcia/vaultwarden/discussions?discussions_q=) regarding your topic before posting! :bangbang: # - type: checkboxes id: checklist attributes: label: Prerequisites - description: Please confirm you have completed the following before submitting an issue + description: Please confirm you have completed the following before submitting an issue! options: - - label: I have searched the existing issues and discussions + - label: I have searched the existing **Closed _AND_ Open** [Issues](https://github.com/dani-garcia/vaultwarden/issues?q=is%3Aissue%20) **_AND_** [Discussions](https://github.com/dani-garcia/vaultwarden/discussions?discussions_q=) required: true - - label: I have read the documentation + - label: I have searched and read the [documentation](https://github.com/dani-garcia/vaultwarden/wiki/) required: true # - id: support-string From 25865efd799d0321428e1dff1489b834e1206021 Mon Sep 17 00:00:00 2001 From: Richy Date: Fri, 25 Jul 2025 20:58:41 +0200 Subject: [PATCH 2/6] fix: resolve group permission conflicts with multiple groups (#6017) * fix: resolve group permission conflicts with multiple groups When a user belonged to multiple groups with different permissions for the same collection, only the permissions from one group were applied instead of combining them properly. This caused users to see incorrect access levels when initially viewing collection items. The fix combines permissions from all user groups by taking the most permissive settings: - read_only: false if ANY group allows write access - hide_passwords: false if ANY group allows password viewing - manage: true if ANY group allows management This ensures users immediately see the correct permissions when opening collection entries, matching the behavior after editing and saving. * Update src/api/core/ciphers.rs Co-authored-by: Mathijs van Veluw * fix: format * fix: restrict collection manage permissions to managers only Prevent users from getting logged out when they have manage permissions by only allowing manage permissions for MembershipType::Manager and higher roles. * refactor: cipher permission logic to prioritize user access Updated permission checks to return user collection permissions if available, otherwise fallback to group permissions. Clarified comments to indicate user permissions overrule group permissions and corrected the logic for the 'manage' flag to use boolean OR instead of AND. --------- Co-authored-by: Mathijs van Veluw --- src/api/core/ciphers.rs | 20 +++++++++++++++----- src/db/models/cipher.rs | 24 +++++++++++++++--------- src/db/models/collection.rs | 8 +++++--- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index aecbe28a..1c89aed7 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -1924,11 +1924,21 @@ impl CipherSyncData { // Generate a HashMap with the collections_uuid as key and the CollectionGroup record let user_collections_groups: HashMap = if CONFIG.org_groups_enabled() { - CollectionGroup::find_by_user(user_id, conn) - .await - .into_iter() - .map(|collection_group| (collection_group.collections_uuid.clone(), collection_group)) - .collect() + CollectionGroup::find_by_user(user_id, conn).await.into_iter().fold( + HashMap::new(), + |mut combined_permissions, cg| { + combined_permissions + .entry(cg.collections_uuid.clone()) + .and_modify(|existing| { + // Combine permissions: take the most permissive settings. + existing.read_only &= cg.read_only; // false if ANY group allows write + existing.hide_passwords &= cg.hide_passwords; // false if ANY group allows password view + existing.manage |= cg.manage; // true if ANY group allows manage + }) + .or_insert(cg); + combined_permissions + }, + ) } else { HashMap::new() }; diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index 5e8971c8..f8d60505 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -609,22 +609,23 @@ impl Cipher { let mut rows: Vec<(bool, bool, bool)> = Vec::new(); if let Some(collections) = cipher_sync_data.cipher_collections.get(&self.uuid) { for collection in collections { - //User permissions + // User permissions if let Some(cu) = cipher_sync_data.user_collections.get(collection) { rows.push((cu.read_only, cu.hide_passwords, cu.manage)); - } - - //Group permissions - if let Some(cg) = cipher_sync_data.user_collections_groups.get(collection) { + // Group permissions + } else if let Some(cg) = cipher_sync_data.user_collections_groups.get(collection) { rows.push((cg.read_only, cg.hide_passwords, cg.manage)); } } } rows } else { - let mut access_flags = self.get_user_collections_access_flags(user_uuid, conn).await; - access_flags.append(&mut self.get_group_collections_access_flags(user_uuid, conn).await); - access_flags + let user_permissions = self.get_user_collections_access_flags(user_uuid, conn).await; + if !user_permissions.is_empty() { + user_permissions + } else { + self.get_group_collections_access_flags(user_uuid, conn).await + } }; if rows.is_empty() { @@ -633,6 +634,9 @@ impl Cipher { } // A cipher can be in multiple collections with inconsistent access flags. + // Also, user permission overrule group permissions + // and only user permissions are returned by the code above. + // // For example, a cipher could be in one collection where the user has // read-only access, but also in another collection where the user has // read/write access. For a flag to be in effect for a cipher, upstream @@ -641,13 +645,15 @@ impl Cipher { // and `hide_passwords` columns. This could ideally be done as part of the // query, but Diesel doesn't support a min() or bool_and() function on // booleans and this behavior isn't portable anyway. + // + // The only exception is for the `manage` flag, that needs a boolean OR! let mut read_only = true; let mut hide_passwords = true; let mut manage = false; for (ro, hp, mn) in rows.iter() { read_only &= ro; hide_passwords &= hp; - manage &= mn; + manage |= mn; } Some((read_only, hide_passwords, manage)) diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs index 4f192a25..c14c5946 100644 --- a/src/db/models/collection.rs +++ b/src/db/models/collection.rs @@ -97,13 +97,13 @@ impl Collection { ( cu.read_only, cu.hide_passwords, - cu.manage || (is_manager && !cu.read_only && !cu.hide_passwords), + is_manager && (cu.manage || (!cu.read_only && !cu.hide_passwords)), ) } else if let Some(cg) = cipher_sync_data.user_collections_groups.get(&self.uuid) { ( cg.read_only, cg.hide_passwords, - cg.manage || (is_manager && !cg.read_only && !cg.hide_passwords), + is_manager && (cg.manage || (!cg.read_only && !cg.hide_passwords)), ) } else { (false, false, false) @@ -114,7 +114,9 @@ impl Collection { } else { match Membership::find_confirmed_by_user_and_org(user_uuid, &self.org_uuid, conn).await { Some(m) if m.has_full_access() => (false, false, m.atype >= MembershipType::Manager), - Some(_) if self.is_manageable_by_user(user_uuid, conn).await => (false, false, true), + Some(m) if m.atype == MembershipType::Manager && self.is_manageable_by_user(user_uuid, conn).await => { + (false, false, true) + } Some(m) => { let is_manager = m.atype == MembershipType::Manager; let read_only = !self.is_writable_by_user(user_uuid, conn).await; From dfad931dcafbd61e3a3a9500bbba24f358ac78d3 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Sat, 26 Jul 2025 14:58:39 +0200 Subject: [PATCH 3/6] Update crates (#6100) Updated crates and made adjustments where needed. Also removed a struct which wasn't used and the nightly compiler complained about it. Used pinact to update GitHub Actions. Validated GitHub Actions with zizmor. Signed-off-by: BlackDex --- .github/workflows/trivy.yml | 2 +- Cargo.lock | 162 ++++++++++++++++++---------------- Cargo.toml | 14 +-- src/api/admin.rs | 1 + src/api/core/organizations.rs | 9 -- 5 files changed, 93 insertions(+), 95 deletions(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index abfbfddf..74720373 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -48,6 +48,6 @@ jobs: severity: CRITICAL,HIGH - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 + uses: github/codeql-action/upload-sarif@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 with: sarif_file: 'trivy-results.sarif' diff --git a/Cargo.lock b/Cargo.lock index d4ecc9e0..55995fad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,9 +128,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.25" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4" +checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" dependencies = [ "brotli", "flate2", @@ -173,9 +173,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" dependencies = [ "async-lock", "cfg-if", @@ -186,8 +186,7 @@ dependencies = [ "polling", "rustix", "slab", - "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -203,9 +202,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" +checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00" dependencies = [ "async-channel 2.5.0", "async-io", @@ -217,14 +216,13 @@ dependencies = [ "event-listener 5.4.0", "futures-lite", "rustix", - "tracing", ] [[package]] name = "async-signal" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" +checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1" dependencies = [ "async-io", "async-lock", @@ -235,7 +233,7 @@ dependencies = [ "rustix", "signal-hook-registry", "slab", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -333,9 +331,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c18d005c70d2b9c0c1ea8876c039db0ec7fb71164d25c73ccea21bf41fd02171" +checksum = "c0baa720ebadea158c5bda642ac444a2af0cdf7bb66b46d1e4533de5d1f449d0" dependencies = [ "aws-credential-types", "aws-runtime", @@ -363,9 +361,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687bc16bc431a8533fe0097c7f0182874767f920989d7260950172ae8e3c4465" +checksum = "b68c2194a190e1efc999612792e25b1ab3abfefe4306494efaaabc25933c0cbe" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -375,9 +373,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.5.8" +version = "1.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f6c68419d8ba16d9a7463671593c54f81ba58cab466e9b759418da606dcc2e2" +checksum = "b2090e664216c78e766b6bac10fe74d2f451c02441d43484cd76ac9a295075f7" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -399,9 +397,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.74.0" +version = "1.78.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a69de9c1b9272da2872af60c7402683e7f45c06267735b4332deacb203239b" +checksum = "dbd7bc4bd34303733bded362c4c997a39130eac4310257c79aae8484b1c4b724" dependencies = [ "aws-credential-types", "aws-runtime", @@ -421,9 +419,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.75.0" +version = "1.79.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b161d836fac72bdd5ac1a4cd1cdc38ab888c7af26cfd95f661be4409505e63" +checksum = "77358d25f781bb106c1a69531231d4fd12c6be904edb0c47198c604df5a2dbca" dependencies = [ "aws-credential-types", "aws-runtime", @@ -443,9 +441,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.76.0" +version = "1.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb1cd79a3412751a341a28e2cd0d6fa4345241976da427b075a0c0cd5409f886" +checksum = "06e3ed2a9b828ae7763ddaed41d51724d2661a50c45f845b08967e52f4939cfc" dependencies = [ "aws-credential-types", "aws-runtime", @@ -499,9 +497,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.62.1" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99335bec6cdc50a346fda1437f9fefe33abf8c99060739a546a16457f2862ca9" +checksum = "43c82ba4cab184ea61f6edaafc1072aad3c2a17dcf4c0fce19ac5694b90d8b5f" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -547,9 +545,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.8.4" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3aaec682eb189e43c8a19c3dab2fe54590ad5f2cc2d26ab27608a20f2acf81c" +checksum = "660f70d9d8af6876b4c9aa8dcb0dbaf0f89b04ee9a4455bea1b4ba03b15f26f6" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -570,9 +568,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.8.3" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852b9226cb60b78ce9369022c0df678af1cac231c882d5da97a0c4e03be6e67" +checksum = "937a49ecf061895fca4a6dd8e864208ed9be7546c0527d04bc07d502ec5fba1c" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -619,9 +617,9 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.3.7" +version = "1.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a322fec39e4df22777ed3ad8ea868ac2f94cd15e1a55f6ee8d8d6305057689a" +checksum = "b069d19bf01e46298eaedd7c6f283fe565a59263e53eebec945f3e6398f42390" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -813,16 +811,16 @@ dependencies = [ [[package]] name = "cached" -version = "0.55.1" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0839c297f8783316fcca9d90344424e968395413f0662a5481f79c6648bbc14" +checksum = "801927ee168e17809ab8901d9f01f700cd7d8d6a6527997fee44e4b0327a253c" dependencies = [ "ahash", "async-trait", "cached_proc_macro", "cached_proc_macro_types", "futures", - "hashbrown 0.14.5", + "hashbrown 0.15.4", "once_cell", "thiserror 2.0.12", "tokio", @@ -831,9 +829,9 @@ dependencies = [ [[package]] name = "cached_proc_macro" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673992d934f0711b68ebb3e1b79cdc4be31634b37c98f26867ced0438ca5c603" +checksum = "9225bdcf4e4a9a4c08bf16607908eb2fbf746828d5e0b5e019726dbf6571f201" dependencies = [ "darling", "proc-macro2", @@ -858,9 +856,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.29" +version = "1.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" dependencies = [ "jobserver", "libc", @@ -1040,9 +1038,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -1795,7 +1793,7 @@ dependencies = [ "parking_lot", "portable-atomic", "quanta", - "rand 0.9.1", + "rand 0.9.2", "smallvec", "spinning_top", "web-time", @@ -1911,7 +1909,7 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.9.1", + "rand 0.9.2", "ring", "thiserror 2.0.12", "tinyvec", @@ -1933,7 +1931,7 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand 0.9.1", + "rand 0.9.2", "resolv-conf", "smallvec", "thiserror 2.0.12", @@ -2063,7 +2061,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -2110,9 +2108,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64 0.22.1", "bytes", @@ -2126,7 +2124,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.0", "system-configuration", "tokio", "tower-service", @@ -2300,9 +2298,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ "bitflags", "cfg-if", @@ -2315,7 +2313,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2", + "socket2 0.5.10", "widestring", "windows-sys 0.48.0", "winreg", @@ -2458,7 +2456,7 @@ dependencies = [ "rustls 0.23.29", "rustls-native-certs", "serde", - "socket2", + "socket2 0.5.10", "tokio", "tokio-rustls 0.26.2", "tracing", @@ -2512,9 +2510,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" @@ -2877,12 +2875,11 @@ dependencies = [ [[package]] name = "opendal" -version = "0.53.3" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f947c4efbca344c1a125753366033c8107f552b2e3f8251815ed1908f116ca3e" +checksum = "ffb9838d0575c6dbaf3fcec7255af8d5771996d4af900bbb6fa9a314dec00a1a" dependencies = [ "anyhow", - "async-trait", "backon", "base64 0.22.1", "bytes", @@ -3269,17 +3266,16 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "polling" -version = "3.8.0" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +checksum = "8ee9b2fa7a4517d2c91ff5bc6c297a427a96749d15f98fcdbb22c05571a4d4b7" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", "rustix", - "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3413,7 +3409,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.29", - "socket2", + "socket2 0.5.10", "thiserror 2.0.12", "tokio", "tracing", @@ -3429,7 +3425,7 @@ dependencies = [ "bytes", "getrandom 0.3.3", "lru-slab", - "rand 0.9.1", + "rand 0.9.2", "ring", "rustc-hash", "rustls 0.23.29", @@ -3450,7 +3446,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.5.10", "tracing", "windows-sys 0.59.0", ] @@ -3500,9 +3496,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -3557,9 +3553,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" dependencies = [ "bitflags", ] @@ -3938,15 +3934,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -4170,9 +4166,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", "memchr", @@ -4316,6 +4312,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "spin" version = "0.9.8" @@ -4627,7 +4633,7 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "slab", - "socket2", + "socket2 0.5.10", "tokio-macros", "windows-sys 0.52.0", ] @@ -5076,7 +5082,7 @@ dependencies = [ "pastey", "percent-encoding", "pico-args", - "rand 0.9.1", + "rand 0.9.2", "regex", "reqsign", "reqwest", @@ -5280,9 +5286,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] @@ -5791,7 +5797,7 @@ dependencies = [ "form_urlencoded", "futures", "hmac", - "rand 0.9.1", + "rand 0.9.2", "reqwest", "sha1", "threadpool", diff --git a/Cargo.toml b/Cargo.toml index 4ae0c413..6f11a6d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ tokio-util = { version = "0.7.15", features = ["compat"]} # A generic serialization/deserialization framework serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0.140" +serde_json = "1.0.141" # A safe, extensible ORM and Query builder diesel = { version = "2.2.12", features = ["chrono", "r2d2", "numeric"] } @@ -92,7 +92,7 @@ diesel-derive-newtype = "2.1.2" libsqlite3-sys = { version = "0.35.0", features = ["bundled"], optional = true } # Crypto-related libraries -rand = "0.9.1" +rand = "0.9.2" ring = "0.17.14" subtle = "2.6.1" @@ -145,7 +145,7 @@ bytes = "1.10.1" svg-hush = "0.9.5" # Cache function results (Used for version check and favicon fetching) -cached = { version = "0.55.1", features = ["async"] } +cached = { version = "0.56.0", features = ["async"] } # Used for custom short lived cookie jar during favicon extraction cookie = "0.18.1" @@ -180,13 +180,13 @@ rpassword = "7.4.0" grass_compiler = { version = "0.13.4", default-features = false } # File are accessed through Apache OpenDAL -opendal = { version = "0.53.3", features = ["services-fs"], default-features = false } +opendal = { version = "0.54.0", features = ["services-fs"], default-features = false } # For retrieving AWS credentials, including temporary SSO credentials anyhow = { version = "1.0.98", optional = true } -aws-config = { version = "1.8.1", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true } -aws-credential-types = { version = "1.2.3", optional = true } -aws-smithy-runtime-api = { version = "1.8.3", optional = true } +aws-config = { version = "1.8.3", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true } +aws-credential-types = { version = "1.2.4", optional = true } +aws-smithy-runtime-api = { version = "1.8.5", optional = true } http = { version = "1.3.1", optional = true } reqsign = { version = "0.16.5", optional = true } diff --git a/src/api/admin.rs b/src/api/admin.rs index b2601cab..e39db1a1 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -613,6 +613,7 @@ use cached::proc_macro::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 +use std::time::Duration; // Needed for cached #[cached(time = 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. diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 737484a1..9c933d1b 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -726,15 +726,6 @@ async fn delete_organization_collection( _delete_organization_collection(&org_id, &col_id, &headers, &mut conn).await } -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct DeleteCollectionData { - #[allow(dead_code)] - id: String, - #[allow(dead_code)] - org_id: OrganizationId, -} - #[post("/organizations//collections//delete")] async fn post_organization_collection_delete( org_id: OrganizationId, From a0198d8d7cadc666643a4e32a2a8d4217038ea3a Mon Sep 17 00:00:00 2001 From: Stefan Melmuk <509385+stefan0xC@users.noreply.github.com> Date: Sun, 27 Jul 2025 12:18:54 +0200 Subject: [PATCH 4/6] fix account key rotation (#6105) --- src/api/core/accounts.rs | 103 ++++++++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 22 deletions(-) diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 7ad625a4..a0219f13 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -556,14 +556,45 @@ use super::sends::{update_send_from_data, SendData}; #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct KeyData { + account_unlock_data: RotateAccountUnlockData, + account_keys: RotateAccountKeys, + account_data: RotateAccountData, + old_master_key_authentication_hash: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct RotateAccountUnlockData { + emergency_access_unlock_data: Vec, + master_password_unlock_data: MasterPasswordUnlockData, + organization_account_recovery_unlock_data: Vec, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct MasterPasswordUnlockData { + kdf_type: i32, + kdf_iterations: i32, + kdf_parallelism: Option, + kdf_memory: Option, + email: String, + master_key_authentication_hash: String, + master_key_encrypted_user_key: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct RotateAccountKeys { + user_key_encrypted_account_private_key: String, + account_public_key: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct RotateAccountData { ciphers: Vec, folders: Vec, sends: Vec, - emergency_access_keys: Vec, - reset_password_keys: Vec, - key: String, - master_password_hash: String, - private_key: String, } fn validate_keydata( @@ -573,10 +604,24 @@ fn validate_keydata( existing_emergency_access: &[EmergencyAccess], existing_memberships: &[Membership], existing_sends: &[Send], + user: &User, ) -> EmptyResult { + if user.client_kdf_type != data.account_unlock_data.master_password_unlock_data.kdf_type + || user.client_kdf_iter != data.account_unlock_data.master_password_unlock_data.kdf_iterations + || user.client_kdf_memory != data.account_unlock_data.master_password_unlock_data.kdf_memory + || user.client_kdf_parallelism != data.account_unlock_data.master_password_unlock_data.kdf_parallelism + || user.email != data.account_unlock_data.master_password_unlock_data.email + { + err!("Changing the kdf variant or email is not supported during key rotation"); + } + if user.public_key.as_ref() != Some(&data.account_keys.account_public_key) { + err!("Changing the asymmetric keypair is not possible during key rotation") + } + // Check that we're correctly rotating all the user's ciphers let existing_cipher_ids = existing_ciphers.iter().map(|c| &c.uuid).collect::>(); let provided_cipher_ids = data + .account_data .ciphers .iter() .filter(|c| c.organization_id.is_none()) @@ -588,7 +633,8 @@ fn validate_keydata( // Check that we're correctly rotating all the user's folders let existing_folder_ids = existing_folders.iter().map(|f| &f.uuid).collect::>(); - let provided_folder_ids = data.folders.iter().filter_map(|f| f.id.as_ref()).collect::>(); + let provided_folder_ids = + data.account_data.folders.iter().filter_map(|f| f.id.as_ref()).collect::>(); if !provided_folder_ids.is_superset(&existing_folder_ids) { err!("All existing folders must be included in the rotation") } @@ -596,8 +642,12 @@ fn validate_keydata( // Check that we're correctly rotating all the user's emergency access keys let existing_emergency_access_ids = existing_emergency_access.iter().map(|ea| &ea.uuid).collect::>(); - let provided_emergency_access_ids = - data.emergency_access_keys.iter().map(|ea| &ea.id).collect::>(); + let provided_emergency_access_ids = data + .account_unlock_data + .emergency_access_unlock_data + .iter() + .map(|ea| &ea.id) + .collect::>(); if !provided_emergency_access_ids.is_superset(&existing_emergency_access_ids) { err!("All existing emergency access keys must be included in the rotation") } @@ -605,15 +655,19 @@ fn validate_keydata( // Check that we're correctly rotating all the user's reset password keys let existing_reset_password_ids = existing_memberships.iter().map(|m| &m.org_uuid).collect::>(); - let provided_reset_password_ids = - data.reset_password_keys.iter().map(|rp| &rp.organization_id).collect::>(); + let provided_reset_password_ids = data + .account_unlock_data + .organization_account_recovery_unlock_data + .iter() + .map(|rp| &rp.organization_id) + .collect::>(); if !provided_reset_password_ids.is_superset(&existing_reset_password_ids) { err!("All existing reset password keys must be included in the rotation") } // Check that we're correctly rotating all the user's sends let existing_send_ids = existing_sends.iter().map(|s| &s.uuid).collect::>(); - let provided_send_ids = data.sends.iter().filter_map(|s| s.id.as_ref()).collect::>(); + let provided_send_ids = data.account_data.sends.iter().filter_map(|s| s.id.as_ref()).collect::>(); if !provided_send_ids.is_superset(&existing_send_ids) { err!("All existing sends must be included in the rotation") } @@ -621,12 +675,12 @@ fn validate_keydata( Ok(()) } -#[post("/accounts/key", data = "")] +#[post("/accounts/key-management/rotate-user-account-keys", data = "")] async fn post_rotatekey(data: Json, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { // TODO: See if we can wrap everything within a SQL Transaction. If something fails it should revert everything. let data: KeyData = data.into_inner(); - if !headers.user.check_valid_password(&data.master_password_hash) { + if !headers.user.check_valid_password(&data.old_master_key_authentication_hash) { err!("Invalid password") } @@ -634,7 +688,7 @@ async fn post_rotatekey(data: Json, headers: Headers, mut conn: DbConn, // Bitwarden does not process the import if there is one item invalid. // Since we check for the size of the encrypted note length, we need to do that here to pre-validate it. // TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks. - Cipher::validate_cipher_data(&data.ciphers)?; + Cipher::validate_cipher_data(&data.account_data.ciphers)?; let user_id = &headers.user.uuid; @@ -655,10 +709,11 @@ async fn post_rotatekey(data: Json, headers: Headers, mut conn: DbConn, &existing_emergency_access, &existing_memberships, &existing_sends, + &headers.user, )?; // Update folder data - for folder_data in data.folders { + for folder_data in data.account_data.folders { // Skip `null` folder id entries. // See: https://github.com/bitwarden/clients/issues/8453 if let Some(folder_id) = folder_data.id { @@ -672,7 +727,7 @@ async fn post_rotatekey(data: Json, headers: Headers, mut conn: DbConn, } // Update emergency access data - for emergency_access_data in data.emergency_access_keys { + for emergency_access_data in data.account_unlock_data.emergency_access_unlock_data { let Some(saved_emergency_access) = existing_emergency_access.iter_mut().find(|ea| ea.uuid == emergency_access_data.id) else { @@ -684,7 +739,7 @@ async fn post_rotatekey(data: Json, headers: Headers, mut conn: DbConn, } // Update reset password data - for reset_password_data in data.reset_password_keys { + for reset_password_data in data.account_unlock_data.organization_account_recovery_unlock_data { let Some(membership) = existing_memberships.iter_mut().find(|m| m.org_uuid == reset_password_data.organization_id) else { @@ -696,7 +751,7 @@ async fn post_rotatekey(data: Json, headers: Headers, mut conn: DbConn, } // Update send data - for send_data in data.sends { + for send_data in data.account_data.sends { let Some(send) = existing_sends.iter_mut().find(|s| &s.uuid == send_data.id.as_ref().unwrap()) else { err!("Send doesn't exist") }; @@ -707,7 +762,7 @@ async fn post_rotatekey(data: Json, headers: Headers, mut conn: DbConn, // Update cipher data use super::ciphers::update_cipher_from_data; - for cipher_data in data.ciphers { + for cipher_data in data.account_data.ciphers { if cipher_data.organization_id.is_none() { let Some(saved_cipher) = existing_ciphers.iter_mut().find(|c| &c.uuid == cipher_data.id.as_ref().unwrap()) else { @@ -724,9 +779,13 @@ async fn post_rotatekey(data: Json, headers: Headers, mut conn: DbConn, // Update user data let mut user = headers.user; - user.akey = data.key; - user.private_key = Some(data.private_key); - user.reset_security_stamp(); + user.private_key = Some(data.account_keys.user_key_encrypted_account_private_key); + user.set_password( + &data.account_unlock_data.master_password_unlock_data.master_key_authentication_hash, + Some(data.account_unlock_data.master_password_unlock_data.master_key_encrypted_user_key), + true, + None, + ); let save_result = user.save(&mut conn).await; From 0db4b00007ad1db53ca0f554d2c5298cdc001a8e Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Mon, 28 Jul 2025 21:31:02 +0200 Subject: [PATCH 5/6] Update crates to trigger rebuild for mysql issue (#6111) Signed-off-by: BlackDex --- Cargo.lock | 45 +++++++++++++++++++++++---------------------- Cargo.toml | 4 ++-- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55995fad..183a3550 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2097,7 +2097,7 @@ dependencies = [ "http 1.3.1", "hyper 1.6.0", "hyper-util", - "rustls 0.23.29", + "rustls 0.23.30", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -2433,9 +2433,9 @@ dependencies = [ [[package]] name = "lettre" -version = "0.11.17" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb2a0354e9ece2fcdcf9fa53417f6de587230c0c248068eb058fa26c4a753179" +checksum = "5cb54db6ff7a89efac87dba5baeac57bb9ccd726b49a9b6f21fb92b3966aaf56" dependencies = [ "async-std", "async-trait", @@ -2453,10 +2453,10 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "rustls 0.23.29", + "rustls 0.23.30", "rustls-native-certs", "serde", - "socket2 0.5.10", + "socket2 0.6.0", "tokio", "tokio-rustls 0.26.2", "tracing", @@ -3408,7 +3408,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.29", + "rustls 0.23.30", "socket2 0.5.10", "thiserror 2.0.12", "tokio", @@ -3428,7 +3428,7 @@ dependencies = [ "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.29", + "rustls 0.23.30", "rustls-pki-types", "slab", "thiserror 2.0.12", @@ -3553,9 +3553,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.15" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] @@ -3702,7 +3702,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.29", + "rustls 0.23.30", "rustls-native-certs", "rustls-pki-types", "serde", @@ -3913,9 +3913,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -3959,9 +3959,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "069a8df149a16b1a12dcc31497c3396a173844be3cac4bd40c9e7671fef96671" dependencies = [ "log", "once_cell", @@ -4620,9 +4620,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.46.1" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" dependencies = [ "backtrace", "bytes", @@ -4633,9 +4633,9 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "slab", - "socket2 0.5.10", + "socket2 0.6.0", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4665,7 +4665,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.29", + "rustls 0.23.30", "tokio", ] @@ -5496,7 +5496,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -5532,10 +5532,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", diff --git a/Cargo.toml b/Cargo.toml index 6f11a6d7..55e23400 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,7 @@ dashmap = "6.1.0" # Async futures futures = "0.3.31" -tokio = { version = "1.46.1", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal", "net"] } +tokio = { version = "1.47.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal", "net"] } tokio-util = { version = "0.7.15", features = ["compat"]} # A generic serialization/deserialization framework @@ -126,7 +126,7 @@ webauthn-rs = "0.3.2" url = "2.5.4" # Email libraries -lettre = { version = "0.11.17", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "hostname", "tracing", "tokio1-rustls", "ring", "rustls-native-certs"], default-features = false } +lettre = { version = "0.11.18", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "hostname", "tracing", "tokio1-rustls", "ring", "rustls-native-certs"], default-features = false } percent-encoding = "2.3.1" # URL encoding library used for URL's in the emails email_address = "0.2.9" From 5d84f17600e179280e44c391b92ee9eecc2b7cdc Mon Sep 17 00:00:00 2001 From: Stefan Melmuk <509385+stefan0xC@users.noreply.github.com> Date: Tue, 29 Jul 2025 12:13:02 +0200 Subject: [PATCH 6/6] fix hiding of signup link (#6113) The registration link should be hidden if signup is not allowed and whitelist is empty unless mail is disabled and invitations are allowed --- src/api/core/mod.rs | 2 +- src/api/web.rs | 2 +- src/config.rs | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index c30b4e8d..ca1c1561 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -225,7 +225,7 @@ fn config() -> Json { "url": "https://github.com/dani-garcia/vaultwarden" }, "settings": { - "disableUserRegistration": !crate::CONFIG.signups_allowed() && crate::CONFIG.signups_domains_whitelist().is_empty(), + "disableUserRegistration": crate::CONFIG.is_signup_disabled() }, "environment": { "vault": domain, diff --git a/src/api/web.rs b/src/api/web.rs index c4faf58c..f3516b47 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -55,7 +55,7 @@ fn not_found() -> ApiResult> { #[get("/css/vaultwarden.css")] fn vaultwarden_css() -> Cached> { let css_options = json!({ - "signup_disabled": !CONFIG.signups_allowed() && CONFIG.signups_domains_whitelist().is_empty(), + "signup_disabled": CONFIG.is_signup_disabled(), "mail_enabled": CONFIG.mail_enabled(), "mail_2fa_enabled": CONFIG._enable_email_2fa(), "yubico_enabled": CONFIG._enable_yubico() && CONFIG.yubico_client_id().is_some() && CONFIG.yubico_secret_key().is_some(), diff --git a/src/config.rs b/src/config.rs index 5a3d060f..86e11517 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1354,6 +1354,14 @@ impl Config { } } + // The registration link should be hidden if signup is not allowed and whitelist is empty + // unless mail is disabled and invitations are allowed + pub fn is_signup_disabled(&self) -> bool { + !self.signups_allowed() + && self.signups_domains_whitelist().is_empty() + && (self.mail_enabled() || !self.invitations_allowed()) + } + /// Tests whether the specified user is allowed to create an organization. pub fn is_org_creation_allowed(&self, email: &str) -> bool { let users = self.org_creation_users();