Compare commits

..
1 Commits
Author SHA1 Message Date
Daniel García 537f5eb973 Implement V2 registration support
Signed-off-by: BlackDex <black.dex@gmail.com>
2026-09-08 12:54:05 +02:00
40 changed files with 1147 additions and 616 deletions

No files matched your search

+2 -2
View File
@@ -1,7 +1,7 @@
# Ignore everything // Ignore everything
* *
# Allow what is needed // Allow what is needed
!.git !.git
!docker/healthcheck.sh !docker/healthcheck.sh
!docker/start.sh !docker/start.sh
+1 -1
View File
@@ -23,4 +23,4 @@ jobs:
# When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too # When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too
- name: Spell Check Repo - name: Spell Check Repo
uses: crate-ci/typos@d43b6c087ac471e2ea7b8af622ff15f05c0c365b # v1.50.1 uses: crate-ci/typos@4d9c206a77c041268485162b8e2579ad7a5cb9a3 # v1.50.0
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Run zizmor - name: Run zizmor
uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4 uses: zizmorcore/zizmor-action@70fb788f84895a7701f5643d103d587e460b5c99 # v0.6.3
with: with:
# intentionally not scanning the entire repository, # intentionally not scanning the entire repository,
# since it contains integration tests. # since it contains integration tests.
+1 -1
View File
@@ -18,7 +18,7 @@ repos:
# When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too # When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too
- repo: https://github.com/crate-ci/typos - repo: https://github.com/crate-ci/typos
rev: d43b6c087ac471e2ea7b8af622ff15f05c0c365b # v1.50.1 rev: 4d9c206a77c041268485162b8e2579ad7a5cb9a3 # v1.50.0
hooks: hooks:
- id: typos - id: typos
always_run: true always_run: true
+1
View File
@@ -15,6 +15,7 @@ extend-ignore-re = [
"(?i)helo_name", "(?i)helo_name",
"Server name sent during.+HELO", "Server name sent during.+HELO",
# COSE Is short for CBOR Object Signing and Encryption, ignore these specific items # COSE Is short for CBOR Object Signing and Encryption, ignore these specific items
"COSE",
"COSEKey", "COSEKey",
"COSEAlgorithm", "COSEAlgorithm",
# Ignore this specific string as it's valid # Ignore this specific string as it's valid
Generated
+159 -213
View File
@@ -151,9 +151,9 @@ dependencies = [
[[package]] [[package]]
name = "async-compression" name = "async-compression"
version = "0.4.45" version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24a8ec73eb862508b7041723c89386365894b4e7d9f6998bf1b8529e5b0ee254" checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8"
dependencies = [ dependencies = [
"compression-codecs", "compression-codecs",
"compression-core", "compression-core",
@@ -318,7 +318,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -360,9 +360,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]] [[package]]
name = "aws-config" name = "aws-config"
version = "1.12.0" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8d7b388a9fc3a6db15a5ec778c38b354eff1364882c94d08e0252f7a47dcaa4" checksum = "a767267da9e2c2e189b2f9df8b5657e850ecf5352644734ba130d4a57095cf1b"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -403,9 +403,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-runtime" name = "aws-runtime"
version = "1.9.2" version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef47857a1d4488b528f4a5d5715fa7c3300820897824152234d3fa22b1426657" checksum = "c9007227e10b5fed2f3e0a2beff489211e2b5604c400b7a9d5d81ca9d64c24bb"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-sigv4", "aws-sigv4",
@@ -428,9 +428,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-sso" name = "aws-sdk-sso"
version = "1.109.0" version = "1.108.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3cfe74df5d9ad2fedd691973ad3521ebf4f27a3c68c792556686aedb5519bab" checksum = "c15301b04372832947916607983b114b3374b9db0be058a00fb7513800de1f05"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"aws-credential-types", "aws-credential-types",
@@ -454,9 +454,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-ssooidc" name = "aws-sdk-ssooidc"
version = "1.111.0" version = "1.110.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81b0ec31ed6191bd11350aae4b2004198f2db21350cb0a20c57e0a92e55dd161" checksum = "72cc2c205cb27108183cf1856333f7d584c2ba0f505421b4209ca5828f9ea899"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"aws-credential-types", "aws-credential-types",
@@ -480,9 +480,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-sts" name = "aws-sdk-sts"
version = "1.114.0" version = "1.113.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef45745026107ec30c4ef86bd8ae4b002e7e5f6a86e4225240bdf6b06a0b944a" checksum = "68182ecb449f7537db0f4d5d25917789cf41e32074a9fe47b6a0b847fe1d2032"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"aws-credential-types", "aws-credential-types",
@@ -619,9 +619,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-runtime-api" name = "aws-smithy-runtime-api"
version = "1.16.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c054752dd9e4dc73d0b75748c99ac2d0feafbf2f25c7b0516f03a3534161223" checksum = "954c563ce84507722d2679f07a35d21b9c6466b3872d513020d0281fc8112ac9"
dependencies = [ dependencies = [
"aws-smithy-async", "aws-smithy-async",
"aws-smithy-runtime-api-macros", "aws-smithy-runtime-api-macros",
@@ -659,9 +659,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-types" name = "aws-smithy-types"
version = "1.6.3" version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f94d16e797ec62cd999fc9d5942b48fa7050c3093ddadff48e4d7528d16fcb9" checksum = "fce83ce9abbb198d25bc7131e468d0f9fe1257125e58c39f3f9fc9f5098c9647"
dependencies = [ dependencies = [
"base64-simd", "base64-simd",
"bytes", "bytes",
@@ -694,9 +694,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-types" name = "aws-types"
version = "1.6.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "209f3a6d82a6e9e5f94abbed94c7a26e1c052341002bf57a5fb5481f625896fc" checksum = "eec1cd5469f328c782dc3e33d4153cf118a54e33cbb3356d60d16f89883e1f94"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-smithy-async", "aws-smithy-async",
@@ -895,9 +895,9 @@ dependencies = [
[[package]] [[package]]
name = "cached" name = "cached"
version = "4.0.0" version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5a6cf8262820194a1488ece477f5fb5ba9256ef25229d525470e71d6e9a5835" checksum = "133b6b7d6a828c24d5055ef51e67457002b4017ae5ea3d1b1552ca35a44b1119"
dependencies = [ dependencies = [
"ahash", "ahash",
"async-lock", "async-lock",
@@ -911,9 +911,9 @@ dependencies = [
[[package]] [[package]]
name = "cached_proc_macro" name = "cached_proc_macro"
version = "3.0.1" version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ece0579b43cf6e927b3370c7d44359c7122e7e52b3ea635bc8484da571442edb" checksum = "da80977bd46ecf98c593b651e393260f852678283989b8b7c4079304fe5e8936"
dependencies = [ dependencies = [
"darling 0.20.11", "darling 0.20.11",
"proc-macro-crate", "proc-macro-crate",
@@ -930,9 +930,9 @@ checksum = "f5813789573ae815c8b4be58c4428e0e7ae05f0227678ba9de332ded585b9159"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.4.5" version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@@ -1011,9 +1011,9 @@ dependencies = [
[[package]] [[package]]
name = "compression-codecs" name = "compression-codecs"
version = "0.4.40" version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "100590da849306918656ffbb22576bbdfc1382b50ad10d1d50ec177d3b205fb2" checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf"
dependencies = [ dependencies = [
"brotli", "brotli",
"compression-core", "compression-core",
@@ -1025,9 +1025,9 @@ dependencies = [
[[package]] [[package]]
name = "compression-core" name = "compression-core"
version = "0.4.33" version = "0.4.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789"
[[package]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
@@ -1134,12 +1134,6 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "core_detect"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.17" version = "0.2.17"
@@ -1197,27 +1191,27 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.17" version = "0.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]] [[package]]
name = "crossbeam-epoch" name = "crossbeam-epoch"
version = "0.9.21" version = "0.9.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.23" version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]] [[package]]
name = "crunchy" name = "crunchy"
@@ -1314,12 +1308,12 @@ dependencies = [
[[package]] [[package]]
name = "darling" name = "darling"
version = "0.24.1" version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
dependencies = [ dependencies = [
"darling_core 0.24.1", "darling_core 0.23.0",
"darling_macro 0.24.1", "darling_macro 0.23.0",
] ]
[[package]] [[package]]
@@ -1352,15 +1346,15 @@ dependencies = [
[[package]] [[package]]
name = "darling_core" name = "darling_core"
version = "0.24.1" version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
dependencies = [ dependencies = [
"ident_case", "ident_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 3.0.5", "syn 2.0.119",
] ]
[[package]] [[package]]
@@ -1387,13 +1381,13 @@ dependencies = [
[[package]] [[package]]
name = "darling_macro" name = "darling_macro"
version = "0.24.1" version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
dependencies = [ dependencies = [
"darling_core 0.24.1", "darling_core 0.23.0",
"quote", "quote",
"syn 3.0.5", "syn 2.0.119",
] ]
[[package]] [[package]]
@@ -1576,9 +1570,9 @@ dependencies = [
[[package]] [[package]]
name = "diesel" name = "diesel"
version = "2.3.13" version = "2.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b934ddbdcb2abb9f9fc9c30bd47bcc5618b615eea1d334cda5fdf8ff9b072a" checksum = "715377c6e464cb44bb89bd8487584240516c8d5052bc645d6babc50bb8be46c3"
dependencies = [ dependencies = [
"bigdecimal", "bigdecimal",
"bitflags 2.13.1", "bitflags 2.13.1",
@@ -1613,9 +1607,9 @@ dependencies = [
[[package]] [[package]]
name = "diesel_derives" name = "diesel_derives"
version = "2.3.10" version = "2.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecbd51fb6c020672543641167efa4e6417ff7ad76849ed556ace3595e72de03a" checksum = "d1817b7f4279b947fc4cafddec12b0e5f8727141706561ce3ac94a60bddd1cf5"
dependencies = [ dependencies = [
"diesel_table_macro_syntax", "diesel_table_macro_syntax",
"dsl_auto_type", "dsl_auto_type",
@@ -1676,7 +1670,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -1815,17 +1809,11 @@ dependencies = [
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.41" version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"core_detect",
"multiversion",
"multiversion_no_op",
"rustversion",
"scopeguard",
"simdutf8",
] ]
[[package]] [[package]]
@@ -1920,9 +1908,9 @@ dependencies = [
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.12" version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
[[package]] [[package]]
name = "flate2" name = "flate2"
@@ -2040,7 +2028,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -2189,7 +2177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6"
dependencies = [ dependencies = [
"codemap", "codemap",
"indexmap 2.14.2", "indexmap 2.14.1",
"lasso", "lasso",
"once_cell", "once_cell",
"phf 0.11.3", "phf 0.11.3",
@@ -2218,7 +2206,7 @@ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"http 1.5.0", "http 1.5.0",
"indexmap 2.14.2", "indexmap 2.14.1",
"slab", "slab",
"tokio", "tokio",
"tokio-util", "tokio-util",
@@ -2311,9 +2299,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "hickory-net" name = "hickory-net"
version = "0.26.2" version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "084e7bd6a377435d568f652153e571b50970d7ccc1d1eeec0519f834632287e1" checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"cfg-if", "cfg-if",
@@ -2335,9 +2323,9 @@ dependencies = [
[[package]] [[package]]
name = "hickory-proto" name = "hickory-proto"
version = "0.26.2" version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e2da0694c15b44c6f68a6b05e0233617008c54080e31d6eb848d858a9c5b38d" checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643"
dependencies = [ dependencies = [
"data-encoding", "data-encoding",
"idna", "idna",
@@ -2355,9 +2343,9 @@ dependencies = [
[[package]] [[package]]
name = "hickory-resolver" name = "hickory-resolver"
version = "0.26.2" version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4f9f4603319422d482738f3f6fe5aac03157fdbfed1cd85a3ff45adb09072f" checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"futures-util", "futures-util",
@@ -2495,9 +2483,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "hybrid-array" name = "hybrid-array"
version = "0.4.15" version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
dependencies = [ dependencies = [
"typenum", "typenum",
] ]
@@ -2555,9 +2543,9 @@ dependencies = [
"http 1.5.0", "http 1.5.0",
"hyper 1.11.1", "hyper 1.11.1",
"hyper-util", "hyper-util",
"rustls 0.23.44", "rustls 0.23.43",
"tokio", "tokio",
"tokio-rustls 0.26.5", "tokio-rustls 0.26.4",
"tower-service", "tower-service",
] ]
@@ -2733,9 +2721,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.14.2" version = "2.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.17.1", "hashbrown 0.17.1",
@@ -2764,9 +2752,9 @@ dependencies = [
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.12.2" version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@@ -2930,9 +2918,9 @@ dependencies = [
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.105" version = "0.3.104"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"futures-util", "futures-util",
@@ -3011,12 +2999,12 @@ dependencies = [
"nom 8.0.0", "nom 8.0.0",
"percent-encoding", "percent-encoding",
"quoted_printable", "quoted_printable",
"rustls 0.23.44", "rustls 0.23.43",
"rustls-native-certs", "rustls-native-certs",
"serde", "serde",
"socket2 0.6.5", "socket2 0.6.5",
"tokio", "tokio",
"tokio-rustls 0.26.5", "tokio-rustls 0.26.4",
"tracing", "tracing",
"url", "url",
] ]
@@ -3109,7 +3097,7 @@ name = "macros"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"quote", "quote",
"syn 3.0.5", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -3201,9 +3189,9 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.2.3" version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi",
@@ -3249,33 +3237,6 @@ dependencies = [
"version_check", "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]] [[package]]
name = "mysqlclient-sys" name = "mysqlclient-sys"
version = "0.5.2" version = "0.5.2"
@@ -3379,7 +3340,7 @@ checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -3485,9 +3446,9 @@ dependencies = [
[[package]] [[package]]
name = "opendal" name = "opendal"
version = "0.59.1" version = "0.58.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950151f9587a51a7bed70a15fa0cff464eae96e41ae7499f97067bdafdf43eb" checksum = "33dbff14cc9bb085224256d6a81289d2f3202e85b06f408d42534b42162a4231"
dependencies = [ dependencies = [
"opendal-core", "opendal-core",
"opendal-service-fs", "opendal-service-fs",
@@ -3496,9 +3457,9 @@ dependencies = [
[[package]] [[package]]
name = "opendal-core" name = "opendal-core"
version = "0.59.1" version = "0.58.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a43405d217dfdfb543f58847336d3af672897dd1939bb7dcf314b63cf364f1c9" checksum = "48dbcef97d3eb7591db2c18d5cae95c836bcce07359b98d98dd6f4e861eb77b7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"asyncband", "asyncband",
@@ -3520,25 +3481,11 @@ dependencies = [
"web-time", "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]] [[package]]
name = "opendal-service-fs" name = "opendal-service-fs"
version = "0.59.1" version = "0.58.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb9caf04d6d38713299dd4abac984b95ab16ee20e1ff09160f495c5a64644083" checksum = "c7ef1e1c45f3f89282a59073897e0d685e51385fed0aea771714789525cff996"
dependencies = [ dependencies = [
"bytes", "bytes",
"log", "log",
@@ -3550,9 +3497,9 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-s3" name = "opendal-service-s3"
version = "0.59.1" version = "0.58.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388b1d39b62535c62803754ebef89808859558697366dbedd0299345887ba461" checksum = "c64335f9f24ccb62ac36f1d976342b48611a75ba61979813a4f78a4ebd94de42"
dependencies = [ dependencies = [
"base64 0.23.1", "base64 0.23.1",
"bytes", "bytes",
@@ -3803,9 +3750,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.9.1" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d45aeb61b4bf818e12d4205f2466f8c4748f85f4fce0146d1c03d69d753f0ad" checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf"
dependencies = [ dependencies = [
"memchr", "memchr",
"ucd-trie", "ucd-trie",
@@ -3813,9 +3760,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_derive" name = "pest_derive"
version = "2.9.1" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89cc5a242e25ed4e7704d0be240f2cfbe20a8c27e7e252d94835be93d92dc39f" checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d"
dependencies = [ dependencies = [
"pest", "pest",
"pest_generator", "pest_generator",
@@ -3823,9 +3770,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_generator" name = "pest_generator"
version = "2.9.1" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7abf21475cc3820fe4b2ca2dc2142902f67a02189f3b5b3a229f4febc01a43e5" checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a"
dependencies = [ dependencies = [
"pest", "pest",
"pest_meta", "pest_meta",
@@ -3836,9 +3783,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_meta" name = "pest_meta"
version = "2.9.1" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adba4db388f687393c18c51348d44a41d870ca9df71a2c98172ea3035dc6936e" checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496"
dependencies = [ dependencies = [
"pest", "pest",
] ]
@@ -3992,9 +3939,9 @@ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
[[package]] [[package]]
name = "portable-atomic-util" name = "portable-atomic-util"
version = "0.2.8" version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
dependencies = [ dependencies = [
"portable-atomic", "portable-atomic",
] ]
@@ -4281,7 +4228,7 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -4401,11 +4348,11 @@ dependencies = [
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.13.5" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
dependencies = [ dependencies = [
"base64 0.23.1", "base64 0.22.1",
"bytes", "bytes",
"cookie", "cookie",
"cookie_store", "cookie_store",
@@ -4424,7 +4371,7 @@ dependencies = [
"mime", "mime",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rustls 0.23.44", "rustls 0.23.43",
"rustls-pki-types", "rustls-pki-types",
"rustls-platform-verifier", "rustls-platform-verifier",
"serde", "serde",
@@ -4432,7 +4379,7 @@ dependencies = [
"serde_urlencoded", "serde_urlencoded",
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-rustls 0.26.5", "tokio-rustls 0.26.4",
"tokio-util", "tokio-util",
"tower", "tower",
"tower-http", "tower-http",
@@ -4506,7 +4453,7 @@ dependencies = [
"either", "either",
"figment", "figment",
"futures", "futures",
"indexmap 2.14.2", "indexmap 2.14.1",
"log", "log",
"memchr", "memchr",
"multer", "multer",
@@ -4538,7 +4485,7 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46"
dependencies = [ dependencies = [
"devise", "devise",
"glob", "glob",
"indexmap 2.14.2", "indexmap 2.14.1",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rocket_http", "rocket_http",
@@ -4558,7 +4505,7 @@ dependencies = [
"futures", "futures",
"http 0.2.12", "http 0.2.12",
"hyper 0.14.32", "hyper 0.14.32",
"indexmap 2.14.2", "indexmap 2.14.1",
"log", "log",
"memchr", "memchr",
"pear", "pear",
@@ -4693,9 +4640,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.44" version = "0.23.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
dependencies = [ dependencies = [
"log", "log",
"once_cell", "once_cell",
@@ -4747,7 +4694,7 @@ dependencies = [
"jni", "jni",
"log", "log",
"once_cell", "once_cell",
"rustls 0.23.44", "rustls 0.23.43",
"rustls-native-certs", "rustls-native-certs",
"rustls-platform-verifier-android", "rustls-platform-verifier-android",
"rustls-webpki 0.103.15", "rustls-webpki 0.103.15",
@@ -4959,7 +4906,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -5027,16 +4974,16 @@ dependencies = [
[[package]] [[package]]
name = "serde_with" name = "serde_with"
version = "3.23.0" version = "3.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "935177bb8c0cd8ca1a4e6d1a2ac8988bea69cab4f9d3a31311e012ad27868ea4" checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a"
dependencies = [ dependencies = [
"base64 0.23.1", "base64 0.22.1",
"bs58", "bs58",
"chrono", "chrono",
"hex", "hex",
"indexmap 1.9.3", "indexmap 1.9.3",
"indexmap 2.14.2", "indexmap 2.14.1",
"jiff", "jiff",
"schemars 0.9.0", "schemars 0.9.0",
"schemars 1.2.2", "schemars 1.2.2",
@@ -5048,14 +4995,14 @@ dependencies = [
[[package]] [[package]]
name = "serde_with_macros" name = "serde_with_macros"
version = "3.23.0" version = "3.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d607aa01a3cb0ad757d6fd216136910db3c97b102fe686585689615a02dbcdc" checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46"
dependencies = [ dependencies = [
"darling 0.24.1", "darling 0.23.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 2.0.119",
] ]
[[package]] [[package]]
@@ -5195,9 +5142,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.16.0" version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]] [[package]]
name = "socket2" name = "socket2"
@@ -5324,9 +5271,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "3.0.5" version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -5442,7 +5389,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -5507,9 +5454,9 @@ dependencies = [
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.13.2" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
dependencies = [ dependencies = [
"tinyvec_macros", "tinyvec_macros",
] ]
@@ -5545,7 +5492,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -5560,11 +5507,11 @@ dependencies = [
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.26.5" version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [ dependencies = [
"rustls 0.23.44", "rustls 0.23.43",
"tokio", "tokio",
] ]
@@ -5664,7 +5611,7 @@ version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [ dependencies = [
"indexmap 2.14.2", "indexmap 2.14.1",
"serde", "serde",
"serde_spanned 0.6.9", "serde_spanned 0.6.9",
"toml_datetime 0.6.11", "toml_datetime 0.6.11",
@@ -5678,7 +5625,7 @@ version = "0.25.13+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
dependencies = [ dependencies = [
"indexmap 2.14.2", "indexmap 2.14.1",
"toml_datetime 1.1.1+spec-1.1.0", "toml_datetime 1.1.1+spec-1.1.0",
"toml_parser", "toml_parser",
"winnow 1.0.4", "winnow 1.0.4",
@@ -5954,9 +5901,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]] [[package]]
name = "value-bag" name = "value-bag"
version = "1.14.1" version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2799ffb329a792ecfd902b71306c8a815a6ef1c0470fa9953a6aa4d4cecbe511" checksum = "068e763e8279de7ab94b6afebded2cb701678af094feb1c12ccb061b4783c1be"
[[package]] [[package]]
name = "vaultwarden" name = "vaultwarden"
@@ -6003,7 +5950,6 @@ dependencies = [
"num-derive", "num-derive",
"num-traits", "num-traits",
"opendal", "opendal",
"opendal-http-transport-reqwest",
"openidconnect", "openidconnect",
"openssl", "openssl",
"pastey 0.2.3", "pastey 0.2.3",
@@ -6019,7 +5965,7 @@ dependencies = [
"rocket", "rocket",
"rocket_ws", "rocket_ws",
"rpassword", "rpassword",
"rustls 0.23.44", "rustls 0.23.43",
"semver", "semver",
"serde", "serde",
"serde_json", "serde_json",
@@ -6094,9 +6040,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.128" version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@@ -6107,9 +6053,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.78" version = "0.4.77"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -6117,9 +6063,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.128" version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -6127,22 +6073,22 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.128" version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 2.0.119",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.128" version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -6162,9 +6108,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.105" version = "0.3.104"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -6637,18 +6583,18 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.57" version = "0.8.56"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.57" version = "0.8.56"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -6726,7 +6672,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -6743,27 +6689,27 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
[[package]] [[package]]
name = "zstd" name = "zstd"
version = "0.14.0" version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf06bd8162af0734b344780deb55b42a2429ae430870d13fcc12f238e880fe6e" checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
dependencies = [ dependencies = [
"zstd-safe", "zstd-safe",
] ]
[[package]] [[package]]
name = "zstd-safe" name = "zstd-safe"
version = "8.0.0" version = "7.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae42c0555055784c70058d19ba8e275528e8a99a706684868ace5da4e716a4ab" checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
dependencies = [ dependencies = [
"zstd-sys", "zstd-sys",
] ]
[[package]] [[package]]
name = "zstd-sys" name = "zstd-sys"
version = "2.1.0+zstd.1.5.7" version = "2.0.16+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
dependencies = [ dependencies = [
"cc", "cc",
"pkg-config", "pkg-config",
+10 -11
View File
@@ -12,6 +12,7 @@ members = ["macros"]
name = "vaultwarden" name = "vaultwarden"
version = "1.0.0" version = "1.0.0"
authors = ["Daniel García <dani-garcia@users.noreply.github.com>"] authors = ["Daniel García <dani-garcia@users.noreply.github.com>"]
readme = "README.md"
build = "build.rs" build = "build.rs"
repository.workspace = true repository.workspace = true
edition.workspace = true edition.workspace = true
@@ -40,7 +41,6 @@ vendored_openssl = ["openssl/vendored"]
enable_mimalloc = ["dep:mimalloc"] enable_mimalloc = ["dep:mimalloc"]
s3 = [ s3 = [
"opendal/services-s3", "opendal/services-s3",
"dep:opendal-http-transport-reqwest",
"dep:aws-config", "dep:aws-config",
"dep:aws-credential-types", "dep:aws-credential-types",
"dep:aws-smithy-runtime-api", "dep:aws-smithy-runtime-api",
@@ -107,7 +107,7 @@ serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.151" serde_json = "1.0.151"
# A safe, extensible ORM and Query builder # A safe, extensible ORM and Query builder
diesel = { version = "2.3.13", features = ["chrono", "r2d2", "numeric"] } diesel = { version = "2.3.12", features = ["chrono", "r2d2", "numeric", "64-column-tables"] }
diesel_migrations = "2.3.2" diesel_migrations = "2.3.2"
derive_more = { version = "2.1.1", features = [ derive_more = { version = "2.1.1", features = [
@@ -125,7 +125,7 @@ libsqlite3-sys = { version = "0.38.2", optional = true }
# Crypto-related libraries # Crypto-related libraries
rand = "0.10.2" rand = "0.10.2"
ring = "0.17.14" ring = "0.17.14"
rustls = { version = "0.23.44", features = ["ring", "std"], default-features = false } rustls = { version = "0.23.43", features = ["ring", "std"], default-features = false }
subtle = "2.6.1" subtle = "2.6.1"
# UUID generation # UUID generation
@@ -183,7 +183,7 @@ email_address = "0.2.9"
handlebars = { version = "6.4.4", features = ["dir_source"] } handlebars = { version = "6.4.4", features = ["dir_source"] }
# HTTP client (Used for favicons, version check, DUO and HIBP API) # HTTP client (Used for favicons, version check, DUO and HIBP API)
reqwest = { version = "0.13.5", default-features = false, features = [ reqwest = { version = "0.13.4", default-features = false, features = [
# Misc # Misc
"charset", "charset",
"cookies", "cookies",
@@ -201,7 +201,7 @@ reqwest = { version = "0.13.5", default-features = false, features = [
"socks", "socks",
"system-proxy", "system-proxy",
] } ] }
hickory-resolver = "0.26.2" hickory-resolver = "0.26.1"
# Favicon extraction libraries # Favicon extraction libraries
html5gum = "0.8.4" html5gum = "0.8.4"
@@ -215,7 +215,7 @@ bytes = "1.12.1"
svg-hush = "0.9.7" svg-hush = "0.9.7"
# Cache function results (Used for version check and favicon fetching) # Cache function results (Used for version check and favicon fetching)
cached = { version = "4.0.0", features = ["async"] } cached = { version = "3.1.1", features = ["async"] }
# Used for custom short lived cookie jar during favicon extraction # Used for custom short lived cookie jar during favicon extraction
cookie = "0.18.2" cookie = "0.18.2"
@@ -232,7 +232,7 @@ pastey = "0.2.3"
governor = "0.10.4" governor = "0.10.4"
# CIDR parsing for the trusted proxies of the client IP header # CIDR parsing for the trusted proxies of the client IP header
ipnet = "2.12.2" ipnet = "2.12.1"
# OIDC for SSO # OIDC for SSO
openidconnect = { version = "4.0.1", default-features = false } openidconnect = { version = "4.0.1", default-features = false }
@@ -257,18 +257,17 @@ rpassword = "7.5.4"
grass_compiler = { version = "0.13.4", default-features = false } grass_compiler = { version = "0.13.4", default-features = false }
# File are accessed through Apache OpenDAL # File are accessed through Apache OpenDAL
opendal = { version = "0.59.1", default-features = false, features = ["services-fs"] } opendal = { version = "0.58.2", 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 # For retrieving AWS credentials, including temporary SSO credentials
aws-config = { version = "1.12.0", optional = true, default-features = false, features = [ aws-config = { version = "1.11.0", optional = true, default-features = false, features = [
"behavior-version-latest", "behavior-version-latest",
"credentials-process", "credentials-process",
"rt-tokio", "rt-tokio",
"sso", "sso",
] } ] }
aws-credential-types = { version = "1.3.0", optional = true } aws-credential-types = { version = "1.3.0", optional = true }
aws-smithy-runtime-api = { version = "1.16.0", optional = true } aws-smithy-runtime-api = { version = "1.15.0", optional = true }
http = { version = "1.5.0", optional = true } http = { version = "1.5.0", optional = true }
reqsign-aws-v4 = { version = "3.3.0", optional = true } reqsign-aws-v4 = { version = "3.3.0", optional = true }
reqsign-core = { version = "3.3.1", optional = true } reqsign-core = { version = "3.3.1", optional = true }
+1 -2
View File
@@ -5,8 +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 # 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 # https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags
xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707" xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707"
# The `rust_version` variable is extracted from `rust-toolchain.toml` rust_version: 1.98.0 # Rust version to be used
# rust_version: x.yy.z # Rust version to be used
debian_version: trixie # Debian release name to be used debian_version: trixie # Debian release name to be used
alpine_version: "3.24" # Alpine version to be used alpine_version: "3.24" # Alpine version to be used
# For which platforms/architectures will we try to build images # For which platforms/architectures will we try to build images
+4 -4
View File
@@ -32,10 +32,10 @@ FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330
########################## ALPINE BUILD IMAGES ########################## ########################## ALPINE BUILD IMAGES ##########################
## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64 ## 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 ## 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.1 AS build_amd64 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.1 AS build_arm64 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.1 AS build_armv7 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.1 AS build_armv6 FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.98.0 AS build_armv6
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# hadolint ignore=DL3006 # hadolint ignore=DL3006
+1 -1
View File
@@ -36,7 +36,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# hadolint ignore=DL3006 # hadolint ignore=DL3006
FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.98.1-slim-trixie AS build FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.98.0-slim-trixie AS build
# hadolint ignore=DL3067 # hadolint ignore=DL3067
COPY --from=xx / / COPY --from=xx / /
ARG TARGETARCH ARG TARGETARCH
+2 -8
View File
@@ -3,23 +3,17 @@
import os import os
import argparse import argparse
import json import json
import tomllib
import yaml import yaml
import jinja2 import jinja2
# Load settings file # Load settings file
with open('DockerSettings.yaml', 'r', encoding='utf-8') as yaml_file: with open("DockerSettings.yaml", 'r') as yaml_file:
yaml_data = yaml.safe_load(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( settings_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.getcwd()), 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 = argparse.ArgumentParser()
args_parser.add_argument('template_file', help='Jinja2 template file to render.') args_parser.add_argument('template_file', help='Jinja2 template file to render.')
+1 -1
View File
@@ -14,7 +14,7 @@ proc-macro = true
[dependencies] [dependencies]
quote = "1.0.47" quote = "1.0.47"
syn = "3.0.5" syn = "3.0.4"
[lints] [lints]
workspace = true workspace = true
@@ -0,0 +1,6 @@
DROP TABLE IF EXISTS user_signature_key_pairs;
ALTER TABLE users DROP COLUMN signed_public_key;
ALTER TABLE users DROP COLUMN security_state;
ALTER TABLE users DROP COLUMN security_version;
ALTER TABLE users DROP COLUMN v2_upgrade_token;
@@ -0,0 +1,17 @@
ALTER TABLE users ADD COLUMN signed_public_key TEXT;
ALTER TABLE users ADD COLUMN security_state TEXT;
ALTER TABLE users ADD COLUMN security_version INTEGER;
ALTER TABLE users ADD COLUMN v2_upgrade_token TEXT;
DROP TABLE IF EXISTS user_signature_key_pairs;
CREATE TABLE user_signature_key_pairs (
uuid CHAR(36) NOT NULL PRIMARY KEY,
user_uuid CHAR(36) NOT NULL UNIQUE,
signature_algorithm INTEGER NOT NULL, -- 0 = ed25519, 1 = mldsa44
signing_key TEXT NOT NULL,
verifying_key TEXT NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
FOREIGN KEY (user_uuid) REFERENCES users (uuid) ON DELETE CASCADE
);
@@ -0,0 +1,6 @@
DROP TABLE IF EXISTS user_signature_key_pairs;
ALTER TABLE users DROP COLUMN signed_public_key;
ALTER TABLE users DROP COLUMN security_state;
ALTER TABLE users DROP COLUMN security_version;
ALTER TABLE users DROP COLUMN v2_upgrade_token;
@@ -0,0 +1,16 @@
ALTER TABLE users ADD COLUMN signed_public_key TEXT;
ALTER TABLE users ADD COLUMN security_state TEXT;
ALTER TABLE users ADD COLUMN security_version INTEGER;
ALTER TABLE users ADD COLUMN v2_upgrade_token TEXT;
DROP TABLE IF EXISTS user_signature_key_pairs;
CREATE TABLE user_signature_key_pairs (
uuid CHAR(36) NOT NULL PRIMARY KEY,
user_uuid CHAR(36) NOT NULL UNIQUE REFERENCES users (uuid) ON DELETE CASCADE,
signature_algorithm INTEGER NOT NULL, -- 0 = ed25519, 1 = mldsa44
signing_key TEXT NOT NULL,
verifying_key TEXT NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
@@ -0,0 +1,6 @@
DROP TABLE IF EXISTS user_signature_key_pairs;
ALTER TABLE users DROP COLUMN signed_public_key;
ALTER TABLE users DROP COLUMN security_state;
ALTER TABLE users DROP COLUMN security_version;
ALTER TABLE users DROP COLUMN v2_upgrade_token;
@@ -0,0 +1,16 @@
ALTER TABLE users ADD COLUMN signed_public_key TEXT;
ALTER TABLE users ADD COLUMN security_state TEXT;
ALTER TABLE users ADD COLUMN security_version INTEGER;
ALTER TABLE users ADD COLUMN v2_upgrade_token TEXT;
DROP TABLE IF EXISTS user_signature_key_pairs;
CREATE TABLE user_signature_key_pairs (
uuid TEXT NOT NULL PRIMARY KEY,
user_uuid TEXT NOT NULL UNIQUE REFERENCES users (uuid) ON DELETE CASCADE,
signature_algorithm INTEGER NOT NULL, -- 0 = ed25519, 1 = mldsa44
signing_key TEXT NOT NULL,
verifying_key TEXT NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL
);
+1 -1
View File
@@ -1,4 +1,4 @@
[toolchain] [toolchain]
channel = "1.98.1" channel = "1.98.0"
components = [ "rustfmt", "clippy" ] components = [ "rustfmt", "clippy" ]
profile = "minimal" profile = "minimal"
+278 -51
View File
@@ -22,7 +22,8 @@ use crate::{
models::{ models::{
AuthRequest, AuthRequestId, Cipher, CipherId, Device, DeviceId, DeviceType, DeviceWithAuthRequest, AuthRequest, AuthRequestId, Cipher, CipherId, Device, DeviceId, DeviceType, DeviceWithAuthRequest,
EmergencyAccess, EmergencyAccessId, EventType, Folder, FolderId, Invitation, Membership, MembershipId, EmergencyAccess, EmergencyAccessId, EventType, Folder, FolderId, Invitation, Membership, MembershipId,
OrgPolicy, OrgPolicyType, Organization, OrganizationId, Send, SendId, User, UserId, UserKdfType, OrgPolicy, OrgPolicyType, Organization, OrganizationId, Send, SendId, SignatureAlgorithm, User, UserId,
UserKdfType, UserSignatureKeyPair,
}, },
}, },
mail, mail,
@@ -41,6 +42,8 @@ pub fn routes() -> Vec<rocket::Route> {
post_profile, post_profile,
put_avatar, put_avatar,
get_public_keys, get_public_keys,
get_account_public_keys,
get_keys,
post_keys, post_keys,
post_password, post_password,
post_set_password, post_set_password,
@@ -102,6 +105,9 @@ pub struct RegisterData {
#[serde(alias = "userAsymmetricKeys")] #[serde(alias = "userAsymmetricKeys")]
keys: Option<KeysData>, keys: Option<KeysData>,
// Supersedes `keys`, and the only way a v2 account can be registered.
account_keys: Option<AccountKeysData>,
master_password_hint: Option<String>, master_password_hint: Option<String>,
name: Option<String>, name: Option<String>,
@@ -116,36 +122,6 @@ pub struct RegisterData {
org_invite_token: Option<String>, org_invite_token: Option<String>,
} }
impl RegisterData {
fn hash(&self) -> String {
self.compat.fold(|rdc| &rdc.master_password_hash, |rdcu| &rdcu.master_password_authentication.hash).to_owned()
}
fn kdf(&self) -> &KDFData {
self.compat.fold(|rdc| &rdc.kdf, |rdcu| &rdcu.master_password_authentication.kdf)
}
fn key(&self) -> String {
self.compat.fold(|rdc| &rdc.key, |rdcu| &rdcu.master_password_unlock.key).to_owned()
}
// When comparing with salt, email need to be normalized:
// - https://github.com/bitwarden/clients/blob/web-v2026.5.0/libs/common/src/key-management/master-password/services/master-password.service.ts#L171
fn unprocessable(&self) -> bool {
let mut unprocessable = false;
*self.compat.fold(
|_| &false,
|rdcu| {
let email = self.email.trim().to_lowercase();
unprocessable = rdcu.master_password_authentication.kdf != rdcu.master_password_unlock.kdf
|| rdcu.master_password_authentication.salt != email
|| rdcu.master_password_unlock.salt != email;
&unprocessable
},
)
}
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct RegisterDataOld { struct RegisterDataOld {
#[serde(flatten)] #[serde(flatten)]
@@ -183,6 +159,34 @@ impl RegisterDataCompat {
RegisterDataCompat::RegisterDataCur(rdcu) => fcu(rdcu), RegisterDataCompat::RegisterDataCur(rdcu) => fcu(rdcu),
} }
} }
fn hash(&self) -> String {
self.fold(|rdc| &rdc.master_password_hash, |rdcu| &rdcu.master_password_authentication.hash).to_owned()
}
fn kdf(&self) -> &KDFData {
self.fold(|rdc| &rdc.kdf, |rdcu| &rdcu.master_password_authentication.kdf)
}
fn key(&self) -> String {
self.fold(|rdc| &rdc.key, |rdcu| &rdcu.master_password_unlock.key).to_owned()
}
// When comparing with salt, email need to be normalized:
// - https://github.com/bitwarden/clients/blob/web-v2026.5.0/libs/common/src/key-management/master-password/services/master-password.service.ts#L171
fn unprocessable(&self, email: &str) -> bool {
let mut unprocessable = false;
*self.fold(
|_| &false,
|rdcu| {
let email = email.trim().to_lowercase();
unprocessable = rdcu.master_password_authentication.kdf != rdcu.master_password_unlock.kdf
|| rdcu.master_password_authentication.salt != email
|| rdcu.master_password_unlock.salt != email;
&unprocessable
},
)
}
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@@ -192,6 +196,167 @@ struct KeysData {
public_key: String, public_key: String,
} }
/// The `accountKeys` payload, which replaces the flat `keys`/`userAsymmetricKeys` object.
///
/// It carries either a "v1" state (just the encryption key pair) or a "v2" one, which adds a
/// signature key pair, a signed public key, and a signed security state. The two deprecated
/// top-level fields are still sent by the SDK alongside the nested ones and are only used as a
/// fallback for clients that don't send `publicKeyEncryptionKeyPair` yet.
///
/// Ref: <https://github.com/bitwarden/server/blob/main/src/Core/KeyManagement/Models/Api/Request/AccountKeysRequestModel.cs>
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountKeysData {
user_key_encrypted_account_private_key: Option<String>,
account_public_key: Option<String>,
public_key_encryption_key_pair: Option<PublicKeyEncryptionKeyPairData>,
signature_key_pair: Option<SignatureKeyPairData>,
security_state: Option<SecurityStateData>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct PublicKeyEncryptionKeyPairData {
wrapped_private_key: String,
public_key: String,
signed_public_key: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SignatureKeyPairData {
signature_algorithm: String,
wrapped_signing_key: String,
verifying_key: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SecurityStateData {
security_state: String,
security_version: i32,
}
pub struct ValidatedAccountKeys {
private_key: String,
public_key: String,
v2: Option<ValidatedV2AccountKeys>,
}
struct ValidatedV2AccountKeys {
signed_public_key: String,
signing_key: String,
verifying_key: String,
signature_algorithm: SignatureAlgorithm,
security_state: String,
security_version: i32,
}
impl AccountKeysData {
/// Checks that the payload describes a complete account cryptographic state.
///
/// The v2 fields have to be all present or all absent: a client that receives a COSE-wrapped
/// private key without the matching signature key pair and security state refuses to unlock the
/// vault, so storing half a state would produce an account nobody can log into.
pub fn validate(self) -> ApiResult<ValidatedAccountKeys> {
let (private_key, public_key, signed_public_key) = if let Some(key_pair) = self.public_key_encryption_key_pair {
(key_pair.wrapped_private_key, key_pair.public_key, key_pair.signed_public_key)
// Older clients only send the deprecated top-level fields, which are always v1.
} else if let (Some(private_key), Some(public_key)) =
(self.user_key_encrypted_account_private_key, self.account_public_key)
{
(private_key, public_key, None)
} else {
err!("The account keys are missing an encryption key pair")
};
let v2 = match (signed_public_key, self.signature_key_pair, self.security_state) {
(Some(signed_public_key), Some(signature_key_pair), Some(security_state)) => {
let Some(signature_algorithm) = SignatureAlgorithm::from_str(&signature_key_pair.signature_algorithm)
else {
err!(format!("Unsupported signature algorithm: {}", signature_key_pair.signature_algorithm))
};
Some(ValidatedV2AccountKeys {
signed_public_key,
signing_key: signature_key_pair.wrapped_signing_key,
verifying_key: signature_key_pair.verifying_key,
signature_algorithm,
security_state: security_state.security_state,
security_version: security_state.security_version,
})
}
(None, None, None) => None,
_ => err!(
"Invalid account keys: the signed public key, signature key pair and security state must either all be present or all be absent"
),
};
Ok(ValidatedAccountKeys {
private_key,
public_key,
v2,
})
}
}
impl From<KeysData> for ValidatedAccountKeys {
fn from(keys: KeysData) -> Self {
Self {
private_key: keys.encrypted_private_key,
public_key: keys.public_key,
v2: None,
}
}
}
impl ValidatedAccountKeys {
/// Writes the parts of the state that live on the user itself. The user still needs saving, and
/// [`Self::save_signature_key_pair`] still needs calling once it has been.
///
/// Rejects downgrading an account from v2 back to v1.
pub fn apply(&self, user: &mut User) -> EmptyResult {
if user.is_v2() && self.v2.is_none() {
err!("Cannot downgrade an account from v2 to v1 encryption")
}
user.private_key = Some(self.private_key.clone());
user.public_key = Some(self.public_key.clone());
user.signed_public_key = self.v2.as_ref().map(|v2| v2.signed_public_key.clone());
user.security_state = self.v2.as_ref().map(|v2| v2.security_state.clone());
user.security_version = self.v2.as_ref().map(|v2| v2.security_version);
Ok(())
}
/// Persists the signature key pair. Separate from [`Self::apply`] because the row has a foreign
/// key to the user, so it can only be written once the user exists.
pub async fn save_signature_key_pair(&self, user_id: &UserId, conn: &DbConn) -> EmptyResult {
// Skip if the account is v1, since v1 accounts don't have a signature key pair.
let Some(v2) = &self.v2 else {
return Ok(());
};
let mut key_pair = match UserSignatureKeyPair::find_active_by_user(user_id, conn).await {
Some(mut key_pair) => {
key_pair.signature_algorithm = v2.signature_algorithm as i32;
key_pair.signing_key.clone_from(&v2.signing_key);
key_pair.verifying_key.clone_from(&v2.verifying_key);
key_pair
}
None => UserSignatureKeyPair::new(
user_id.clone(),
v2.signature_algorithm,
v2.signing_key.clone(),
v2.verifying_key.clone(),
),
};
key_pair.save(conn).await
}
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct MasterPasswordAuthentication { pub struct MasterPasswordAuthentication {
@@ -216,11 +381,12 @@ pub struct MasterPasswordUnlock {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct SetPasswordData { pub struct SetPasswordData {
#[serde(flatten)] #[serde(flatten)]
kdf: KDFData, compat: RegisterDataCompat,
key: String,
keys: Option<KeysData>, keys: Option<KeysData>,
master_password_hash: String, // Supersedes `keys`, and the only way a v2 account can be initialized here.
account_keys: Option<AccountKeysData>,
master_password_hint: Option<String>, master_password_hint: Option<String>,
org_identifier: Option<String>, org_identifier: Option<String>,
} }
@@ -263,7 +429,7 @@ pub async fn register(data: Json<RegisterData>, email_verification: bool, conn:
let mut pending_emergency_access = None; let mut pending_emergency_access = None;
if data.unprocessable() { if data.compat.unprocessable(&data.email) {
err_code!("Unexpected RegisterData format", Status::UnprocessableEntity.code); err_code!("Unexpected RegisterData format", Status::UnprocessableEntity.code);
} }
@@ -386,9 +552,9 @@ pub async fn register(data: Json<RegisterData>, email_verification: bool, conn:
// Make sure we don't leave a lingering invitation. // Make sure we don't leave a lingering invitation.
Invitation::take(&email, &conn).await; Invitation::take(&email, &conn).await;
set_kdf_data(&mut user, data.kdf())?; set_kdf_data(&mut user, data.compat.kdf())?;
user.set_password(&data.hash(), Some(data.key()), true, None, &conn).await?; user.set_password(&data.compat.hash(), Some(data.compat.key()), true, None, &conn).await?;
user.password_hint = password_hint; user.password_hint = password_hint;
// Add extra fields if present // Add extra fields if present
@@ -396,9 +562,13 @@ pub async fn register(data: Json<RegisterData>, email_verification: bool, conn:
user.name = name; user.name = name;
} }
if let Some(keys) = data.keys { let account_keys = match (data.account_keys, data.keys) {
user.private_key = Some(keys.encrypted_private_key); (Some(account_keys), _) => Some(account_keys.validate()?),
user.public_key = Some(keys.public_key); (None, Some(keys)) => Some(keys.into()),
(None, None) => None,
};
if let Some(ref account_keys) = account_keys {
account_keys.apply(&mut user)?;
} }
if email_verified { if email_verified {
@@ -422,6 +592,10 @@ pub async fn register(data: Json<RegisterData>, email_verification: bool, conn:
user.save(&conn).await?; user.save(&conn).await?;
if let Some(account_keys) = account_keys {
account_keys.save_signature_key_pair(&user.uuid, &conn).await?;
}
// accept any open emergency access invitations // accept any open emergency access invitations
if !CONFIG.mail_enabled() && CONFIG.emergency_access_allowed() { if !CONFIG.mail_enabled() && CONFIG.emergency_access_allowed() {
for mut emergency_invite in EmergencyAccess::find_all_invited_by_grantee_email(&user.email, &conn).await { for mut emergency_invite in EmergencyAccess::find_all_invited_by_grantee_email(&user.email, &conn).await {
@@ -444,16 +618,26 @@ async fn post_set_password(data: Json<SetPasswordData>, headers: Headers, conn:
err!("Account already initialized, cannot set password") err!("Account already initialized, cannot set password")
} }
if data.compat.unprocessable(&user.email) {
err_code!("Unexpected SetPasswordData format", Status::UnprocessableEntity.code);
}
// Check against the password hint setting here so if it fails, // Check against the password hint setting here so if it fails,
// the user can retry without losing their invitation below. // the user can retry without losing their invitation below.
let password_hint = clean_password_hint(data.master_password_hint.as_ref()); let password_hint = clean_password_hint(data.master_password_hint.as_ref());
enforce_password_hint_setting(password_hint.as_ref())?; enforce_password_hint_setting(password_hint.as_ref())?;
set_kdf_data(&mut user, &data.kdf)?; let account_keys = match (data.account_keys, data.keys) {
(Some(account_keys), _) => Some(account_keys.validate()?),
(None, Some(keys)) => Some(keys.into()),
(None, None) => None,
};
set_kdf_data(&mut user, data.compat.kdf())?;
user.set_password( user.set_password(
&data.master_password_hash, &data.compat.hash(),
Some(data.key), Some(data.compat.key()),
false, false,
Some(vec![String::from("revision_date")]), // We need to allow revision-date to use the old security_timestamp Some(vec![String::from("revision_date")]), // We need to allow revision-date to use the old security_timestamp
&conn, &conn,
@@ -461,9 +645,8 @@ async fn post_set_password(data: Json<SetPasswordData>, headers: Headers, conn:
.await?; .await?;
user.password_hint = password_hint; user.password_hint = password_hint;
if let Some(keys) = data.keys { if let Some(ref account_keys) = account_keys {
user.private_key = Some(keys.encrypted_private_key); account_keys.apply(&mut user)?;
user.public_key = Some(keys.public_key);
} }
if let Some(identifier) = data.org_identifier if let Some(identifier) = data.org_identifier
@@ -492,6 +675,10 @@ async fn post_set_password(data: Json<SetPasswordData>, headers: Headers, conn:
user.save(&conn).await?; user.save(&conn).await?;
if let Some(account_keys) = account_keys {
account_keys.save_signature_key_pair(&user.uuid, &conn).await?;
}
Ok(Json(json!({ Ok(Json(json!({
"object": "set-password", "object": "set-password",
"captchaBypassToken": "", "captchaBypassToken": "",
@@ -573,20 +760,60 @@ async fn get_public_keys(user_id: UserId, _headers: Headers, conn: DbConn) -> Js
}))) })))
} }
#[get("/users/<user_id>/keys")]
async fn get_account_public_keys(user_id: UserId, _headers: Headers, conn: DbConn) -> JsonResult {
let user = match User::find_by_uuid(&user_id, &conn).await {
Some(user) if user.public_key.is_some() => user,
Some(_) => err_code!("User has no public_key", Status::NotFound.code),
None => err_code!("User doesn't exist", Status::NotFound.code),
};
Ok(Json(user.public_keys_json(&conn).await))
}
#[get("/accounts/keys")]
async fn get_keys(headers: Headers, conn: DbConn) -> JsonResult {
let user = headers.user;
Ok(Json(json!({
"key": user.akey,
"privateKey": user.private_key,
"publicKey": user.public_key,
"accountKeys": user.account_keys_json(&conn).await,
"object": "keys"
})))
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct PostKeysData {
#[serde(flatten)]
keys: Option<KeysData>,
account_keys: Option<AccountKeysData>,
}
#[post("/accounts/keys", data = "<data>")] #[post("/accounts/keys", data = "<data>")]
async fn post_keys(data: Json<KeysData>, headers: Headers, conn: DbConn) -> JsonResult { async fn post_keys(data: Json<PostKeysData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: KeysData = data.into_inner(); let data: PostKeysData = data.into_inner();
let mut user = headers.user; let mut user = headers.user;
user.private_key = Some(data.encrypted_private_key); // `accountKeys` supersedes the flat `keys` object when both are sent.
user.public_key = Some(data.public_key); let account_keys = match (data.account_keys, data.keys) {
(Some(account_keys), _) => account_keys.validate()?,
(None, Some(keys)) => keys.into(),
(None, None) => err!("No account keys provided"),
};
account_keys.apply(&mut user)?;
user.save(&conn).await?; user.save(&conn).await?;
account_keys.save_signature_key_pair(&user.uuid, &conn).await?;
Ok(Json(json!({ Ok(Json(json!({
"key": user.akey,
"privateKey": user.private_key, "privateKey": user.private_key,
"publicKey": user.public_key, "publicKey": user.public_key,
"accountKeys": user.account_keys_json(&conn).await,
"object":"keys" "object":"keys"
}))) })))
} }
@@ -1611,7 +1838,7 @@ async fn post_auth_request(
_ => err!("AuthRequest doesn't exist", "Device verification failed"), _ => err!("AuthRequest doesn't exist", "Device verification failed"),
}; };
let auth_request = AuthRequest::new( let mut auth_request = AuthRequest::new(
user.uuid.clone(), user.uuid.clone(),
data.device_identifier.clone(), data.device_identifier.clone(),
client_headers.device_type, client_headers.device_type,
+4 -4
View File
@@ -198,6 +198,7 @@ async fn sync(data: SyncData, headers: Headers, client_version: Option<ClientVer
"sends": sends_json, "sends": sends_json,
"userDecryption": { "userDecryption": {
"masterPasswordUnlock": master_password_unlock, "masterPasswordUnlock": master_password_unlock,
"v2UpgradeToken": headers.user.v2_upgrade_token_json(),
}, },
"object": "sync" "object": "sync"
}))) })))
@@ -537,12 +538,11 @@ pub async fn update_cipher_from_data(
cipher.move_to_folder(data.folder_id, &headers.user.uuid, conn).await?; cipher.move_to_folder(data.folder_id, &headers.user.uuid, conn).await?;
cipher.set_favorite(data.favorite, &headers.user.uuid, conn).await?; cipher.set_favorite(data.favorite, &headers.user.uuid, conn).await?;
match data.archived_date { if let Some(dt_str) = data.archived_date {
Some(dt_str) => match NaiveDateTime::parse_from_str(&dt_str, "%+") { match NaiveDateTime::parse_from_str(&dt_str, "%+") {
Ok(dt) => cipher.set_archived_at(dt, &headers.user.uuid, conn).await?, Ok(dt) => cipher.set_archived_at(dt, &headers.user.uuid, conn).await?,
Err(err) => warn!("Error parsing ArchivedDate '{dt_str}': {err}"), Err(err) => warn!("Error parsing ArchivedDate '{dt_str}': {err}"),
}, }
None => cipher.unarchive(&headers.user.uuid, conn).await?,
} }
if ut != UpdateType::None { if ut != UpdateType::None {
+4 -37
View File
@@ -3,7 +3,7 @@ use num_traits::FromPrimitive;
use rocket::{ use rocket::{
Route, Route,
form::{Form, FromForm}, form::{Form, FromForm},
http::{Accept, Cookie, CookieJar, MediaType, SameSite}, http::{Cookie, CookieJar, SameSite},
response::Redirect, response::Redirect,
serde::json::Json, serde::json::Json,
}; };
@@ -560,18 +560,7 @@ async fn authenticated_response(
Value::Null Value::Null
}; };
let account_keys = if user.private_key.is_some() { let account_keys = user.account_keys_json(conn).await;
json!({
"publicKeyEncryptionKeyPair": {
"wrappedPrivateKey": user.private_key,
"publicKey": user.public_key,
"Object": "publicKeyEncryptionKeyPair"
},
"Object": "privateKeys"
})
} else {
Value::Null
};
let mut result = json!({ let mut result = json!({
"access_token": auth_tokens.access_token(), "access_token": auth_tokens.access_token(),
@@ -711,18 +700,7 @@ async fn user_api_key_login(
Value::Null Value::Null
}; };
let account_keys = if user.private_key.is_some() { let account_keys = user.account_keys_json(conn).await;
json!({
"publicKeyEncryptionKeyPair": {
"wrappedPrivateKey": user.private_key,
"publicKey": user.public_key,
"Object": "publicKeyEncryptionKeyPair"
},
"Object": "privateKeys"
})
} else {
Value::Null
};
// Note: No refresh_token is returned. The CLI just repeats the // Note: No refresh_token is returned. The CLI just repeats the
// client_credentials login flow when the existing token expires. // client_credentials login flow when the existing token expires.
@@ -1083,18 +1061,11 @@ enum RegisterVerificationResponse {
#[response(status = 204)] #[response(status = 204)]
NoContent(()), NoContent(()),
Token(Json<String>), Token(Json<String>),
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 = "<data>")] #[post("/accounts/register/send-verification-email", data = "<data>")]
async fn register_verification_email( async fn register_verification_email(
data: Json<RegisterVerificationData>, data: Json<RegisterVerificationData>,
accept: Option<&Accept>,
ip: ClientIp, ip: ClientIp,
conn: DbConn, conn: DbConn,
) -> ApiResult<RegisterVerificationResponse> { ) -> ApiResult<RegisterVerificationResponse> {
@@ -1132,11 +1103,7 @@ async fn register_verification_email(
} else { } else {
// If email verification is not required, return the token directly // If email verification is not required, return the token directly
// the clients will use this token to finish the registration // the clients will use this token to finish the registration
Ok(if accepts_json(accept) { Ok(RegisterVerificationResponse::Token(Json(token)))
RegisterVerificationResponse::Token(Json(token))
} else {
RegisterVerificationResponse::PlainToken(token)
})
} }
} }
+3 -6
View File
@@ -41,20 +41,17 @@ impl Archive {
) -> EmptyResult { ) -> EmptyResult {
User::update_uuid_revision(user_uuid, conn).await; User::update_uuid_revision(user_uuid, conn).await;
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(archives::table) diesel::replace_into(archives::table)
.values(( .values((
archives::user_uuid.eq(user_uuid), archives::user_uuid.eq(user_uuid),
archives::cipher_uuid.eq(cipher_uuid), archives::cipher_uuid.eq(cipher_uuid),
archives::archived_at.eq(archived_at), archives::archived_at.eq(archived_at),
)) ))
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(archives::archived_at.eq(archived_at))
.execute(conn) .execute(conn)
.map_res("Error saving archive") .map_res("Error saving archive")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(archives::table) diesel::insert_into(archives::table)
.values(( .values((
archives::user_uuid.eq(user_uuid), archives::user_uuid.eq(user_uuid),
+15 -7
View File
@@ -82,16 +82,24 @@ impl Attachment {
impl Attachment { impl Attachment {
pub async fn save(&self, conn: &DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(attachments::table) match diesel::replace_into(attachments::table)
.values(self) .values(self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(self)
.execute(conn) .execute(conn)
.map_res("Error saving attachment") {
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")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(attachments::table) diesel::insert_into(attachments::table)
.values(self) .values(self)
.on_conflict(attachments::id) .on_conflict(attachments::id)
+19 -11
View File
@@ -82,23 +82,31 @@ impl AuthRequest {
} }
impl AuthRequest { impl AuthRequest {
pub async fn save(&self, conn: &DbConn) -> EmptyResult { pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(auth_requests::table) match diesel::replace_into(auth_requests::table)
.values(self) .values(&*self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(self)
.execute(conn) .execute(conn)
.map_res("Error saving auth_request") {
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, sqlite { postgresql {
diesel::insert_into(auth_requests::table) diesel::insert_into(auth_requests::table)
.values(self) .values(&*self)
.on_conflict(auth_requests::uuid) .on_conflict(auth_requests::uuid)
.do_update() .do_update()
.set(self) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving auth_request") .map_res("Error saving auth_request")
} }
+15 -7
View File
@@ -440,16 +440,24 @@ impl Cipher {
self.updated_at = Utc::now().naive_utc(); self.updated_at = Utc::now().naive_utc();
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(ciphers::table) match diesel::replace_into(ciphers::table)
.values(&*self) .values(&*self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving cipher") {
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")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(ciphers::table) diesel::insert_into(ciphers::table)
.values(&*self) .values(&*self)
.on_conflict(ciphers::uuid) .on_conflict(ciphers::uuid)
+62 -30
View File
@@ -168,16 +168,24 @@ impl Collection {
self.update_users_revision(conn).await; self.update_users_revision(conn).await;
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(collections::table) match diesel::replace_into(collections::table)
.values(self) .values(self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(self)
.execute(conn) .execute(conn)
.map_res("Error saving collection") {
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")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(collections::table) diesel::insert_into(collections::table)
.values(self) .values(self)
.on_conflict(collections::uuid) .on_conflict(collections::uuid)
@@ -720,30 +728,53 @@ impl CollectionUser {
) -> EmptyResult { ) -> EmptyResult {
User::update_uuid_revision(user_uuid, conn).await; 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: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(users_collections::table) match diesel::replace_into(users_collections::table)
.values(values) .values((
.on_conflict(diesel::dsl::DuplicatedKeys) users_collections::user_uuid.eq(user_uuid),
.do_update() users_collections::collection_uuid.eq(collection_uuid),
.set(values) users_collections::read_only.eq(read_only),
users_collections::hide_passwords.eq(hide_passwords),
users_collections::manage.eq(manage),
))
.execute(conn) .execute(conn)
.map_res("Error adding user to collection") {
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, sqlite { postgresql {
diesel::insert_into(users_collections::table) diesel::insert_into(users_collections::table)
.values(values) .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),
))
.on_conflict((users_collections::user_uuid, users_collections::collection_uuid)) .on_conflict((users_collections::user_uuid, users_collections::collection_uuid))
.do_update() .do_update()
.set(values) .set((
users_collections::read_only.eq(read_only),
users_collections::hide_passwords.eq(hide_passwords),
users_collections::manage.eq(manage),
))
.execute(conn) .execute(conn)
.map_res("Error adding user to collection") .map_res("Error adding user to collection")
} }
@@ -878,18 +909,19 @@ impl CollectionCipher {
Self::update_users_revision(collection_uuid, conn).await; Self::update_users_revision(collection_uuid, conn).await;
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(ciphers_collections::table) // 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)
.values(( .values((
ciphers_collections::cipher_uuid.eq(cipher_uuid), ciphers_collections::cipher_uuid.eq(cipher_uuid),
ciphers_collections::collection_uuid.eq(collection_uuid), ciphers_collections::collection_uuid.eq(collection_uuid),
)) ))
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_nothing()
.execute(conn) .execute(conn)
.map_res("Error adding cipher to collection") .map_res("Error adding cipher to collection")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(ciphers_collections::table) diesel::insert_into(ciphers_collections::table)
.values(( .values((
ciphers_collections::cipher_uuid.eq(cipher_uuid), ciphers_collections::cipher_uuid.eq(cipher_uuid),
+3 -6
View File
@@ -146,18 +146,15 @@ impl Device {
} }
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
crate::util::retry(|| crate::util::retry(||
diesel::insert_into(devices::table) diesel::replace_into(devices::table)
.values(&*self) .values(&*self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(&*self)
.execute(conn), .execute(conn),
10, 10,
).map_res("Error saving device") ).map_res("Error saving device")
} }
postgresql, sqlite { postgresql {
crate::util::retry(|| crate::util::retry(||
diesel::insert_into(devices::table) diesel::insert_into(devices::table)
.values(&*self) .values(&*self)
+15 -7
View File
@@ -146,16 +146,24 @@ impl EmergencyAccess {
self.updated_at = Utc::now().naive_utc(); self.updated_at = Utc::now().naive_utc();
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(emergency_access::table) match diesel::replace_into(emergency_access::table)
.values(&*self) .values(&*self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving emergency access") {
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")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(emergency_access::table) diesel::insert_into(emergency_access::table)
.values(&*self) .values(&*self)
.on_conflict(emergency_access::uuid) .on_conflict(emergency_access::uuid)
+12 -15
View File
@@ -208,23 +208,20 @@ impl Event {
/// Basic Queries /// Basic Queries
pub async fn save(&self, conn: &DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(event::table) diesel::replace_into(event::table)
.values(self) .values(self)
.on_conflict(diesel::dsl::DuplicatedKeys) .execute(conn)
.do_update() .map_res("Error saving event")
.set(self)
.execute(conn)
.map_res("Error saving event")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(event::table) diesel::insert_into(event::table)
.values(self) .values(self)
.on_conflict(event::uuid) .on_conflict(event::uuid)
.do_update() .do_update()
.set(self) .set(self)
.execute(conn) .execute(conn)
.map_res("Error saving event") .map_res("Error saving event")
} }
} }
} }
+21 -12
View File
@@ -77,16 +77,24 @@ impl Folder {
self.updated_at = Utc::now().naive_utc(); self.updated_at = Utc::now().naive_utc();
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(folders::table) match diesel::replace_into(folders::table)
.values(&*self) .values(&*self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving folder") {
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")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(folders::table) diesel::insert_into(folders::table)
.values(&*self) .values(&*self)
.on_conflict(folders::uuid) .on_conflict(folders::uuid)
@@ -139,15 +147,16 @@ impl Folder {
impl FolderCipher { impl FolderCipher {
pub async fn save(&self, conn: &DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(folders_ciphers::table) // 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)
.values(self) .values(self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_nothing()
.execute(conn) .execute(conn)
.map_res("Error adding cipher to folder") .map_res("Error adding cipher to folder")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(folders_ciphers::table) diesel::insert_into(folders_ciphers::table)
.values(self) .values(self)
.on_conflict((folders_ciphers::cipher_uuid, folders_ciphers::folder_uuid)) .on_conflict((folders_ciphers::cipher_uuid, folders_ciphers::folder_uuid))
+88 -39
View File
@@ -166,16 +166,24 @@ impl Group {
self.revision_date = Utc::now().naive_utc(); self.revision_date = Utc::now().naive_utc();
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(groups::table) match diesel::replace_into(groups::table)
.values(&*self) .values(&*self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving group") {
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")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(groups::table) diesel::insert_into(groups::table)
.values(&*self) .values(&*self)
.on_conflict(groups::uuid) .on_conflict(groups::uuid)
@@ -318,30 +326,53 @@ impl CollectionGroup {
group_user.update_user_revision(conn).await; 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: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(collections_groups::table) match diesel::replace_into(collections_groups::table)
.values(values) .values((
.on_conflict(diesel::dsl::DuplicatedKeys) collections_groups::collections_uuid.eq(&self.collections_uuid),
.do_update() collections_groups::groups_uuid.eq(&self.groups_uuid),
.set(values) collections_groups::read_only.eq(&self.read_only),
collections_groups::hide_passwords.eq(&self.hide_passwords),
collections_groups::manage.eq(&self.manage),
))
.execute(conn) .execute(conn)
.map_res("Error adding group to collection") {
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, sqlite { postgresql {
diesel::insert_into(collections_groups::table) diesel::insert_into(collections_groups::table)
.values(values) .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),
))
.on_conflict((collections_groups::collections_uuid, collections_groups::groups_uuid)) .on_conflict((collections_groups::collections_uuid, collections_groups::groups_uuid))
.do_update() .do_update()
.set(values) .set((
collections_groups::read_only.eq(self.read_only),
collections_groups::hide_passwords.eq(self.hide_passwords),
collections_groups::manage.eq(self.manage),
))
.execute(conn) .execute(conn)
.map_res("Error adding group to collection") .map_res("Error adding group to collection")
} }
@@ -466,25 +497,43 @@ impl GroupUser {
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
self.update_user_revision(conn).await; 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: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(groups_users::table) match diesel::replace_into(groups_users::table)
.values(values) .values((
.on_conflict(diesel::dsl::DuplicatedKeys) groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid),
.do_nothing() groups_users::groups_uuid.eq(&self.groups_uuid),
))
.execute(conn) .execute(conn)
.map_res("Error adding user to group") {
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, sqlite { postgresql {
diesel::insert_into(groups_users::table) diesel::insert_into(groups_users::table)
.values(values) .values((
groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid),
groups_users::groups_uuid.eq(&self.groups_uuid),
))
.on_conflict((groups_users::users_organizations_uuid, groups_users::groups_uuid)) .on_conflict((groups_users::users_organizations_uuid, groups_users::groups_uuid))
.do_nothing() .do_update()
.set((
groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid),
groups_users::groups_uuid.eq(&self.groups_uuid),
))
.execute(conn) .execute(conn)
.map_res("Error adding user to group") .map_res("Error adding user to group")
} }
+2
View File
@@ -17,6 +17,7 @@ mod two_factor;
mod two_factor_duo_context; mod two_factor_duo_context;
mod two_factor_incomplete; mod two_factor_incomplete;
mod user; mod user;
mod user_signature_key_pair;
pub use self::archive::Archive; pub use self::archive::Archive;
pub use self::attachment::{Attachment, AttachmentId}; pub use self::attachment::{Attachment, AttachmentId};
@@ -40,3 +41,4 @@ pub use self::two_factor::{TwoFactor, TwoFactorType};
pub use self::two_factor_duo_context::TwoFactorDuoContext; pub use self::two_factor_duo_context::TwoFactorDuoContext;
pub use self::two_factor_incomplete::TwoFactorIncomplete; pub use self::two_factor_incomplete::TwoFactorIncomplete;
pub use self::user::{Invitation, SsoUser, User, UserId, UserKdfType, UserStampException}; pub use self::user::{Invitation, SsoUser, User, UserId, UserKdfType, UserStampException};
pub use self::user_signature_key_pair::{SignatureAlgorithm, UserSignatureKeyPair};
+47 -21
View File
@@ -353,16 +353,25 @@ impl Organization {
} }
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(organizations::table) match diesel::replace_into(organizations::table)
.values(self) .values(self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(self)
.execute(conn) .execute(conn)
.map_res("Error saving organization") {
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")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(organizations::table) diesel::insert_into(organizations::table)
.values(self) .values(self)
.on_conflict(organizations::uuid) .on_conflict(organizations::uuid)
@@ -744,16 +753,24 @@ impl Membership {
User::update_uuid_revision(&self.user_uuid, conn).await; User::update_uuid_revision(&self.user_uuid, conn).await;
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(users_organizations::table) match diesel::replace_into(users_organizations::table)
.values(self) .values(self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(self)
.execute(conn) .execute(conn)
.map_res("Error adding user to organization") {
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")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(users_organizations::table) diesel::insert_into(users_organizations::table)
.values(self) .values(self)
.on_conflict(users_organizations::uuid) .on_conflict(users_organizations::uuid)
@@ -1169,16 +1186,25 @@ impl Membership {
impl OrganizationApiKey { impl OrganizationApiKey {
pub async fn save(&self, conn: &DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(organization_api_key::table) match diesel::replace_into(organization_api_key::table)
.values(self) .values(self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(self)
.execute(conn) .execute(conn)
.map_res("Error saving organization") {
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")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(organization_api_key::table) diesel::insert_into(organization_api_key::table)
.values(self) .values(self)
.on_conflict((organization_api_key::uuid, organization_api_key::org_uuid)) .on_conflict((organization_api_key::uuid, organization_api_key::org_uuid))
+15 -7
View File
@@ -202,16 +202,24 @@ impl Send {
self.revision_date = Utc::now().naive_utc(); self.revision_date = Utc::now().naive_utc();
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(sends::table) match diesel::replace_into(sends::table)
.values(&*self) .values(&*self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving send") {
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")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(sends::table) diesel::insert_into(sends::table)
.values(&*self) .values(&*self)
.on_conflict(sends::uuid) .on_conflict(sends::uuid)
+84 -25
View File
@@ -20,6 +20,7 @@ use macros::UuidFromParam;
use super::{ use super::{
Cipher, Device, EmergencyAccess, Favorite, Folder, Membership, MembershipType, TwoFactor, TwoFactorIncomplete, Cipher, Device, EmergencyAccess, Favorite, Folder, Membership, MembershipType, TwoFactor, TwoFactorIncomplete,
UserSignatureKeyPair,
}; };
#[derive(Identifiable, Queryable, Insertable, AsChangeset, Selectable)] #[derive(Identifiable, Queryable, Insertable, AsChangeset, Selectable)]
@@ -69,6 +70,15 @@ pub struct User {
pub avatar_color: Option<String>, pub avatar_color: Option<String>,
pub external_id: Option<String>, // Todo: Needs to be removed in the future, this is not used anymore. pub external_id: Option<String>, // Todo: Needs to be removed in the future, this is not used anymore.
// "v2" account cryptographic state. Either all of these are set (together with a row in
// `user_signature_key_pairs`) or none of them are; see `User::is_v2`.
pub signed_public_key: Option<String>,
pub security_state: Option<String>,
pub security_version: Option<i32>,
/// JSON `{"wrappedUserKey1": ..., "wrappedUserKey2": ...}`, letting clients that still hold the
/// v1 user key obtain the v2 one after another client performed the upgrade. Opaque to us.
pub v2_upgrade_token: Option<String>,
} }
#[derive(Identifiable, Queryable, Insertable)] #[derive(Identifiable, Queryable, Insertable)]
@@ -154,9 +164,18 @@ impl User {
avatar_color: None, avatar_color: None,
external_id: None, // Todo: Needs to be removed in the future, this is not used anymore. external_id: None, // Todo: Needs to be removed in the future, this is not used anymore.
signed_public_key: None,
security_state: None,
security_version: None,
v2_upgrade_token: None,
} }
} }
pub fn is_v2(&self) -> bool {
self.signed_public_key.is_some() && self.security_state.is_some() && self.security_version.is_some()
}
pub fn check_valid_password(&self, password: &str) -> bool { pub fn check_valid_password(&self, password: &str) -> bool {
crypto::verify_password_hash( crypto::verify_password_hash(
password.as_bytes(), password.as_bytes(),
@@ -253,6 +272,61 @@ impl User {
/// Database methods /// Database methods
impl User { impl User {
async fn v2_signature_key_pair(&self, conn: &DbConn) -> Option<UserSignatureKeyPair> {
if !self.is_v2() {
return None;
}
UserSignatureKeyPair::find_active_by_user(&self.uuid, conn).await
}
pub async fn account_keys_json(&self, conn: &DbConn) -> Value {
if self.private_key.is_none() {
return Value::Null;
}
let (signed_public_key, signature_key_pair, security_state) = match self.v2_signature_key_pair(conn).await {
Some(key_pair) => (
json!(self.signed_public_key),
key_pair.to_json(),
json!({
"securityState": self.security_state,
"securityVersion": self.security_version,
}),
),
None => (Value::Null, Value::Null, Value::Null),
};
json!({
"publicKeyEncryptionKeyPair": {
"wrappedPrivateKey": self.private_key,
"publicKey": self.public_key,
"signedPublicKey": signed_public_key,
"object": "publicKeyEncryptionKeyPair",
},
"signatureKeyPair": signature_key_pair,
"securityState": security_state,
"object": "privateKeys"
})
}
pub async fn public_keys_json(&self, conn: &DbConn) -> Value {
let (signed_public_key, verifying_key) = match self.v2_signature_key_pair(conn).await {
Some(key_pair) => (json!(self.signed_public_key), json!(key_pair.verifying_key)),
None => (Value::Null, Value::Null),
};
json!({
"publicKey": self.public_key,
"signedPublicKey": signed_public_key,
"verifyingKey": verifying_key,
"object": "publicKeys"
})
}
pub fn v2_upgrade_token_json(&self) -> Value {
self.v2_upgrade_token.as_ref().and_then(|token| serde_json::from_str(token).ok()).unwrap_or(Value::Null)
}
pub async fn to_json(&self, conn: &DbConn) -> Value { pub async fn to_json(&self, conn: &DbConn) -> Value {
let mut orgs_json = Vec::new(); let mut orgs_json = Vec::new();
for c in Membership::find_confirmed_by_user(&self.uuid, conn).await { for c in Membership::find_confirmed_by_user(&self.uuid, conn).await {
@@ -268,21 +342,7 @@ impl User {
UserStatus::Enabled UserStatus::Enabled
}; };
let account_keys = if self.private_key.is_some() { let account_keys = self.account_keys_json(conn).await;
json!({
"publicKeyEncryptionKeyPair": {
"wrappedPrivateKey": self.private_key,
"publicKey": self.public_key,
"signedPublicKey": null,
"object": "publicKeyEncryptionKeyPair",
},
"securityState": null,
"signatureKeyPair": null,
"object": "privateKeys"
})
} else {
Value::Null
};
json!({ json!({
"_status": status as i32, "_status": status as i32,
@@ -357,6 +417,7 @@ impl User {
Device::delete_all_by_user(&self.uuid, conn).await?; Device::delete_all_by_user(&self.uuid, conn).await?;
TwoFactor::delete_all_by_user(&self.uuid, conn).await?; TwoFactor::delete_all_by_user(&self.uuid, conn).await?;
TwoFactorIncomplete::delete_all_by_user(&self.uuid, conn).await?; TwoFactorIncomplete::delete_all_by_user(&self.uuid, conn).await?;
UserSignatureKeyPair::delete_all_by_user(&self.uuid, conn).await?;
Invitation::take(&self.email, conn).await; // Delete invitation if any Invitation::take(&self.email, conn).await; // Delete invitation if any
conn.run(move |conn| { conn.run(move |conn| {
@@ -463,17 +524,15 @@ impl Invitation {
} }
db_run! { conn: db_run! { conn:
// Not checking for ForeignKey Constraints here sqlite, mysql {
// Table invitations does not have any ForeignKey Constraints. // Not checking for ForeignKey Constraints here
mysql { // Table invitations does not have any ForeignKey Constraints.
diesel::insert_into(invitations::table) diesel::replace_into(invitations::table)
.values(self) .values(self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_nothing()
.execute(conn) .execute(conn)
.map_res("Error saving invitation") .map_res("Error saving invitation")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(invitations::table) diesel::insert_into(invitations::table)
.values(self) .values(self)
.on_conflict(invitations::email) .on_conflict(invitations::email)
@@ -530,13 +589,13 @@ pub struct UserId(String);
impl SsoUser { impl SsoUser {
pub async fn save(&self, conn: &DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
db_run! { conn: db_run! { conn:
mysql { sqlite, mysql {
diesel::insert_into(sso_users::table) diesel::replace_into(sso_users::table)
.values(self) .values(self)
.execute(conn) .execute(conn)
.map_res("Error saving SSO user") .map_res("Error saving SSO user")
} }
postgresql, sqlite { postgresql {
diesel::insert_into(sso_users::table) diesel::insert_into(sso_users::table)
.values(self) .values(self)
.execute(conn) .execute(conn)
+169
View File
@@ -0,0 +1,169 @@
use chrono::{NaiveDateTime, Utc};
use derive_more::{AsRef, Deref, Display, From};
use diesel::prelude::*;
use serde_json::Value;
use crate::{
api::EmptyResult,
db::{DbConn, schema::user_signature_key_pairs},
error::MapResult,
util::get_uuid,
};
use macros::UuidFromParam;
use super::UserId;
/// A user's signature key pair, part of the "v2" account cryptographic state.
///
/// Upstream keeps this in its own table rather than as columns on the user, with a unique index on
/// the user id. The stated intent is to eventually keep superseded key pairs around (an `active`
/// flag was sketched but not shipped), which is why this is modelled as a row with its own identity
/// instead of a set of user attributes.
///
/// Ref: <https://github.com/bitwarden/server/blob/main/src/Sql/dbo/KeyManagement/Tables/UserSignatureKeyPair.sql>
#[derive(Identifiable, Queryable, Insertable, AsChangeset, Selectable)]
#[diesel(table_name = user_signature_key_pairs)]
#[diesel(treat_none_as_null = true)]
#[diesel(primary_key(uuid))]
pub struct UserSignatureKeyPair {
pub uuid: UserSignatureKeyPairId,
pub user_uuid: UserId,
pub signature_algorithm: i32,
/// The signing (private) key, wrapped by the user key.
pub signing_key: String,
/// The COSE-encoded public verifying key.
pub verifying_key: String,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
}
/// https://github.com/bitwarden/server/blob/main/src/Core/KeyManagement/Enums/SignatureAlgorithm.cs
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SignatureAlgorithm {
Ed25519 = 0,
MlDsa44 = 1,
}
impl SignatureAlgorithm {
pub fn from_str(algorithm: &str) -> Option<Self> {
match algorithm {
"ed25519" => Some(Self::Ed25519),
"mldsa44" => Some(Self::MlDsa44),
_ => None,
}
}
pub fn from_i32(algorithm: i32) -> Option<Self> {
match algorithm {
0 => Some(Self::Ed25519),
1 => Some(Self::MlDsa44),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::Ed25519 => "ed25519",
Self::MlDsa44 => "mldsa44",
}
}
}
/// Local methods
impl UserSignatureKeyPair {
pub fn new(
user_uuid: UserId,
signature_algorithm: SignatureAlgorithm,
signing_key: String,
verifying_key: String,
) -> Self {
let now = Utc::now().naive_utc();
Self {
uuid: UserSignatureKeyPairId(get_uuid()),
user_uuid,
signature_algorithm: signature_algorithm as i32,
signing_key,
verifying_key,
created_at: now,
updated_at: now,
}
}
pub fn to_json(&self) -> Value {
json!({
"wrappedSigningKey": self.signing_key,
"verifyingKey": self.verifying_key,
"object": "signatureKeyPair",
})
}
}
/// Database methods
impl UserSignatureKeyPair {
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
self.updated_at = Utc::now().naive_utc();
db_run! { conn:
mysql {
diesel::insert_into(user_signature_key_pairs::table)
.values(&*self)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(&*self)
.execute(conn)
.map_res("Error saving user signature key pair")
}
postgresql, sqlite {
diesel::insert_into(user_signature_key_pairs::table)
.values(&*self)
.on_conflict(user_signature_key_pairs::user_uuid)
.do_update()
.set(&*self)
.execute(conn)
.map_res("Error saving user signature key pair")
}
}
}
/// The key pair currently in use by the user. There is at most one today, enforced by a unique
/// index on `user_uuid`.
pub async fn find_active_by_user(user_uuid: &UserId, conn: &DbConn) -> Option<Self> {
conn.run(move |conn| {
user_signature_key_pairs::table
.filter(user_signature_key_pairs::user_uuid.eq(user_uuid))
.first::<Self>(conn)
.ok()
})
.await
}
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
conn.run(move |conn| {
diesel::delete(user_signature_key_pairs::table.filter(user_signature_key_pairs::user_uuid.eq(user_uuid)))
.execute(conn)
.map_res("Error deleting user signature key pairs")
})
.await
}
}
#[derive(
Clone,
Debug,
AsRef,
Deref,
DieselNewType,
Display,
From,
FromForm,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
UuidFromParam,
)]
pub struct UserSignatureKeyPairId(String);
+18
View File
@@ -217,6 +217,22 @@ table! {
api_key -> Nullable<Text>, api_key -> Nullable<Text>,
avatar_color -> Nullable<Text>, avatar_color -> Nullable<Text>,
external_id -> Nullable<Text>, external_id -> Nullable<Text>,
signed_public_key -> Nullable<Text>,
security_state -> Nullable<Text>,
security_version -> Nullable<Integer>,
v2_upgrade_token -> Nullable<Text>,
}
}
table! {
user_signature_key_pairs (uuid) {
uuid -> Text,
user_uuid -> Text,
signature_algorithm -> Integer,
signing_key -> Text,
verifying_key -> Text,
created_at -> Timestamp,
updated_at -> Timestamp,
} }
} }
@@ -382,6 +398,7 @@ joinable!(collections_groups -> groups (groups_uuid));
joinable!(event -> users_organizations (uuid)); joinable!(event -> users_organizations (uuid));
joinable!(auth_requests -> users (user_uuid)); joinable!(auth_requests -> users (user_uuid));
joinable!(sso_users -> users (user_uuid)); joinable!(sso_users -> users (user_uuid));
joinable!(user_signature_key_pairs -> users (user_uuid));
allow_tables_to_appear_in_same_query!( allow_tables_to_appear_in_same_query!(
archives, archives,
@@ -408,4 +425,5 @@ allow_tables_to_appear_in_same_query!(
collections_groups, collections_groups,
event, event,
auth_requests, auth_requests,
user_signature_key_pairs,
); );
+14 -73
View File
@@ -14,10 +14,7 @@ use reqwest::{
}; };
use url::Host; use url::Host;
use crate::{ use crate::{CONFIG, util::is_global};
CONFIG,
util::{get_env_bool, is_global},
};
pub fn make_http_request(method: reqwest::Method, url: &str) -> Result<reqwest::RequestBuilder, crate::Error> { pub fn make_http_request(method: reqwest::Method, url: &str) -> Result<reqwest::RequestBuilder, crate::Error> {
static INSTANCE: LazyLock<Client> = static INSTANCE: LazyLock<Client> =
@@ -39,7 +36,7 @@ pub fn get_reqwest_client_builder(enforce_block: bool) -> ClientBuilder {
let mut headers = header::HeaderMap::new(); let mut headers = header::HeaderMap::new();
headers.insert(header::USER_AGENT, header::HeaderValue::from_static("Vaultwarden")); headers.insert(header::USER_AGENT, header::HeaderValue::from_static("Vaultwarden"));
let redirect_policy = reqwest::redirect::Policy::custom(move |attempt| { let redirect_policy = reqwest::redirect::Policy::custom(|attempt| {
if attempt.previous().len() >= 5 { if attempt.previous().len() >= 5 {
return attempt.error("Too many redirects"); return attempt.error("Too many redirects");
} }
@@ -48,7 +45,7 @@ pub fn get_reqwest_client_builder(enforce_block: bool) -> ClientBuilder {
return attempt.error("Invalid host"); return attempt.error("Invalid host");
}; };
if enforce_block && let Err(e) = should_block_host(&host) { if let Err(e) = should_block_host(&host) {
return attempt.error(e); return attempt.error(e);
} }
@@ -62,14 +59,6 @@ pub fn get_reqwest_client_builder(enforce_block: bool) -> ClientBuilder {
.timeout(Duration::from_secs(10)) .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 { fn should_block_ip(ip: IpAddr) -> bool {
if !CONFIG.http_request_block_non_global_ips() { if !CONFIG.http_request_block_non_global_ips() {
return false; return false;
@@ -269,8 +258,12 @@ impl CustomDnsResolver {
fn new() -> Arc<Self> { fn new() -> Arc<Self> {
TokioResolver::builder(TokioRuntimeProvider::default()) TokioResolver::builder(TokioRuntimeProvider::default())
.and_then(|mut builder| { .and_then(|mut builder| {
// Query both families; the preferred order is applied per lookup below. // Hickory's default since v0.26 is `Ipv6AndIpv4`, which sorts IPv6 first
builder.options_mut().ip_strategy = hickory_resolver::config::LookupIpStrategy::Ipv4AndIpv6; // 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;
}
builder.build() builder.build()
}) })
.inspect_err(|e| warn!("Error creating Hickory resolver, falling back to default: {e:?}")) .inspect_err(|e| warn!("Error creating Hickory resolver, falling back to default: {e:?}"))
@@ -296,17 +289,6 @@ 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> { fn pre_resolve(name: &str, enforce_block: bool) -> Result<(), CustomHttpClientError> {
let Ok(host) = get_valid_host(name) else { let Ok(host) = get_valid_host(name) else {
return Err(CustomHttpClientError::Invalid { return Err(CustomHttpClientError::Invalid {
@@ -338,9 +320,7 @@ impl Resolve for CustomDns {
let this = Arc::clone(&self.resolver); let this = Arc::clone(&self.resolver);
Box::pin(async move { Box::pin(async move {
let name = name.as_str(); let name = name.as_str();
let mut results = this.resolve_domain(name, enforce_block).await?; let 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() { if results.is_empty() {
warn!("Unable to resolve {name} to any valid IP address"); warn!("Unable to resolve {name} to any valid IP address");
} }
@@ -359,29 +339,10 @@ pub(crate) mod aws {
}; };
use reqwest::Client; use reqwest::Client;
use super::get_reqwest_client_builder;
// Adapter that wraps reqwest to be compatible with the AWS SDK // Adapter that wraps reqwest to be compatible with the AWS SDK
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct AwsReqwestConnector { pub(crate) struct AwsReqwestConnector {
client: Client, pub(crate) 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 { impl HttpConnector for AwsReqwestConnector {
@@ -401,10 +362,10 @@ pub(crate) mod aws {
req_builder = req_builder.body(body_bytes.to_vec()); req_builder = req_builder.body(body_bytes.to_vec());
} }
let response = req_builder.send().await.map_err(connector_error)?; let response = req_builder.send().await.map_err(|e| ConnectorError::io(Box::new(e)))?;
let status = response.status().into(); let status = response.status().into();
let bytes = response.bytes().await.map_err(connector_error)?; let bytes = response.bytes().await.map_err(|e| ConnectorError::io(Box::new(e)))?;
Ok(HttpResponse::new(status, bytes.into())) Ok(HttpResponse::new(status, bytes.into()))
}; };
@@ -430,7 +391,7 @@ pub(crate) mod aws {
mod tests { mod tests {
use super::*; use super::*;
use crate::util::is_global_hardcoded; use crate::util::is_global_hardcoded;
use std::net::{Ipv4Addr, Ipv6Addr}; use std::net::Ipv4Addr;
use url::Host; use url::Host;
// === // ===
@@ -443,26 +404,6 @@ 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] #[test]
fn dotted_decimal_loopback_normalizes() { fn dotted_decimal_loopback_normalizes() {
let ip = parse_to_ip("127.0.0.1").unwrap(); let ip = parse_to_ip("127.0.0.1").unwrap();
+7 -12
View File
@@ -77,18 +77,10 @@ pub(crate) fn operator_for_path(path: &str) -> Result<opendal::Operator, crate::
#[cfg(s3)] #[cfg(s3)]
mod s3 { mod s3 {
use std::sync::LazyLock;
use opendal_http_transport_reqwest::ReqwestTransport;
use reqwest::Url; use reqwest::Url;
use crate::error::Error; use crate::error::Error;
static HTTP_CLIENT: LazyLock<reqwest::Client> = 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 { pub(super) fn is_uri(path: &str) -> bool {
path.starts_with("s3://") path.starts_with("s3://")
} }
@@ -185,7 +177,12 @@ mod s3 {
let chain = DEFAULT_CREDENTIAL_CHAIN let chain = DEFAULT_CREDENTIAL_CHAIN
.get_or_init(|| { .get_or_init(|| {
let conf = ProviderConfig::default().with_http_client(AwsReqwestConnector::new()); let reqwest_client = reqwest::Client::builder().build().unwrap();
let connector = AwsReqwestConnector {
client: reqwest_client,
};
let conf = ProviderConfig::default().with_http_client(connector);
DefaultCredentialsChain::builder().configure(conf).build() DefaultCredentialsChain::builder().configure(conf).build()
}) })
@@ -239,9 +236,7 @@ mod s3 {
builder.credential_provider_chain(ProvideCredentialChain::new().push(OpenDALS3CredentialProvider)); builder.credential_provider_chain(ProvideCredentialChain::new().push(OpenDALS3CredentialProvider));
} }
let http_transport = opendal::HttpTransporter::new(ReqwestTransport::new(HTTP_CLIENT.clone())); Ok(opendal::Operator::new(builder)?)
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 { fn uri_has_option(uri: &opendal::OperatorUri, names: &[&str]) -> bool {