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 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 93a54a04..1a84d365 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", @@ -825,16 +823,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", @@ -843,9 +841,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", @@ -901,9 +899,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", @@ -1982,7 +1980,7 @@ dependencies = [ "parking_lot", "portable-atomic", "quanta", - "rand 0.9.1", + "rand 0.9.2", "smallvec", "spinning_top", "web-time", @@ -2115,7 +2113,7 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.9.1", + "rand 0.9.2", "ring", "thiserror 2.0.12", "tinyvec", @@ -2137,7 +2135,7 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand 0.9.1", + "rand 0.9.2", "resolv-conf", "smallvec", "thiserror 2.0.12", @@ -2276,7 +2274,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -2312,7 +2310,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", @@ -2339,9 +2337,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", @@ -2355,7 +2353,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.0", "system-configuration", "tokio", "tower-service", @@ -2540,9 +2538,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", @@ -2555,7 +2553,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", @@ -2684,9 +2682,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", @@ -2704,10 +2702,10 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "rustls 0.23.29", + "rustls 0.23.30", "rustls-native-certs", "serde", - "socket2", + "socket2 0.6.0", "tokio", "tokio-rustls 0.26.2", "tracing", @@ -2761,9 +2759,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" @@ -3178,12 +3176,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", @@ -3634,17 +3631,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]] @@ -3797,8 +3793,8 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.29", - "socket2", + "rustls 0.23.30", + "socket2 0.5.10", "thiserror 2.0.12", "tokio", "tracing", @@ -3814,10 +3810,10 @@ dependencies = [ "bytes", "getrandom 0.3.3", "lru-slab", - "rand 0.9.1", + "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.29", + "rustls 0.23.30", "rustls-pki-types", "slab", "thiserror 2.0.12", @@ -3835,7 +3831,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.5.10", "tracing", "windows-sys 0.59.0", ] @@ -3885,9 +3881,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", @@ -3942,9 +3938,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] @@ -4093,7 +4089,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.29", + "rustls 0.23.30", "rustls-native-certs", "rustls-pki-types", "serde", @@ -4315,9 +4311,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" @@ -4361,9 +4357,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", @@ -4632,9 +4628,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", @@ -4844,6 +4840,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" @@ -5142,9 +5148,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", @@ -5155,9 +5161,9 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "slab", - "socket2", + "socket2 0.6.0", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5197,7 +5203,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", ] @@ -5628,7 +5634,7 @@ dependencies = [ "pastey", "percent-encoding", "pico-args", - "rand 0.9.1", + "rand 0.9.2", "regex", "reqsign", "reqwest", @@ -5832,9 +5838,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", ] @@ -6042,7 +6048,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]] @@ -6078,10 +6084,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", @@ -6343,7 +6350,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 ed725927..e9cc91d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,12 +77,12 @@ 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 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"] } @@ -96,7 +96,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" @@ -130,7 +130,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" @@ -149,7 +149,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" @@ -188,13 +188,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 166425cc..d52e24ef 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -640,6 +640,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/accounts.rs b/src/api/core/accounts.rs index 4aea371e..b8d8fa30 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -639,14 +639,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( @@ -656,10 +687,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()) @@ -671,7 +716,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") } @@ -679,8 +725,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") } @@ -688,15 +738,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") } @@ -704,12 +758,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") } @@ -717,7 +771,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; @@ -738,10 +792,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 { @@ -755,7 +810,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 { @@ -767,7 +822,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 { @@ -779,7 +834,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") }; @@ -790,7 +845,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 { @@ -807,9 +862,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; 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/api/core/mod.rs b/src/api/core/mod.rs index 43a29ed4..28737bdf 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -226,7 +226,7 @@ fn config() -> Json { "url": "https://github.com/dani-garcia/vaultwarden" }, "settings": { - "disableUserRegistration": crate::CONFIG.is_signup_disabled(), + "disableUserRegistration": crate::CONFIG.is_signup_disabled() }, "environment": { "vault": domain, diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index e5dbdccd..a0e79fc4 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -758,15 +758,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, diff --git a/src/api/web.rs b/src/api/web.rs index a6d9f0c2..8a29a2c2 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -60,7 +60,7 @@ fn vaultwarden_css() -> Cached> { "mail_2fa_enabled": CONFIG._enable_email_2fa(), "mail_enabled": CONFIG.mail_enabled(), "sends_allowed": CONFIG.sends_allowed(), - "signup_disabled": !CONFIG.signups_allowed() && CONFIG.signups_domains_whitelist().is_empty(), + "signup_disabled": CONFIG.is_signup_disabled(), "sso_disabled": !CONFIG.sso_enabled(), "sso_only": CONFIG.sso_enabled() && CONFIG.sso_only(), "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 ea2b3b87..6d415eeb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1431,14 +1431,6 @@ impl Config { self.update_config(builder, false).await } - // The `signups_allowed` setting is overrided if: - // - The email whitelist is not empty (will allow signups). - // - The sso is activated and password login is disabled (will disable signups). - pub fn is_signup_disabled(&self) -> bool { - (!self.signups_allowed() && self.signups_domains_whitelist().is_empty()) - || (self.sso_enabled() && self.sso_only()) - } - /// Tests whether an email's domain is allowed. A domain is allowed if it /// is in signups_domains_whitelist, or if no whitelist is set (so there /// are no domain restrictions in effect). @@ -1457,7 +1449,22 @@ impl Config { /// Tests whether signup is allowed for an email address, taking into /// account the signups_allowed and signups_domains_whitelist settings. pub fn is_signup_allowed(&self, email: &str) -> bool { - !self.is_signup_disabled() && self.is_email_domain_allowed(email) + if !self.signups_domains_whitelist().is_empty() { + // The whitelist setting overrides the signups_allowed setting. + self.is_email_domain_allowed(email) + } else { + self.signups_allowed() + } + } + + // 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. + pub fn is_signup_disabled(&self) -> bool { + (!self.signups_allowed() + && self.signups_domains_whitelist().is_empty() + && (self.mail_enabled() || !self.invitations_allowed())) + || (self.sso_enabled() && self.sso_only()) } /// Tests whether the specified user is allowed to create an organization. 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;