diff --git a/.env.template b/.env.template
index 7484053f..f34ea249 100644
--- a/.env.template
+++ b/.env.template
@@ -78,7 +78,7 @@
# DB_CONNECTION_RETRIES=15
## Database timeout
-## Timeout in seconds when acquiring database connection
+## Timeout when acquiring database connection
# DATABASE_TIMEOUT=30
## Database idle timeout
@@ -173,7 +173,7 @@
## Defaults to daily. Set blank to disable this job. Also without EVENTS_DAYS_RETAIN set, this job will not start.
# EVENT_CLEANUP_SCHEDULE="0 10 0 * * *"
## Number of days to retain events stored in the database.
-## If unset (the default) or 0, events are kept indefinitely and the scheduled job is disabled!
+## If unset (the default), events are kept indefinitely and the scheduled job is disabled!
# EVENTS_DAYS_RETAIN=
##
## Cron schedule of the job that cleans old auth requests from the auth request.
@@ -233,7 +233,7 @@
# USER_SEND_LIMIT=
## Number of days to wait before auto-deleting a trashed item.
-## If unset (the default) or 0, trashed items are not auto-deleted.
+## If unset (the default), trashed items are not auto-deleted.
## This setting applies globally, so make sure to inform all users of any changes to this setting.
# TRASH_AUTO_DELETE_DAYS=
@@ -386,34 +386,19 @@
# HTTP_REQUEST_BLOCK_NON_GLOBAL_IPS=true
## Client Settings
-## Server-wide Fill Assist rules feed base directory URL, not a JSON file.
-## Defaults to https://fillassist.bitwarden.com.
-## Must be a valid HTTPS URL without credentials, query parameters, fragments, or surrounding whitespace.
-# FILL_ASSIST_RULES_URL=https://fillassist.bitwarden.com
-
## Enable experimental feature flags for clients.
## This is a comma-separated list of flags, e.g. "flag1,flag2,flag3".
## Note that clients cache the /api/config endpoint for about 1 hour and it could take some time before they are enabled or disabled!
##
## The following flags are available:
-## - "fill-assist-targeting-rules": Enable Fill Assist targeting rules in the browser extension. (Browser >= 2026.6.0)
## - "pm-5594-safari-account-switching": Enable account switching in Safari. (Safari >= 2026.2.0)
## - "pm-32413-multi-client-password-management": Enable changing the master password directly in the client. (Desktop/Extension >= 2026.4.0)
## - "ssh-agent-v2": Enable newer SSH agent support. (Desktop >= 2026.2.1)
-## - "windows-desktop-autotype": Enable the autotype feature preview on Windows. (Desktop >= 2025.8.0)
-## - "windows-desktop-autotype-ga": Enable the new, still in development, autotype implementation on Windows. (Desktop >= 2026.9.1)
-## Only enable one of the two autotype flags, the clients disable autotype completely when both are enabled.
-## - "pm-30529-webauthn-related-origins": Allow passkeys stored in the vault to be used on related origins listed in a site's /.well-known/webauthn file (Browser >= 2026.2.0)
+## - "pm-30529-webauthn-related-origins":
## - "pm-32009-new-item-types": Enable new item types: Bank Account, Driver's License, and Passport (Clients >= 2026.4.0)
## - "pm-34171-card-scanner": Enable the new card scanner feature on mobile (Android >= 2026.4.1, iOS >= 2026.4.1)
## - "enable-basic-auth-response": Enable HTTP Basic Auth autofill in the browser extension (Browser >= 2026.9.0)
## - "undetermined-cipher-scenario-logic": Enable the rewritten add/update login notification triggering logic in the browser extension (Browser >= 2026.2.0)
-## - "biometrics-sdk-ipc": Enable biometric unlock over the SDK IPC framework (Desktop >= 2026.9.0, Browser >= 2026.9.0)
-## - "windows-native-credential-sync": Register the desktop app as a passkey provider in Windows, so passkeys in the vault can be used by other apps and browsers.
-## Requires the Microsoft Store (appx) build of the desktop app. Stable releases up to and including 2026.9.1 lack the required plugin registration, so it has no effect there yet.
-## - "pm-27278-v2-password-registration": Register new accounts with v2 encryption. (Web >= 2026.6.4, Desktop/Browser/CLI >= 2026.7.0, Android >= 2026.4.1, iOS >= 2026.5.1)
-## - "enable-account-encryption-v2-jit-password-registration": Give SSO users who set their master password on their first login v2 encryption. (Web >= 2026.1.1, Desktop/Browser/CLI >= 2026.1.0, Android >= 2026.4.1, iOS >= 2026.6.0)
-## These two flags can't be undone: a v2 account stays v2, and it can only log in from clients that are 2026.10.1 or newer (2026.10.2 for the desktop app), so the versions above are only enough to create one.
# EXPERIMENTAL_CLIENT_FEATURE_FLAGS=
## Require new device emails. When a user logs in an email is required to be sent.
@@ -474,7 +459,7 @@
## Allowed connect-src (Know the risks!)
## https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src
-## Allows other domains for URLs which can be loaded using script interfaces like the Forwarded email alias feature
+## Allows other domains to URLs which can be loaded using script interfaces like the Forwarded email alias feature
## This adds the configured value to the 'Content-Security-Policy' headers 'connect-src' value.
## Multiple values must be separated with a whitespace. And only HTTPS values are allowed.
## Example: "https://my-addy-io.domain.tld https://my-simplelogin.domain.tld"
@@ -500,15 +485,6 @@
## KNOW WHAT YOU ARE DOING!
# ORG_GROUPS_ENABLED=false
-## EXPERIMENTAL FEATURE: Custom roles
-## Enables the Custom member role with granular permissions for organizations.
-## This is still being developed: only the permissions this version enforces can be granted.
-## This is enforced by the server and is unrelated to EXPERIMENTAL_CLIENT_FEATURE_FLAGS.
-## Disabling it again keeps the stored Custom roles and permissions, but they grant nothing until it is re-enabled.
-## Disabled by default because this is an experimental feature!
-## KNOW WHAT YOU ARE DOING!
-# EXPERIMENTAL_CUSTOM_ROLES=false
-
## Increase secure note size limit (Know the risks!)
## Sets the secure note size limit to 100_000 instead of the default 10_000.
## WARNING: This could cause issues with clients. Also exports will not work on Bitwarden servers!
@@ -626,9 +602,6 @@
## Note that the checkbox would still be present, but ignored.
# DISABLE_2FA_REMEMBER=false
##
-## Number of days before a remembered 2FA login expires (min: 1, max: 90).
-# TWO_FACTOR_REMEMBER_DAYS=30
-##
## Authenticator Settings
## Disable authenticator time drifted codes to be valid.
## TOTP codes of the previous and next 30 seconds will be invalid
@@ -637,7 +610,7 @@
## we allow by default the TOTP code which was valid one step back and one in the future.
## This can however allow attackers to be a bit more lucky with their attempts because there are 3 valid codes.
## You can disable this, so that only the current TOTP Code is allowed.
-## Keep in mind that when a server drifts out of time, valid codes could be marked as invalid.
+## Keep in mind that when a sever drifts out of time, valid codes could be marked as invalid.
## In any case, if a code has been used it can not be used again, also codes which predates it will be invalid.
# AUTHENTICATOR_DISABLE_TIME_DRIFT=false
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 673137cf..f52bd8e4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -113,7 +113,7 @@ jobs:
# Enable Rust Caching
- name: Rust Caching
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
+ uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
# Use a custom prefix-key to force a fresh start. This is sometimes needed with bigger changes.
# Like changing the build host from Ubuntu 20.04 to 22.04 for example.
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 6f80509d..903a5e22 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -106,7 +106,7 @@ jobs:
# Login to Docker Hub
- name: Login to Docker Hub
- uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -121,7 +121,7 @@ jobs:
# Login to GitHub Container Registry
- name: Login to GitHub Container Registry
- uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -137,7 +137,7 @@ jobs:
# Login to Quay.io
- name: Login to Quay.io
- uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
@@ -185,7 +185,7 @@ jobs:
- name: Bake ${{ matrix.base_image }} containers
id: bake_vw
- uses: docker/bake-action@59cb1203cd42bcefd40a1b6d73b589e48c86040e # v7.5.0
+ uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # v7.4.0
env:
BASE_TAGS: "${{ steps.determine-version.outputs.BASE_TAGS }}"
SOURCE_COMMIT: "${{ env.SOURCE_COMMIT }}"
@@ -222,7 +222,7 @@ jobs:
touch "${RUNNER_TEMP}/digests/${digest#sha256:}"
- name: Upload digest
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: digests-${{ env.NORMALIZED_ARCH }}-${{ matrix.base_image }}
path: ${{ runner.temp }}/digests/*
@@ -242,7 +242,7 @@ jobs:
subject-path: vaultwarden-${{ env.NORMALIZED_ARCH }}
- name: Upload binaries as artifacts
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-${{ env.NORMALIZED_ARCH }}-${{ matrix.base_image }}
path: vaultwarden-${{ env.NORMALIZED_ARCH }}
@@ -264,7 +264,7 @@ jobs:
steps:
- name: Download digests
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: ${{ runner.temp }}/digests
pattern: digests-*-${{ matrix.base_image }}
@@ -272,7 +272,7 @@ jobs:
# Login to Docker Hub
- name: Login to Docker Hub
- uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -287,7 +287,7 @@ jobs:
# Login to GitHub Container Registry
- name: Login to GitHub Container Registry
- uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -303,7 +303,7 @@ jobs:
# Login to Quay.io
- name: Login to Quay.io
- uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml
index 7ffbe632..81c51847 100644
--- a/.github/workflows/trivy.yml
+++ b/.github/workflows/trivy.yml
@@ -50,6 +50,6 @@ jobs:
severity: CRITICAL,HIGH
- name: Upload Trivy scan results to GitHub Security tab
- uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
+ uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
with:
sarif_file: 'trivy-results.sarif'
diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml
index f7d1f92c..45f90bb9 100644
--- a/.github/workflows/typos.yml
+++ b/.github/workflows/typos.yml
@@ -23,4 +23,4 @@ jobs:
# When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too
- name: Spell Check Repo
- uses: crate-ci/typos@7f9d0819ca1c008ade73f4b41fa9d258d48f605b # v1.51.1
+ uses: crate-ci/typos@00f422f3b19c57bc6338715ebfe3316d38768461 # v1.50.3
diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml
index 7372759b..31153b07 100644
--- a/.github/workflows/zizmor.yml
+++ b/.github/workflows/zizmor.yml
@@ -24,7 +24,7 @@ jobs:
persist-credentials: false
- name: Run zizmor
- uses: zizmorcore/zizmor-action@53290badfa369cb0bf7ad16342cb62c2fa47ac7b # v0.8.0
+ uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
with:
# intentionally not scanning the entire repository,
# since it contains integration tests.
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b6352bbb..ab96a84f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -18,7 +18,7 @@ repos:
# When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too
- repo: https://github.com/crate-ci/typos
- rev: 7f9d0819ca1c008ade73f4b41fa9d258d48f605b # v1.51.1
+ rev: 00f422f3b19c57bc6338715ebfe3316d38768461 # v1.50.3
hooks:
- id: typos
always_run: true
diff --git a/.typos.toml b/.typos.toml
index dabcadb4..87c0c4a6 100644
--- a/.typos.toml
+++ b/.typos.toml
@@ -14,8 +14,9 @@ extend-ignore-re = [
# In SMTP it's called HELO, so ignore it
"(?i)helo_name",
"Server name sent during.+HELO",
- # COSE Is short for CBOR Object Signing and Encryption
- "(?i)cose",
+ # COSE Is short for CBOR Object Signing and Encryption, ignore these specific items
+ "COSEKey",
+ "COSEAlgorithm",
# Ignore this specific string as it's valid
"Ensure they are valid OTPs",
# This word is misspelled upstream
diff --git a/Cargo.lock b/Cargo.lock
index 587ac6b6..6c9d9237 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -68,9 +68,9 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "arc-swap"
-version = "1.9.3"
+version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "380962b3d93b99642e50b9febf72c071e97e8b32d86eae9cf4ef582bb960ae6e"
+checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b"
dependencies = [
"rustversion",
]
@@ -151,9 +151,9 @@ dependencies = [
[[package]]
name = "async-compression"
-version = "0.4.51"
+version = "0.4.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d5755640120b24961f8231e05892e4a4f7e0d04e8aeca161508c9c397946970"
+checksum = "ee19bd99b43e3691acbad4e840420a4881cea6c0b66a208125a824f8fd53f5a1"
dependencies = [
"compression-codecs",
"compression-core",
@@ -318,14 +318,20 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
]
[[package]]
name = "asyncband"
-version = "0.7.4"
+version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d2050892d4691cf110586070786712b504985a4d37ed64a9cd8a402718e1706"
+checksum = "5161fb43cb8f231f06983cb2b8f9ee9cee14be0b1356d8383398cc0f312800d1"
+
+[[package]]
+name = "atomic"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
[[package]]
name = "atomic"
@@ -350,9 +356,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "aws-config"
-version = "1.13.0"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "939e256cfe0ed68e96d32f194339d88210d5c10685cff0787400158f852c6819"
+checksum = "b8d7b388a9fc3a6db15a5ec778c38b354eff1364882c94d08e0252f7a47dcaa4"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -381,9 +387,9 @@ dependencies = [
[[package]]
name = "aws-credential-types"
-version = "1.3.1"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "576e8da35751dc85409ec1cd15999f692c277a1fbe9336780fe6f15e49dd637d"
+checksum = "e93964ffdaf57857f544be3666a5f57570bb699e934700f11b49708f61bb556e"
dependencies = [
"aws-smithy-async",
"aws-smithy-runtime-api",
@@ -393,9 +399,9 @@ dependencies = [
[[package]]
name = "aws-runtime"
-version = "1.10.2"
+version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a0cc2144962cc5e358467b16e3d82981b1d424a7c3d34602be0036103814a1a"
+checksum = "2b8a9911551b4ea6ca13805ef52ed96f7d2bbb43cc3b4a14cb0776a71f33cfaa"
dependencies = [
"aws-credential-types",
"aws-sigv4",
@@ -409,7 +415,7 @@ dependencies = [
"bytes-utils",
"fastrand",
"http 1.5.0",
- "http-body",
+ "http-body 1.1.0",
"percent-encoding",
"pin-project-lite",
"tracing",
@@ -418,9 +424,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sso"
-version = "1.119.0"
+version = "1.114.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c414f25ec4aa9dc18f5cc8f55e18848bb509fcda4ce7c77bee6072e95806bc9"
+checksum = "12be2f9c8eef7f5fc919c96d538e629698469a02b4cb75408b26b1bd984ebe79"
dependencies = [
"arc-swap",
"aws-credential-types",
@@ -443,9 +449,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ssooidc"
-version = "1.121.0"
+version = "1.116.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc638bb4355e6514334dcc3228cd4622f5ee54d704bd6a9010a5a91d7856d9d9"
+checksum = "d7645db8724ea3b82fdccfb67e1b0f637c9d8ab0e7ef29d84884d8d8d73f805d"
dependencies = [
"arc-swap",
"aws-credential-types",
@@ -468,9 +474,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sts"
-version = "1.124.0"
+version = "1.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f8a0dd8fbde04ba58ad722113c7addb11bf94cf549b1ea0eb9ea427fb87198a"
+checksum = "03e490aa904849b38e770922faac779dd245f5cf8be81f19065acf18d276e4ad"
dependencies = [
"arc-swap",
"aws-credential-types",
@@ -494,9 +500,9 @@ dependencies = [
[[package]]
name = "aws-sigv4"
-version = "1.6.4"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f385da9b7757ed4c8214797ad663ff534fb622006143530aa80e21f8e06ff85c"
+checksum = "2312577f088c9fbf4206dfdb884cf1de9407b43e1a923cbed5237775116fc24b"
dependencies = [
"aws-credential-types",
"aws-smithy-http",
@@ -508,16 +514,16 @@ dependencies = [
"hmac 0.13.0",
"http 1.5.0",
"percent-encoding",
- "sha2 0.11.1",
+ "sha2 0.11.0",
"time",
"tracing",
]
[[package]]
name = "aws-smithy-async"
-version = "1.3.1"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de681a9df7ff79823b759ee17061076ec049cd61a8fa28ece4293d89c9f6964e"
+checksum = "f02e407fb3b54891734224b9ffac8a71fdd35f542500fa1af95754a6b2beb316"
dependencies = [
"futures-util",
"pin-project-lite",
@@ -526,9 +532,9 @@ dependencies = [
[[package]]
name = "aws-smithy-http"
-version = "0.64.3"
+version = "0.64.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19af609dd5edf8b056cd0bb001c706c8b3b1458055328b334ec13d7f46f6aaa8"
+checksum = "639b4d8f8555f24a9be649811c3eb0b4d4616f4d61daf0c32e28873bc1ea9af1"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
@@ -537,18 +543,19 @@ dependencies = [
"futures-core",
"futures-util",
"http 1.5.0",
- "http-body",
+ "http-body 1.1.0",
"http-body-util",
"percent-encoding",
"pin-project-lite",
+ "pin-utils",
"tracing",
]
[[package]]
name = "aws-smithy-json"
-version = "0.65.1"
+version = "0.63.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b45f9fc77ffe92c92c341165a01a565bd30c39ecf5aa8c0719b41719009604f9"
+checksum = "3385d469edbe8b60cc72002784652b5efca39178192aa9cc4b44c9875c6bdc18"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-schema",
@@ -566,9 +573,9 @@ dependencies = [
[[package]]
name = "aws-smithy-query"
-version = "0.64.0"
+version = "0.62.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "165f6265342a313ad6f7a1ac17a0cdba30c522039d3f41d8d6871ce8a2b34ecd"
+checksum = "f1d1d71f6562be974caa85442ecd90194c40fdb5df045f182a6c2e872ce95056"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-schema",
@@ -579,9 +586,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime"
-version = "1.16.2"
+version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cb80fb19c084da5fca9d797845735e184445e5b958599440d67529bf6ef1b64"
+checksum = "d6e302ac1d88b99652489df31abdec6ac42a2ab2ac3982ad0ac49f64dfaf28ba"
dependencies = [
"aws-smithy-async",
"aws-smithy-http",
@@ -592,18 +599,19 @@ dependencies = [
"bytes",
"fastrand",
"http 1.5.0",
- "http-body",
+ "http-body 1.1.0",
"http-body-util",
"pin-project-lite",
+ "pin-utils",
"tokio",
"tracing",
]
[[package]]
name = "aws-smithy-runtime-api"
-version = "1.19.2"
+version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c4e85cffe9944b10d576b075432bd8f98dbc081e8681cb37d5b2d87dc27bc84"
+checksum = "c0730c16f91124c6a2abb4932c77e299288b3dd9f967ea2e9ec48cc6731e87a4"
dependencies = [
"aws-smithy-async",
"aws-smithy-runtime-api-macros",
@@ -618,9 +626,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime-api-macros"
-version = "1.2.0"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce237f890a2283c9b328955c3b0918d2d5bf66a1cb6ceccd36a0047246fbc6e6"
+checksum = "221eaa237ddf1ca79b60d1372aad77e47f9c0ea5b3ce5099da8c61d027dc77b3"
dependencies = [
"proc-macro2",
"quote",
@@ -629,9 +637,9 @@ dependencies = [
[[package]]
name = "aws-smithy-schema"
-version = "1.1.2"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9543fc722ed4865ab29fb66bd7ed0bef7b029d265ba8c587144511e30006d431"
+checksum = "e8f395d93304280b64b7632fea798d177e74897fe7f063416ce627cd6fa24829"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
@@ -640,19 +648,20 @@ dependencies = [
[[package]]
name = "aws-smithy-types"
-version = "1.9.1"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf183d70774ea2cfcc101ca74a8884e4c76be8b9df65cd303f6a1175c88e8f80"
+checksum = "69bb407740a197147da48238ecc94498493c9e85445732360cec180296ca45f1"
dependencies = [
"base64-simd",
"bytes",
"bytes-utils",
"http 1.5.0",
- "http-body",
+ "http-body 1.1.0",
"http-body-util",
"itoa",
"num-integer",
"pin-project-lite",
+ "pin-utils",
"ryu",
"serde",
"time",
@@ -660,9 +669,9 @@ dependencies = [
[[package]]
name = "aws-smithy-xml"
-version = "0.64.1"
+version = "0.62.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "590fdc1a70546b2f7f2061c42eaf173fb6f26d935330d069edc04dbd8f01d924"
+checksum = "0b932c8d6dc127fc980eecd78f8694ae9b9551b69a93a7def2a199c1c0033daf"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-schema",
@@ -672,9 +681,9 @@ dependencies = [
[[package]]
name = "aws-types"
-version = "1.7.0"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ba0d970d3947bb064d2ae1d823efa186c1dbde88f88afa8f6549946e5d8deee"
+checksum = "209f3a6d82a6e9e5f94abbed94c7a26e1c052341002bf57a5fb5481f625896fc"
dependencies = [
"aws-credential-types",
"aws-smithy-async",
@@ -691,12 +700,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
-[[package]]
-name = "base16ct"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6"
-
[[package]]
name = "base64"
version = "0.21.7"
@@ -815,9 +818,9 @@ dependencies = [
[[package]]
name = "brotli"
-version = "9.1.1"
+version = "9.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "627b7cfb0578f0eafce087a90fcd40add1231900b69e4d4de2d456260a4f5d81"
+checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -826,9 +829,9 @@ dependencies = [
[[package]]
name = "brotli-decompressor"
-version = "6.2.0"
+version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34c6783f42c21c952ad7112b354f0ba2f10760e9decea5b1b60523d6e270faaf"
+checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -879,16 +882,16 @@ dependencies = [
[[package]]
name = "cached"
-version = "4.1.0"
+version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f886e989b5b6e7991606f2550e45a99e130024465ba8d67f9f9b256eb7044bf"
+checksum = "292f39b5c459538e148825347c400cb2005a618b897d0e6be183bad677d460b7"
dependencies = [
"ahash",
"async-lock",
"cached_proc_macro",
"cached_proc_macro_types",
"hashbrown 0.17.1",
- "parking_lot 0.12.5",
+ "parking_lot",
"thiserror 2.0.21",
"web-time",
]
@@ -914,9 +917,9 @@ checksum = "f5813789573ae815c8b4be58c4428e0e7ae05f0227678ba9de332ded585b9159"
[[package]]
name = "cc"
-version = "1.10.0"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0"
+checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -985,9 +988,9 @@ checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24"
[[package]]
name = "combine"
-version = "4.6.9"
+version = "4.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92"
+checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e"
dependencies = [
"bytes",
"memchr",
@@ -995,9 +998,9 @@ dependencies = [
[[package]]
name = "compression-codecs"
-version = "0.4.46"
+version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cae36683101b2899f48bd31f8db9839437356188e3b1ff23096e73dab09ba8fe"
+checksum = "98fc98460ba0ad5317075d3632b8dfc45d0be8c4a49347c2a38272019717614a"
dependencies = [
"brotli",
"compression-core",
@@ -1009,9 +1012,9 @@ dependencies = [
[[package]]
name = "compression-core"
-version = "0.4.34"
+version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ebdda89edc534bf8d5dd2857562633b6495557e5846552ade994b99ed13c36a"
+checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414"
[[package]]
name = "concurrent-queue"
@@ -1124,12 +1127,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
-[[package]]
-name = "cpubits"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
-
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -1160,9 +1157,9 @@ dependencies = [
[[package]]
name = "crc32fast"
-version = "1.5.3"
+version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055"
+checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
dependencies = [
"cfg-if",
]
@@ -1227,22 +1224,6 @@ dependencies = [
"zeroize",
]
-[[package]]
-name = "crypto-bigint"
-version = "0.7.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c035cb64cba03dad2fb921f3e218785e8cf9a41790da5eb3a1400f62fa2be11"
-dependencies = [
- "cpubits",
- "ctutils",
- "getrandom 0.4.3",
- "hybrid-array",
- "num-traits",
- "rand_core 0.10.1",
- "subtle",
- "zeroize",
-]
-
[[package]]
name = "crypto-common"
version = "0.1.6"
@@ -1259,19 +1240,16 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
dependencies = [
- "getrandom 0.4.3",
"hybrid-array",
- "rand_core 0.10.1",
]
[[package]]
name = "ctutils"
-version = "0.4.7"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2376ffd6f96d8a9db59875b4b6b0237d6900b5baf64279714e05493ce64828a"
+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
dependencies = [
"cmov",
- "subtle",
]
[[package]]
@@ -1284,23 +1262,7 @@ dependencies = [
"cpufeatures 0.2.17",
"curve25519-dalek-derive",
"digest 0.10.7",
- "fiat-crypto 0.2.9",
- "rustc_version",
- "subtle",
- "zeroize",
-]
-
-[[package]]
-name = "curve25519-dalek"
-version = "5.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23"
-dependencies = [
- "cfg-if",
- "cpufeatures 0.3.1",
- "curve25519-dalek-derive",
- "digest 0.11.3",
- "fiat-crypto 0.3.0",
+ "fiat-crypto",
"rustc_version",
"subtle",
"zeroize",
@@ -1385,7 +1347,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
- "syn 3.0.9",
+ "syn 3.0.6",
]
[[package]]
@@ -1418,7 +1380,7 @@ checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
dependencies = [
"darling_core 0.24.1",
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
]
[[package]]
@@ -1430,9 +1392,9 @@ dependencies = [
"cfg-if",
"crossbeam-utils",
"hashbrown 0.14.5",
- "lock_api 0.4.14",
+ "lock_api",
"once_cell",
- "parking_lot_core 0.9.12",
+ "parking_lot_core",
]
[[package]]
@@ -1449,9 +1411,9 @@ checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
[[package]]
name = "defmt"
-version = "1.2.0"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4a0795ac740cb54d7b3775c9c17345f6aaecf884c01733b39d6017738e0e3e1"
+checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
dependencies = [
"bitflags 1.3.2",
"defmt-macros",
@@ -1459,21 +1421,21 @@ dependencies = [
[[package]]
name = "defmt-macros"
-version = "1.2.0"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "840bb056bd99c495c9188198a258ab787c79f306d864db3e6e8451442d6d8366"
+checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
dependencies = [
"defmt-parser",
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 2.0.119",
]
[[package]]
name = "defmt-parser"
-version = "1.1.0"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71958f69fb2de856c5dd51ac9377e986ca1aa5919f931bf5395a807277f889a0"
+checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
dependencies = [
"thiserror 2.0.21",
]
@@ -1485,18 +1447,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid 0.9.6",
- "pem-rfc7468 0.7.0",
- "zeroize",
-]
-
-[[package]]
-name = "der"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a996a108feb8b427a6e12fcf6af4aa83409b79d72c15337d7741368131ee004"
-dependencies = [
- "const-oid 0.10.2",
- "pem-rfc7468 1.0.0",
+ "pem-rfc7468",
"zeroize",
]
@@ -1516,9 +1467,9 @@ dependencies = [
[[package]]
name = "deranged"
-version = "0.5.9"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [
"serde_core",
]
@@ -1579,9 +1530,9 @@ dependencies = [
[[package]]
name = "devise"
-version = "0.5.0"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "143afb21c617f66c792ef2d75f9fb4c9dd774ff206bcc24df08a1bda294a37bc"
+checksum = "f1d90b0c4c777a2cad215e3c7be59ac7c15adf45cf76317009b7d096d46f651d"
dependencies = [
"devise_codegen",
"devise_core",
@@ -1589,9 +1540,9 @@ dependencies = [
[[package]]
name = "devise_codegen"
-version = "0.5.0"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50b3187e4283046e01d16d724ed05326427aa9c1c3a376548ef31820e6eb9717"
+checksum = "71b28680d8be17a570a2334922518be6adc3f58ecc880cbb404eaeb8624fd867"
dependencies = [
"devise_core",
"quote",
@@ -1599,22 +1550,22 @@ dependencies = [
[[package]]
name = "devise_core"
-version = "0.5.0"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "970e4b2fcd605ffa79715510663a9fe76b87f5483d59857399129cf047f73d27"
+checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7"
dependencies = [
"bitflags 2.13.2",
"proc-macro2",
- "proc-macro2-diagnostics 0.11.0",
+ "proc-macro2-diagnostics",
"quote",
- "syn 3.0.9",
+ "syn 2.0.119",
]
[[package]]
name = "diesel"
-version = "2.3.14"
+version = "2.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebe9f7eaef33febd60290c5a9f3b0571d03c4c04a24739cd64808ce30e65a5d3"
+checksum = "e3b934ddbdcb2abb9f9fc9c30bd47bcc5618b615eea1d334cda5fdf8ff9b072a"
dependencies = [
"bigdecimal",
"bitflags 2.13.2",
@@ -1712,7 +1663,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
]
[[package]]
@@ -1771,27 +1722,12 @@ version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
- "der 0.7.10",
+ "der",
"digest 0.10.7",
- "elliptic-curve 0.13.8",
- "rfc6979 0.4.0",
- "signature 2.2.0",
- "spki 0.7.3",
-]
-
-[[package]]
-name = "ecdsa"
-version = "0.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0"
-dependencies = [
- "der 0.8.3",
- "digest 0.11.3",
- "elliptic-curve 0.14.3",
- "rfc6979 0.6.0",
- "signature 3.0.0",
- "spki 0.8.1",
- "zeroize",
+ "elliptic-curve",
+ "rfc6979",
+ "signature",
+ "spki",
]
[[package]]
@@ -1800,18 +1736,8 @@ version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
- "pkcs8 0.10.2",
- "signature 2.2.0",
-]
-
-[[package]]
-name = "ed25519"
-version = "3.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a"
-dependencies = [
- "pkcs8 0.11.0",
- "signature 3.0.0",
+ "pkcs8",
+ "signature",
]
[[package]]
@@ -1820,34 +1746,19 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
dependencies = [
- "curve25519-dalek 4.1.3",
- "ed25519 2.2.3",
+ "curve25519-dalek",
+ "ed25519",
"serde",
"sha2 0.10.9",
"subtle",
"zeroize",
]
-[[package]]
-name = "ed25519-dalek"
-version = "3.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de"
-dependencies = [
- "curve25519-dalek 5.0.0",
- "ed25519 3.0.0",
- "serde",
- "sha2 0.11.1",
- "signature 3.0.0",
- "subtle",
- "zeroize",
-]
-
[[package]]
name = "either"
-version = "1.19.0"
+version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
+checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
[[package]]
name = "elliptic-curve"
@@ -1855,38 +1766,17 @@ version = "0.13.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
- "base16ct 0.2.0",
- "crypto-bigint 0.5.5",
+ "base16ct",
+ "crypto-bigint",
"digest 0.10.7",
- "ff 0.13.1",
+ "ff",
"generic-array",
- "group 0.13.0",
+ "group",
"hkdf",
- "pem-rfc7468 0.7.0",
- "pkcs8 0.10.2",
+ "pem-rfc7468",
+ "pkcs8",
"rand_core 0.6.4",
- "sec1 0.7.3",
- "subtle",
- "zeroize",
-]
-
-[[package]]
-name = "elliptic-curve"
-version = "0.14.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "900f37e7e18d74a2347389af6cd69e3a30d5cc9e697f40004865679aa75e9c7c"
-dependencies = [
- "base16ct 1.0.0",
- "crypto-bigint 0.7.10",
- "crypto-common 0.2.2",
- "digest 0.11.3",
- "ff 0.14.0",
- "group 0.14.0",
- "hybrid-array",
- "pem-rfc7468 1.0.0",
- "pkcs8 0.11.0",
- "rand_core 0.10.1",
- "sec1 0.8.1",
+ "sec1",
"subtle",
"zeroize",
]
@@ -1912,9 +1802,9 @@ dependencies = [
[[package]]
name = "encoding_rs"
-version = "0.8.44"
+version = "0.8.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89253175e704ab869b04f9db224d38630e0211a53841a79181deae0889e169f5"
+checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679"
dependencies = [
"cfg-if",
"core_detect",
@@ -1926,9 +1816,9 @@ dependencies = [
[[package]]
name = "equivalent"
-version = "1.0.3"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
@@ -1994,36 +1884,20 @@ dependencies = [
"subtle",
]
-[[package]]
-name = "ff"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f"
-dependencies = [
- "rand_core 0.10.1",
- "subtle",
-]
-
[[package]]
name = "fiat-crypto"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
-[[package]]
-name = "fiat-crypto"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
-
[[package]]
name = "figment"
version = "0.10.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3"
dependencies = [
- "atomic",
- "pear 0.2.9",
+ "atomic 0.6.1",
+ "pear",
"serde",
"toml 0.8.23",
"uncased",
@@ -2032,9 +1906,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
-version = "0.1.15"
+version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48"
+checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
[[package]]
name = "flate2"
@@ -2152,7 +2026,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
]
[[package]]
@@ -2248,11 +2122,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
- "js-sys",
"libc",
"r-efi 6.0.0",
"rand_core 0.10.1",
- "wasm-bindgen",
]
[[package]]
@@ -2287,7 +2159,7 @@ dependencies = [
"getrandom 0.3.4",
"hashbrown 0.16.1",
"nonzero_ext",
- "parking_lot 0.12.5",
+ "parking_lot",
"portable-atomic",
"quanta",
"rand 0.9.5",
@@ -2315,27 +2187,16 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
dependencies = [
- "ff 0.13.1",
+ "ff",
"rand_core 0.6.4",
"subtle",
]
-[[package]]
-name = "group"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4"
-dependencies = [
- "ff 0.14.0",
- "rand_core 0.10.1",
- "subtle",
-]
-
[[package]]
name = "h2"
-version = "0.4.20"
+version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d29020232d6aa3fb1daca64c1127cf662cf97f254ae16c18c05b8ab635fc118"
+checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16"
dependencies = [
"atomic-waker",
"bytes",
@@ -2494,7 +2355,7 @@ dependencies = [
"moka",
"ndk-context",
"once_cell",
- "parking_lot 0.12.5",
+ "parking_lot",
"rand 0.10.3",
"resolv-conf",
"smallvec",
@@ -2544,9 +2405,12 @@ dependencies = [
[[package]]
name = "html5gum"
-version = "0.9.0"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbae3133814a10216af5242a254420a74cb0d2f7715646924f5a8e7720cd188b"
+checksum = "428502d3ec1742c35e015871aef742bc4722a9acfcb616b8ff79b519922e1c36"
+dependencies = [
+ "jetscii",
+]
[[package]]
name = "http"
@@ -2569,6 +2433,17 @@ dependencies = [
"itoa",
]
+[[package]]
+name = "http-body"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
+dependencies = [
+ "bytes",
+ "http 0.2.12",
+ "pin-project-lite",
+]
+
[[package]]
name = "http-body"
version = "1.1.0"
@@ -2588,7 +2463,7 @@ dependencies = [
"bytes",
"futures-core",
"http 1.5.0",
- "http-body",
+ "http-body 1.1.0",
"pin-project-lite",
]
@@ -2610,16 +2485,37 @@ version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
dependencies = [
- "subtle",
"typenum",
- "zeroize",
]
[[package]]
name = "hyper"
-version = "1.12.0"
+version = "0.14.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c"
+checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2 0.5.10",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43"
dependencies = [
"atomic-waker",
"bytes",
@@ -2627,9 +2523,8 @@ dependencies = [
"futures-core",
"h2",
"http 1.5.0",
- "http-body",
+ "http-body 1.1.0",
"httparse",
- "httpdate",
"itoa",
"pin-project-lite",
"smallvec",
@@ -2644,11 +2539,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53"
dependencies = [
"http 1.5.0",
- "hyper",
+ "hyper 1.11.1",
"hyper-util",
- "rustls",
+ "rustls 0.23.45",
"tokio",
- "tokio-rustls",
+ "tokio-rustls 0.26.6",
"tower-service",
]
@@ -2663,14 +2558,14 @@ dependencies = [
"futures-channel",
"futures-util",
"http 1.5.0",
- "http-body",
+ "http-body 1.1.0",
"httparse",
- "hyper",
+ "hyper 1.11.1",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite",
- "socket2",
+ "socket2 0.6.5",
"system-configuration",
"tokio",
"tower-service",
@@ -2704,9 +2599,9 @@ dependencies = [
[[package]]
name = "icu_collections"
-version = "2.4.0"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd"
+checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
dependencies = [
"displaydoc",
"potential_utf",
@@ -2718,9 +2613,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
-version = "2.4.0"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0"
+checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
dependencies = [
"displaydoc",
"litemap",
@@ -2731,9 +2626,9 @@ dependencies = [
[[package]]
name = "icu_normalizer"
-version = "2.4.0"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599"
+checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
dependencies = [
"icu_collections",
"icu_normalizer_data",
@@ -2745,15 +2640,15 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
-version = "2.4.0"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc"
+checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
[[package]]
name = "icu_properties"
-version = "2.4.0"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f"
+checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
dependencies = [
"displaydoc",
"icu_collections",
@@ -2766,15 +2661,15 @@ dependencies = [
[[package]]
name = "icu_properties_data"
-version = "2.4.0"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf"
+checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
[[package]]
name = "icu_provider"
-version = "2.4.0"
+version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514"
+checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
dependencies = [
"displaydoc",
"icu_locale_core",
@@ -2847,7 +2742,7 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222"
dependencies = [
- "socket2",
+ "socket2 0.6.5",
"widestring",
"windows-registry",
"windows-result",
@@ -2876,9 +2771,9 @@ dependencies = [
[[package]]
name = "itertools"
-version = "0.14.0"
+version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
@@ -2890,10 +2785,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
-name = "jiff"
-version = "0.2.38"
+name = "jetscii"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383"
+checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e"
+
+[[package]]
+name = "jiff"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb"
dependencies = [
"defmt",
"jiff-core",
@@ -2920,9 +2821,9 @@ dependencies = [
[[package]]
name = "jiff-static"
-version = "0.2.38"
+version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c"
+checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212"
dependencies = [
"jiff-core",
"proc-macro2",
@@ -2932,9 +2833,9 @@ dependencies = [
[[package]]
name = "jiff-tzdb"
-version = "0.1.9"
+version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa8377070c6bae868759445e5a77f66d84f0b72f3a054bfb00e6d038b8282da7"
+checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e"
[[package]]
name = "jiff-tzdb-platform"
@@ -3017,9 +2918,9 @@ dependencies = [
[[package]]
name = "js-sys"
-version = "0.3.107"
+version = "0.3.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a"
+checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
dependencies = [
"cfg-if",
"futures-util",
@@ -3033,19 +2934,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e75fe14a82d81e5f5af639997db37d8b96045938a7ac6ab18cdbe1c7467e05e1"
dependencies = [
"base64 0.22.1",
- "ed25519-dalek 2.2.0",
+ "ed25519-dalek",
"getrandom 0.2.17",
"hmac 0.12.1",
"js-sys",
- "p256 0.13.2",
- "p384 0.13.1",
+ "p256",
+ "p384",
"pem",
"rand 0.8.8",
"rsa",
"serde",
"serde_json",
"sha2 0.10.9",
- "signature 2.2.0",
+ "signature",
"simple_asn1",
"zeroize",
]
@@ -3098,21 +2999,21 @@ dependencies = [
"nom 8.0.0",
"percent-encoding",
"quoted_printable",
- "rustls",
+ "rustls 0.23.45",
"rustls-native-certs",
"serde",
- "socket2",
+ "socket2 0.6.5",
"tokio",
- "tokio-rustls",
+ "tokio-rustls 0.26.6",
"tracing",
"url",
]
[[package]]
name = "libc"
-version = "0.2.191"
+version = "0.2.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
+checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
[[package]]
name = "libm"
@@ -3154,9 +3055,9 @@ checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
[[package]]
name = "litrs"
-version = "1.0.1"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4744e383959f0db86ede514b809b1c53251889093803c05267acc7d4e7030d70"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "lock_api"
@@ -3167,20 +3068,11 @@ dependencies = [
"scopeguard",
]
-[[package]]
-name = "lock_api"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65d91b22da179613dcf4d6ec8349563f52a363f6849e03b00dd0a185ec4a59af"
-dependencies = [
- "scopeguard",
-]
-
[[package]]
name = "log"
-version = "0.4.35"
+version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
dependencies = [
"value-bag",
]
@@ -3205,7 +3097,7 @@ name = "macros"
version = "0.1.0"
dependencies = [
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
]
[[package]]
@@ -3245,14 +3137,13 @@ dependencies = [
[[package]]
name = "migrations_macros"
-version = "2.3.1"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9423d6affd681fb4d25d35885d0d0262c52f8ade16e96f87c064c442e5aa5d47"
+checksum = "36fc5ac76be324cfd2d3f2cf0fdf5d5d3c4f14ed8aaebadb09e304ba42282703"
dependencies = [
"migrations_internals",
"proc-macro2",
"quote",
- "syn 3.0.9",
]
[[package]]
@@ -3310,7 +3201,7 @@ dependencies = [
"equivalent",
"event-listener 5.4.2",
"futures-util",
- "parking_lot 0.12.5",
+ "parking_lot",
"portable-atomic",
"smallvec",
"tagptr",
@@ -3445,7 +3336,7 @@ checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
]
[[package]]
@@ -3469,9 +3360,9 @@ dependencies = [
[[package]]
name = "num-modular"
-version = "0.6.5"
+version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd8e500409e6cd603b03e477c26a6caecdc27ac58979a53e881c75eafc079f44"
+checksum = "26ac76200f74e658124f95fa63e1a82b2fd2181c5b2fdde80b3d89d2d3f905e7"
[[package]]
name = "num-order"
@@ -3504,9 +3395,9 @@ dependencies = [
[[package]]
name = "num_threads"
-version = "0.1.8"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cabcc654086861f9e1bc5115258de62ef92c79b20f4615a1b0cc57c4e82027d4"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
@@ -3551,9 +3442,9 @@ dependencies = [
[[package]]
name = "opendal"
-version = "0.59.5"
+version = "0.59.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46081824c74bac9f68f0bc4408077f77b0e315c006514719c97af1c2227ac2d2"
+checksum = "3cbd193aec6d62efaff87611dfae52c132aed650bb3cb0c684c0a9394f09a42a"
dependencies = [
"opendal-core",
"opendal-service-fs",
@@ -3562,9 +3453,9 @@ dependencies = [
[[package]]
name = "opendal-core"
-version = "0.59.5"
+version = "0.59.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33d5a635dfea36a7a0c3bbb2ca34c97af17f98dd69b0cdf31a1d436a346e0a12"
+checksum = "54b5555d753dd6a6383dc0891254b72f3cdc40c011ba0372b560e0ae5aa29f5c"
dependencies = [
"anyhow",
"asyncband",
@@ -3576,7 +3467,7 @@ dependencies = [
"log",
"md-5",
"percent-encoding",
- "quick-xml",
+ "quick-xml 0.41.0",
"reqsign-core",
"serde",
"serde_json",
@@ -3588,23 +3479,23 @@ dependencies = [
[[package]]
name = "opendal-http-transport-reqwest"
-version = "0.59.5"
+version = "0.59.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc025e163c43993e30cf1829a30b791934e5b0f7cd4d7e03acc902a9efb7e8f5"
+checksum = "4d6588678f3040a3eab1819364dcbc6a746a8286d1ceb23a70a6614faa01dc6c"
dependencies = [
"bytes",
"futures",
"http 1.5.0",
- "http-body",
+ "http-body 1.1.0",
"opendal-core",
"reqwest",
]
[[package]]
name = "opendal-service-fs"
-version = "0.59.5"
+version = "0.59.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70768b8f5af6996a494e784a1dc2a76b5cf890c422aff31ad094319bde98feb7"
+checksum = "ec5c16fe91b77c0364f66546a1c32b2999e9c3c3c75c2e908a282268e0890bac"
dependencies = [
"bytes",
"log",
@@ -3616,9 +3507,9 @@ dependencies = [
[[package]]
name = "opendal-service-s3"
-version = "0.59.5"
+version = "0.59.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0932321e27c712a30ef72db56b12f31e41e9f0d2d3841d63a91bc8075cd8f0ef"
+checksum = "1d83e716c25a81fe2ca701894b8e14b9966acca3812af250e50132accb19da02"
dependencies = [
"base64 0.23.1",
"bytes",
@@ -3627,7 +3518,7 @@ dependencies = [
"log",
"md-5",
"opendal-core",
- "quick-xml",
+ "quick-xml 0.41.0",
"reqsign-aws-v4",
"reqsign-core",
"reqsign-file-read-tokio",
@@ -3637,22 +3528,21 @@ dependencies = [
[[package]]
name = "openidconnect"
-version = "4.1.0"
+version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df0d098a59353679624d406825940139e2b2d554a52b64c9fb1b1bc42a11fe5c"
+checksum = "0d8c6709ba2ea764bbed26bce1adf3c10517113ddea6f2d4196e4851757ef2b2"
dependencies = [
- "base64 0.22.1",
+ "base64 0.21.7",
"chrono",
"dyn-clone",
- "ed25519-dalek 3.0.0",
- "getrandom 0.4.3",
+ "ed25519-dalek",
"hmac 0.12.1",
"http 1.5.0",
"itertools",
"log",
"oauth2",
- "p256 0.14.0",
- "p384 0.14.0",
+ "p256",
+ "p384",
"rand 0.8.8",
"rsa",
"serde",
@@ -3694,9 +3584,9 @@ dependencies = [
[[package]]
name = "openssl-probe"
-version = "0.2.2"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e036b411754132fd6b74e94b74e9a13c5cf606fa93d1885c35860d3060820b1"
+checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "openssl-src"
@@ -3751,51 +3641,24 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
dependencies = [
- "ecdsa 0.16.9",
- "elliptic-curve 0.13.8",
- "primeorder 0.13.6",
+ "ecdsa",
+ "elliptic-curve",
+ "primeorder",
"sha2 0.10.9",
]
-[[package]]
-name = "p256"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a"
-dependencies = [
- "ecdsa 0.17.0",
- "elliptic-curve 0.14.3",
- "primefield",
- "primeorder 0.14.0",
- "sha2 0.11.1",
-]
-
[[package]]
name = "p384"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
dependencies = [
- "ecdsa 0.16.9",
- "elliptic-curve 0.13.8",
- "primeorder 0.13.6",
+ "ecdsa",
+ "elliptic-curve",
+ "primeorder",
"sha2 0.10.9",
]
-[[package]]
-name = "p384"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d17b851e6b3e378ab4ecb07fa2ed23f4d15f075735f8fec9fa1e7bdce5f8301f"
-dependencies = [
- "ecdsa 0.17.0",
- "elliptic-curve 0.14.3",
- "fiat-crypto 0.3.0",
- "primefield",
- "primeorder 0.14.0",
- "sha2 0.11.1",
-]
-
[[package]]
name = "parking"
version = "2.2.1"
@@ -3808,18 +3671,8 @@ version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
- "lock_api 0.4.14",
- "parking_lot_core 0.9.12",
-]
-
-[[package]]
-name = "parking_lot"
-version = "0.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9cf937ca4e1d0b7578784559063422fc39ae7c5a00f02471f2614d5486fd226"
-dependencies = [
- "lock_api 0.5.0",
- "parking_lot_core 0.10.0",
+ "lock_api",
+ "parking_lot_core",
]
[[package]]
@@ -3830,19 +3683,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
- "redox_syscall 0.5.18",
- "smallvec",
- "windows-link",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cfff060068cc18aa7815010fcaf80c68c7fb9e85fc7f5c0f9bf142c21d19826"
-dependencies = [
- "libc",
- "redox_syscall 0.9.5",
+ "redox_syscall",
"smallvec",
"windows-link",
]
@@ -3876,18 +3717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467"
dependencies = [
"inlinable_string",
- "pear_codegen 0.2.9",
- "yansi",
-]
-
-[[package]]
-name = "pear"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bdc582f5ece4663ba62cb9cc81b9f86aa23884ee92cddb1d5e75a94991119cb"
-dependencies = [
- "inlinable_string",
- "pear_codegen 0.3.0",
+ "pear_codegen",
"yansi",
]
@@ -3898,23 +3728,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147"
dependencies = [
"proc-macro2",
- "proc-macro2-diagnostics 0.10.1",
+ "proc-macro2-diagnostics",
"quote",
"syn 2.0.119",
]
-[[package]]
-name = "pear_codegen"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b4262794e70dac544a759fd50088c6331e7a2ba4fc204b4b6e29c9b5257f9c4"
-dependencies = [
- "proc-macro2",
- "proc-macro2-diagnostics 0.11.0",
- "quote",
- "syn 3.0.9",
-]
-
[[package]]
name = "pem"
version = "3.0.6"
@@ -3934,15 +3752,6 @@ dependencies = [
"base64ct",
]
-[[package]]
-name = "pem-rfc7468"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9"
-dependencies = [
- "base64ct",
-]
-
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -3951,20 +3760,19 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pest"
-version = "2.9.5"
+version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8db75153506001cb36ed924f08cadc862eb3f8c4d0d534139f29074769d7660"
+checksum = "45d3aca230fad2e6f6317ca0a72724338c4960cb97168a85cdee66df4a9a21a8"
dependencies = [
"memchr",
- "pest_stack_guard",
"ucd-trie",
]
[[package]]
name = "pest_derive"
-version = "2.9.5"
+version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20aa17e11fb35664b67d5214a0afac57a360d231b6ec53d6eba0700b27d933b4"
+checksum = "284b60557f2c4a2e72ad3f2d34d42685a2fa4a6a61d0d2a10c0ae2a5e916c2cf"
dependencies = [
"pest",
"pest_generator",
@@ -3972,36 +3780,26 @@ dependencies = [
[[package]]
name = "pest_generator"
-version = "2.9.5"
+version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "626ec5afccf44ccbfeb2b232316510bb55b4bf34d891afcb0fb07d2b17d7fe9d"
+checksum = "1d9d1f08a115309ee99268cf85e5228e0e56aa9caf8841ec12866b6be07c3109"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 2.0.119",
]
[[package]]
name = "pest_meta"
-version = "2.9.5"
+version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3f45b6caa65c1b08186832745159c2529994146340a7bf44168095469720f5d"
+checksum = "ed93ba1a9ffcca32130a5188701c81c0c49cf00d4b7c5007d5148951d743adcb"
dependencies = [
"pest",
]
-[[package]]
-name = "pest_stack_guard"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97e6cd2217057fa86b6eaa512917e58089d875b957a31e35e7ab729977e0a5f2"
-dependencies = [
- "psm",
- "stacker",
-]
-
[[package]]
name = "phc"
version = "0.6.1"
@@ -4108,9 +3906,9 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
dependencies = [
- "der 0.7.10",
- "pkcs8 0.10.2",
- "spki 0.7.3",
+ "der",
+ "pkcs8",
+ "spki",
]
[[package]]
@@ -4119,18 +3917,8 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
- "der 0.7.10",
- "spki 0.7.3",
-]
-
-[[package]]
-name = "pkcs8"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
-dependencies = [
- "der 0.8.3",
- "spki 0.8.1",
+ "der",
+ "spki",
]
[[package]]
@@ -4194,9 +3982,9 @@ dependencies = [
[[package]]
name = "pq-sys"
-version = "0.7.8"
+version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "297a95d6f2378891f949d9556723e15140e50d29eac07458dabcee8688995b1d"
+checksum = "9152654059e4fb5b66a4052acf3ee43a7a7132332e44cdb72a81ee93bd038950"
dependencies = [
"libc",
"pkg-config",
@@ -4214,40 +4002,13 @@ dependencies = [
"num-traits",
]
-[[package]]
-name = "primefield"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4"
-dependencies = [
- "crypto-bigint 0.7.10",
- "crypto-common 0.2.2",
- "ff 0.14.0",
- "rand_core 0.10.1",
- "subtle",
- "zeroize",
-]
-
[[package]]
name = "primeorder"
version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
dependencies = [
- "elliptic-curve 0.13.8",
-]
-
-[[package]]
-name = "primeorder"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06"
-dependencies = [
- "elliptic-curve 0.14.3",
- "once_cell",
- "primefield",
- "serdect",
- "wnaf",
+ "elliptic-curve",
]
[[package]]
@@ -4256,7 +4017,7 @@ version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [
- "toml_edit 0.25.17+spec-1.1.0",
+ "toml_edit 0.25.15+spec-1.1.0",
]
[[package]]
@@ -4281,33 +4042,12 @@ dependencies = [
"yansi",
]
-[[package]]
-name = "proc-macro2-diagnostics"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df921e1ef974ebc8dd9effdb0c037a96928ae8cbf871438e02d6fd67204b595e"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 3.0.9",
- "version_check",
-]
-
[[package]]
name = "psl-types"
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
-[[package]]
-name = "psm"
-version = "0.1.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810"
-dependencies = [
- "cc",
-]
-
[[package]]
name = "publicsuffix"
version = "2.3.0"
@@ -4339,6 +4079,16 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+[[package]]
+name = "quick-xml"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
[[package]]
name = "quick-xml"
version = "0.42.0"
@@ -4383,7 +4133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93"
dependencies = [
"log",
- "parking_lot 0.12.5",
+ "parking_lot",
"scheduled-thread-pool",
]
@@ -4481,15 +4231,6 @@ dependencies = [
"bitflags 2.13.2",
]
-[[package]]
-name = "redox_syscall"
-version = "0.9.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "580c875845b41ecb1564ad1ae4703044cef774145e3d7a94dc2b3fa7c97868fd"
-dependencies = [
- "bitflags 2.13.2",
-]
-
[[package]]
name = "ref-cast"
version = "1.0.27"
@@ -4507,15 +4248,9 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
]
-[[package]]
-name = "ref-swap"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09c30c54dffee5b40af088d5d50aa3455c91a0127164b51f0215efc4cb28fb3c"
-
[[package]]
name = "regex"
version = "1.13.1"
@@ -4574,7 +4309,7 @@ dependencies = [
"http 1.5.0",
"log",
"percent-encoding",
- "quick-xml",
+ "quick-xml 0.42.0",
"reqsign-core",
"rust-ini",
"serde",
@@ -4592,7 +4327,7 @@ dependencies = [
"bytes",
"http 1.5.0",
"log",
- "quick-xml",
+ "quick-xml 0.42.0",
"reqsign-aws-core",
"reqsign-core",
"serde",
@@ -4615,7 +4350,7 @@ dependencies = [
"log",
"percent-encoding",
"sha1 0.11.0",
- "sha2 0.11.1",
+ "sha2 0.11.0",
"windows-sys 0.61.2",
]
@@ -4645,9 +4380,9 @@ dependencies = [
"futures-util",
"h2",
"http 1.5.0",
- "http-body",
+ "http-body 1.1.0",
"http-body-util",
- "hyper",
+ "hyper 1.11.1",
"hyper-rustls",
"hyper-util",
"js-sys",
@@ -4655,7 +4390,7 @@ dependencies = [
"mime",
"percent-encoding",
"pin-project-lite",
- "rustls",
+ "rustls 0.23.45",
"rustls-pki-types",
"rustls-platform-verifier",
"serde",
@@ -4663,7 +4398,7 @@ dependencies = [
"serde_urlencoded",
"sync_wrapper",
"tokio",
- "tokio-rustls",
+ "tokio-rustls 0.26.6",
"tokio-util",
"tower",
"tower-http",
@@ -4691,16 +4426,6 @@ dependencies = [
"subtle",
]
-[[package]]
-name = "rfc6979"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b"
-dependencies = [
- "crypto-bigint 0.7.10",
- "hmac 0.13.0",
-]
-
[[package]]
name = "ring"
version = "0.17.14"
@@ -4735,28 +4460,27 @@ dependencies = [
[[package]]
name = "rocket"
-version = "0.5.2"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c17431360eb641a861453b57107ee1b425a799f49ec1b0fe9b8a45fe437447ce"
+checksum = "a516907296a31df7dc04310e7043b61d71954d703b603cc6867a026d7e72d73f"
dependencies = [
"async-stream",
"async-trait",
+ "atomic 0.5.3",
"binascii",
"bytes",
"either",
"figment",
"futures",
- "http-body-util",
- "hyper-util",
"indexmap 2.14.2",
"log",
"memchr",
"multer",
"num_cpus",
- "parking_lot 0.12.5",
+ "parking_lot",
"pin-project-lite",
+ "rand 0.8.8",
"ref-cast",
- "ref-swap",
"rocket_codegen",
"rocket_http",
"serde",
@@ -4774,50 +4498,48 @@ dependencies = [
[[package]]
name = "rocket_codegen"
-version = "0.5.2"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "352bd0b773ca6f5bea6cd18efc2fd7f04fcd34c75c242dad6ef1bbd729cc619d"
+checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46"
dependencies = [
"devise",
"glob",
"indexmap 2.14.2",
"proc-macro2",
- "proc-macro2-diagnostics 0.11.0",
"quote",
"rocket_http",
- "syn 3.0.9",
+ "syn 2.0.119",
"unicode-xid",
"version_check",
]
[[package]]
name = "rocket_http"
-version = "0.5.2"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "847886ef963dff77126f110fd2b4401f5149440ab32973bef16c4a2cc83e525f"
+checksum = "e274915a20ee3065f611c044bd63c40757396b6dbc057d6046aec27f14f882b9"
dependencies = [
"cookie",
"either",
"futures",
"http 0.2.12",
- "hyper",
+ "hyper 0.14.32",
"indexmap 2.14.2",
"log",
"memchr",
- "pear 0.3.0",
+ "pear",
"percent-encoding",
"pin-project-lite",
"ref-cast",
- "rustls",
- "rustls-pki-types",
- "rustls-webpki",
+ "rustls 0.21.12",
+ "rustls-pemfile",
"serde",
"smallvec",
"stable-pattern",
"state",
"time",
"tokio",
- "tokio-rustls",
+ "tokio-rustls 0.24.1",
"uncased",
]
@@ -4854,20 +4576,21 @@ dependencies = [
"num-integer",
"num-traits",
"pkcs1",
- "pkcs8 0.10.2",
+ "pkcs8",
"rand_core 0.6.4",
- "signature 2.2.0",
- "spki 0.7.3",
+ "signature",
+ "spki",
"subtle",
"zeroize",
]
[[package]]
name = "rsqlite-vfs"
-version = "0.2.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "353b74f9810d5b085b6c42c9d86a5f0fdb795553235cdd48753b32e3809d18a7"
+checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c"
dependencies = [
+ "hashbrown 0.16.1",
"thiserror 2.0.21",
]
@@ -4911,9 +4634,9 @@ dependencies = [
[[package]]
name = "rustix"
-version = "1.1.6"
+version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5e76742e5294153d4dde1207dbec5bdd81eef6fabdc0d2118babadabfd858aa"
+checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
dependencies = [
"bitflags 2.13.2",
"errno",
@@ -4924,24 +4647,36 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.23.46"
+version = "0.21.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf"
+checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-webpki 0.101.7",
+ "sct",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
- "rustls-webpki",
+ "rustls-webpki 0.103.15",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-native-certs"
-version = "0.8.5"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4"
+checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
dependencies = [
"openssl-probe",
"rustls-pki-types",
@@ -4949,6 +4684,15 @@ dependencies = [
"security-framework",
]
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
+dependencies = [
+ "base64 0.21.7",
+]
+
[[package]]
name = "rustls-pki-types"
version = "1.15.1"
@@ -4969,10 +4713,10 @@ dependencies = [
"jni",
"log",
"once_cell",
- "rustls",
+ "rustls 0.23.45",
"rustls-native-certs",
"rustls-platform-verifier-android",
- "rustls-webpki",
+ "rustls-webpki 0.103.15",
"security-framework",
"security-framework-sys",
"webpki-root-certs",
@@ -4987,9 +4731,19 @@ checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f"
[[package]]
name = "rustls-webpki"
-version = "0.103.16"
+version = "0.101.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281"
+checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
dependencies = [
"ring",
"rustls-pki-types",
@@ -5019,20 +4773,20 @@ dependencies = [
[[package]]
name = "schannel"
-version = "0.1.30"
+version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cb23c424d8ee841bb4d813677ca237e2a610067d594fec130f87d6093495ab5"
+checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "scheduled-thread-pool"
-version = "0.2.8"
+version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f304b290c05b371b2aa1e8718242ff0394530f9334ecedcb9ae51330f37fc1ba"
+checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19"
dependencies = [
- "parking_lot 0.13.0",
+ "parking_lot",
]
[[package]]
@@ -5049,9 +4803,9 @@ dependencies = [
[[package]]
name = "schemars"
-version = "1.2.3"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab508826f74a77ca9d5aba6ff19b522583ee3eaf28a19384ff3d0e5835fadf6e"
+checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a"
dependencies = [
"dyn-clone",
"ref-cast",
@@ -5071,30 +4825,26 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+[[package]]
+name = "sct"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
[[package]]
name = "sec1"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
- "base16ct 0.2.0",
- "der 0.7.10",
+ "base16ct",
+ "der",
"generic-array",
- "pkcs8 0.10.2",
- "subtle",
- "zeroize",
-]
-
-[[package]]
-name = "sec1"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d"
-dependencies = [
- "base16ct 1.0.0",
- "ctutils",
- "der 0.8.3",
- "hybrid-array",
+ "pkcs8",
"subtle",
"zeroize",
]
@@ -5175,14 +4925,14 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
]
[[package]]
name = "serde_json"
-version = "1.0.154"
+version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
+checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"itoa",
"memchr",
@@ -5222,9 +4972,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
-version = "1.1.2"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c"
+checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [
"serde_core",
]
@@ -5255,7 +5005,7 @@ dependencies = [
"indexmap 2.14.2",
"jiff",
"schemars 0.9.0",
- "schemars 1.2.3",
+ "schemars 1.2.2",
"serde_core",
"serde_json",
"serde_with_macros",
@@ -5271,17 +5021,7 @@ dependencies = [
"darling 0.24.1",
"proc-macro2",
"quote",
- "syn 3.0.9",
-]
-
-[[package]]
-name = "serdect"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e"
-dependencies = [
- "base16ct 1.0.0",
- "serde",
+ "syn 3.0.6",
]
[[package]]
@@ -5319,9 +5059,9 @@ dependencies = [
[[package]]
name = "sha2"
-version = "0.11.1"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
dependencies = [
"cfg-if",
"cpufeatures 0.3.1",
@@ -5373,16 +5113,6 @@ dependencies = [
"rand_core 0.6.4",
]
-[[package]]
-name = "signature"
-version = "3.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5"
-dependencies = [
- "digest 0.11.3",
- "rand_core 0.10.1",
-]
-
[[package]]
name = "simd-adler32"
version = "0.3.10"
@@ -5431,9 +5161,19 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
-version = "1.16.4"
+version = "1.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8"
+checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
+
+[[package]]
+name = "socket2"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
[[package]]
name = "socket2"
@@ -5459,11 +5199,11 @@ checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3"
[[package]]
name = "spinning_top"
-version = "0.3.1"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cf02766a1eb909bc7653557c66b384e44edc3ad979e6257c6173c65ea51bc7d"
+checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300"
dependencies = [
- "lock_api 0.4.14",
+ "lock_api",
]
[[package]]
@@ -5473,27 +5213,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
- "der 0.7.10",
-]
-
-[[package]]
-name = "spki"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ef958a98b9d5da290cfc78946e9f3e61e1e62a18db0d92cac0b83cc161491a9"
-dependencies = [
- "base64ct",
- "der 0.8.3",
+ "der",
]
[[package]]
name = "sqlite-wasm-rs"
-version = "0.6.2"
+version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36bd8e29bf08189a005b2df1df99823aa0861cabe9044e5f31ed54ca292ee618"
+checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75"
dependencies = [
"cc",
+ "js-sys",
"rsqlite-vfs",
+ "wasm-bindgen",
]
[[package]]
@@ -5511,19 +5243,6 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
-[[package]]
-name = "stacker"
-version = "0.1.25"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "707f49d46706bacf8a2b00d51dace3f9de527c13eec3778f570c411f89e69967"
-dependencies = [
- "cc",
- "cfg-if",
- "libc",
- "psm",
- "windows-sys 0.61.2",
-]
-
[[package]]
name = "state"
version = "0.6.0"
@@ -5571,9 +5290,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "3.0.9"
+version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
+checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
dependencies = [
"proc-macro2",
"quote",
@@ -5608,7 +5327,7 @@ checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
]
[[package]]
@@ -5700,7 +5419,7 @@ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
]
[[package]]
@@ -5755,9 +5474,9 @@ dependencies = [
[[package]]
name = "tinystr"
-version = "0.8.5"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a"
+checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
dependencies = [
"displaydoc",
"zerovec",
@@ -5771,17 +5490,17 @@ checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
[[package]]
name = "tokio"
-version = "1.54.1"
+version = "1.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3"
+checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044"
dependencies = [
"bytes",
"libc",
"mio",
- "parking_lot 0.12.5",
+ "parking_lot",
"pin-project-lite",
"signal-hook-registry",
- "socket2",
+ "socket2 0.6.5",
"tokio-macros",
"windows-sys 0.61.2",
]
@@ -5794,7 +5513,17 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
+dependencies = [
+ "rustls 0.21.12",
+ "tokio",
]
[[package]]
@@ -5803,7 +5532,7 @@ version = "0.26.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db"
dependencies = [
- "rustls",
+ "rustls 0.23.45",
"tokio",
]
@@ -5832,9 +5561,9 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.20"
+version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b"
+checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
dependencies = [
"bytes",
"futures-core",
@@ -5864,7 +5593,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
dependencies = [
"serde_core",
- "serde_spanned 1.1.2",
+ "serde_spanned 1.1.1",
"toml_datetime 0.7.5+spec-1.1.0",
"toml_parser",
"winnow 0.7.15",
@@ -5890,9 +5619,9 @@ dependencies = [
[[package]]
name = "toml_datetime"
-version = "1.1.2+spec-1.1.0"
+version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [
"serde_core",
]
@@ -5913,21 +5642,21 @@ dependencies = [
[[package]]
name = "toml_edit"
-version = "0.25.17+spec-1.1.0"
+version = "0.25.15+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c"
+checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614"
dependencies = [
"indexmap 2.14.2",
- "toml_datetime 1.1.2+spec-1.1.0",
+ "toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"winnow 1.0.4",
]
[[package]]
name = "toml_parser"
-version = "1.1.5+spec-1.1.0"
+version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c"
+checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
dependencies = [
"winnow 1.0.4",
]
@@ -5977,7 +5706,7 @@ dependencies = [
"futures-core",
"futures-util",
"http 1.5.0",
- "http-body",
+ "http-body 1.1.0",
"http-body-util",
"pin-project-lite",
"tokio",
@@ -6120,9 +5849,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
-version = "1.0.27"
+version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
+checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]]
name = "unicode-segmentation"
@@ -6169,24 +5898,15 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8_iter"
-version = "1.1.0"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af3bd9d3e571c26fcce9d033b87ea5ab061b8d2ffe9b7a5675d810d68f03edf0"
-dependencies = [
- "utf_types",
-]
-
-[[package]]
-name = "utf_types"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a3b425286980a367278453f3b6d28cdcb134b574e5a35824a6ff5eebad50bb6"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
-version = "1.29.0"
+version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514"
+checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a"
dependencies = [
"getrandom 0.4.3",
"js-sys",
@@ -6267,7 +5987,7 @@ dependencies = [
"rocket",
"rocket_ws",
"rpassword",
- "rustls",
+ "rustls 0.23.45",
"semver",
"serde",
"serde_json",
@@ -6318,9 +6038,9 @@ dependencies = [
[[package]]
name = "want"
-version = "0.3.2"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
@@ -6342,11 +6062,12 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
-version = "0.2.130"
+version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c"
+checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
dependencies = [
"cfg-if",
+ "once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
@@ -6354,9 +6075,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.80"
+version = "0.4.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e14e0327cb5368ad60ccb22d04a9827a693c95a926d8d125e9fa39e0baec501"
+checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e"
dependencies = [
"js-sys",
"tokio",
@@ -6365,9 +6086,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.130"
+version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9"
+checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -6375,22 +6096,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.130"
+version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a"
+checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.130"
+version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e"
+checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
dependencies = [
"unicode-ident",
]
@@ -6410,9 +6131,9 @@ dependencies = [
[[package]]
name = "web-sys"
-version = "0.3.107"
+version = "0.3.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7d179596ff5ec0f6de4db64d54011bde6289cc49603d3a6e4ce3f329207ab88"
+checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -6498,9 +6219,9 @@ dependencies = [
[[package]]
name = "webpki-root-certs"
-version = "1.0.11"
+version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e49d0e65abc00c142e8f9428da991da9f1a250c39be97459901eb12b9b49f62a"
+checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
dependencies = [
"rustls-pki-types",
]
@@ -6793,18 +6514,6 @@ version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
-[[package]]
-name = "wnaf"
-version = "0.14.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "795ca18b3fdb5e62bf982199278341ddcf7ebf7d32e25e212ad05d496e95f6fa"
-dependencies = [
- "ff 0.14.0",
- "group 0.14.0",
- "hybrid-array",
- "primefield",
-]
-
[[package]]
name = "writeable"
version = "0.6.4"
@@ -6830,9 +6539,9 @@ dependencies = [
[[package]]
name = "xattr"
-version = "1.7.0"
+version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "963d6fab92d5504b971dc8bd90ca743c2575e590bc8071ea5bc571598667ae08"
+checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [
"libc",
"rustix",
@@ -6861,9 +6570,9 @@ dependencies = [
[[package]]
name = "yoke"
-version = "0.8.4"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
dependencies = [
"stable_deref_trait",
"yoke-derive",
@@ -6878,7 +6587,7 @@ checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
"synstructure 0.14.0",
]
@@ -6897,18 +6606,18 @@ dependencies = [
[[package]]
name = "zerocopy"
-version = "0.8.63"
+version = "0.8.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8"
+checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.63"
+version = "0.8.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f"
+checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82"
dependencies = [
"proc-macro2",
"quote",
@@ -6932,15 +6641,15 @@ checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
"synstructure 0.14.0",
]
[[package]]
name = "zeroize"
-version = "1.9.1"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
+checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
dependencies = [
"zeroize_derive",
]
@@ -6969,9 +6678,9 @@ dependencies = [
[[package]]
name = "zerovec"
-version = "0.11.9"
+version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40"
+checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
dependencies = [
"yoke",
"zerofrom",
@@ -6980,13 +6689,13 @@ dependencies = [
[[package]]
name = "zerovec-derive"
-version = "0.11.7"
+version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a"
+checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.9",
+ "syn 3.0.6",
]
[[package]]
@@ -7003,27 +6712,27 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
[[package]]
name = "zstd"
-version = "0.14.3"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db3ae9bf90af6e40197bef767eeefc46ad6397420fe873830bd85e5fbbfb3c70"
+checksum = "bf06bd8162af0734b344780deb55b42a2429ae430870d13fcc12f238e880fe6e"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
-version = "8.2.0"
+version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ded520f16b29e1a9b771c722221b2b0a0398c2316d9bb3c42b458a5414d7434b"
+checksum = "ae42c0555055784c70058d19ba8e275528e8a99a706684868ace5da4e716a4ab"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
-version = "2.1.2+zstd.1.5.7"
+version = "2.1.0+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "481ada9c543b2323cbb7bbdbb5249177fbf0188ed959ed2c15e4b22421c84ed3"
+checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0"
dependencies = [
"cc",
"pkg-config",
diff --git a/Cargo.toml b/Cargo.toml
index 1717ec63..764fbd53 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -66,7 +66,7 @@ syslog = "7.0.0"
macros = { path = "./macros" }
# Logging
-log = "0.4.35"
+log = "0.4.34"
fern = { version = "0.7.1", features = ["syslog-7", "reopen-1"] }
# We need the `log` feature for `tracing` to enable logging for several crates to work, like lettre or webauthn-rs
tracing = { version = "0.1.44", features = ["log"] }
@@ -80,7 +80,7 @@ num-derive = "0.5.1"
bigdecimal = "0.4.11"
# Web framework
-rocket = { version = "0.5.2", default-features = false, features = ["json", "tls"] }
+rocket = { version = "0.5.1", default-features = false, features = ["json", "tls"] }
rocket_ws = { version = "0.1.1" }
# WebSockets libraries
@@ -91,7 +91,7 @@ dashmap = "6.2.1"
# Async futures
futures = "0.3.34"
-tokio = { version = "1.54.1", features = [
+tokio = { version = "1.53.2", features = [
"fs",
"io-util",
"net",
@@ -100,14 +100,14 @@ tokio = { version = "1.54.1", features = [
"signal",
"time",
] }
-tokio-util = { version = "0.7.20", features = ["compat"] }
+tokio-util = { version = "0.7.19", features = ["compat"] }
# A generic serialization/deserialization framework
serde = { version = "1.0.229", features = ["derive"] }
-serde_json = "1.0.154"
+serde_json = "1.0.151"
# A safe, extensible ORM and Query builder
-diesel = { version = "2.3.14", features = ["chrono", "r2d2", "numeric", "64-column-tables"] }
+diesel = { version = "2.3.13", features = ["chrono", "r2d2", "numeric"] }
diesel_migrations = "2.3.2"
derive_more = { version = "2.1.1", features = [
@@ -125,11 +125,11 @@ libsqlite3-sys = { version = "0.38.2", optional = true }
# Crypto-related libraries
rand = "0.10.3"
ring = "0.17.14"
-rustls = { version = "0.23.46", features = ["ring", "std"], default-features = false }
+rustls = { version = "0.23.45", features = ["ring", "std"], default-features = false }
subtle = "2.6.1"
# UUID generation
-uuid = { version = "1.29.0", features = ["v4"] }
+uuid = { version = "1.27.0", features = ["v4"] }
# Date and time libraries
chrono = { version = "0.4.45", default-features = false, features = ["clock", "serde"] }
@@ -204,7 +204,7 @@ reqwest = { version = "0.13.5", default-features = false, features = [
hickory-resolver = "0.26.3"
# Favicon extraction libraries
-html5gum = "0.9.0"
+html5gum = "0.8.4"
regex = { version = "1.13.1", default-features = false, features = [
"perf",
"std",
@@ -215,7 +215,7 @@ bytes = "1.12.1"
svg-hush = "0.9.7"
# Cache function results (Used for version check and favicon fetching)
-cached = { version = "4.1.0", features = ["async"] }
+cached = { version = "4.0.1", features = ["async"] }
# Used for custom short lived cookie jar during favicon extraction
cookie = "0.18.2"
@@ -235,7 +235,7 @@ governor = "0.10.4"
ipnet = "2.12.2"
# OIDC for SSO
-openidconnect = { version = "4.1.0", default-features = false }
+openidconnect = { version = "4.0.1", default-features = false }
moka = { version = "0.12.16", features = ["future"] }
# Check client versions for specific features.
@@ -257,18 +257,18 @@ rpassword = "7.5.4"
grass_compiler = { version = "0.13.4", default-features = false }
# File are accessed through Apache OpenDAL
-opendal = { version = "0.59.5", default-features = false, features = ["services-fs"] }
-opendal-http-transport-reqwest = { version = "0.59.5", default-features = false, features = ["rustls-no-provider"], optional = true }
+opendal = { version = "0.59.4", default-features = false, features = ["services-fs"] }
+opendal-http-transport-reqwest = { version = "0.59.4", default-features = false, features = ["rustls-no-provider"], optional = true }
# For retrieving AWS credentials, including temporary SSO credentials
-aws-config = { version = "1.13.0", optional = true, default-features = false, features = [
+aws-config = { version = "1.12.0", optional = true, default-features = false, features = [
"behavior-version-latest",
"credentials-process",
"rt-tokio",
"sso",
] }
-aws-credential-types = { version = "1.3.1", optional = true }
-aws-smithy-runtime-api = { version = "1.19.2", optional = true }
+aws-credential-types = { version = "1.3.0", optional = true }
+aws-smithy-runtime-api = { version = "1.19.0", optional = true }
http = { version = "1.5.0", optional = true }
reqsign-aws-v4 = { version = "3.3.1", optional = true }
reqsign-core = { version = "3.3.2", optional = true }
diff --git a/docker/DockerSettings.yaml b/docker/DockerSettings.yaml
index 4ef24c9d..6ae6e9a8 100644
--- a/docker/DockerSettings.yaml
+++ b/docker/DockerSettings.yaml
@@ -1,10 +1,10 @@
---
-vault_version: "v2026.10.1"
-vault_image_digest: "sha256:7baaa7d5349742548b6c44e557c1512157b22010cdf314ccc399ffdcacb5eb52"
-# Cross Compile Docker Helper Scripts v1.10.0
+vault_version: "v2026.7.0"
+vault_image_digest: "sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c"
+# Cross Compile Docker Helper Scripts v1.9.0
# We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts
# https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags
-xx_image_digest: "sha256:40fe21f44bb8cddf1eb1226db190d0a984118f62ee02a91b77d99448d28264a6"
+xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707"
# The `rust_version` variable is extracted from `rust-toolchain.toml`
# rust_version: x.yy.z # Rust version to be used
debian_version: trixie # Debian release name to be used
diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine
index 95e8784d..6e6a79a3 100644
--- a/docker/Dockerfile.alpine
+++ b/docker/Dockerfile.alpine
@@ -19,23 +19,23 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
-# $ docker pull docker.io/vaultwarden/web-vault:v2026.10.1
-# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.10.1
-# [docker.io/vaultwarden/web-vault@sha256:7baaa7d5349742548b6c44e557c1512157b22010cdf314ccc399ffdcacb5eb52]
+# $ docker pull docker.io/vaultwarden/web-vault:v2026.7.0
+# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.7.0
+# [docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c]
#
# - Conversely, to get the tag name from the digest:
-# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:7baaa7d5349742548b6c44e557c1512157b22010cdf314ccc399ffdcacb5eb52
-# [docker.io/vaultwarden/web-vault:v2026.10.1]
+# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c
+# [docker.io/vaultwarden/web-vault:v2026.7.0]
#
-FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:7baaa7d5349742548b6c44e557c1512157b22010cdf314ccc399ffdcacb5eb52 AS vault
+FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c AS vault
########################## ALPINE BUILD IMAGES ##########################
## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64
## And for Alpine we define all build images here, they will only be loaded when actually used
-FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.99.1 AS build_amd64
-FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.99.1 AS build_arm64
-FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.99.1 AS build_armv7
-FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.99.1 AS build_armv6
+FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.99.0 AS build_amd64
+FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.99.0 AS build_arm64
+FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.99.0 AS build_armv7
+FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.99.0 AS build_armv6
########################## BUILD IMAGE ##########################
# hadolint ignore=DL3006
diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian
index 621cfafe..44d068ef 100644
--- a/docker/Dockerfile.debian
+++ b/docker/Dockerfile.debian
@@ -19,24 +19,24 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
-# $ docker pull docker.io/vaultwarden/web-vault:v2026.10.1
-# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.10.1
-# [docker.io/vaultwarden/web-vault@sha256:7baaa7d5349742548b6c44e557c1512157b22010cdf314ccc399ffdcacb5eb52]
+# $ docker pull docker.io/vaultwarden/web-vault:v2026.7.0
+# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.7.0
+# [docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c]
#
# - Conversely, to get the tag name from the digest:
-# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:7baaa7d5349742548b6c44e557c1512157b22010cdf314ccc399ffdcacb5eb52
-# [docker.io/vaultwarden/web-vault:v2026.10.1]
+# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c
+# [docker.io/vaultwarden/web-vault:v2026.7.0]
#
-FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:7baaa7d5349742548b6c44e557c1512157b22010cdf314ccc399ffdcacb5eb52 AS vault
+FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c AS vault
########################## Cross Compile Docker Helper Scripts ##########################
## We use the linux/amd64 no matter which Build Platform, since these are all bash scripts
## And these bash scripts do not have any significant difference if at all
-FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:40fe21f44bb8cddf1eb1226db190d0a984118f62ee02a91b77d99448d28264a6 AS xx
+FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707 AS xx
########################## BUILD IMAGE ##########################
# hadolint ignore=DL3006
-FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.99.1-slim-trixie AS build
+FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.99.0-slim-trixie AS build
# hadolint ignore=DL3067
COPY --from=xx / /
ARG TARGETARCH
diff --git a/docker/README.md b/docker/README.md
index 3bbc31e1..d64d5789 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -15,7 +15,6 @@ Some unsupported platforms for Debian based images. These are not built and test
- 386 (linux/386)
- ppc64le (linux/ppc64le)
- s390x (linux/s390x)
-- riscv64 (linux/riscv64)
To build these containers you need to enable QEMU binfmt support to be able to run/emulate architectures which are different then your host.
This ensures the container build process can run binaries from other architectures.
diff --git a/docker/docker-bake.hcl b/docker/docker-bake.hcl
index 30902bba..c27c39a6 100644
--- a/docker/docker-bake.hcl
+++ b/docker/docker-bake.hcl
@@ -148,12 +148,6 @@ target "debian-s390x" {
platforms = ["linux/s390x"]
tags = generate_tags("", "-s390x")
}
-
-target "debian-riscv64" {
- inherits = ["debian"]
- platforms = ["linux/riscv64"]
- tags = generate_tags("", "-riscv64")
-}
// ==== End of unsupported Debian architecture targets ===
// A Group to build all platforms individually for local testing
diff --git a/macros/Cargo.toml b/macros/Cargo.toml
index ee9fd206..b397fbe1 100644
--- a/macros/Cargo.toml
+++ b/macros/Cargo.toml
@@ -14,7 +14,7 @@ proc-macro = true
[dependencies]
quote = "1.0.47"
-syn = "3.0.9"
+syn = "3.0.6"
[lints]
workspace = true
diff --git a/migrations/mysql/2026-10-07-120000_add_org_policy_revision_date/down.sql b/migrations/mysql/2026-10-07-120000_add_org_policy_revision_date/down.sql
deleted file mode 100644
index 27c33623..00000000
--- a/migrations/mysql/2026-10-07-120000_add_org_policy_revision_date/down.sql
+++ /dev/null
@@ -1 +0,0 @@
-ALTER TABLE org_policies DROP COLUMN revision_date;
diff --git a/migrations/mysql/2026-10-07-120000_add_org_policy_revision_date/up.sql b/migrations/mysql/2026-10-07-120000_add_org_policy_revision_date/up.sql
deleted file mode 100644
index 97a84117..00000000
--- a/migrations/mysql/2026-10-07-120000_add_org_policy_revision_date/up.sql
+++ /dev/null
@@ -1,7 +0,0 @@
--- DATETIME (not TIMESTAMP) to match this repo's convention for revision_date
--- columns elsewhere, and to avoid MySQL's implicit session-timezone
--- conversion and 2038 range limit on TIMESTAMP.
-ALTER TABLE org_policies
-ADD COLUMN revision_date DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00';
-
-UPDATE org_policies SET revision_date = UTC_TIMESTAMP();
diff --git a/migrations/mysql/2026-10-08-120000_cipher_data_longtext/down.sql b/migrations/mysql/2026-10-08-120000_cipher_data_longtext/down.sql
deleted file mode 100644
index e69de29b..00000000
diff --git a/migrations/mysql/2026-10-08-120000_cipher_data_longtext/up.sql b/migrations/mysql/2026-10-08-120000_cipher_data_longtext/up.sql
deleted file mode 100644
index 09abbaee..00000000
--- a/migrations/mysql/2026-10-08-120000_cipher_data_longtext/up.sql
+++ /dev/null
@@ -1,3 +0,0 @@
--- A blob-encrypted cipher keeps all of its content in `data`, up to 500,000 characters, so the
--- 64 KiB of TEXT is too small. Like upstream, which uses LONGTEXT.
-ALTER TABLE ciphers MODIFY data LONGTEXT NOT NULL;
diff --git a/migrations/mysql/2026-10-09-120000_user_crypto_v2/down.sql b/migrations/mysql/2026-10-09-120000_user_crypto_v2/down.sql
deleted file mode 100644
index 49700440..00000000
--- a/migrations/mysql/2026-10-09-120000_user_crypto_v2/down.sql
+++ /dev/null
@@ -1,5 +0,0 @@
-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;
diff --git a/migrations/mysql/2026-10-09-120000_user_crypto_v2/up.sql b/migrations/mysql/2026-10-09-120000_user_crypto_v2/up.sql
deleted file mode 100644
index 73edfa6a..00000000
--- a/migrations/mysql/2026-10-09-120000_user_crypto_v2/up.sql
+++ /dev/null
@@ -1,14 +0,0 @@
-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;
-
-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
-);
diff --git a/migrations/mysql/2026-10-10-120000_add_custom_role_permissions/down.sql b/migrations/mysql/2026-10-10-120000_add_custom_role_permissions/down.sql
deleted file mode 100644
index 85ebcbfb..00000000
--- a/migrations/mysql/2026-10-10-120000_add_custom_role_permissions/down.sql
+++ /dev/null
@@ -1,22 +0,0 @@
--- Dropping the permissions would silently take them away from the Custom members, who would also keep a role (4) the
--- versions before this one don't know. So while there are any, this fails on the duplicate key below, before anything
--- is dropped: change their role first, or restore the backup taken before the upgrade.
--- A failed attempt leaves the temporary table on this connection. Clear it before retrying.
-DROP TEMPORARY TABLE IF EXISTS custom_role_downgrade;
-CREATE TEMPORARY TABLE custom_role_downgrade (blocked_by VARCHAR(255) NOT NULL PRIMARY KEY);
-INSERT INTO custom_role_downgrade VALUES ('Custom members: change their role first, or restore the backup taken before the upgrade');
-INSERT INTO custom_role_downgrade
- SELECT 'Custom members: change their role first, or restore the backup taken before the upgrade'
- FROM users_organizations WHERE atype = 4 LIMIT 1;
-DROP TEMPORARY TABLE custom_role_downgrade;
-
-ALTER TABLE users_organizations
- DROP COLUMN manage_users,
- DROP COLUMN manage_groups,
- DROP COLUMN manage_policies,
- DROP COLUMN create_new_collections,
- DROP COLUMN edit_any_collection,
- DROP COLUMN delete_any_collection,
- DROP COLUMN access_event_logs,
- DROP COLUMN access_import_export,
- DROP COLUMN access_reports;
diff --git a/migrations/mysql/2026-10-10-120000_add_custom_role_permissions/up.sql b/migrations/mysql/2026-10-10-120000_add_custom_role_permissions/up.sql
deleted file mode 100644
index d28b0d64..00000000
--- a/migrations/mysql/2026-10-10-120000_add_custom_role_permissions/up.sql
+++ /dev/null
@@ -1,13 +0,0 @@
--- The granular permissions of the Custom member role. They only take effect on a Custom membership
--- (atype 4) while EXPERIMENTAL_CUSTOM_ROLES is enabled, and every existing membership starts without any.
--- A single statement, so an interrupted upgrade cannot leave only some of the columns behind.
-ALTER TABLE users_organizations
- ADD COLUMN manage_users BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN manage_groups BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN manage_policies BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN create_new_collections BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN edit_any_collection BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN delete_any_collection BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN access_event_logs BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN access_import_export BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN access_reports BOOLEAN NOT NULL DEFAULT FALSE;
diff --git a/migrations/mysql/2026-11-02-120000_reset_user_key_id/down.sql b/migrations/mysql/2026-11-02-120000_reset_user_key_id/down.sql
deleted file mode 100644
index e69de29b..00000000
diff --git a/migrations/mysql/2026-11-02-120000_reset_user_key_id/up.sql b/migrations/mysql/2026-11-02-120000_reset_user_key_id/up.sql
deleted file mode 100644
index eeffa854..00000000
--- a/migrations/mysql/2026-11-02-120000_reset_user_key_id/up.sql
+++ /dev/null
@@ -1,4 +0,0 @@
--- Key rotations before this kept the id of the old user key, so every new
--- cipher was refused from then on. Stale ids can't be told apart, so clear
--- all of them; clients record their current key's id again when it's empty.
-UPDATE users SET key_id = NULL;
diff --git a/migrations/mysql/2026-11-04-120000_add_v2_upgrade_token/down.sql b/migrations/mysql/2026-11-04-120000_add_v2_upgrade_token/down.sql
deleted file mode 100644
index 89e12c15..00000000
--- a/migrations/mysql/2026-11-04-120000_add_v2_upgrade_token/down.sql
+++ /dev/null
@@ -1,2 +0,0 @@
-ALTER TABLE users DROP COLUMN v2_upgrade_token;
-ALTER TABLE users_organizations DROP COLUMN v2_upgrade_token;
diff --git a/migrations/mysql/2026-11-04-120000_add_v2_upgrade_token/up.sql b/migrations/mysql/2026-11-04-120000_add_v2_upgrade_token/up.sql
deleted file mode 100644
index 45c58491..00000000
--- a/migrations/mysql/2026-11-04-120000_add_v2_upgrade_token/up.sql
+++ /dev/null
@@ -1,2 +0,0 @@
-ALTER TABLE users ADD COLUMN v2_upgrade_token TEXT;
-ALTER TABLE users_organizations ADD COLUMN v2_upgrade_token TEXT;
diff --git a/migrations/postgresql/2026-10-07-120000_add_org_policy_revision_date/down.sql b/migrations/postgresql/2026-10-07-120000_add_org_policy_revision_date/down.sql
deleted file mode 100644
index 27c33623..00000000
--- a/migrations/postgresql/2026-10-07-120000_add_org_policy_revision_date/down.sql
+++ /dev/null
@@ -1 +0,0 @@
-ALTER TABLE org_policies DROP COLUMN revision_date;
diff --git a/migrations/postgresql/2026-10-07-120000_add_org_policy_revision_date/up.sql b/migrations/postgresql/2026-10-07-120000_add_org_policy_revision_date/up.sql
deleted file mode 100644
index 44229de2..00000000
--- a/migrations/postgresql/2026-10-07-120000_add_org_policy_revision_date/up.sql
+++ /dev/null
@@ -1,9 +0,0 @@
--- Backfill via `now() AT TIME ZONE 'utc'` rather than a DEFAULT of now():
--- assigning timestamptz now() into a naive TIMESTAMP column casts through
--- the server's TimeZone GUC, so a DEFAULT now() would store local wall-clock
--- instead of UTC on non-UTC servers, unlike every other naive-UTC timestamp
--- column in this schema.
-ALTER TABLE org_policies
-ADD COLUMN revision_date TIMESTAMP NOT NULL DEFAULT '1970-01-01 00:00:00';
-
-UPDATE org_policies SET revision_date = (now() AT TIME ZONE 'utc');
diff --git a/migrations/postgresql/2026-10-08-120000_cipher_data_longtext/down.sql b/migrations/postgresql/2026-10-08-120000_cipher_data_longtext/down.sql
deleted file mode 100644
index e69de29b..00000000
diff --git a/migrations/postgresql/2026-10-08-120000_cipher_data_longtext/up.sql b/migrations/postgresql/2026-10-08-120000_cipher_data_longtext/up.sql
deleted file mode 100644
index aab2e86a..00000000
--- a/migrations/postgresql/2026-10-08-120000_cipher_data_longtext/up.sql
+++ /dev/null
@@ -1,3 +0,0 @@
--- TEXT has no size limit here, only MySQL needed the change.
--- PostgreSQL rejects a migration that is only comments ("Received an empty query"), hence the no-op.
-SELECT 1;
diff --git a/migrations/postgresql/2026-10-09-120000_user_crypto_v2/down.sql b/migrations/postgresql/2026-10-09-120000_user_crypto_v2/down.sql
deleted file mode 100644
index 49700440..00000000
--- a/migrations/postgresql/2026-10-09-120000_user_crypto_v2/down.sql
+++ /dev/null
@@ -1,5 +0,0 @@
-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;
diff --git a/migrations/postgresql/2026-10-09-120000_user_crypto_v2/up.sql b/migrations/postgresql/2026-10-09-120000_user_crypto_v2/up.sql
deleted file mode 100644
index d3186ef0..00000000
--- a/migrations/postgresql/2026-10-09-120000_user_crypto_v2/up.sql
+++ /dev/null
@@ -1,13 +0,0 @@
-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;
-
-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
-);
diff --git a/migrations/postgresql/2026-10-10-120000_add_custom_role_permissions/down.sql b/migrations/postgresql/2026-10-10-120000_add_custom_role_permissions/down.sql
deleted file mode 100644
index 0cc5dd03..00000000
--- a/migrations/postgresql/2026-10-10-120000_add_custom_role_permissions/down.sql
+++ /dev/null
@@ -1,20 +0,0 @@
--- Dropping the permissions would silently take them away from the Custom members, who would also keep a role (4) the
--- versions before this one don't know. So while there are any, this fails on the duplicate key below: change their
--- role first, or restore the backup taken before the upgrade. It needs to run in one transaction, as Diesel does.
-CREATE TEMPORARY TABLE custom_role_downgrade (blocked_by VARCHAR(255) NOT NULL PRIMARY KEY);
-INSERT INTO custom_role_downgrade VALUES ('Custom members: change their role first, or restore the backup taken before the upgrade');
-INSERT INTO custom_role_downgrade
- SELECT 'Custom members: change their role first, or restore the backup taken before the upgrade'
- FROM users_organizations WHERE atype = 4 LIMIT 1;
-DROP TABLE custom_role_downgrade;
-
-ALTER TABLE users_organizations
- DROP COLUMN manage_users,
- DROP COLUMN manage_groups,
- DROP COLUMN manage_policies,
- DROP COLUMN create_new_collections,
- DROP COLUMN edit_any_collection,
- DROP COLUMN delete_any_collection,
- DROP COLUMN access_event_logs,
- DROP COLUMN access_import_export,
- DROP COLUMN access_reports;
diff --git a/migrations/postgresql/2026-10-10-120000_add_custom_role_permissions/up.sql b/migrations/postgresql/2026-10-10-120000_add_custom_role_permissions/up.sql
deleted file mode 100644
index d28b0d64..00000000
--- a/migrations/postgresql/2026-10-10-120000_add_custom_role_permissions/up.sql
+++ /dev/null
@@ -1,13 +0,0 @@
--- The granular permissions of the Custom member role. They only take effect on a Custom membership
--- (atype 4) while EXPERIMENTAL_CUSTOM_ROLES is enabled, and every existing membership starts without any.
--- A single statement, so an interrupted upgrade cannot leave only some of the columns behind.
-ALTER TABLE users_organizations
- ADD COLUMN manage_users BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN manage_groups BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN manage_policies BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN create_new_collections BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN edit_any_collection BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN delete_any_collection BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN access_event_logs BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN access_import_export BOOLEAN NOT NULL DEFAULT FALSE,
- ADD COLUMN access_reports BOOLEAN NOT NULL DEFAULT FALSE;
diff --git a/migrations/postgresql/2026-11-02-120000_reset_user_key_id/down.sql b/migrations/postgresql/2026-11-02-120000_reset_user_key_id/down.sql
deleted file mode 100644
index e69de29b..00000000
diff --git a/migrations/postgresql/2026-11-02-120000_reset_user_key_id/up.sql b/migrations/postgresql/2026-11-02-120000_reset_user_key_id/up.sql
deleted file mode 100644
index eeffa854..00000000
--- a/migrations/postgresql/2026-11-02-120000_reset_user_key_id/up.sql
+++ /dev/null
@@ -1,4 +0,0 @@
--- Key rotations before this kept the id of the old user key, so every new
--- cipher was refused from then on. Stale ids can't be told apart, so clear
--- all of them; clients record their current key's id again when it's empty.
-UPDATE users SET key_id = NULL;
diff --git a/migrations/postgresql/2026-11-04-120000_add_v2_upgrade_token/down.sql b/migrations/postgresql/2026-11-04-120000_add_v2_upgrade_token/down.sql
deleted file mode 100644
index 89e12c15..00000000
--- a/migrations/postgresql/2026-11-04-120000_add_v2_upgrade_token/down.sql
+++ /dev/null
@@ -1,2 +0,0 @@
-ALTER TABLE users DROP COLUMN v2_upgrade_token;
-ALTER TABLE users_organizations DROP COLUMN v2_upgrade_token;
diff --git a/migrations/postgresql/2026-11-04-120000_add_v2_upgrade_token/up.sql b/migrations/postgresql/2026-11-04-120000_add_v2_upgrade_token/up.sql
deleted file mode 100644
index 45c58491..00000000
--- a/migrations/postgresql/2026-11-04-120000_add_v2_upgrade_token/up.sql
+++ /dev/null
@@ -1,2 +0,0 @@
-ALTER TABLE users ADD COLUMN v2_upgrade_token TEXT;
-ALTER TABLE users_organizations ADD COLUMN v2_upgrade_token TEXT;
diff --git a/migrations/sqlite/2026-10-07-120000_add_org_policy_revision_date/down.sql b/migrations/sqlite/2026-10-07-120000_add_org_policy_revision_date/down.sql
deleted file mode 100644
index e69de29b..00000000
diff --git a/migrations/sqlite/2026-10-07-120000_add_org_policy_revision_date/up.sql b/migrations/sqlite/2026-10-07-120000_add_org_policy_revision_date/up.sql
deleted file mode 100644
index 86595711..00000000
--- a/migrations/sqlite/2026-10-07-120000_add_org_policy_revision_date/up.sql
+++ /dev/null
@@ -1,6 +0,0 @@
--- SQLite forbids non-constant defaults in ALTER TABLE ... ADD COLUMN, so add
--- the column with a constant placeholder and backfill separately.
-ALTER TABLE org_policies
-ADD COLUMN revision_date DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00';
-
-UPDATE org_policies SET revision_date = CURRENT_TIMESTAMP;
diff --git a/migrations/sqlite/2026-10-08-120000_cipher_data_longtext/down.sql b/migrations/sqlite/2026-10-08-120000_cipher_data_longtext/down.sql
deleted file mode 100644
index e69de29b..00000000
diff --git a/migrations/sqlite/2026-10-08-120000_cipher_data_longtext/up.sql b/migrations/sqlite/2026-10-08-120000_cipher_data_longtext/up.sql
deleted file mode 100644
index f18eb816..00000000
--- a/migrations/sqlite/2026-10-08-120000_cipher_data_longtext/up.sql
+++ /dev/null
@@ -1 +0,0 @@
--- TEXT has no size limit here, only MySQL needed the change
diff --git a/migrations/sqlite/2026-10-09-120000_user_crypto_v2/down.sql b/migrations/sqlite/2026-10-09-120000_user_crypto_v2/down.sql
deleted file mode 100644
index 49700440..00000000
--- a/migrations/sqlite/2026-10-09-120000_user_crypto_v2/down.sql
+++ /dev/null
@@ -1,5 +0,0 @@
-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;
diff --git a/migrations/sqlite/2026-10-09-120000_user_crypto_v2/up.sql b/migrations/sqlite/2026-10-09-120000_user_crypto_v2/up.sql
deleted file mode 100644
index c15e5b5e..00000000
--- a/migrations/sqlite/2026-10-09-120000_user_crypto_v2/up.sql
+++ /dev/null
@@ -1,13 +0,0 @@
-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;
-
-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
-);
diff --git a/migrations/sqlite/2026-10-10-120000_add_custom_role_permissions/down.sql b/migrations/sqlite/2026-10-10-120000_add_custom_role_permissions/down.sql
deleted file mode 100644
index 11c30bec..00000000
--- a/migrations/sqlite/2026-10-10-120000_add_custom_role_permissions/down.sql
+++ /dev/null
@@ -1,19 +0,0 @@
--- Dropping the permissions would silently take them away from the Custom members, who would also keep a role (4) the
--- versions before this one don't know. So while there are any, this fails on the duplicate key below: change their
--- role first, or restore the backup taken before the upgrade. It needs to run in one transaction, as Diesel does.
-CREATE TEMPORARY TABLE custom_role_downgrade (blocked_by VARCHAR(255) NOT NULL PRIMARY KEY);
-INSERT INTO custom_role_downgrade VALUES ('Custom members: change their role first, or restore the backup taken before the upgrade');
-INSERT INTO custom_role_downgrade
- SELECT 'Custom members: change their role first, or restore the backup taken before the upgrade'
- FROM users_organizations WHERE atype = 4 LIMIT 1;
-DROP TABLE custom_role_downgrade;
-
-ALTER TABLE users_organizations DROP COLUMN manage_users;
-ALTER TABLE users_organizations DROP COLUMN manage_groups;
-ALTER TABLE users_organizations DROP COLUMN manage_policies;
-ALTER TABLE users_organizations DROP COLUMN create_new_collections;
-ALTER TABLE users_organizations DROP COLUMN edit_any_collection;
-ALTER TABLE users_organizations DROP COLUMN delete_any_collection;
-ALTER TABLE users_organizations DROP COLUMN access_event_logs;
-ALTER TABLE users_organizations DROP COLUMN access_import_export;
-ALTER TABLE users_organizations DROP COLUMN access_reports;
diff --git a/migrations/sqlite/2026-10-10-120000_add_custom_role_permissions/up.sql b/migrations/sqlite/2026-10-10-120000_add_custom_role_permissions/up.sql
deleted file mode 100644
index c708c693..00000000
--- a/migrations/sqlite/2026-10-10-120000_add_custom_role_permissions/up.sql
+++ /dev/null
@@ -1,11 +0,0 @@
--- The granular permissions of the Custom member role. They only take effect on a Custom membership
--- (atype 4) while EXPERIMENTAL_CUSTOM_ROLES is enabled, and every existing membership starts without any.
-ALTER TABLE users_organizations ADD COLUMN manage_users BOOLEAN NOT NULL DEFAULT 0; -- FALSE
-ALTER TABLE users_organizations ADD COLUMN manage_groups BOOLEAN NOT NULL DEFAULT 0; -- FALSE
-ALTER TABLE users_organizations ADD COLUMN manage_policies BOOLEAN NOT NULL DEFAULT 0; -- FALSE
-ALTER TABLE users_organizations ADD COLUMN create_new_collections BOOLEAN NOT NULL DEFAULT 0; -- FALSE
-ALTER TABLE users_organizations ADD COLUMN edit_any_collection BOOLEAN NOT NULL DEFAULT 0; -- FALSE
-ALTER TABLE users_organizations ADD COLUMN delete_any_collection BOOLEAN NOT NULL DEFAULT 0; -- FALSE
-ALTER TABLE users_organizations ADD COLUMN access_event_logs BOOLEAN NOT NULL DEFAULT 0; -- FALSE
-ALTER TABLE users_organizations ADD COLUMN access_import_export BOOLEAN NOT NULL DEFAULT 0; -- FALSE
-ALTER TABLE users_organizations ADD COLUMN access_reports BOOLEAN NOT NULL DEFAULT 0; -- FALSE
diff --git a/migrations/sqlite/2026-11-02-120000_reset_user_key_id/down.sql b/migrations/sqlite/2026-11-02-120000_reset_user_key_id/down.sql
deleted file mode 100644
index e69de29b..00000000
diff --git a/migrations/sqlite/2026-11-02-120000_reset_user_key_id/up.sql b/migrations/sqlite/2026-11-02-120000_reset_user_key_id/up.sql
deleted file mode 100644
index eeffa854..00000000
--- a/migrations/sqlite/2026-11-02-120000_reset_user_key_id/up.sql
+++ /dev/null
@@ -1,4 +0,0 @@
--- Key rotations before this kept the id of the old user key, so every new
--- cipher was refused from then on. Stale ids can't be told apart, so clear
--- all of them; clients record their current key's id again when it's empty.
-UPDATE users SET key_id = NULL;
diff --git a/migrations/sqlite/2026-11-04-120000_add_v2_upgrade_token/down.sql b/migrations/sqlite/2026-11-04-120000_add_v2_upgrade_token/down.sql
deleted file mode 100644
index 89e12c15..00000000
--- a/migrations/sqlite/2026-11-04-120000_add_v2_upgrade_token/down.sql
+++ /dev/null
@@ -1,2 +0,0 @@
-ALTER TABLE users DROP COLUMN v2_upgrade_token;
-ALTER TABLE users_organizations DROP COLUMN v2_upgrade_token;
diff --git a/migrations/sqlite/2026-11-04-120000_add_v2_upgrade_token/up.sql b/migrations/sqlite/2026-11-04-120000_add_v2_upgrade_token/up.sql
deleted file mode 100644
index 45c58491..00000000
--- a/migrations/sqlite/2026-11-04-120000_add_v2_upgrade_token/up.sql
+++ /dev/null
@@ -1,2 +0,0 @@
-ALTER TABLE users ADD COLUMN v2_upgrade_token TEXT;
-ALTER TABLE users_organizations ADD COLUMN v2_upgrade_token TEXT;
diff --git a/playwright/README.md b/playwright/README.md
index cd18bebd..000725d7 100644
--- a/playwright/README.md
+++ b/playwright/README.md
@@ -2,7 +2,7 @@
This allows running integration tests using [Playwright](https://playwright.dev/).
\
-It uses its own [test.env](./test.env) with different ports to not collide with a running dev instance.
+It usse its own [test.env](/test/scenarios/test.env) with different ports to not collide with a running dev instance.
## Install
@@ -62,7 +62,7 @@ DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env run Pl
### Keep services running
-If you want you can keep the DB and Keycloak running (states are not impacted by the tests):
+If you want you can keep the DB and Keycloak runnning (states are not impacted by the tests):
```bash
PW_KEEP_SERVICE_RUNNING=true npx playwright test
diff --git a/playwright/tests/emergency.spec.ts b/playwright/tests/emergency.spec.ts
deleted file mode 100644
index 4c836986..00000000
--- a/playwright/tests/emergency.spec.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-import { test, expect, type Page, type TestInfo, Test } from '@playwright/test';
-import { MailDev } from 'maildev';
-
-import * as utils from "../global-utils";
-import { createAccount, logUser } from './setups/user';
-import { activateTOTP } from './setups/2fa';
-
-let users = utils.loadEnv();
-let mailserver;
-
-test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => {
- mailserver = new MailDev({
- port: process.env.MAILDEV_SMTP_PORT,
- web: { port: process.env.MAILDEV_HTTP_PORT },
- })
-
- await mailserver.listen();
-
- await utils.startVault(browser, testInfo, {
- SMTP_HOST: process.env.MAILDEV_HOST,
- SMTP_FROM: process.env.PW_SMTP_FROM,
- });
-});
-
-test.afterAll('Teardown', async ({}) => {
- utils.stopVault();
- if( mailserver ){
- await mailserver.close();
- }
-});
-
-async function emergencyAccess(test: Test, page: Page, user: { name: string }) {
- await test.step('Navigate', async () => {
- await page.getByRole('button', { name: user.name }).click();
- await page.getByRole('menuitem', { name: 'Account settings' }).click();
- await page.getByRole('link', { name: 'Emergency access' }).click();
- await expect(page.locator('#main-content').getByText('Emergency access', { exact: true })).toBeVisible();
- });
-}
-
-test('Emergency access', async ({ browser, page }) => {
- const context2 = await browser.newContext();
- const page2 = await context2.newPage();
-
- const mailBuffer = mailserver.buffer(users.user1.email);
- const mailBuffer2 = mailserver.buffer(users.user2.email);
-
- await createAccount(test, page, users.user1);
- await createAccount(test, page2, users.user2);
-
- await test.step('Add test2', async () => {
- await emergencyAccess(test, page, users.user1);
- await page.getByRole('button', { name: 'Add emergency contact' }).click();
- await page.getByRole('textbox', { name: 'Email * (required)' }).fill(users.user2.email);
- await page.getByRole('radio', { name: 'Takeover Can reset your' }).check();
- await page.getByRole('button', { name: 'Save' }).click();
- await utils.checkNotification(page, 'User(s) invited');
- });
-
- await test.step('Accept', async () => {
- const email = await mailBuffer2.expect((m) => m.subject === "Emergency access for " + users.user1.name);
- const pageE = await context2.newPage();
- await pageE.setContent(email.html);
- const link = await pageE.getByTestId("emergency").getAttribute("href");
- await pageE.close();
-
- await page2.goto(link);
- await utils.checkNotification(page2, 'Invitation accepted');
- });
-
- await test.step('Confirm', async () => {
- await emergencyAccess(test, page, users.user1);
- await expect(page.locator('#main-content').getByText('Needs confirmation')).toBeVisible();
- await page.getByRole('button', { name: 'Options' }).click();
- await page.getByRole('menuitem', { name: 'Confirm' }).click();
- await page.getByRole('button', { name: 'Confirm' }).click();
- await utils.checkNotification(page, users.user2.name + ' confirmed');
- await mailBuffer2.expect((m) => m.subject === "Emergency access contact for " + users.user1.name + " confirmed");
- });
-
- await test.step('Request', async () => {
- await emergencyAccess(test, page2, users.user2);
- await page2.getByRole('button', { name: 'Options' }).click();
- await page2.getByRole('menuitem', { name: 'Request Access' }).click();
- await page2.getByRole('button', { name: 'Request Access' }).click();
- await utils.checkNotification(page2, 'Emergency access requested');
- await mailBuffer.expect((m) => m.subject === "Emergency access request by " + users.user2.name + " initiated");
- });
-
- await test.step('Approved', async () => {
- await emergencyAccess(test, page, users.user1);
- await page.getByRole('button', { name: 'Options' }).click();
- await page.getByRole('menuitem', { name: 'Approve' }).click();
- await page.getByRole('button', { name: 'Approve' }).click();
- await utils.checkNotification(page, 'Emergency access approved');
- await mailBuffer2.expect((m) => m.subject === "Emergency access request for " + users.user1.name + " approved");
- });
- await activateTOTP(test, page, users.user1);
-
- let newPassword = "TotoNewPassword";
- await test.step('Access', async () => {
- await emergencyAccess(test, page2, users.user2);
- await page2.getByRole('button', { name: 'Options' }).click();
- await page2.getByRole('menuitem', { name: 'Takeover' }).click();
- await page2.getByRole('textbox', { name: 'New master password * (required)', exact: true }).fill(newPassword);
- await page2.getByRole('textbox', { name: 'Confirm new master password' }).fill(newPassword);
- await page2.getByRole('button', { name: 'Save' }).click();
- await utils.checkNotification(page2, 'Password reset for ' + users.user1.name);
- });
-
- await test.step('Changed no 2fa', async () => {
- users.user1.password = newPassword;
- await logUser(test, page, users.user1);
- });
-
- await test.step('Reject', async () => {
- await emergencyAccess(test, page, users.user1);
- await page.getByRole('button', { name: 'Options' }).click();
- await page.getByRole('menuitem', { name: 'Reject' }).click();
- await utils.checkNotification(page, 'Emergency access rejected');
- await mailBuffer2.expect((m) => m.subject === "Emergency access request to " + users.user1.name + " rejected");
- });
-
- await test.step('Remove', async () => {
- await page.getByRole('button', { name: 'Options' }).click();
- await page.getByRole('menuitem', { name: 'Remove' }).click();
- await page.getByRole('button', { name: 'Yes' }).click();
- await utils.checkNotification(page, 'Removed user ' + users.user2.name);
- await expect(page.getByText('You have not added any emergency contacts')).toBeVisible();
- });
-});
diff --git a/playwright/tests/login.spec.ts b/playwright/tests/login.spec.ts
index bc229c43..194976ea 100644
--- a/playwright/tests/login.spec.ts
+++ b/playwright/tests/login.spec.ts
@@ -3,7 +3,7 @@ import * as OTPAuth from "otpauth";
import * as utils from "../global-utils";
import { createAccount, logUser } from './setups/user';
-import { activateTOTP, disableTOTP, recoveryCodes } from './setups/2fa';
+import { activateTOTP, disableTOTP } from './setups/2fa';
let users = utils.loadEnv();
let totp;
@@ -31,42 +31,21 @@ test('Authenticator 2fa', async ({ page }) => {
await utils.logout(test, page, users.user1);
- await logUser(test, page, users.user1, { totp });
-
- await disableTOTP(test, page, users.user1);
-});
-
-test('Recovery codes', async ({ context, page }) => {
- await logUser(test, page, users.user1);
-
- await activateTOTP(test, page, users.user1);
- let recovery = await recoveryCodes(test, page, users.user1);
-
- await utils.logout(test, page, users.user1);
-
await test.step('login', async () => {
+ let timestamp = Date.now(); // Needed to use the next token
+ timestamp = timestamp + (totp.period - (Math.floor(timestamp / 1000) % totp.period) + 1) * 1000;
+
await page.getByLabel(/Email address/).fill(users.user1.email);
await page.getByRole('button', { name: 'Continue' }).click();
await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(users.user1.password);
await page.getByRole('button', { name: 'Log in', exact: true }).click();
await expect(page.getByRole('heading', { name: 'Verify your Identity' })).toBeVisible();
+ await page.getByLabel(/Verification code/).fill(totp.generate({timestamp}));
+ await page.getByRole('button', { name: 'Continue' }).click();
await expect(page).toHaveTitle(/Vaultwarden Web/);
-
- const newPagePromise = context.waitForEvent('page');
- await page.getByRole('button', { name: 'Use your recovery code' }).click();
- const newPage = await newPagePromise;
-
- const tabs = context.pages();
- await tabs[1].bringToFront();
- await expect(tabs[1].getByRole('heading', { name: 'Recover account two-step login' })).toBeVisible();
- await tabs[1].getByRole('textbox', { name: 'Email address * (required)' }).fill(users.user1.email);
- await tabs[1].getByRole('textbox', { name: 'Master password * (required)' }).fill(users.user1.password);
- await tabs[1].getByRole('textbox', { name: 'Recovery code * (required)' }).fill(recovery);
- await tabs[1].getByRole('button', { name: 'Submit' }).click();
-
- await expect(tabs[1]).toHaveTitle(/Two-step login/);
});
-});
+ await disableTOTP(test, page, users.user1);
+});
diff --git a/playwright/tests/setups/2fa.ts b/playwright/tests/setups/2fa.ts
index 0b1711a1..d430d053 100644
--- a/playwright/tests/setups/2fa.ts
+++ b/playwright/tests/setups/2fa.ts
@@ -4,24 +4,6 @@ import * as OTPAuth from "otpauth";
import * as utils from '../../global-utils';
-export async function recoveryCodes(test: Test, page: Page, user: { name: string, password: string }): string {
- return await test.step('Recovery code', async () => {
- await page.getByRole('button', { name: user.name }).click();
- await page.getByRole('menuitem', { name: 'Account settings' }).click();
- await page.getByRole('link', { name: 'Security' }).click();
- await page.getByRole('link', { name: 'Two-step login' }).click();
-
- await page.getByRole('button', { name: 'View recovery code' }).click();
- await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(user.password);
- await page.getByRole('button', { name: 'Continue' }).click();
-
- const recovery = await page.getByRole('code').innerText();
- await page.getByLabel('Close').click();
-
- return recovery;
- })
-}
-
export async function activateTOTP(test: Test, page: Page, user: { name: string, password: string }): OTPAuth.TOTP {
return await test.step('Activate TOTP 2FA', async () => {
await page.getByRole('button', { name: user.name }).click();
@@ -39,6 +21,7 @@ export async function activateTOTP(test: Test, page: Page, user: { name: string,
await page.getByLabel(/Verification code/).fill(totp.generate());
await page.getByRole('button', { name: 'Turn on' }).click();
await page.getByRole('heading', { name: 'Turned on', exact: true });
+ await page.getByLabel('Close').click();
return totp;
})
diff --git a/playwright/tests/setups/user.ts b/playwright/tests/setups/user.ts
index 11b39763..3d3990e9 100644
--- a/playwright/tests/setups/user.ts
+++ b/playwright/tests/setups/user.ts
@@ -2,7 +2,6 @@ import { expect, type Browser, Page } from '@playwright/test';
import { type MailBuffer } from 'maildev';
-import * as OTPAuth from "otpauth";
import * as utils from '../../global-utils';
import { retrieveEmailCode } from './2fa';
@@ -44,7 +43,6 @@ export async function logUser(
mailBuffer ?: MailBuffer,
mail2fa?: boolean,
notNewDevice?: boolean,
- totp?: OTPAuth.TOTP,
} = {}
) {
await test.step(`Log user ${user.email}`, async () => {
@@ -57,23 +55,11 @@ export async function logUser(
await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(user.password);
await page.getByRole('button', { name: 'Log in', exact: true }).click();
- if( options.mail2fa || options.totp ){
- let code;
-
+ if( options.mail2fa ){
await test.step('2FA check', async () => {
await expect(page.getByRole('heading', { name: 'Verify your Identity' })).toBeVisible();
-
- if( options.totp ) {
- const totp = options.totp;
- let timestamp = Date.now(); // Needed to use the next token
- timestamp = timestamp + (totp.period - (Math.floor(timestamp / 1000) % totp.period) + 1) * 1000;
- code = totp.generate({timestamp});
- } else if( options.mail2fa ){
- code = await retrieveEmailCode(test, page, options.mailBuffer);
- }
-
+ let code = await retrieveEmailCode(test, page, options.mailBuffer);
await page.getByLabel(/Verification code/).fill(code);
-
await page.getByRole('button', { name: 'Continue' }).click();
});
}
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
index b195f645..a66db95c 100644
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,4 +1,4 @@
[toolchain]
-channel = "1.99.1"
+channel = "1.99.0"
components = [ "rustfmt", "clippy" ]
profile = "minimal"
diff --git a/src/api/admin.rs b/src/api/admin.rs
index 40df44ae..2a84f611 100644
--- a/src/api/admin.rs
+++ b/src/api/admin.rs
@@ -1,4 +1,4 @@
-use std::{collections::HashMap, env, sync::LazyLock};
+use std::{env, sync::LazyLock};
use reqwest::Method;
use rocket::{
@@ -17,7 +17,7 @@ use crate::{
api::{
ApiResult, EmptyResult, JsonResult, Notify,
core::{log_event, two_factor},
- push_logout_to_relay, unregister_push_device,
+ unregister_push_device,
},
auth::{ClientIp, Secure, decode_admin, encode_jwt, generate_admin_claims},
config::ConfigBuilder,
@@ -26,7 +26,6 @@ use crate::{
models::{
Attachment, Cipher, Collection, Device, Event, EventType, Group, Invitation, Membership, MembershipId,
MembershipType, OrgPolicy, Organization, OrganizationId, SsoUser, TwoFactor, User, UserId,
- custom_roles_enabled,
},
},
error::{Error, MapResult},
@@ -202,15 +201,12 @@ fn post_admin_login(
// If the token is invalid, redirect to login page
if validate_token(&data.token) {
// If the token received is valid, generate JWT and save it as a cookie
- let claims = match generate_admin_claims() {
- Ok(claims) => claims,
- Err(e) => return Err(AdminResponse::Ok(render_admin_login(Some(e.message()), redirect.as_deref()))),
- };
+ let claims = generate_admin_claims();
let jwt = encode_jwt(&claims);
let cookie = Cookie::build((COOKIE_NAME, jwt))
.path(admin_path())
- .max_age(time::Duration::minutes(i64::from(CONFIG.admin_session_lifetime())))
+ .max_age(time::Duration::minutes(CONFIG.admin_session_lifetime()))
.same_site(SameSite::Strict)
.http_only(true)
.secure(secure.https);
@@ -257,7 +253,6 @@ struct AdminTemplateData {
logged_in: bool,
urlpath: String,
sso_enabled: bool,
- custom_roles_enabled: bool,
}
impl AdminTemplateData {
@@ -268,7 +263,6 @@ impl AdminTemplateData {
logged_in: true,
urlpath: CONFIG.domain_path(),
sso_enabled: CONFIG.sso_enabled(),
- custom_roles_enabled: custom_roles_enabled(),
}
}
@@ -393,18 +387,6 @@ async fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult = Membership::find_confirmed_by_user(&u.uuid, &conn)
- .await
- .into_iter()
- .map(|member| (member.org_uuid.to_string(), member.atype))
- .collect();
- for org in usr["organizations"].as_array_mut().into_iter().flatten() {
- if let Some(atype) = org["id"].as_str().and_then(|org_id| roles.get(org_id)) {
- org["type"] = json!(atype);
- }
- }
-
users_json.push(usr);
}
@@ -434,21 +416,13 @@ async fn get_user_json(user_id: UserId, _token: AdminToken, conn: DbConn) -> Jso
}
#[post("/users//delete", format = "application/json")]
-async fn delete_user(user_id: UserId, token: AdminToken, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
+async fn delete_user(user_id: UserId, token: AdminToken, conn: DbConn) -> EmptyResult {
let user = get_user_or_404(&user_id, &conn).await?;
- // Get the membership records and push devices before deleting the actual user
+ // Get the membership records before deleting the actual user
let memberships = Membership::find_any_state_by_user(&user_id, &conn).await;
- let has_push_device = CONFIG.push_enabled() && Device::check_user_has_push_device(&user_id, &conn).await;
let res = user.delete(&conn).await;
- if res.is_ok() {
- nt.send_logout(&user, None, &conn).await;
- if has_push_device {
- push_logout_to_relay(&user, None, None);
- }
- }
-
for membership in memberships {
log_event(
EventType::OrganizationUserDeleted,
@@ -580,14 +554,10 @@ async fn update_membership_type(data: Json, token: AdminToke
err!("The specified user isn't member of the organization")
};
- let new_type = match MembershipType::from_str(&data.user_type.into_string()) {
- // Unlike the clients, the admin panel tells the Custom and the legacy Manager role apart. Unless Custom roles
- // are enabled, a Custom role is a Manager here as well, but an existing Custom member is kept as one.
- Some(MembershipType::Custom) if !custom_roles_enabled() && !member_to_edit.has_type(MembershipType::Custom) => {
- MembershipType::Manager
- }
- Some(new_type) => new_type,
- None => err!("Invalid type"),
+ let new_type = if let Some(new_type) = MembershipType::from_str(&data.user_type.into_string()) {
+ new_type as i32
+ } else {
+ err!("Invalid type")
};
if member_to_edit.atype == MembershipType::Owner && new_type != MembershipType::Owner {
@@ -597,14 +567,7 @@ async fn update_membership_type(data: Json, token: AdminToke
}
}
- // A Manager only becomes a Custom member if that changes nothing they may do
- if new_type == MembershipType::Custom
- && let Some(loss) = member_to_edit.custom_role_conversion_loss(&conn).await
- {
- err!(loss)
- }
-
- member_to_edit.set_type(new_type);
+ member_to_edit.atype = new_type;
// This check is also done at api::organizations::{accept_invite, _confirm_invite, _activate_member, edit_member}, update_membership_type
OrgPolicy::check_user_allowed(&member_to_edit, "modify", &conn).await?;
@@ -816,8 +779,6 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> A
.into_keys()
.collect();
- let invalid_env_vars = crate::util::INVALID_ENV_VARS.lock().unwrap().join(", ");
-
let diagnostics_json = json!({
"dns_resolved": dns_resolved,
"current_release": VERSION,
@@ -840,9 +801,7 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> A
"db_version": get_sql_server_version(&conn).await,
"admin_url": format!("{}/diagnostics", admin_url()),
"overrides": &CONFIG.get_overrides().join(", "),
- "config_file_status": &CONFIG.get_config_file_status(),
"template_overrides": check_template_overrides().join(", "),
- "invalid_env_vars": invalid_env_vars,
"invalid_feature_flags": invalid_feature_flags,
"host_arch": env::consts::ARCH,
"host_os": env::consts::OS,
diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs
index b2dd5845..b3499a93 100644
--- a/src/api/core/accounts.rs
+++ b/src/api/core/accounts.rs
@@ -1,7 +1,6 @@
use std::collections::HashSet;
use chrono::Utc;
-use num_traits::FromPrimitive;
use rocket::{
http::Status,
request::{FromRequest, Outcome, Request},
@@ -12,9 +11,9 @@ use serde_json::Value;
use crate::{
CONFIG,
api::{
- AnonymousNotify, ApiResult, EmptyResult, JsonResult, LogOutReason, Notify, PasswordOrOtpData,
+ AnonymousNotify, ApiResult, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType,
core::{accept_org_invite, log_user_event, two_factor::email},
- master_password_policy, push_logout_to_relay, register_push_device, unregister_push_device,
+ master_password_policy, register_push_device, unregister_push_device,
},
auth::{ClientHeaders, ClientIp, Headers, decode_delete, decode_invite, decode_verify_email},
crypto,
@@ -23,8 +22,8 @@ use crate::{
models::{
AuthRequest, AuthRequestId, Cipher, CipherId, Device, DeviceId, DeviceType, DeviceWithAuthRequest,
EmergencyAccess, EmergencyAccessId, EventType, Folder, FolderId, Invitation, KeyId, Membership,
- MembershipId, OrgPolicy, OrgPolicyType, Organization, OrganizationId, Send, SendId, SignatureAlgorithm,
- User, UserId, UserKdfType, UserSignatureKeyPair,
+ MembershipId, OrgPolicy, OrgPolicyType, Organization, OrganizationId, Send, SendId, User, UserId,
+ UserKdfType,
},
},
mail,
@@ -32,7 +31,7 @@ use crate::{
};
use super::{
- ciphers::{CipherData, rotate_cipher_data, validate_rotated_cipher},
+ ciphers::{CipherData, update_cipher_from_data},
sends::SendData,
};
@@ -43,16 +42,13 @@ pub fn routes() -> Vec {
post_profile,
put_avatar,
get_public_keys,
- get_account_public_keys,
get_keys,
post_keys,
post_password,
post_set_password,
post_kdf,
- get_key_rotation_data,
post_rotatekey,
post_user_key,
- post_rotate_user_keys,
post_sstamp,
post_email_token,
post_email,
@@ -97,15 +93,6 @@ pub struct KDFData {
kdf_parallelism: Option,
}
-impl KDFData {
- pub(super) fn matches_user(&self, user: &User) -> bool {
- self.kdf == user.client_kdf_type
- && self.kdf_iterations == user.client_kdf_iter
- && self.kdf_memory == user.client_kdf_memory
- && self.kdf_parallelism == user.client_kdf_parallelism
- }
-}
-
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisterData {
@@ -117,9 +104,6 @@ pub struct RegisterData {
#[serde(alias = "userAsymmetricKeys")]
keys: Option,
- // Supersedes `keys`, and the only way a v2 account can be registered.
- account_keys: Option,
-
master_password_hint: Option,
organization_user_id: Option,
@@ -131,6 +115,36 @@ pub struct RegisterData {
org_invite_token: Option,
}
+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)]
struct RegisterDataOld {
#[serde(flatten)]
@@ -170,42 +184,6 @@ impl RegisterDataCompat {
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()
- }
-
- /// The id of the user key, which only the current format carries.
- fn key_id(&self) -> Option {
- match self {
- RegisterDataCompat::RegisterDataOld(_) => None,
- RegisterDataCompat::RegisterDataCur(rdcu) => rdcu.master_password_unlock.contained_key_id.clone(),
- }
- }
-
- // 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)]
@@ -215,215 +193,6 @@ struct KeysData {
public_key: String,
}
-/// Upstream's implicit `[Required]` on a non-nullable string: present and not blank.
-fn required(value: Option, field: &str) -> ApiResult {
- match value {
- Some(value) if !value.trim().is_empty() => Ok(value),
- _ => err!(format!("The {field} field is required.")),
- }
-}
-
-/// The `accountKeys` payload: a v1 key pair, or v2 with a signature key pair and security state.
-///
-/// Ref:
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct AccountKeysData {
- // Required like upstream, but only used without `public_key_encryption_key_pair`
- user_key_encrypted_account_private_key: Option,
- account_public_key: Option,
-
- public_key_encryption_key_pair: Option,
- signature_key_pair: Option,
- security_state: Option,
-}
-
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct PublicKeyEncryptionKeyPairData {
- wrapped_private_key: String,
- public_key: String,
- signed_public_key: Option,
-}
-
-#[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,
-}
-
-struct ValidatedAccountKeys {
- private_key: String,
- public_key: String,
- v2: Option,
-}
-
-struct ValidatedV2AccountKeys {
- signed_public_key: String,
- signing_key: String,
- verifying_key: String,
- signature_algorithm: SignatureAlgorithm,
- security_state: String,
- security_version: i32,
-}
-
-impl AccountKeysData {
- /// Upstream's model checks, plus the v2 fields all or none: no client can unlock a partial v2 state.
- fn validate(self) -> ApiResult {
- let top_private_key =
- required(self.user_key_encrypted_account_private_key, "UserKeyEncryptedAccountPrivateKey")?;
- let top_public_key = required(self.account_public_key, "AccountPublicKey")?;
- let (private_key, public_key, signed_public_key) = match self.public_key_encryption_key_pair {
- Some(key_pair) => (
- required(Some(key_pair.wrapped_private_key), "WrappedPrivateKey")?,
- required(Some(key_pair.public_key), "PublicKey")?,
- key_pair.signed_public_key,
- ),
- // Older clients only send the top-level fields, which are always v1
- None => (top_private_key, top_public_key, None),
- };
-
- if let Some(signature_key_pair) = &self.signature_key_pair {
- required(Some(signature_key_pair.signature_algorithm.clone()), "SignatureAlgorithm")?;
- required(Some(signature_key_pair.wrapped_signing_key.clone()), "WrappedSigningKey")?;
- required(Some(signature_key_pair.verifying_key.clone()), "VerifyingKey")?;
- }
- if let Some(security_state) = &self.security_state {
- required(Some(security_state.security_state.clone()), "SecurityState")?;
- if security_state.security_state.encode_utf16().count() > 10_000 {
- err!("The field SecurityState must be a string with a maximum length of 10000.")
- }
- }
-
- let v2 = match (signed_public_key, self.signature_key_pair, self.security_state) {
- (Some(signed_public_key), Some(signature_key_pair), Some(security_state)) => {
- // Upstream fails on a null one, but takes an empty one that no client can unlock with
- let signed_public_key = required(Some(signed_public_key), "SignedPublicKey")?;
- let Some(signature_algorithm) = SignatureAlgorithm::parse(&signature_key_pair.signature_algorithm)
- else {
- err!(format!(
- "Unsupported signature algorithm: {}",
- signature_key_pair.signature_algorithm.escape_debug()
- ))
- };
-
- 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 WrappedAccountCryptographicState {
- /// v2-only: the nested pair stands in for the top-level keys, so a partial state can't pass as v1.
- fn validate(self) -> ApiResult {
- let key_pair = self.public_key_encryption_key_pair;
- AccountKeysData {
- user_key_encrypted_account_private_key: Some(key_pair.wrapped_private_key.clone()),
- account_public_key: Some(key_pair.public_key.clone()),
- public_key_encryption_key_pair: Some(key_pair),
- signature_key_pair: Some(self.signature_key_pair),
- security_state: Some(self.security_state),
- }
- .validate()
- }
-}
-
-impl From for ValidatedAccountKeys {
- fn from(keys: KeysData) -> Self {
- Self {
- private_key: keys.encrypted_private_key,
- public_key: keys.public_key,
- v2: None,
- }
- }
-}
-
-impl ValidatedAccountKeys {
- /// The keys of a registration: `accountKeys` only for a v2 account, like upstream.
- ///
- /// Ref:
- fn for_registration(account_keys: Option, keys: Option) -> ApiResult {
- if let Some(account_keys) = account_keys {
- let account_keys = account_keys.validate()?;
- if account_keys.is_v2() {
- return Ok(account_keys);
- }
- }
- let Some(keys) = keys else {
- err!("PublicKey and WrappedPrivateKey not found in RequestModel")
- };
- Ok(keys.into())
- }
-
- fn is_v2(&self) -> bool {
- self.v2.is_some()
- }
-
- /// Sets the keys on the user. Save [`Self::save_signature_key_pair`] before the user: a v2 user without its key
- /// pair can't unlock, while a key pair saved for a user who isn't v2 yet is ignored until it is, and replaced on
- /// the next try. A rotation of a v2 account replaces its key pair, see `rotate_account`.
- fn apply(&self, user: &mut User) -> EmptyResult {
- 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(())
- }
-
- /// Saves the signature key pair, which needs the user to exist for its foreign key.
- 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_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)]
#[serde(rename_all = "camelCase")]
pub struct MasterPasswordAuthentication {
@@ -444,19 +213,17 @@ pub struct MasterPasswordUnlock {
#[serde(alias = "masterKeyWrappedUserKey")]
key: String,
- contained_key_id: Option,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetPasswordData {
#[serde(flatten)]
- compat: RegisterDataCompat,
+ kdf: KDFData,
+ key: String,
keys: Option,
- // Supersedes `keys`, and the only way a v2 account can be initialized here.
- account_keys: Option,
-
+ master_password_hash: String,
master_password_hint: Option,
org_identifier: Option,
}
@@ -498,7 +265,7 @@ pub async fn register(data: Json, conn: DbConn) -> JsonResult {
let mut name = None;
let mut pending_emergency_access = None;
- if data.compat.unprocessable(&data.email) {
+ if data.unprocessable() {
err_code!("Unexpected RegisterData format", Status::UnprocessableEntity.code);
}
@@ -567,15 +334,12 @@ pub async fn register(data: Json, conn: DbConn) -> JsonResult {
err!("The field Name must be a string with a maximum length of 50.");
}
- // Check against the password hint setting and the keys here so if they fail,
- // the user can retry without losing their invitation below.
+ // Check against the password hint setting here so if it fails, the user
+ // can retry without losing their invitation below.
let password_hint = clean_password_hint(data.master_password_hint.as_ref());
enforce_password_hint_setting(password_hint.as_ref())?;
- let account_keys = ValidatedAccountKeys::for_registration(data.account_keys, data.keys)?;
- let existing_user = User::find_by_mail(&email, &conn).await;
- let is_new_user = existing_user.is_none();
- let mut user = match existing_user {
+ let mut user = match User::find_by_mail(&email, &conn).await {
Some(user) => {
if !user.password_hash.is_empty() {
err!("Registration not allowed or user already exists")
@@ -620,9 +384,9 @@ pub async fn register(data: Json, conn: DbConn) -> JsonResult {
// Make sure we don't leave a lingering invitation.
Invitation::take(&email, &conn).await;
- set_kdf_data(&mut user, data.compat.kdf())?;
+ set_kdf_data(&mut user, data.kdf())?;
- user.set_password(&data.compat.hash(), Some(data.compat.key()), true, None, &conn).await?;
+ user.set_password(&data.hash(), Some(data.key()), true, None, &conn).await?;
user.password_hint = password_hint;
// Add extra fields if present
@@ -630,10 +394,9 @@ pub async fn register(data: Json, conn: DbConn) -> JsonResult {
user.name = name;
}
- account_keys.apply(&mut user)?;
- // Like upstream, only a v2 registration records the key id; v1 accounts report it through `user-key-id`
- if account_keys.is_v2() {
- user.key_id = data.compat.key_id();
+ if let Some(keys) = data.keys {
+ user.private_key = Some(keys.encrypted_private_key);
+ user.public_key = Some(keys.public_key);
}
if email_verified {
@@ -655,14 +418,6 @@ pub async fn register(data: Json, conn: DbConn) -> JsonResult {
}
}
- // The key pair first, as in a rotation, but its foreign key needs the user: a new one is saved before without a
- // password or keys, like an invited user, so a failure in between leaves a registration that can be retried.
- if is_new_user && account_keys.is_v2() {
- let mut unregistered = User::new(&user.email, None);
- unregistered.uuid.clone_from(&user.uuid);
- unregistered.save(&conn).await?;
- }
- account_keys.save_signature_key_pair(&user.uuid, &conn).await?;
user.save(&conn).await?;
// accept any open emergency access invitations
@@ -677,81 +432,25 @@ pub async fn register(data: Json, conn: DbConn) -> JsonResult {
})))
}
-/// Upstream's shape checks of the set-password body, on the raw JSON that the untagged compat would hide.
-///
-/// Ref:
-fn validate_set_password_shape(body: &Value) -> EmptyResult {
- let has = |name: &str| {
- body.as_object().is_some_and(|o| o.iter().any(|(k, v)| k.eq_ignore_ascii_case(name) && !v.is_null()))
- };
-
- if has("accountKeys") && has("keys") {
- err!("Cannot specify both AccountKeys and Keys. Provide exactly one keypair.")
- }
- let (authentication, unlock) = (has("masterPasswordAuthentication"), has("masterPasswordUnlock"));
- if authentication != unlock {
- err!(
- "Must provide both MasterPasswordAuthentication and MasterPasswordUnlock together. Cannot provide one without the other."
- )
- }
- if authentication && (has("masterPasswordHash") || has("key") || has("kdf")) {
- err!(
- "Cannot mix modern (MasterPasswordAuthentication/MasterPasswordUnlock) and legacy (MasterPasswordHash/Key/Kdf) fields. Provide one shape or the other."
- )
- }
- Ok(())
-}
-
#[post("/accounts/set-password", data = "")]
-async fn post_set_password(data: Json, headers: Headers, conn: DbConn) -> JsonResult {
- let data = data.into_inner();
- validate_set_password_shape(&data)?;
- let Ok(data) = serde_json::from_value::(data) else {
- err_code!("Unexpected SetPasswordData format", Status::UnprocessableEntity.code)
- };
- if data.master_password_hint.as_ref().is_some_and(|h| h.encode_utf16().count() > 50) {
- err!("The field MasterPasswordHint must be a string with a maximum length of 50.")
- }
+async fn post_set_password(data: Json, headers: Headers, conn: DbConn) -> JsonResult {
+ let data: SetPasswordData = data.into_inner();
let mut user = headers.user;
if user.private_key.is_some() || !user.password_hash.is_empty() {
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,
// the user can retry without losing their invitation below.
let password_hint = clean_password_hint(data.master_password_hint.as_ref());
enforce_password_hint_setting(password_hint.as_ref())?;
- // Like upstream, `accountKeys` only through the v2 JIT flow
- // Ref:
- let account_keys = match data.account_keys {
- Some(account_keys) => {
- if !matches!(data.compat, RegisterDataCompat::RegisterDataCur(_))
- || !crate::util::is_client_feature_flag_enabled(
- "enable-account-encryption-v2-jit-password-registration",
- )
- {
- err!("Request includes V2 AccountKeys but V2 encryption is not enabled.")
- }
- let account_keys = account_keys.validate()?;
- if !account_keys.is_v2() {
- err!("AccountKeys are only supported for V2 encryption.")
- }
- Some(account_keys)
- }
- None => data.keys.map(ValidatedAccountKeys::from),
- };
-
- set_kdf_data(&mut user, data.compat.kdf())?;
+ set_kdf_data(&mut user, &data.kdf)?;
user.set_password(
- &data.compat.hash(),
- Some(data.compat.key()),
+ &data.master_password_hash,
+ Some(data.key),
false,
Some(vec![String::from("revision_date")]), // We need to allow revision-date to use the old security_timestamp
&conn,
@@ -759,12 +458,9 @@ async fn post_set_password(data: Json, headers: Headers, conn: DbConn) ->
.await?;
user.password_hint = password_hint;
- if let Some(ref account_keys) = account_keys {
- account_keys.apply(&mut user)?;
- // As in `register`, only a v2 account records the user key id here
- if account_keys.is_v2() {
- user.key_id = data.compat.key_id();
- }
+ if let Some(keys) = data.keys {
+ user.private_key = Some(keys.encrypted_private_key);
+ user.public_key = Some(keys.public_key);
}
if let Some(identifier) = data.org_identifier
@@ -788,12 +484,9 @@ async fn post_set_password(data: Json, headers: Headers, conn: DbConn) ->
Membership::accept_user_invitations(&user.uuid, &conn).await?;
}
- log_user_event(EventType::UserChangedPassword, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await;
+ log_user_event(EventType::UserChangedPassword as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn)
+ .await;
- // The key pair first, as in a rotation: if saving the user then fails, the password isn't set either
- if let Some(account_keys) = account_keys {
- account_keys.save_signature_key_pair(&user.uuid, &conn).await?;
- }
user.save(&conn).await?;
Ok(Json(json!({
@@ -876,19 +569,8 @@ async fn get_public_keys(user_id: UserId, _headers: Headers, conn: DbConn) -> Js
})))
}
-#[get("/users//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 {
+fn get_keys(headers: Headers) -> JsonResult {
let user = headers.user;
// The SDK reads a 404 as the user having no key pair yet
@@ -900,67 +582,29 @@ async fn get_keys(headers: Headers, conn: DbConn) -> JsonResult {
"key": (!user.akey.is_empty()).then_some(&user.akey),
"publicKey": user.public_key,
"privateKey": user.private_key,
- "accountKeys": user.account_keys_json(&conn).await,
+ "accountKeys": user.account_keys_json(),
"object": "keys",
})))
}
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct PostKeysData {
- // Required like upstream, even next to `accountKeys`
- public_key: Option,
- encrypted_private_key: Option,
- account_keys: Option,
- // The id of the user key these account keys belong to, only honored for v2 `accountKeys`
- user_key_id: Option,
-}
-
#[post("/accounts/keys", data = "")]
-async fn post_keys(data: Json, headers: Headers, conn: DbConn) -> JsonResult {
- let data: PostKeysData = data.into_inner();
+async fn post_keys(data: Json, headers: Headers, conn: DbConn) -> JsonResult {
+ let data: KeysData = data.into_inner();
let mut user = headers.user;
- // Only for an account without keys, replacing them is what a key rotation does
if user.private_key.is_some() || user.public_key.is_some() {
err!("User has existing keypair")
}
- // Ref:
- let public_key = required(data.public_key, "PublicKey")?;
- let encrypted_private_key = required(data.encrypted_private_key, "EncryptedPrivateKey")?;
+ user.private_key = Some(data.encrypted_private_key);
+ user.public_key = Some(data.public_key);
- // `accountKeys` supersedes the flat keys when both are sent
- let account_keys = match data.account_keys {
- Some(account_keys) => {
- let account_keys = account_keys.validate()?;
- if !account_keys.is_v2() {
- err!("AccountKeys are only supported for V2 encryption.")
- }
- // A client that predates key ids sends none, and reports it later through `user-key-id`
- if data.user_key_id.is_some() {
- user.key_id = data.user_key_id;
- }
- account_keys
- }
- None => KeysData {
- encrypted_private_key,
- public_key,
- }
- .into(),
- };
-
- account_keys.apply(&mut user)?;
- // The key pair first, as in a rotation: if saving the user then fails, the account stays without keys
- account_keys.save_signature_key_pair(&user.uuid, &conn).await?;
user.save(&conn).await?;
Ok(Json(json!({
- "key": (!user.akey.is_empty()).then_some(&user.akey),
"privateKey": user.private_key,
"publicKey": user.public_key,
- "accountKeys": user.account_keys_json(&conn).await,
"object":"keys"
})))
}
@@ -987,7 +631,8 @@ async fn post_password(data: Json, headers: Headers, conn: DbCon
err!("Invalid password")
}
- log_user_event(EventType::UserChangedPassword, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await;
+ log_user_event(EventType::UserChangedPassword as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn)
+ .await;
let (new_master_password_hash, new_key) =
if let (Some(unlock_data), Some(authentication_data)) = (data.unlock_data, data.authentication_data) {
@@ -995,13 +640,10 @@ async fn post_password(data: Json, headers: Headers, conn: DbCon
err!("KDF settings must be equal for authentication and unlock")
}
- let salt = user.master_password_salt();
- if salt != authentication_data.salt || salt != unlock_data.salt {
+ if user.email != authentication_data.salt || user.email != unlock_data.salt {
err!("Invalid master password salt")
}
- validate_key_id_unchanged(&user, &unlock_data)?;
-
(authentication_data.master_password_authentication_hash, unlock_data.master_key_wrapped_user_key)
} else if let (Some(new_master_password_hash), Some(new_key)) = (data.new_master_password_hash, data.key) {
(new_master_password_hash, new_key)
@@ -1075,59 +717,21 @@ fn set_kdf_data(user: &mut User, data: &KDFData) -> EmptyResult {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
-pub(super) struct AuthenticationData {
+struct AuthenticationData {
#[serde(alias = "Salt")]
salt: String,
#[serde(alias = "Kdf")]
- pub(super) kdf: KDFData,
+ kdf: KDFData,
#[serde(alias = "MasterPasswordAuthenticationHash")]
- pub(super) master_password_authentication_hash: String,
-}
-
-impl AuthenticationData {
- pub(super) fn check(&self, user: &User, unlock: &UnlockData) -> EmptyResult {
- if self.kdf != unlock.kdf {
- err!("KDF settings must be equal for authentication and unlock")
- }
-
- if self.salt != user.master_password_salt() || self.salt != unlock.salt {
- err!("Invalid master password salt")
- }
-
- Ok(())
- }
+ master_password_authentication_hash: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
-pub(super) struct UnlockData {
+struct UnlockData {
salt: String,
kdf: KDFData,
- pub(super) master_key_wrapped_user_key: String,
- contained_key_id: Option,
-}
-
-/// A password or KDF change keeps the user key, so its key id must be the current one, when both are known.
-///
-/// Ref:
-fn validate_key_id_unchanged(user: &User, unlock_data: &UnlockData) -> EmptyResult {
- if let (Some(current), Some(contained)) = (&user.key_id, &unlock_data.contained_key_id)
- && current != contained
- {
- err!("Invalid user key sent in master-password unlock data.")
- }
- Ok(())
-}
-
-/// A rotation's unlock data has to wrap the new user key. Neither id is sent by clients that predate them.
-///
-/// Ref:
-fn validate_contained_key_id(contained_key_id: Option<&KeyId>, new_user_key_id: Option<&KeyId>) -> EmptyResult {
- match (contained_key_id, new_user_key_id) {
- (None, None) => Ok(()),
- (Some(contained), Some(new)) if contained == new => Ok(()),
- _ => err!("Invalid user key sent in master-password unlock data."),
- }
+ master_key_wrapped_user_key: String,
}
#[derive(Deserialize)]
@@ -1146,9 +750,13 @@ async fn post_kdf(data: Json, headers: Headers, conn: DbConn, nt:
err!("Invalid password")
}
- data.authentication_data.check(&headers.user, &data.unlock_data)?;
+ if data.authentication_data.kdf != data.unlock_data.kdf {
+ err!("KDF settings must be equal for authentication and unlock")
+ }
- validate_key_id_unchanged(&headers.user, &data.unlock_data)?;
+ if headers.user.email != data.authentication_data.salt || headers.user.email != data.unlock_data.salt {
+ err!("Invalid master password salt")
+ }
let mut user = headers.user;
@@ -1184,72 +792,50 @@ struct UpdateFolderData {
#[serde(rename_all = "camelCase")]
struct UpdateEmergencyAccessData {
id: EmergencyAccessId,
- key_encrypted: Option,
- // Only validated, upstream doesn't store it either
- wait_time_days: Option,
+ key_encrypted: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct UpdateResetPasswordData {
organization_id: OrganizationId,
- // Absent in a v1 -> v2 upgrade, which keeps the key the organization already holds
- reset_password_key: Option,
+ reset_password_key: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct KeyData {
account_unlock_data: RotateAccountUnlockData,
- account_keys: AccountKeysData,
+ account_keys: RotateAccountKeys,
account_data: RotateAccountData,
old_master_key_authentication_hash: String,
- old_user_key_id: Option,
- new_user_key_id: Option,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct RotateAccountUnlockData {
+ emergency_access_unlock_data: Vec,
master_password_unlock_data: MasterPasswordUnlockData,
- #[serde(flatten)]
- common: CommonUnlockData,
+ organization_account_recovery_unlock_data: Vec,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct MasterPasswordUnlockData {
- #[serde(flatten)]
- kdf: KDFData,
+ kdf_type: i32,
+ kdf_iterations: i32,
+ kdf_parallelism: Option,
+ kdf_memory: Option,
email: String,
master_key_authentication_hash: String,
master_key_encrypted_user_key: String,
- master_password_hint: Option,
- master_password_salt: Option,
- contained_key_id: Option,
}
-/// The unlock data of both rotation endpoints; passkey and device keys are ignored, as we support neither.
-///
-/// Ref:
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
-struct CommonUnlockData {
- emergency_access_unlock_data: Vec,
- organization_account_recovery_unlock_data: Vec,
- #[expect(dead_code, reason = "Required like upstream, but unused")]
- passkey_unlock_data: Vec,
- #[expect(dead_code, reason = "Required like upstream, but unused")]
- device_key_unlock_data: Vec,
- v2_upgrade_token: Option,
-}
-
-/// Lets other sessions with the v1 user key unwrap the v2 one after an upgrade. Opaque to us.
-#[derive(Deserialize, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct V2UpgradeTokenData {
- wrapped_user_key1: String,
- wrapped_user_key2: String,
+struct RotateAccountKeys {
+ user_key_encrypted_account_private_key: String,
+ account_public_key: String,
}
#[derive(Deserialize)]
@@ -1260,117 +846,27 @@ struct RotateAccountData {
sends: Vec,
}
-/// Body of `rotate-user-keys`, which rotates the keys without touching the master password.
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct RotateUserKeysData {
- wrapped_account_cryptographic_state: WrappedAccountCryptographicState,
- unlock_data: CommonUnlockData,
- account_data: RotateAccountData,
- unlock_method_data: UnlockMethodData,
- old_user_key_id: Option,
- new_user_key_id: Option,
-}
-
-/// The v2-only account cryptographic state, where all three parts are mandatory.
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct WrappedAccountCryptographicState {
- public_key_encryption_key_pair: PublicKeyEncryptionKeyPairData,
- signature_key_pair: SignatureKeyPairData,
- security_state: SecurityStateData,
-}
-
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct UnlockMethodData {
- unlock_method: i32,
- master_password_unlock_data: Option,
- key_connector_key_wrapped_user_key: Option,
-}
-
-/// Ref:
-#[derive(num_derive::FromPrimitive)]
-enum UnlockMethod {
- Tde = 0,
- MasterPassword = 1,
- KeyConnector = 2,
-}
-
-/// The rotation checks on the new keys: the public key never changes, nor the verifying key of a v2 account.
-///
-/// Ref:
-async fn validate_rotation_account_keys(keys: &ValidatedAccountKeys, user: &User, conn: &DbConn) -> EmptyResult {
- if user.public_key.as_ref() != Some(&keys.public_key) {
- err!("Changing the asymmetric keypair is not possible during key rotation")
- }
-
- let Some(v2) = &keys.v2 else {
- if user.is_v2() {
- err!("Cannot downgrade an account from v2 to v1 encryption during key rotation")
- }
- // A v1 rotation: the private key stays wrapped by an AES-CBC-HMAC user key
- if enc_string_type(&keys.private_key) != Some(ENC_TYPE_AES_CBC_256_HMAC_SHA256) {
- err!("The provided account private key was not wrapped with AES-256-CBC-HMAC")
- }
- return Ok(());
- };
-
- // Both a v2 rotation and the v1 -> v2 upgrade end up with a COSE user key wrapping the private keys
- if enc_string_type(&v2.signing_key) != Some(ENC_TYPE_COSE_ENCRYPT0) {
- err!("The provided signing key data is not wrapped with XChaCha20-Poly1305.")
- }
- if enc_string_type(&keys.private_key) != Some(ENC_TYPE_COSE_ENCRYPT0) {
- err!("The provided private key encryption key is not wrapped with XChaCha20-Poly1305.")
- }
- if v2.verifying_key.is_empty() || v2.signed_public_key.is_empty() || v2.security_state.is_empty() {
- err!("The v2 account keys are missing the verifying key, signed public key or security state")
- }
-
- if !user.is_v2() {
- // The v1 -> v2 upgrade, which is where the signature key pair comes from
- return Ok(());
- }
-
- let Some(key_pair) = UserSignatureKeyPair::find_by_user(&user.uuid, conn).await else {
- err!("The account is missing its signature key pair")
- };
- if key_pair.verifying_key != v2.verifying_key {
- err!("Changing the verifying key is not possible during key rotation")
- }
-
- Ok(())
-}
-
-/// `AesCbc256_HmacSha256_B64`, the v1 user key
-const ENC_TYPE_AES_CBC_256_HMAC_SHA256: &str = "2";
-/// `CoseEncrypt0B64`, the v2 user key
-const ENC_TYPE_COSE_ENCRYPT0: &str = "7";
-
-/// The encryption type of an EncString, the number before the first `.`.
-fn enc_string_type(enc_string: &str) -> Option<&str> {
- enc_string.split_once('.').map(|(enc_type, _)| enc_type)
-}
-
-impl KDFData {
- /// Whether these are the settings the user already has, which a key rotation can't change.
- fn is_unchanged_for(&self, user: &User) -> bool {
- user.client_kdf_type == self.kdf
- && user.client_kdf_iter == self.kdf_iterations
- && user.client_kdf_memory == self.kdf_memory
- && user.client_kdf_parallelism == self.kdf_parallelism
- }
-}
-
-/// Every existing item must be in the rotation, or it would be unreadable afterwards.
-fn validate_rotation_data(
- data: &RotateData,
+fn validate_keydata(
+ data: &KeyData,
existing_ciphers: &[Cipher],
existing_folders: &[Folder],
existing_emergency_access: &[EmergencyAccess],
existing_memberships: &[Membership],
existing_sends: &[Send],
+ user: &User,
) -> EmptyResult {
+ if user.client_kdf_type != data.account_unlock_data.master_password_unlock_data.kdf_type
+ || user.client_kdf_iter != data.account_unlock_data.master_password_unlock_data.kdf_iterations
+ || user.client_kdf_memory != data.account_unlock_data.master_password_unlock_data.kdf_memory
+ || user.client_kdf_parallelism != data.account_unlock_data.master_password_unlock_data.kdf_parallelism
+ || user.email != data.account_unlock_data.master_password_unlock_data.email
+ {
+ err!("Changing the kdf variant or email is not supported during key rotation");
+ }
+ if user.public_key.as_ref() != Some(&data.account_keys.account_public_key) {
+ err!("Changing the asymmetric keypair is not possible during key rotation")
+ }
+
// Check that we're correctly rotating all the user's ciphers
let existing_cipher_ids = existing_ciphers.iter().map(|c| &c.uuid).collect::>();
let provided_cipher_ids = data
@@ -1395,8 +891,12 @@ fn validate_rotation_data(
// Check that we're correctly rotating all the user's emergency access keys
let existing_emergency_access_ids =
existing_emergency_access.iter().map(|ea| &ea.uuid).collect::>();
- let provided_emergency_access_ids =
- data.unlock_data.emergency_access_unlock_data.iter().map(|ea| &ea.id).collect::>();
+ let provided_emergency_access_ids = data
+ .account_unlock_data
+ .emergency_access_unlock_data
+ .iter()
+ .map(|ea| &ea.id)
+ .collect::>();
if !provided_emergency_access_ids.is_superset(&existing_emergency_access_ids) {
err!("All existing emergency access keys must be included in the rotation")
}
@@ -1405,7 +905,7 @@ fn validate_rotation_data(
let existing_reset_password_ids =
existing_memberships.iter().map(|m| &m.org_uuid).collect::>();
let provided_reset_password_ids = data
- .unlock_data
+ .account_unlock_data
.organization_account_recovery_unlock_data
.iter()
.map(|rp| &rp.organization_id)
@@ -1424,441 +924,129 @@ fn validate_rotation_data(
Ok(())
}
-/// The keys a rotation re-shares the new user key with. The SDK needs all four lists, even when empty.
-///
-/// Ref:
-#[get("/accounts/key-management/key-rotation-data")]
-async fn get_key_rotation_data(headers: Headers, conn: DbConn) -> JsonResult {
- let user_id = &headers.user.uuid;
-
- let mut organization_data = Vec::new();
- for membership in Membership::find_by_user(user_id, &conn).await {
- // Only memberships actually enrolled in account recovery take part in the rotation.
- if membership.reset_password_key.is_none() {
- continue;
- }
- let Some(org) = Organization::find_by_uuid(&membership.org_uuid, &conn).await else {
- continue;
- };
- let Some(public_key) = org.public_key else {
- continue;
- };
- organization_data.push(json!({
- "organizationId": org.uuid,
- "organizationName": org.name,
- "organizationPublicKey": public_key,
- "object": "organizationPasswordResetKeyData",
- }));
- }
-
- let mut emergency_access_data = Vec::new();
- for emergency_access in EmergencyAccess::find_all_confirmed_by_grantor_uuid(user_id, &conn).await {
- // Without a stored key there is nothing to re-share.
- if emergency_access.key_encrypted.is_none() {
- continue;
- }
- let Some(grantee_id) = emergency_access.grantee_uuid.clone() else {
- continue;
- };
- let Some(grantee) = User::find_by_uuid(&grantee_id, &conn).await else {
- continue;
- };
- let Some(public_key) = grantee.public_key.clone() else {
- continue;
- };
- emergency_access_data.push(json!({
- "id": emergency_access.uuid,
- "granteeId": grantee_id,
- "granteeName": grantee.name,
- "granteeEmail": grantee.email,
- "publicKey": public_key,
- "object": "emergencyAccessKeyData",
- }));
- }
-
- Ok(Json(json!({
- "organizationPasswordResetKeyData": organization_data,
- "emergencyAccessKeyData": emergency_access_data,
- "trustedDeviceKeyData": [],
- "passkeyKeyData": [],
- "object": "keyRotationData",
- })))
-}
-
-/// Everything a rotation replaces, once each endpoint's wrapper has been peeled off.
-struct RotateData {
- account_keys: ValidatedAccountKeys,
- account_data: RotateAccountData,
- unlock_data: CommonUnlockData,
- /// The id of the key the client rotates from, `None` from clients that don't send it.
- old_user_key_id: Option,
- /// `None` from clients that predate key ids.
- new_user_key_id: Option,
- /// The new user key, wrapped by the master key.
- wrapped_user_key: String,
- /// Only for `rotate-user-account-keys`: the new password hash and hint.
- new_password: Option<(String, Option)>,
-}
-
-/// Upstream's `[StringLength(max)]`, which counts UTF-16 code units.
-fn validate_max_length(value: &str, max: usize, field: &str) -> EmptyResult {
- if value.encode_utf16().count() > max {
- err!(format!("The field {field} must be a string with a maximum length of {max}."))
- }
- Ok(())
-}
-
-impl KDFData {
- /// Upstream's check that the parameters fit the KDF type.
- fn validate_parameters(&self) -> EmptyResult {
- let has_argon2_parameters = (self.kdf_memory.is_some(), self.kdf_parallelism.is_some());
- if self.kdf == UserKdfType::Pbkdf2 as i32 {
- if has_argon2_parameters != (false, false) {
- err!("KdfMemory and KdfParallelism must be null for PBKDF2_SHA256")
- }
- } else if self.kdf == UserKdfType::Argon2id as i32 {
- if has_argon2_parameters != (true, true) {
- err!("KdfMemory and KdfParallelism must have values for Argon2id")
- }
- } else {
- err!("Invalid KdfType")
- }
- Ok(())
- }
-}
-
-impl MasterPasswordUnlockData {
- /// Upstream's model validation.
- ///
- /// Ref:
- fn validate(&self) -> EmptyResult {
- self.kdf.validate_parameters()?;
- if !crate::util::is_valid_email(&self.email) {
- err!("The Email field is not a supported e-mail address format.")
- }
- validate_max_length(&self.email, 256, "Email")?;
- required(Some(self.master_key_authentication_hash.clone()), "MasterKeyAuthenticationHash")?;
- validate_max_length(&self.master_key_authentication_hash, 300, "MasterKeyAuthenticationHash")?;
- required(Some(self.master_key_encrypted_user_key.clone()), "MasterKeyEncryptedUserKey")?;
- if let Some(hint) = &self.master_password_hint {
- validate_max_length(hint, 50, "MasterPasswordHint")?;
- }
- if self.master_password_salt.as_ref().is_some_and(|salt| salt.encode_utf16().count() > 256) {
- err!("The field MasterPasswordSalt must be a string or array type with a maximum length of '256'.")
- }
- Ok(())
- }
-}
-
-impl CommonUnlockData {
- /// Upstream's model validation.
- fn validate(&self) -> EmptyResult {
- // Ref:
- for emergency_access in &self.emergency_access_unlock_data {
- if !emergency_access.wait_time_days.is_some_and(|days| (1..=i32::from(i16::MAX)).contains(&days)) {
- err!("The field WaitTimeDays must be between 1 and 32767.")
- }
- }
- if let Some(token) = &self.v2_upgrade_token {
- required(Some(token.wrapped_user_key1.clone()), "WrappedUserKey1")?;
- required(Some(token.wrapped_user_key2.clone()), "WrappedUserKey2")?;
- }
- Ok(())
- }
-}
-
-/// The model validation of the parts both rotation endpoints share, which upstream runs before anything else.
-fn validate_rotation_request(account_data: &RotateAccountData, unlock_data: &CommonUnlockData) -> EmptyResult {
- unlock_data.validate()?;
-
- // Validate the import before continuing
- // Bitwarden does not process the import if there is one item invalid.
- // Since we check for the size of the encrypted note length, we need to do that here to pre-validate it.
- // TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks.
- Cipher::validate_cipher_data(&account_data.ciphers)?;
-
- if account_data.ciphers.iter().any(|cipher| cipher.id.is_none()) {
- err!("The Id field is required.")
- }
- if account_data.sends.iter().any(|send| send.id.is_none()) {
- err!("The Id field is required.")
- }
- Ok(())
-}
-
#[post("/accounts/key-management/rotate-user-account-keys", data = "")]
async fn post_rotatekey(data: Json, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
+ // TODO: See if we can wrap everything within a SQL Transaction. If something fails it should revert everything.
let data: KeyData = data.into_inner();
- let unlock_data = data.account_unlock_data.master_password_unlock_data;
- validate_max_length(&data.old_master_key_authentication_hash, 300, "OldMasterKeyAuthenticationHash")?;
- unlock_data.validate()?;
- validate_rotation_request(&data.account_data, &data.account_unlock_data.common)?;
-
if !headers.user.check_valid_password(&data.old_master_key_authentication_hash) {
err!("Invalid password")
}
- // Ref:
- let salt = unlock_data.master_password_salt.as_ref().unwrap_or(&unlock_data.email);
- if !unlock_data.kdf.is_unchanged_for(&headers.user) || *salt != headers.user.master_password_salt() {
- err!("The provided master password unlock data is not valid for this user.")
- }
- validate_contained_key_id(unlock_data.contained_key_id.as_ref(), data.new_user_key_id.as_ref())?;
- let password_hint = clean_password_hint(unlock_data.master_password_hint.as_ref());
- enforce_password_hint_setting(password_hint.as_ref())?;
+ // Validate the import before continuing
+ // Bitwarden does not process the import if there is one item invalid.
+ // Since we check for the size of the encrypted note length, we need to do that here to pre-validate it.
+ // TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks.
+ Cipher::validate_cipher_data(&data.account_data.ciphers)?;
- let mut common = data.account_unlock_data.common;
- // Like upstream, this endpoint logs every session out, so an upgrade token is never kept
- // Ref:
- common.v2_upgrade_token = None;
-
- rotate_account(
- RotateData {
- account_keys: data.account_keys.validate()?,
- account_data: data.account_data,
- unlock_data: common,
- old_user_key_id: data.old_user_key_id,
- new_user_key_id: data.new_user_key_id,
- wrapped_user_key: unlock_data.master_key_encrypted_user_key,
- new_password: Some((unlock_data.master_key_authentication_hash, password_hint)),
- },
- headers,
- conn,
- nt,
- )
- .await
-}
-
-/// Both rotation endpoints: every check first, then the re-encrypted data and the new keys are saved.
-async fn rotate_account(data: RotateData, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let user_id = &headers.user.uuid;
+
+ // TODO: Ideally we'd do everything after this point in a single transaction.
+
let mut existing_ciphers = Cipher::find_owned_by_user(user_id, &conn).await;
let mut existing_folders = Folder::find_by_user(user_id, &conn).await;
let mut existing_emergency_access = EmergencyAccess::find_all_confirmed_by_grantor_uuid(user_id, &conn).await;
- // Like upstream, only the ones with a key take part
- existing_emergency_access.retain(|ea| ea.key_encrypted.is_some());
let mut existing_memberships = Membership::find_by_user(user_id, &conn).await;
// We only rotate the reset password key if it is set.
existing_memberships.retain(|m| m.reset_password_key.is_some());
let mut existing_sends = Send::find_by_user(user_id, &conn).await;
- validate_rotation_data(
+ validate_keydata(
&data,
&existing_ciphers,
&existing_folders,
&existing_emergency_access,
&existing_memberships,
&existing_sends,
+ &headers.user,
)?;
- // Like upstream, a client that rotates a user key that was already replaced has to sync first
- // Ref:
- if let (Some(current), Some(old)) = (&headers.user.key_id, &data.old_user_key_id)
- && current != old
- {
- err_code!("The user key being rotated is not the current user key. Sync and try again.", Status::Conflict.code)
- }
-
- validate_rotation_account_keys(&data.account_keys, &headers.user, &conn).await?;
-
- // An upgrade keeps the sessions; for an account already on v2 the token means nothing and is dropped
- let is_upgrade = data.unlock_data.v2_upgrade_token.is_some() && !headers.user.is_v2();
- let upgrade_token = match &data.unlock_data.v2_upgrade_token {
- Some(token) if is_upgrade => Some(serde_json::to_string(token)?),
- _ => None,
- };
-
- // An upgrade can't re-wrap the recovery keys without a trust prompt, so they're kept and get the token
- // Ref:
- for membership in &existing_memberships {
- let has_key = data
- .unlock_data
- .organization_account_recovery_unlock_data
- .iter()
- .find(|rp| rp.organization_id == membership.org_uuid)
- .and_then(|rp| rp.reset_password_key.as_deref())
- .is_some_and(|key| !key.trim().is_empty());
- if is_upgrade && has_key {
- err!("Account recovery keys cannot be rotated during a V1 to V2 upgrade rotation.")
- }
- if !is_upgrade && !has_key {
- err!("Account recovery keys cannot be null or empty during rotation.")
- }
- }
-
- // Ref:
- for emergency_access in &existing_emergency_access {
- if data
- .unlock_data
- .emergency_access_unlock_data
- .iter()
- .find(|ea| ea.id == emergency_access.uuid)
- .is_some_and(|ea| ea.key_encrypted.is_none())
- {
- err!("Emergency access keys cannot be set to null during rotation.")
- }
- }
-
- // Saving's own checks, so that they can't fail halfway
- for cipher_data in data.account_data.ciphers.iter().filter(|c| c.organization_id.is_none()) {
- if let Some(cipher) = existing_ciphers.iter().find(|c| cipher_data.id.as_ref() == Some(&c.uuid)) {
- validate_rotated_cipher(cipher, cipher_data, &headers, &conn).await?;
- }
- }
-
- // TODO: Ideally we'd do everything after this point in a single transaction.
-
- // Update folder data, ignoring ids that aren't the user's, like upstream
+ // Update folder data
for folder_data in data.account_data.folders {
// Skip `null` folder id entries.
// See: https://github.com/bitwarden/clients/issues/8453
- if let Some(saved_folder) = folder_data.id.and_then(|id| existing_folders.iter_mut().find(|f| f.uuid == id)) {
+ if let Some(folder_id) = folder_data.id {
+ let Some(saved_folder) = existing_folders.iter_mut().find(|f| f.uuid == folder_id) else {
+ err!("Folder doesn't exist")
+ };
+
saved_folder.name = folder_data.name;
saved_folder.save(&conn).await?;
}
}
- // Like upstream, the unlock data below comes from the first entry for each, as checked above
- for saved_emergency_access in &mut existing_emergency_access {
- if let Some(emergency_access_data) =
- data.unlock_data.emergency_access_unlock_data.iter().find(|ea| ea.id == saved_emergency_access.uuid)
- {
- saved_emergency_access.key_encrypted.clone_from(&emergency_access_data.key_encrypted);
- saved_emergency_access.save(&conn).await?;
- }
+ // Update emergency access data
+ for emergency_access_data in data.account_unlock_data.emergency_access_unlock_data {
+ let Some(saved_emergency_access) =
+ existing_emergency_access.iter_mut().find(|ea| ea.uuid == emergency_access_data.id)
+ else {
+ err!("Emergency access doesn't exist or is not owned by the user")
+ };
+
+ saved_emergency_access.key_encrypted = Some(emergency_access_data.key_encrypted);
+ saved_emergency_access.save(&conn).await?;
}
- for membership in &mut existing_memberships {
- if let Some(reset_password_data) = data
- .unlock_data
- .organization_account_recovery_unlock_data
- .iter()
- .find(|rp| rp.organization_id == membership.org_uuid)
- {
- if !is_upgrade {
- membership.reset_password_key.clone_from(&reset_password_data.reset_password_key);
- }
- membership.v2_upgrade_token.clone_from(&upgrade_token);
- membership.save(&conn).await?;
- }
+ // Update reset password data
+ for reset_password_data in data.account_unlock_data.organization_account_recovery_unlock_data {
+ let Some(membership) =
+ existing_memberships.iter_mut().find(|m| m.org_uuid == reset_password_data.organization_id)
+ else {
+ err!("Reset password doesn't exist")
+ };
+
+ membership.reset_password_key = Some(reset_password_data.reset_password_key);
+ membership.save(&conn).await?;
}
// Update send data
for send_data in data.account_data.sends {
- if let Some(send) = send_data.id.as_ref().and_then(|id| existing_sends.iter_mut().find(|s| &s.uuid == id)) {
- // Like upstream, only the key changes on rotation
- send.akey = send_data.key;
- send.save(&conn).await?;
- }
+ let Some(send) = send_data.id.as_ref().and_then(|id| existing_sends.iter_mut().find(|s| &s.uuid == id)) else {
+ err!("Send doesn't exist")
+ };
+
+ // Like upstream, only the key changes on rotation
+ send.akey = send_data.key;
+ send.save(&conn).await?;
}
// Update cipher data
for cipher_data in data.account_data.ciphers {
- if cipher_data.organization_id.is_none()
- && let Some(saved_cipher) =
+ if cipher_data.organization_id.is_none() {
+ let Some(saved_cipher) =
cipher_data.id.as_ref().and_then(|id| existing_ciphers.iter_mut().find(|c| &c.uuid == id))
- {
- // No cipher pushes: the other sessions still hold the old user key
- rotate_cipher_data(saved_cipher, cipher_data, &headers, &conn, &nt).await?;
+ else {
+ err!("Cipher doesn't exist")
+ };
+
+ // Prevent triggering cipher updates via WebSockets by settings UpdateType::None
+ // The user sessions are invalidated because all the ciphers were re-encrypted and thus triggering an update could cause issues.
+ // We force the users to logout after the user has been saved to try and prevent these issues.
+ update_cipher_from_data(saved_cipher, cipher_data, &headers, None, &conn, &nt, UpdateType::None).await?;
}
}
// Update user data
let mut user = headers.user;
- user.v2_upgrade_token = upgrade_token;
- data.account_keys.apply(&mut user)?;
- // The old id names a key that no longer exists, so it is replaced even when the new key has none.
- user.key_id = data.new_user_key_id;
+ user.private_key = Some(data.account_keys.user_key_encrypted_account_private_key);
+ user.set_password(
+ &data.account_unlock_data.master_password_unlock_data.master_key_authentication_hash,
+ Some(data.account_unlock_data.master_password_unlock_data.master_key_encrypted_user_key),
+ true,
+ None,
+ &conn,
+ )
+ .await?;
- user.akey = data.wrapped_user_key;
- if let Some((new_password_hash, new_password_hint)) = data.new_password {
- // A password change ends every session, as `/accounts/password` does
- user.password_hint = new_password_hint;
- user.set_password(&new_password_hash, None, true, None, &conn).await?;
- } else if !is_upgrade {
- // An upgrade keeps the sessions alive, see `is_upgrade` above
- user.reset_security_stamp_after_key_rotation(&conn).await?;
- }
-
- // The key pair first, so that a v2 account is never saved without one. The data above is already re-encrypted by
- // now, and for an account that was already v2 so is the signing key saved here, so a failure in either save
- // leaves them under a key that isn't stored (see the TODO before the first write).
- data.account_keys.save_signature_key_pair(&user.uuid, &conn).await?;
- user.save(&conn).await?;
+ let save_result = user.save(&conn).await;
// Prevent logging out the client where the user requested this endpoint from.
// If you do logout the user it will causes issues at the client side.
// Adding the device uuid will prevent this.
- // On an upgrade, the reason lets clients sync instead
- let reason = is_upgrade.then_some(LogOutReason::KeyRotation);
- nt.send_logout_with_reason(&user, Some(&headers.device), reason, &conn).await;
+ nt.send_logout(&user, Some(&headers.device), &conn).await;
- Ok(())
-}
-
-/// Rotates the keys without changing the master password; like upstream, the session is the only proof.
-///
-/// Ref:
-#[post("/accounts/key-management/rotate-user-keys", data = "")]
-async fn post_rotate_user_keys(
- data: Json,
- headers: Headers,
- conn: DbConn,
- nt: Notify<'_>,
-) -> EmptyResult {
- let data: RotateUserKeysData = data.into_inner();
- validate_rotation_request(&data.account_data, &data.unlock_data)?;
-
- // Ref:
- let wrapped_user_key = match UnlockMethod::from_i32(data.unlock_method_data.unlock_method) {
- Some(UnlockMethod::MasterPassword) => {
- let (Some(unlock_data), None) = (
- data.unlock_method_data.master_password_unlock_data,
- data.unlock_method_data.key_connector_key_wrapped_user_key,
- ) else {
- err!(
- "Invalid MasterPassword unlock method request, MasterPasswordUnlockData must be provided and KeyConnectorKeyWrappedUserKey must be null"
- )
- };
- required(Some(unlock_data.master_key_wrapped_user_key.clone()), "MasterKeyWrappedUserKey")?;
- required(Some(unlock_data.salt.clone()), "Salt")?;
- validate_max_length(&unlock_data.salt, 256, "Salt")?;
-
- // Ref:
- if headers.user.password_hash.is_empty() || headers.user.akey.is_empty() {
- err!("User is in an invalid state for master password key rotation.")
- }
- if unlock_data.salt != headers.user.master_password_salt() {
- err!("Invalid master password salt.")
- }
- if !unlock_data.kdf.is_unchanged_for(&headers.user) {
- err!("Invalid KDF settings.")
- }
- validate_contained_key_id(unlock_data.contained_key_id.as_ref(), data.new_user_key_id.as_ref())?;
- unlock_data.master_key_wrapped_user_key
- }
- Some(UnlockMethod::Tde) => err!("Trusted device encryption is not supported"),
- Some(UnlockMethod::KeyConnector) => err!("Key connector is not supported"),
- None => err!("Unrecognized unlock method"),
- };
-
- rotate_account(
- RotateData {
- account_keys: data.wrapped_account_cryptographic_state.validate()?,
- account_data: data.account_data,
- unlock_data: data.unlock_data,
- old_user_key_id: data.old_user_key_id,
- new_user_key_id: data.new_user_key_id,
- wrapped_user_key,
- new_password: None,
- },
- headers,
- conn,
- nt,
- )
- .await
+ save_result
}
#[derive(Deserialize)]
@@ -1870,9 +1058,8 @@ struct KeyIdData {
#[post("/accounts/key-management/user-key-id", data = "")]
async fn post_user_key(data: Json, headers: Headers, conn: DbConn) -> EmptyResult {
let mut user = headers.user;
- // Only a backfill for accounts that have none. Afterwards the id changes with the key, in a rotation.
if user.key_id.is_some() {
- err!("User key id is already set.")
+ err_code!("Unexpected data", Status::UnprocessableEntity.code);
}
user.key_id = Some(data.into_inner().user_key_id);
@@ -1946,11 +1133,7 @@ async fn post_email_token(data: Json, headers: Headers, conn: Db
error!("Error sending change-email email: {e:#?}");
}
} else {
- debug!(
- "Email change request for user ({}) to email ({}) with token ({token})",
- user.uuid,
- data.new_email.escape_debug()
- );
+ debug!("Email change request for user ({}) to email ({}) with token ({token})", user.uuid, data.new_email);
}
user.email_new = Some(data.new_email);
@@ -2103,7 +1286,7 @@ struct DeleteRecoverTokenData {
}
#[post("/accounts/delete-recover-token", data = "")]
-async fn post_delete_recover_token(data: Json, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
+async fn post_delete_recover_token(data: Json, conn: DbConn) -> EmptyResult {
let data: DeleteRecoverTokenData = data.into_inner();
let Ok(claims) = decode_delete(&data.token) else {
@@ -2117,44 +1300,22 @@ async fn post_delete_recover_token(data: Json, conn: DbC
if claims.sub != *user.uuid {
err!("Invalid claim");
}
-
- // The devices are deleted together with the user, so check for push devices beforehand
- let has_push_device = CONFIG.push_enabled() && Device::check_user_has_push_device(&user.uuid, &conn).await;
- user.delete(&conn).await?;
-
- nt.send_logout(&user, None, &conn).await;
- if has_push_device {
- push_logout_to_relay(&user, None, None);
- }
- Ok(())
+ user.delete(&conn).await
}
#[post("/accounts/delete", data = "")]
-async fn post_delete_account(
- data: Json,
- headers: Headers,
- conn: DbConn,
- nt: Notify<'_>,
-) -> EmptyResult {
- delete_account(data, headers, conn, nt).await
+async fn post_delete_account(data: Json, headers: Headers, conn: DbConn) -> EmptyResult {
+ delete_account(data, headers, conn).await
}
#[delete("/accounts", data = "")]
-async fn delete_account(data: Json, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
+async fn delete_account(data: Json, headers: Headers, conn: DbConn) -> EmptyResult {
let data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
data.validate(&user, true, &conn).await?;
- // The devices are deleted together with the user, so check for push devices beforehand
- let has_push_device = CONFIG.push_enabled() && Device::check_user_has_push_device(&user.uuid, &conn).await;
- user.delete(&conn).await?;
-
- nt.send_logout(&user, None, &conn).await;
- if has_push_device {
- push_logout_to_relay(&user, None, None);
- }
- Ok(())
+ user.delete(&conn).await
}
#[expect(clippy::needless_pass_by_value, reason = "Not beneficial for Headers")]
@@ -2205,7 +1366,7 @@ async fn password_hint(data: Json, ip: ClientIp, conn: DbConn)
mail::send_password_hint(email, hint).await?;
Ok(())
} else if let Some(hint) = hint {
- err_silent!(format!("Your password hint is: {hint}"));
+ err!(format!("Your password hint is: {hint}"));
} else {
err!(NO_HINT);
}
@@ -2518,7 +1679,7 @@ async fn post_auth_request(
nt.send_auth_request(&user.uuid, &auth_request.uuid, &device, &conn).await;
log_user_event(
- EventType::UserRequestedDeviceApproval,
+ EventType::UserRequestedDeviceApproval as i32,
&user.uuid,
client_headers.device_type,
&client_headers.ip.ip,
@@ -2612,7 +1773,7 @@ async fn put_auth_request(
nt.send_auth_response(&auth_request.user_uuid, &auth_request.uuid, &headers.device, &conn).await;
log_user_event(
- EventType::OrganizationUserApprovedAuthRequest,
+ EventType::OrganizationUserApprovedAuthRequest as i32,
&headers.user.uuid,
headers.device.atype,
&headers.ip.ip,
@@ -2623,7 +1784,7 @@ async fn put_auth_request(
// If denied, there's no reason to keep the request
auth_request.delete(&conn).await?;
log_user_event(
- EventType::OrganizationUserRejectedAuthRequest,
+ EventType::OrganizationUserRejectedAuthRequest as i32,
&headers.user.uuid,
headers.device.atype,
&headers.ip.ip,
@@ -2756,10 +1917,10 @@ mod tests {
)
.unwrap();
- assert!(!data.compat.unprocessable(&data.email));
- assert_eq!(data.compat.hash(), "hash");
- assert_eq!(data.compat.key(), "key");
- assert_eq!(data.compat.kdf().kdf_iterations, 600_000);
+ assert!(!data.unprocessable());
+ assert_eq!(data.hash(), "hash");
+ assert_eq!(data.key(), "key");
+ assert_eq!(data.kdf().kdf_iterations, 600_000);
assert!(data.keys.is_some());
assert_eq!(data.email_verification_token.as_deref(), Some("token"));
}
@@ -2781,10 +1942,10 @@ mod tests {
)
.unwrap();
- assert!(!data.compat.unprocessable(&data.email));
- assert_eq!(data.compat.hash(), "hash");
- assert_eq!(data.compat.key(), "key");
- assert_eq!(data.compat.kdf().kdf_iterations, 600_000);
+ assert!(!data.unprocessable());
+ assert_eq!(data.hash(), "hash");
+ assert_eq!(data.key(), "key");
+ assert_eq!(data.kdf().kdf_iterations, 600_000);
assert!(data.keys.is_some());
}
}
diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs
index f70f2ccb..46766896 100644
--- a/src/api/core/ciphers.rs
+++ b/src/api/core/ciphers.rs
@@ -13,7 +13,7 @@ use serde_json::Value;
use crate::{
CONFIG,
- api::{self, ApiResult, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType, core::log_event},
+ api::{self, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType, core::log_event},
auth::ClientVersion,
auth::{Headers, OrgIdGuard, OwnerHeaders},
config::PathType,
@@ -23,8 +23,7 @@ use crate::{
models::{
Archive, Attachment, AttachmentId, Cipher, CipherId, Collection, CollectionCipher, CollectionGroup,
CollectionId, CollectionUser, EventType, Favorite, Folder, FolderCipher, FolderId, Group, KeyId,
- Membership, MembershipType, OrgPolicy, OrgPolicyType, OrganizationId, RepromptType, Send, User, UserId,
- is_data_blob_encrypted,
+ Membership, MembershipType, OrgPolicy, OrgPolicyType, OrganizationId, RepromptType, Send, UserId,
},
},
util::{NumberOrString, deser_opt_nonempty_str, save_temp_file},
@@ -190,22 +189,12 @@ async fn sync(data: SyncData, headers: Headers, client_version: Option,
- // Added in web-v2025.6.0. Deprecated upstream for `encrypted_by_key_id`, but still checked when sent
- pub encrypted_for: Option,
+ pub encrypted_for: UserId, // Added in web-v2025.6.0
// Added in web-v2025.8.1, Optional for compat
pub encrypted_by_key_id: Option,
@@ -293,8 +284,7 @@ pub struct CipherData {
Passport = 8
*/
pub r#type: i32,
- // Absent on a blob-encrypted cipher, whose name is sealed inside `data`
- pub name: Option,
+ pub name: String,
pub notes: Option,
fields: Option,
@@ -308,9 +298,6 @@ pub struct CipherData {
drivers_license: Option,
passport: Option,
- // The sealed blob of a v2 account's cipher, which replaces all of the fields above
- data: Option,
-
favorite: Option,
reprompt: Option,
@@ -332,98 +319,6 @@ pub struct CipherData {
archived_date: Option,
}
-/// A field of a [`CipherData`] that fails upstream's model validation.
-#[derive(Debug)]
-pub struct CipherValidationError {
- pub field: &'static str,
- pub message: String,
-}
-
-impl From for crate::Error {
- fn from(e: CipherValidationError) -> Self {
- Self::new_msg(e.message)
- }
-}
-
-/// The cipher must have been encrypted for the acting user. Only checked when the client sends the field.
-///
-/// Ref:
-fn validate_encrypted_for_user(data: &CipherData, user_id: &UserId) -> EmptyResult {
- if data.encrypted_for.as_ref().is_some_and(|encrypted_for| encrypted_for != user_id) {
- err!("Cipher was not encrypted for the current user. Please try again.")
- }
- Ok(())
-}
-
-/// [`validate_encrypted_for_user`], plus the key id of a user-owned cipher, when both ids are known.
-///
-/// Ref:
-fn validate_encrypted_by_user(data: &CipherData, user: &User, is_org_cipher: bool) -> EmptyResult {
- validate_encrypted_for_user(data, &user.uuid)?;
-
- if !is_org_cipher
- && let (Some(cipher_key_id), Some(user_key_id)) = (&data.encrypted_by_key_id, &user.key_id)
- && cipher_key_id != user_key_id
- {
- err!("Cipher was not encrypted with the current user key. Please try again.")
- }
- Ok(())
-}
-
-/// Upstream's `[StringLength(500000)]` on `CipherRequestModel.Data`
-const MAX_CIPHER_DATA_LENGTH: usize = 500_000;
-
-impl CipherData {
- /// Whether `data` is a blob rather than the per-type fields. Parses `data`, so keep the result.
- pub fn is_blob(&self) -> bool {
- self.data.as_deref().is_some_and(is_data_blob_encrypted)
- }
-
- /// Saving's last checks: a known type, and its data unless it's a blob.
- pub fn validate_type_data(&self, is_blob: bool) -> EmptyResult {
- let type_data = match self.r#type {
- 1 => &self.login,
- 2 => &self.secure_note,
- 3 => &self.card,
- 4 => &self.identity,
- 5 => &self.ssh_key,
- 6 => &self.bank_account,
- 7 => &self.drivers_license,
- 8 => &self.passport,
- _ => err!("Invalid type"),
- };
- if !is_blob && type_data.is_none() {
- err!("Data missing")
- }
- Ok(())
- }
-
- /// Upstream's model checks that depend on the format: the size of `data`, and a name unless it's a blob.
- ///
- /// Ref:
- /// and
- pub fn validate_content(&self, is_blob: bool) -> Result<(), CipherValidationError> {
- if let Some(data) = &self.data
- && data.len() > MAX_CIPHER_DATA_LENGTH
- {
- return Err(CipherValidationError {
- field: "Data",
- message: format!("The field Data must be a string with a maximum length of {MAX_CIPHER_DATA_LENGTH}."),
- });
- }
-
- // A blob carries the name inside it, so only the other formats need one
- if !is_blob && self.name.as_deref().is_none_or(|n| n.trim().is_empty()) {
- return Err(CipherValidationError {
- field: "Name",
- message: String::from("The Name field is required."),
- });
- }
-
- Ok(())
- }
-}
-
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PartialCipherData {
@@ -457,14 +352,16 @@ async fn post_ciphers_create(
) -> JsonResult {
let mut data: ShareCipherData = data.into_inner();
- validate_encrypted_by_user(&data.cipher, &headers.user, data.cipher.organization_id.is_some())?;
+ if data.cipher.encrypted_for != headers.user.uuid {
+ err_code!("Invalid user cipher", Status::UnprocessableEntity.code);
+ }
// This check is usually only needed in update_cipher_from_data(), but we
// need it here as well to avoid creating an empty cipher in the call to
// cipher.save() below.
enforce_personal_ownership_policy(Some(&data.cipher), &headers, &conn).await?;
- let mut cipher = Cipher::new(data.cipher.r#type);
+ let mut cipher = Cipher::new(data.cipher.r#type, data.cipher.name.clone());
cipher.user_uuid = Some(headers.user.uuid.clone());
cipher.save(&conn).await?;
@@ -488,7 +385,16 @@ async fn post_ciphers_create(
async fn post_ciphers(data: Json, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
let mut data: CipherData = data.into_inner();
- validate_encrypted_by_user(&data, &headers.user, data.organization_id.is_some())?;
+ if data.encrypted_for != headers.user.uuid {
+ err_code!("Invalid user cipher", Status::UnprocessableEntity.code);
+ }
+
+ if let Some(cipher_key_id) = &data.encrypted_by_key_id
+ && let Some(user_key_id) = &headers.user.key_id
+ && cipher_key_id != user_key_id
+ {
+ err_code!("Invalid key cipher", Status::UnprocessableEntity.code);
+ }
// The web/browser clients set this field to null as expected, but the
// mobile clients seem to set the invalid value `0001-01-01T00:00:00`,
@@ -496,7 +402,7 @@ async fn post_ciphers(data: Json, headers: Headers, conn: DbConn, nt
// needed when creating a new cipher, so just ignore it unconditionally.
data.last_known_revision_date = None;
- let mut cipher = Cipher::new(data.r#type);
+ let mut cipher = Cipher::new(data.r#type, data.name.clone());
update_cipher_from_data(&mut cipher, data, &headers, None, &conn, &nt, UpdateType::SyncCipherCreate).await?;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &conn).await?))
@@ -520,44 +426,6 @@ async fn enforce_personal_ownership_policy(data: Option<&CipherData>, headers: &
Ok(())
}
-/// The checks of saving a cipher, for callers that run them before writing anything else
-async fn validate_cipher_update(
- cipher: &Cipher,
- data: &CipherData,
- headers: &Headers,
- conn: &DbConn,
- ut: UpdateType,
-) -> EmptyResult {
- // Check that the client isn't updating an existing cipher with stale data.
- // And only perform this check when not importing ciphers, else the date/time check will fail.
- if ut != UpdateType::None
- && let Some(dt) = &data.last_known_revision_date
- {
- match NaiveDateTime::parse_from_str(dt, "%+") {
- // ISO 8601 format
- Err(err) => warn!("Error parsing LastKnownRevisionDate '{}': {err}", dt.escape_debug()),
- Ok(dt) if cipher.updated_at.signed_duration_since(dt).num_seconds() > 1 => {
- err!("The client copy of this cipher is out of date. Resync the client and try again.")
- }
- Ok(_) => (),
- }
- }
-
- if let Some(note) = &data.notes {
- let max_note_size = CONFIG._max_note_size();
- if note.len() > max_note_size {
- err!(format!("The field Notes exceeds the maximum encrypted value length of {max_note_size} characters."))
- }
- }
-
- if let Some(folder_id) = &data.folder_id
- && Folder::find_by_uuid_and_user(folder_id, &headers.user.uuid, conn).await.is_none()
- {
- err!("Invalid folder", "Folder does not exist or belongs to another user");
- }
- Ok(())
-}
-
pub async fn update_cipher_from_data(
cipher: &mut Cipher,
data: CipherData,
@@ -566,40 +434,6 @@ pub async fn update_cipher_from_data(
conn: &DbConn,
nt: &Notify<'_>,
ut: UpdateType,
-) -> EmptyResult {
- enforce_personal_ownership_policy(Some(&data), headers, conn).await?;
- save_cipher_data(cipher, data, headers, shared_to_collections, conn, nt, ut).await
-}
-
-/// [`update_cipher_from_data`] for a rotation: like upstream, the personal ownership policy doesn't apply.
-pub async fn rotate_cipher_data(
- cipher: &mut Cipher,
- data: CipherData,
- headers: &Headers,
- conn: &DbConn,
- nt: &Notify<'_>,
-) -> EmptyResult {
- save_cipher_data(cipher, data, headers, None, conn, nt, UpdateType::None).await
-}
-
-/// The checks of [`rotate_cipher_data`] that need the database, to run on every cipher before the first is saved
-pub async fn validate_rotated_cipher(
- cipher: &Cipher,
- data: &CipherData,
- headers: &Headers,
- conn: &DbConn,
-) -> EmptyResult {
- validate_cipher_update(cipher, data, headers, conn, UpdateType::None).await
-}
-
-async fn save_cipher_data(
- cipher: &mut Cipher,
- data: CipherData,
- headers: &Headers,
- shared_to_collections: Option>,
- conn: &DbConn,
- nt: &Notify<'_>,
- ut: UpdateType,
) -> EmptyResult {
// Cleanup cipher data, like removing the 'Response' key.
// This key is somewhere generated during Javascript so no way for us this fix this.
@@ -615,15 +449,34 @@ async fn save_cipher_data(
json_data
}
- let is_blob = data.is_blob();
- data.validate_content(is_blob)?;
- data.validate_type_data(is_blob)?;
- validate_cipher_update(cipher, &data, headers, conn, ut).await?;
+ enforce_personal_ownership_policy(Some(&data), headers, conn).await?;
+
+ // Check that the client isn't updating an existing cipher with stale data.
+ // And only perform this check when not importing ciphers, else the date/time check will fail.
+ if ut != UpdateType::None
+ && let Some(dt) = data.last_known_revision_date
+ {
+ match NaiveDateTime::parse_from_str(&dt, "%+") {
+ // ISO 8601 format
+ Err(err) => warn!("Error parsing LastKnownRevisionDate '{dt}': {err}"),
+ Ok(dt) if cipher.updated_at.signed_duration_since(dt).num_seconds() > 1 => {
+ err!("The client copy of this cipher is out of date. Resync the client and try again.")
+ }
+ Ok(_) => (),
+ }
+ }
if cipher.organization_uuid.is_some() && cipher.organization_uuid != data.organization_id {
err!("Organization mismatch. Please resync the client before updating the cipher")
}
+ if let Some(note) = &data.notes {
+ let max_note_size = CONFIG._max_note_size();
+ if note.len() > max_note_size {
+ err!(format!("The field Notes exceeds the maximum encrypted value length of {max_note_size} characters."))
+ }
+ }
+
// Check if this cipher is being transferred from a personal to an organization vault
let transfer_cipher = cipher.organization_uuid.is_none() && data.organization_id.is_some();
@@ -654,6 +507,12 @@ async fn save_cipher_data(
cipher.user_uuid = Some(headers.user.uuid.clone());
}
+ if let Some(ref folder_id) = data.folder_id
+ && Folder::find_by_uuid_and_user(folder_id, &headers.user.uuid, conn).await.is_none()
+ {
+ err!("Invalid folder", "Folder does not exist or belongs to another user");
+ }
+
// Modify attachments name and keys when rotating
if let Some(attachments) = data.attachments2 {
for (id, attachment) in attachments {
@@ -662,7 +521,7 @@ async fn save_cipher_data(
// A missing attachment means it was removed via an other client.
// Also the Desktop Client supports removing attachments and save an update afterwards.
// Bitwarden it self ignores these mismatches server side.
- warn!("Attachment {} doesn't exist", id.escape_debug());
+ warn!("Attachment {id} doesn't exist");
continue;
};
@@ -692,36 +551,26 @@ async fn save_cipher_data(
_ => err!("Invalid type"),
};
- if let Some(blob) = data.data.filter(|_| is_blob) {
- // A blob holds everything, the name included; the column can't be null, so it's left empty
- // TODO: Make `ciphers.name` nullable and store `None` here instead.
- cipher.name = String::new();
- cipher.notes = None;
- cipher.fields = None;
- cipher.password_history = None;
- cipher.data = blob;
- } else {
- let Some(mut type_data) = type_data_opt else {
- err!("Data missing")
- };
+ let type_data = if let Some(mut data) = type_data_opt {
// Remove the 'Response' key from the base object.
- if let Some(data_obj) = type_data.as_object_mut() {
+ if let Some(data_obj) = data.as_object_mut() {
data_obj.remove("response");
}
// Remove the 'Response' key from every Uri.
- if type_data["uris"].is_array() {
- type_data["uris"] = clean_cipher_data(type_data["uris"].clone());
+ if data["uris"].is_array() {
+ data["uris"] = clean_cipher_data(data["uris"].clone());
}
-
- // `validate_content` made sure there is a name
- cipher.name = data.name.unwrap_or_default();
- cipher.notes = data.notes;
- cipher.fields = data.fields.map(|f| clean_cipher_data(f).to_string());
- cipher.password_history = data.password_history.map(|f| f.to_string());
- cipher.data = type_data.to_string();
- }
+ data
+ } else {
+ err!("Data missing")
+ };
cipher.key = data.key;
+ cipher.name = data.name;
+ cipher.notes = data.notes;
+ cipher.fields = data.fields.map(|f| clean_cipher_data(f).to_string());
+ cipher.data = type_data.to_string();
+ cipher.password_history = data.password_history.map(|f| f.to_string());
cipher.reprompt = data.reprompt.filter(|r| *r == RepromptType::None as i32 || *r == RepromptType::Password as i32);
cipher.save(conn).await?;
@@ -731,7 +580,7 @@ async fn save_cipher_data(
match data.archived_date {
Some(dt_str) => match NaiveDateTime::parse_from_str(&dt_str, "%+") {
Ok(dt) => cipher.set_archived_at(dt, &headers.user.uuid, conn).await?,
- Err(err) => warn!("Error parsing ArchivedDate '{}': {err}", dt_str.escape_debug()),
+ Err(err) => warn!("Error parsing ArchivedDate '{dt_str}': {err}"),
},
None => cipher.unarchive(&headers.user.uuid, conn).await?,
}
@@ -817,11 +666,8 @@ async fn post_ciphers_import(data: Json, headers: Headers, conn: DbC
for (index, mut cipher_data) in data.ciphers.into_iter().enumerate() {
let folder_id = relations_map.get(&index).and_then(|i| folders.get(*i).cloned());
cipher_data.folder_id = folder_id;
- // A personal import always creates personal ciphers, the organization ID is ignored instead of rejected
- // Upstream: https://github.com/bitwarden/server/blob/b06b81c1fb33ce9c9872b96397ba2b4d879b66af/src/Api/Tools/Controllers/ImportCiphersController.cs#L58
- cipher_data.organization_id = None;
- let mut cipher = Cipher::new(cipher_data.r#type);
+ let mut cipher = Cipher::new(cipher_data.r#type, cipher_data.name.clone());
update_cipher_from_data(&mut cipher, cipher_data, &headers, None, &conn, &nt, UpdateType::None).await?;
}
@@ -841,11 +687,6 @@ async fn put_cipher_admin(
conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
- // Only organization ciphers can be edited here, put_cipher() rejects an organization ID that isn't the cipher's
- if data.organization_id.is_none() {
- err_code!("Resource not found.", "Cipher is not an organization cipher", Status::NotFound.code);
- }
-
put_cipher(cipher_id, data, headers, conn, nt).await
}
@@ -857,7 +698,7 @@ async fn post_cipher_admin(
conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
- put_cipher_admin(cipher_id, data, headers, conn, nt).await
+ post_cipher(cipher_id, data, headers, conn, nt).await
}
#[post("/ciphers/", data = "")]
@@ -894,14 +735,6 @@ async fn put_cipher(
err!("Cipher is not write accessible")
}
- // Against the cipher we hold rather than the organization the client claims, like upstream
- validate_encrypted_by_user(&data, &headers.user, cipher.organization_uuid.is_some())?;
-
- // Moving a cipher into an organization is only possible through the share endpoints
- if cipher.organization_uuid != data.organization_id {
- err!("Organization mismatch. Please resync the client before updating the cipher")
- }
-
update_cipher_from_data(&mut cipher, data, &headers, None, &conn, &nt, UpdateType::SyncCipherUpdate).await?;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &conn).await?))
@@ -1160,7 +993,9 @@ async fn post_cipher_share(
conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
- put_cipher_share(cipher_id, data, headers, conn, nt).await
+ let data: ShareCipherData = data.into_inner();
+
+ share_cipher_by_uuid(&cipher_id, data, &headers, &conn, &nt, None).await
}
#[put("/ciphers//share", data = "")]
@@ -1173,21 +1008,6 @@ async fn put_cipher_share(
) -> JsonResult {
let data: ShareCipherData = data.into_inner();
- // Upstream's `PutShare` checks, before anything is written.
- // Ref:
- // and
- if data.cipher.organization_id.is_none() {
- err!("Cipher OrganizationId is required.")
- }
- if data.collection_ids.is_empty() {
- err!("You must select at least one collection.")
- }
- if !Cipher::find_by_uuid(&cipher_id, &conn).await.is_some_and(|c| c.user_uuid.as_ref() == Some(&headers.user.uuid))
- {
- err_code!("Cipher doesn't exist", Status::NotFound.code)
- }
- validate_encrypted_for_user(&data.cipher, &headers.user.uuid)?;
-
share_cipher_by_uuid(&cipher_id, data, &headers, &conn, &nt, None).await
}
@@ -1198,117 +1018,46 @@ struct ShareSelectedCipherData {
collection_ids: Vec,
}
-// A bulk share returns cipherMini models, not the cipherDetails models used by
-// the single-share route. Keep the encrypted legacy fields and a future v2
-// `data` blob, but do not expose details-only fields in this response.
-fn cipher_mini_from_json(mut cipher: Value) -> Value {
- const FIELDS: &[&str] = &[
- "id",
- "organizationId",
- "type",
- "data",
- "partialData",
- "name",
- "notes",
- "login",
- "card",
- "identity",
- "secureNote",
- "sshKey",
- "bankAccount",
- "driversLicense",
- "passport",
- "fields",
- "passwordHistory",
- "attachments",
- "organizationUseTotp",
- "revisionDate",
- "creationDate",
- "deletedDate",
- "reprompt",
- "key",
- ];
-
- let Some(object) = cipher.as_object_mut() else {
- return json!({ "object": "cipherMini" });
- };
- object.retain(|field, _| FIELDS.contains(&field.as_str()));
- object.insert("object".into(), "cipherMini".into());
- cipher
-}
-
#[put("/ciphers/share", data = "")]
async fn put_cipher_share_selected(
data: Json,
headers: Headers,
conn: DbConn,
nt: Notify<'_>,
-) -> JsonResult {
- let data: ShareSelectedCipherData = data.into_inner();
+) -> EmptyResult {
+ let mut data: ShareSelectedCipherData = data.into_inner();
- // Upstream's `PutShareMany` checks, before anything is written.
- // Ref:
- // and
if data.ciphers.is_empty() {
err!("You must select at least one cipher.")
}
- if data.ciphers.iter().any(|c| c.id.is_none() || c.organization_id.is_none()) {
- err!("All Ciphers must have an Id and OrganizationId.")
- }
- if data.ciphers.iter().map(|c| &c.organization_id).collect::>().len() != 1 {
- err!("All ciphers must be for the same organization.")
- }
+
if data.collection_ids.is_empty() {
err!("You must select at least one collection.")
}
+
for cipher in &data.ciphers {
- let is_blob = cipher.is_blob();
- cipher.validate_content(is_blob)?;
- cipher.validate_type_data(is_blob)?;
- }
- for cipher in &data.ciphers {
- validate_encrypted_for_user(cipher, &headers.user.uuid)?;
- }
- let owned_ciphers = Cipher::find_owned_by_user(&headers.user.uuid, &conn).await;
- for cipher_data in &data.ciphers {
- let Some(cipher) = owned_ciphers.iter().find(|c| cipher_data.id.as_ref() == Some(&c.uuid)) else {
- err!("Trying to share ciphers that you do not own.")
- };
- validate_cipher_update(cipher, cipher_data, &headers, &conn, UpdateType::None).await?;
+ if cipher.id.is_none() {
+ err!("Request missing ids field")
+ }
}
- let mut responses = Vec::with_capacity(data.ciphers.len());
- for cipher in data.ciphers {
+ while let Some(cipher) = data.ciphers.pop() {
let mut shared_cipher_data = ShareCipherData {
cipher,
collection_ids: data.collection_ids.clone(),
};
if let Some(id) = shared_cipher_data.cipher.id.take() {
- let response =
- share_cipher_by_uuid(&id, shared_cipher_data, &headers, &conn, &nt, Some(UpdateType::None)).await?;
-
- // Like upstream's VisibleToClient, withhold a cipher if sharing it
- // removed the user's access to it. Only then include its encrypted
- // mini response; the full single-share response stays internal.
- if let Some(shared_cipher) = Cipher::find_by_uuid(&id, &conn).await
- && shared_cipher.is_accessible_to_user(&headers.user.uuid, &conn).await
- {
- responses.push(cipher_mini_from_json(response.into_inner()));
- }
+ share_cipher_by_uuid(&id, shared_cipher_data, &headers, &conn, &nt, Some(UpdateType::None)).await?
} else {
err!("Request missing ids field")
- }
+ };
}
// Multi share actions do not send out a push for each cipher, we need to send a general sync here
nt.send_user_update(UpdateType::SyncCiphers, &headers.user, headers.device.push_uuid.as_ref(), &conn).await;
- Ok(Json(json!({
- "data": responses,
- "object": "list",
- "continuationToken": null,
- })))
+ Ok(())
}
async fn share_cipher_by_uuid(
@@ -1336,22 +1085,6 @@ async fn share_cipher_by_uuid(
err!("Organization mismatch. Please resync the client before updating the cipher")
}
- // When LastKnownRevisionDate is None, it is a new cipher, so send CipherCreate.
- // If there is an override, like when handling multiple items, we want to prevent a push notification for every single item
- let ut = if let Some(ut) = override_ut {
- ut
- } else if data.cipher.last_known_revision_date.is_some() {
- UpdateType::SyncCipherUpdate
- } else {
- UpdateType::SyncCipherCreate
- };
-
- // For the same reason, the other checks of saving
- let is_blob = data.cipher.is_blob();
- data.cipher.validate_content(is_blob)?;
- data.cipher.validate_type_data(is_blob)?;
- validate_cipher_update(&cipher, &data.cipher, headers, conn, ut).await?;
-
let mut shared_to_collections = vec![];
if let Some(organization_id) = &data.cipher.organization_id {
@@ -1370,6 +1103,16 @@ async fn share_cipher_by_uuid(
}
}
+ // When LastKnownRevisionDate is None, it is a new cipher, so send CipherCreate.
+ // If there is an override, like when handling multiple items, we want to prevent a push notification for every single item
+ let ut = if let Some(ut) = override_ut {
+ ut
+ } else if data.cipher.last_known_revision_date.is_some() {
+ UpdateType::SyncCipherUpdate
+ } else {
+ UpdateType::SyncCipherCreate
+ };
+
update_cipher_from_data(&mut cipher, data.cipher, headers, Some(shared_to_collections), conn, nt, ut).await?;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, conn).await?))
@@ -1417,40 +1160,6 @@ enum FileUploadType {
// Azure = 1, // only used upstream
}
-/// Remaining attachment storage in bytes for the cipher's owner, `None` when unlimited.
-/// `size_adjust` credits a v2 attachment whose record already counts as used.
-async fn attachment_size_limit(cipher: &Cipher, size_adjust: i64, conn: &DbConn) -> ApiResult