From 74ceaf23549240bded0a89ae038258bcff6d27a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20=C2=B7=20ASEnough?= <49665315+alexliluz@users.noreply.github.com> Date: Thu, 6 Aug 2026 03:29:31 +0800 Subject: [PATCH 01/26] Fix Debian cross-linking with xx-cargo (#7524) * Fix Debian cross-linking with xx-cargo * Fix SC2155 in Debian cross builds --- docker/Dockerfile.debian | 14 ++++++++++++-- docker/Dockerfile.j2 | 7 ++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 7ebb07bd..9e2e6016 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -96,8 +96,13 @@ ARG DB=sqlite,mysql,postgresql # dummy project, except the target folder # This folder contains the compiled dependencies RUN source /env-cargo && \ - # Workaround for xx related build issues + # Configure xx-cargo for target pkg-config and Debian transitive library lookup # https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977 + # https://github.com/dani-garcia/vaultwarden/discussions/7522 + if xx-info is-cross; then \ + XX_RUSTFLAGS="-C link-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \ + export XX_RUSTFLAGS; \ + fi && \ PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}" && \ find . -not -path "./target*" -delete @@ -113,8 +118,13 @@ RUN source /env-cargo && \ # Also do this for build.rs to ensure the version is rechecked touch build.rs src/main.rs && \ # Create a symlink to the binary target folder to easy copy the binary in the final stage - # Workaround for xx related build issues + # Configure xx-cargo for target pkg-config and Debian transitive library lookup # https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977 + # https://github.com/dani-garcia/vaultwarden/discussions/7522 + if xx-info is-cross; then \ + XX_RUSTFLAGS="-C link-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \ + export XX_RUSTFLAGS; \ + fi && \ PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}" && \ if [[ "${CARGO_PROFILE}" == "dev" ]] ; then \ ln -vfsr "/app/target/${CARGO_TARGET}/debug" /app/target/final ; \ diff --git a/docker/Dockerfile.j2 b/docker/Dockerfile.j2 index 5e33d512..d8b9c8c6 100644 --- a/docker/Dockerfile.j2 +++ b/docker/Dockerfile.j2 @@ -28,8 +28,13 @@ # [docker.io/vaultwarden/web-vault:{{ vault_version | replace('+', '_') }}] # {% macro xx_cargo_config() -%} -# Workaround for xx related build issues +# Configure xx-cargo for target pkg-config and Debian transitive library lookup # https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977 + # https://github.com/dani-garcia/vaultwarden/discussions/7522 + if xx-info is-cross; then \ + XX_RUSTFLAGS="-C link-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \ + export XX_RUSTFLAGS; \ + fi && \ PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}" {%- endmacro %} FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@{{ vault_image_digest }} AS vault From 55f883a5669a5b1c0227bc8341e7a2899da20660 Mon Sep 17 00:00:00 2001 From: Timshel Date: Wed, 5 Aug 2026 19:29:41 +0000 Subject: [PATCH 02/26] Fix playwright test (#7548) * Config server setting suppressOnboardingInterstitials * Backport fix playwright tests --------- Co-authored-by: Timshel --- .env.template | 8 + playwright/.env.template | 16 +- playwright/README.md | 30 +- playwright/compose/keycloak/setup.sh | 4 +- playwright/compose/playwright/Dockerfile | 2 +- playwright/compose/warden/Dockerfile | 1 + playwright/compose/warden/build.sh | 11 + playwright/docker-compose.yml | 15 +- playwright/global-setup.ts | 2 +- playwright/global-utils.ts | 15 +- playwright/package-lock.json | 1162 +++++++++-------- playwright/package.json | 14 +- playwright/playwright.config.ts | 14 +- playwright/test.env | 10 +- playwright/tests/collection.spec.ts | 16 +- playwright/tests/cyphers.spec.ts | 56 + playwright/tests/login.smtp.spec.ts | 31 +- playwright/tests/login.spec.ts | 4 +- playwright/tests/organization.smtp.spec.ts | 44 +- playwright/tests/secrets.spec.ts | 110 ++ playwright/tests/send.spec.ts | 16 +- playwright/tests/setups/2fa.ts | 15 +- playwright/tests/setups/admin.ts | 21 + playwright/tests/setups/db-teardown.ts | 2 +- playwright/tests/setups/orgs.ts | 25 +- playwright/tests/setups/sso-teardown.ts | 2 +- playwright/tests/setups/sso.ts | 25 +- playwright/tests/setups/user.ts | 34 +- playwright/tests/sso_login.smtp.spec.ts | 61 +- playwright/tests/sso_login.spec.ts | 10 +- .../tests/sso_organization.smtp.spec.ts | 16 +- playwright/tests/sso_organization.spec.ts | 37 +- src/api/core/mod.rs | 2 +- src/config.rs | 5 + 34 files changed, 1050 insertions(+), 786 deletions(-) create mode 100644 playwright/tests/cyphers.spec.ts create mode 100644 playwright/tests/secrets.spec.ts create mode 100644 playwright/tests/setups/admin.ts diff --git a/.env.template b/.env.template index fd7c2fd2..9fc29989 100644 --- a/.env.template +++ b/.env.template @@ -316,6 +316,14 @@ ## unauthenticated access to potentially sensitive data. # SHOW_PASSWORD_HINT=false +######################### +### Client settings ### +######################### + +## Control whether clients onboarding interstitials are suppressed +## (post-login welcome dialogs, extension install prompts, setup extension redirects, and premium upsell modals) +# CLIENT_SUPPRESS_ONBOARDING=false + ######################### ### Advanced settings ### ######################### diff --git a/playwright/.env.template b/playwright/.env.template index a6696aab..4ead281d 100644 --- a/playwright/.env.template +++ b/playwright/.env.template @@ -21,11 +21,19 @@ TEST_USER3=test3 TEST_USER3_PASSWORD=${TEST_USER3} TEST_USER3_MAIL=${TEST_USER3}@yopmail.com +TEST_USER4=test4 +TEST_USER4_PASSWORD=${TEST_USER4} +TEST_USER4_MAIL=${TEST_USER4}@yopmail.com + +TEST_USER5=test5 +TEST_USER5_PASSWORD=${TEST_USER5} +TEST_USER5_MAIL=${TEST_USER5}@yopmail.com + ################### # Keycloak Config # ################### -KEYCLOAK_ADMIN=admin -KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN} +KC_BOOTSTRAP_ADMIN_USERNAME=admin +KC_BOOTSTRAP_ADMIN_PASSWORD=${KC_BOOTSTRAP_ADMIN_USERNAME} KC_HTTP_HOST=127.0.0.1 KC_HTTP_PORT=8080 @@ -39,8 +47,10 @@ DUMMY_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${DUMMY_REALM} ###################### ROCKET_ADDRESS=0.0.0.0 ROCKET_PORT=8000 -DOMAIN=http://localhost:${ROCKET_PORT} +ROCKET_TLS={certs="/data/ssl/localhost.crt",key="/data/ssl/localhost.key"} +DOMAIN=https://127.0.0.1:${ROCKET_PORT} LOG_LEVEL=info,oidcwarden::sso=debug +SSO_DEBUG_TOKENS=true I_REALLY_WANT_VOLATILE_STORAGE=true SSO_ENABLED=true diff --git a/playwright/README.md b/playwright/README.md index a27e6105..000725d7 100644 --- a/playwright/README.md +++ b/playwright/README.md @@ -1,8 +1,8 @@ # Integration tests This allows running integration tests using [Playwright](https://playwright.dev/). - -It uses its own `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 @@ -11,11 +11,11 @@ Databases (`Mariadb`, `Mysql` and `Postgres`) and `Playwright` will run in conta ### Running Playwright outside docker -It is possible to run `Playwright` outside of the container, this removes the need to rebuild the image for each change. -You will additionally need `nodejs` then run: +It's possible to run `Playwright` outside of the container, this remove the need to rebuild the image for each change. +You'll additionally need `nodejs` then run: ```bash -npm ci --ignore-scripts +npm ci --ignore-scripts --allow-git=none --allow-remote=none npx playwright install-deps npx playwright install firefox ``` @@ -65,7 +65,7 @@ DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env run Pl If you want you can keep the DB and Keycloak runnning (states are not impacted by the tests): ```bash -PW_KEEP_SERVICE_RUNNNING=true npx playwright test +PW_KEEP_SERVICE_RUNNING=true npx playwright test ``` ### Running specific tests @@ -77,7 +77,7 @@ DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env run Pl DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env run Playwright test --project=sqlite login ``` -To run only a specifc test (It might fail if it has dependency): +To run only a specific test (It might fail if it has dependency): ```bash DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env run Playwright test --project=sqlite -g "Account creation" @@ -92,7 +92,7 @@ This does not start the server, you will need to start it manually. ```bash DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env up Vaultwarden -npx playwright codegen "http://127.0.0.1:8003" +npx playwright codegen "https://127.0.0.1:8000" --ignore-https-errors ``` ## Override web-vault @@ -112,12 +112,11 @@ You can check the result running: DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env up Vaultwarden ``` -Then check `http://127.0.0.1:8003/admin/diagnostics` with `admin`. +Then check `https://127.0.0.1:8003/admin/diagnostics` with `admin`. # OpenID Connect test setup -Additionally this `docker-compose` template allows to run locally Vaultwarden, -[Keycloak](https://www.keycloak.org/) and [Maildev](https://github.com/timshel/maildev) to test OIDC. +Additionally this `docker-compose` template allow to run locally `Vaultwarden`, [Keycloak](https://www.keycloak.org/) and [Maildev](https://github.com/timshel/maildev) to test OIDC. ## Setup @@ -131,18 +130,17 @@ Then start the stack (the `profile` is required to run `Vaultwarden`) : ```bash > docker compose --profile vaultwarden --env-file .env up .... -keycloakSetup_1 | Logging into http://127.0.0.1:8080 as user admin of realm master +keycloakSetup_1 | Logging into https://127.0.0.1:8080 as user admin of realm master keycloakSetup_1 | Created new realm with id 'test' keycloakSetup_1 | 74af4933-e386-4e64-ba15-a7b61212c45e oidc_keycloakSetup_1 exited with code 0 ``` -Wait until `oidc_keycloakSetup_1 exited with code 0` which indicates the correct setup of the Keycloak realm, client and user -(It is normal for this container to stop once the configuration is done). +Wait until `oidc_keycloakSetup_1 exited with code 0` which indicate the correct setup of the Keycloak realm, client and user (It's normal for this container to stop once the configuration is done). Then you can access : -- `Vaultwarden` on http://0.0.0.0:8000 with the default user `test@yopmail.com/test`. +- `Vaultwarden` on https://0.0.0.0:8000 with the default user `test@yopmail.com/test`. - `Keycloak` on http://0.0.0.0:8080/admin/master/console/ with the default user `admin/admin` - `Maildev` on http://0.0.0.0:1080 @@ -171,7 +169,7 @@ docker compose --profile vaultwarden --env-file .env build VaultwardenPrebuild V All configuration for `keycloak` / `Vaultwarden` / `keycloak_setup.sh` can be found in [.env](.env.template). The content of the file will be loaded as environment variables in all containers. -- `keycloak` [configuration](https://www.keycloak.org/server/all-config) includes `KEYCLOAK_ADMIN` / `KEYCLOAK_ADMIN_PASSWORD` and any variable prefixed `KC_` ([more information](https://www.keycloak.org/server/configuration#_example_configuring_the_db_url_host_parameter)). +- `keycloak` [configuration](https://www.keycloak.org/server/all-config) include `KC_BOOTSTRAP_ADMIN_USERNAME` / `KC_BOOTSTRAP_ADMIN_PASSWORD` and any variable prefixed `KC_` ([more information](https://www.keycloak.org/server/configuration#_example_configuring_the_db_url_host_parameter)). - All `Vaultwarden` configuration can be set (EX: `SMTP_*`) ## Cleanup diff --git a/playwright/compose/keycloak/setup.sh b/playwright/compose/keycloak/setup.sh index a27caaff..f1d8a303 100755 --- a/playwright/compose/keycloak/setup.sh +++ b/playwright/compose/keycloak/setup.sh @@ -17,7 +17,7 @@ done set -e -kcadm.sh config credentials --server "http://${KC_HTTP_HOST}:${KC_HTTP_PORT}" --realm master --user "$KEYCLOAK_ADMIN" --password "$KEYCLOAK_ADMIN_PASSWORD" --client admin-cli +kcadm.sh config credentials --server "http://${KC_HTTP_HOST}:${KC_HTTP_PORT}" --realm master --user "$KC_BOOTSTRAP_ADMIN_USERNAME" --password "$KC_BOOTSTRAP_ADMIN_PASSWORD" --client admin-cli kcadm.sh create realms -s realm="$TEST_REALM" -s enabled=true -s "accessTokenLifespan=600" kcadm.sh create clients -r test -s "clientId=$SSO_CLIENT_ID" -s "secret=$SSO_CLIENT_SECRET" -s "redirectUris=[\"$DOMAIN/*\"]" -i @@ -39,6 +39,6 @@ kcadm.sh create realms -s realm="$DUMMY_REALM" -s enabled=true -s "accessTokenLi # THEN in another terminal: # docker exec -it keycloakSetup-dev /bin/bash # export PATH=$PATH:/opt/keycloak/bin -# kcadm.sh config credentials --server "http://${KC_HTTP_HOST}:${KC_HTTP_PORT}" --realm master --user "$KEYCLOAK_ADMIN" --password "$KEYCLOAK_ADMIN_PASSWORD" --client admin-cli +# kcadm.sh config credentials --server "http://${KC_HTTP_HOST}:${KC_HTTP_PORT}" --realm master --user "$KC_BOOTSTRAP_ADMIN_USERNAME" --password "$KC_BOOTSTRAP_ADMIN_PASSWORD" --client admin-cli # ENJOY # Doc: https://wjw465150.gitbooks.io/keycloak-documentation/content/server_admin/topics/admin-cli.html diff --git a/playwright/compose/playwright/Dockerfile b/playwright/compose/playwright/Dockerfile index 4dae1ae4..6b48c7dc 100644 --- a/playwright/compose/playwright/Dockerfile +++ b/playwright/compose/playwright/Dockerfile @@ -28,7 +28,7 @@ RUN mkdir /playwright WORKDIR /playwright COPY package.json package-lock.json . -RUN npm ci --ignore-scripts && npx playwright install-deps && npx playwright install firefox +RUN npm ci --ignore-scripts --allow-git=none --allow-remote=none && npx playwright install-deps && npx playwright install firefox COPY docker-compose.yml test.env ./ COPY compose ./compose diff --git a/playwright/compose/warden/Dockerfile b/playwright/compose/warden/Dockerfile index e472d207..9a369dab 100644 --- a/playwright/compose/warden/Dockerfile +++ b/playwright/compose/warden/Dockerfile @@ -35,6 +35,7 @@ WORKDIR / COPY --from=prebuilt /start.sh . COPY --from=prebuilt /vaultwarden . +COPY --from=build /data ./data COPY --from=build /web-vault ./web-vault ENTRYPOINT ["/start.sh"] diff --git a/playwright/compose/warden/build.sh b/playwright/compose/warden/build.sh index 37e9a25e..ee8b47fe 100755 --- a/playwright/compose/warden/build.sh +++ b/playwright/compose/warden/build.sh @@ -22,3 +22,14 @@ if [[ ! -z "$REPO_URL" ]] && [[ ! -z "$COMMIT_HASH" ]] ; then mv build /web-vault fi + +# Lower the KDF iterations default for faster tests. +sed -i 's/(6e5,2e6,6e5)/(1e5,2e6,1e5)/' /web-vault/app/main.*.js + +# Generate a self signed cert +mkdir -p /data/ssl; cd /data/ssl + +openssl req -x509 -out localhost.crt -keyout localhost.key \ + -newkey rsa:2048 -nodes -sha256 \ + -subj '/CN=localhost' -extensions EXT -config <( \ + printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth") diff --git a/playwright/docker-compose.yml b/playwright/docker-compose.yml index f4402326..5dd04ff4 100644 --- a/playwright/docker-compose.yml +++ b/playwright/docker-compose.yml @@ -24,12 +24,15 @@ services: environment: - ADMIN_TOKEN - DATABASE_URL + - CLIENT_SUPPRESS_ONBOARDING + - EMAIL_2FA_AUTO_FALLBACK - I_REALLY_WANT_VOLATILE_STORAGE - LOG_LEVEL - LOGIN_RATELIMIT_MAX_BURST - SMTP_HOST - SMTP_FROM - SMTP_DEBUG + - SSO_AUTH_ONLY_NOT_SESSION - SSO_DEBUG_TOKENS - SSO_ENABLED - SSO_FRONTEND @@ -70,7 +73,7 @@ services: Mysql: profiles: ["playwright"] container_name: playwright_mysql - image: mysql:8.4.1 + image: mysql:9.7.0 env_file: test.env healthcheck: test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] @@ -82,7 +85,7 @@ services: Postgres: profiles: ["playwright"] container_name: playwright_postgres - image: postgres:16.3 + image: postgres:18.4 env_file: test.env healthcheck: test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"] @@ -94,7 +97,7 @@ services: Maildev: profiles: ["vaultwarden", "maildev"] container_name: maildev - image: timshel/maildev:3.0.4 + image: timshel/maildev:3.2.19 ports: - ${SMTP_PORT}:1025 - 1080:1080 @@ -102,7 +105,7 @@ services: Keycloak: profiles: ["keycloak", "vaultwarden"] container_name: keycloak-${ENV:-dev} - image: quay.io/keycloak/keycloak:26.3.4 + image: quay.io/keycloak/keycloak:26.6.2 network_mode: "host" command: - start-dev @@ -112,12 +115,12 @@ services: profiles: ["keycloak", "vaultwarden"] container_name: keycloakSetup-${ENV:-dev} image: keycloak_setup-${ENV:-dev} + network_mode: "host" build: context: compose/keycloak dockerfile: Dockerfile args: - KEYCLOAK_VERSION: 26.3.4 - network_mode: "host" + KEYCLOAK_VERSION: 26.6.2 depends_on: - Keycloak restart: "no" diff --git a/playwright/global-setup.ts b/playwright/global-setup.ts index 89405f12..9959d247 100644 --- a/playwright/global-setup.ts +++ b/playwright/global-setup.ts @@ -1,4 +1,4 @@ -import { firefox, type FullConfig } from '@playwright/test'; +import { type FullConfig } from '@playwright/test'; import { execSync } from 'node:child_process'; import fs from 'fs'; diff --git a/playwright/global-utils.ts b/playwright/global-utils.ts index 224bb4b8..937de651 100644 --- a/playwright/global-utils.ts +++ b/playwright/global-utils.ts @@ -207,7 +207,7 @@ export async function startVault(browser: Browser, testInfo: TestInfo, env = {}, } export async function stopVault(force: boolean = false) { - if( force === false && process.env.PW_KEEP_SERVICE_RUNNNING === "true" ) { + if( force === false && process.env.PW_KEEP_SERVICE_RUNNING === "true" ) { console.log(`Keep vaultwarden running on: ${process.env.DOMAIN}`); } else { console.log(`Vaultwarden stopping`); @@ -231,6 +231,7 @@ export async function checkNotification(page: Page, hasText: string) { } export async function cleanLanding(page: Page) { + await page.context().clearCookies(); await page.goto('/', { waitUntil: 'domcontentloaded' }); await expect(page.getByRole('button').nth(0)).toBeVisible(); @@ -248,15 +249,3 @@ export async function logout(test: Test, page: Page, user: { name: string }) { await expect(page.getByRole('heading', { name: 'Log in' })).toBeVisible(); }); } - -export async function ignoreExtension(page: Page) { - await page.waitForLoadState('domcontentloaded'); - - try { - await page.getByRole('button', { name: 'Add it later' }).click({timeout: 5_000}); - await page.getByRole('link', { name: 'Skip to web app' }).click(); - } catch (error) { - console.log('Extension setup not visible. Continuing'); - } - -} diff --git a/playwright/package-lock.json b/playwright/package-lock.json index 2f4cd0c1..57f5bcaf 100644 --- a/playwright/package-lock.json +++ b/playwright/package-lock.json @@ -9,41 +9,56 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "mysql2": "3.15.3", - "otpauth": "9.4.1", - "pg": "8.16.3" + "mysql2": "3.22.3", + "otpauth": "9.5.1", + "pg": "8.21.0" }, "devDependencies": { - "@playwright/test": "1.56.1", - "dotenv": "17.2.3", - "dotenv-expand": "12.0.3", - "maildev": "npm:@timshel_npm/maildev@3.2.5" + "@playwright/test": "1.60.0", + "dotenv": "17.4.2", + "dotenv-expand": "13.0.0", + "maildev": "npm:@timshel_npm/maildev@3.2.19" } }, "node_modules/@asamuzakjp/css-color": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", - "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==", + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", "dev": true, "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "lru-cache": "^11.2.1" + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.3.tgz", - "integrity": "sha512-kiGFeY+Hxf5KbPpjRLf+ffWbkos1aGo8MBfd91oxS3O57RgU3XhZrt/6UzoVF9VMpWbC3v87SRc9jxGrc9qHtQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", "dev": true, "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", - "css-tree": "^3.1.0", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.2" + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@asamuzakjp/nwsapi": { @@ -52,10 +67,22 @@ "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", "dev": true }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", "dev": true, "funding": [ { @@ -68,13 +95,13 @@ } ], "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", "dev": true, "funding": [ { @@ -87,17 +114,17 @@ } ], "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.1.tgz", + "integrity": "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==", "dev": true, "funding": [ { @@ -110,21 +137,21 @@ } ], "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.2.1" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "dev": true, "funding": [ { @@ -137,16 +164,16 @@ } ], "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.15.tgz", - "integrity": "sha512-q0p6zkVq2lJnmzZVPR33doA51G7YOja+FBvRdp5ISIthL0MtFCgYHHhR563z9WFGxcOn0WfjSkPDJ5Qig3H3Sw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.4.tgz", + "integrity": "sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==", "dev": true, "funding": [ { @@ -158,14 +185,19 @@ "url": "https://opencollective.com/csstools" } ], - "engines": { - "node": ">=18" + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } } }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "dev": true, "funding": [ { @@ -178,27 +210,44 @@ } ], "engines": { - "node": ">=18" + "node": ">=20.19.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } } }, "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", "engines": { - "node": "^14.21.3 || >=16" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@playwright/test": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", - "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", "dev": true, "dependencies": { - "playwright": "1.56.1" + "playwright": "1.60.0" }, "bin": { "playwright": "cli.js" @@ -246,12 +295,11 @@ } }, "node_modules/@types/node": { - "version": "24.2.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz", - "integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==", - "dev": true, + "version": "24.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", + "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", "dependencies": { - "undici-types": "~7.10.0" + "undici-types": "~7.12.0" } }, "node_modules/@types/trusted-types": { @@ -261,6 +309,38 @@ "dev": true, "optional": true }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@zone-eu/mailsplit": { + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/@zone-eu/mailsplit/-/mailsplit-5.4.8.tgz", + "integrity": "sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA==", + "dev": true, + "dependencies": { + "libbase64": "1.3.0", + "libmime": "5.3.7", + "libqp": "2.1.1" + } + }, + "node_modules/@zone-eu/mailsplit/node_modules/libmime": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.7.tgz", + "integrity": "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==", + "dev": true, + "dependencies": { + "encoding-japanese": "2.2.0", + "iconv-lite": "0.6.3", + "libbase64": "1.3.0", + "libqp": "2.1.1" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -289,15 +369,6 @@ "integrity": "sha512-aQX7AISOMM7HFE0iZ3+YnD07oIeJqWGVnJ+ZIKaBZAk03ftmVYVqsGas/rbXKR21n4D/hKCSHypvcyOkds/xzg==", "dev": true }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -312,15 +383,6 @@ "node": ">= 6.0.0" } }, - "node_modules/base32.js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz", - "integrity": "sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", @@ -340,29 +402,33 @@ } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "dev": true, "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/body-parser/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -376,6 +442,22 @@ } } }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -421,9 +503,9 @@ } }, "node_modules/commander": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", - "integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "engines": { "node": ">=20" @@ -460,15 +542,16 @@ } }, "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "dev": true, - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -499,9 +582,9 @@ } }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "dev": true, "dependencies": { "object-assign": "^4", @@ -509,46 +592,36 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/cssstyle": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz", - "integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==", - "dev": true, - "dependencies": { - "@asamuzakjp/css-color": "^4.0.3", - "@csstools/css-syntax-patches-for-csstree": "^1.0.14", - "css-tree": "^3.1.0" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/data-urls": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", - "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "dev": true, "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/debug": { @@ -634,9 +707,9 @@ } }, "node_modules/dompurify": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", - "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.1.tgz", + "integrity": "sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==", "dev": true, "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -657,9 +730,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", "dev": true, "engines": { "node": ">=12" @@ -669,12 +742,12 @@ } }, "node_modules/dotenv-expand": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", - "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-13.0.0.tgz", + "integrity": "sha512-aBfBS8eYIeXmpHI9ThIlA7/WLq+SLt18iXUZhb52rW89QLKQFoIpPG1bPeewoPZsTyjSSO3T7234FBVUM1V2rA==", "dev": true, "dependencies": { - "dotenv": "^16.4.5" + "dotenv": "^17.4.2" }, "engines": { "node": ">=12" @@ -683,18 +756,6 @@ "url": "https://dotenvx.com" } }, - "node_modules/dotenv-expand/node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -734,20 +795,21 @@ } }, "node_modules/engine.io": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", - "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "version": "6.6.8", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.8.tgz", + "integrity": "sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g==", "dev": true, "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", + "@types/ws": "^8.5.12", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", - "debug": "~4.3.1", + "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" + "ws": "~8.20.1" }, "engines": { "node": ">=10.2.0" @@ -776,9 +838,9 @@ } }, "node_modules/engine.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -828,27 +890,6 @@ "node": ">= 0.6" } }, - "node_modules/engine.io/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -907,18 +948,19 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "dev": true, "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -949,9 +991,9 @@ } }, "node_modules/express/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -972,9 +1014,9 @@ "dev": true }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "dev": true, "dependencies": { "debug": "^4.4.0", @@ -985,13 +1027,17 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/finalhandler/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -1122,9 +1168,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -1143,15 +1189,15 @@ } }, "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "dev": true, "dependencies": { - "whatwg-encoding": "^3.1.1" + "@exodus/bytes": "^1.6.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/html-to-text": { @@ -1190,102 +1236,25 @@ } }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -1337,34 +1306,35 @@ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" }, "node_modules/jsdom": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.1.tgz", - "integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==", + "version": "29.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.0.tgz", + "integrity": "sha512-YNUc7fB9QuvSSQWfrH0xF+TyABkxUwx8sswgIDaCrw4Hol8BghdZDkITtZheRJeMtzWlnTfsM3bBBusRvpO1wg==", "dev": true, "dependencies": { - "@asamuzakjp/dom-selector": "^6.7.2", - "cssstyle": "^5.3.1", - "data-urls": "^6.0.0", + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", + "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", - "parse5": "^8.0.0", - "rrweb-cssom": "^0.8.0", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.0", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.1.0", - "ws": "^8.18.3", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" }, "peerDependencies": { "canvas": "^3.0.0" @@ -1391,17 +1361,33 @@ "dev": true }, "node_modules/libmime": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.7.tgz", - "integrity": "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.8.tgz", + "integrity": "sha512-ZrCY+Q66mPvasAfjsQ/IgahzoBvfE1VdtGRpo1hwRB1oK3wJKxhKA3GOcd2a6j7AH5eMFccxK9fBoCpRZTf8ng==", "dev": true, "dependencies": { "encoding-japanese": "2.2.0", - "iconv-lite": "0.6.3", + "iconv-lite": "0.7.2", "libbase64": "1.3.0", "libqp": "2.1.1" } }, + "node_modules/libmime/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/libqp": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/libqp/-/libqp-2.1.1.tgz", @@ -1423,18 +1409,18 @@ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" }, "node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz", + "integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==", "dev": true, "engines": { "node": "20 || >=22" } }, "node_modules/lru.min": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", - "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", "engines": { "bun": ">=1.0.0", "deno": ">=1.30.0", @@ -1447,25 +1433,25 @@ }, "node_modules/maildev": { "name": "@timshel_npm/maildev", - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@timshel_npm/maildev/-/maildev-3.2.5.tgz", - "integrity": "sha512-suWQu2s2kmO+MXtNJYW9peklznhd+aorIUb4tSNrfaKoEJjDa3vLXTvWf+3cb67o4Yv4Z6nPeKdMTCDZVn/Nyw==", + "version": "3.2.19", + "resolved": "https://registry.npmjs.org/@timshel_npm/maildev/-/maildev-3.2.19.tgz", + "integrity": "sha512-A/f07Fe7hCFy/2cUo0xg2r349RvOAHi4TuqOlZXxPuWShWbiSbvYcWr5wDz7G8aV5RyggNP0m+yB/lwmYx5OQg==", "dev": true, "dependencies": { "@types/mailparser": "3.4.6", "addressparser": "1.0.1", "async": "3.2.6", - "commander": "14.0.1", + "commander": "14.0.3", "compression": "1.8.1", - "cors": "2.8.5", - "dompurify": "3.3.0", - "express": "5.1.0", - "jsdom": "27.0.1", - "mailparser": "3.7.5", + "cors": "2.8.6", + "dompurify": "3.4.1", + "express": "5.2.1", + "jsdom": "29.1.0", + "mailparser": "3.9.8", "mime": "4.1.0", - "nodemailer": "7.0.9", - "smtp-server": "3.15.0", - "socket.io": "4.8.1", + "nodemailer": "8.0.7", + "smtp-server": "3.18.4", + "socket.io": "4.8.3", "wildstring": "1.0.9" }, "bin": { @@ -1476,27 +1462,27 @@ } }, "node_modules/mailparser": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.7.5.tgz", - "integrity": "sha512-o59RgZC+4SyCOn4xRH1mtRiZ1PbEmi6si6Ufnd3tbX/V9zmZN1qcqu8xbXY62H6CwIclOT3ppm5u/wV2nujn4g==", + "version": "3.9.8", + "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.9.8.tgz", + "integrity": "sha512-7jSlFGXiianVnhnb6wdutJFloD34488nrHY7r6FNqwXAhZ7YiJDYrKKTxZJ0oSrXcAPHm8YoYnh97xyGtrBQ3w==", "dev": true, "dependencies": { + "@zone-eu/mailsplit": "5.4.8", "encoding-japanese": "2.2.0", "he": "1.2.0", "html-to-text": "9.0.5", - "iconv-lite": "0.7.0", - "libmime": "5.3.7", + "iconv-lite": "0.7.2", + "libmime": "5.3.8", "linkify-it": "5.0.0", - "mailsplit": "5.4.6", - "nodemailer": "7.0.9", + "nodemailer": "8.0.5", "punycode.js": "2.3.1", - "tlds": "1.260.0" + "tlds": "1.261.0" } }, "node_modules/mailparser/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -1509,16 +1495,13 @@ "url": "https://opencollective.com/express" } }, - "node_modules/mailsplit": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.6.tgz", - "integrity": "sha512-M+cqmzaPG/mEiCDmqQUz8L177JZLZmXAUpq38owtpq2xlXlTSw+kntnxRt2xsxVFFV6+T8Mj/U0l5s7s6e0rNw==", - "deprecated": "This package has been renamed to @zone-eu/mailsplit. Please update your dependencies.", + "node_modules/mailparser/node_modules/nodemailer": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.5.tgz", + "integrity": "sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==", "dev": true, - "dependencies": { - "libbase64": "1.3.0", - "libmime": "5.3.7", - "libqp": "2.1.1" + "engines": { + "node": ">=6.0.0" } }, "node_modules/math-intrinsics": { @@ -1531,9 +1514,9 @@ } }, "node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true }, "node_modules/media-typer": { @@ -1582,15 +1565,19 @@ } }, "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ms": { @@ -1600,28 +1587,30 @@ "dev": true }, "node_modules/mysql2": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", - "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.3.tgz", + "integrity": "sha512-uWWxvZSRvRhtBdh2CdcuK83YcOfPdmEeEYB069bAmPnV93QApDGVPuvCQOLjlh7tYHEWdgQPrn6kosDxHBVLkA==", "dependencies": { - "aws-ssl-profiles": "^1.1.1", + "aws-ssl-profiles": "^1.1.2", "denque": "^2.1.0", "generate-function": "^2.3.1", - "iconv-lite": "^0.7.0", - "long": "^5.2.1", - "lru.min": "^1.0.0", - "named-placeholders": "^1.1.3", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" + "iconv-lite": "^0.7.2", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.3.3" }, "engines": { "node": ">= 8.0" + }, + "peerDependencies": { + "@types/node": ">= 8" } }, "node_modules/mysql2/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -1634,22 +1623,14 @@ } }, "node_modules/named-placeholders": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", - "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", "dependencies": { - "lru-cache": "^7.14.1" + "lru.min": "^1.1.0" }, "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/named-placeholders/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "engines": { - "node": ">=12" + "node": ">=8.0.0" } }, "node_modules/negotiator": { @@ -1662,9 +1643,9 @@ } }, "node_modules/nodemailer": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.9.tgz", - "integrity": "sha512-9/Qm0qXIByEP8lEV2qOqcAW7bRpL8CR9jcTwk3NBnHJNmP9fIJ86g2fgmIXqHY+nj55ZEMwWqYAT2QTDpRUYiQ==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.7.tgz", + "integrity": "sha512-pkjE4mkBzQjdJT4/UmlKl3pX0rC9fZmjh7c6C9o7lv66Ac6w9WCnzPzhbPNxwZAzlF4mdq4CSWB5+FbK6FWCow==", "dev": true, "engines": { "node": ">=6.0.0" @@ -1722,35 +1703,35 @@ } }, "node_modules/otpauth": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.4.1.tgz", - "integrity": "sha512-+iVvys36CFsyXEqfNftQm1II7SW23W1wx9RwNk0Cd97lbvorqAhBDksb/0bYry087QMxjiuBS0wokdoZ0iUeAw==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.5.1.tgz", + "integrity": "sha512-fJmDAHc8wImfqqqOXIlBvT1dEKrZK0Cmb2VEgScpNTolCz0PHh6ExUZGv4sLtOsWNaHCQlD+rRqaPgnoxFoZjQ==", "dependencies": { - "@noble/hashes": "1.8.0" + "@noble/hashes": "2.2.0" }, "funding": { "url": "https://github.com/hectorm/otpauth?sponsor=1" } }, "node_modules/parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", - "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", "dev": true, "dependencies": { - "entities": "^6.0.0" + "entities": "^8.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", "dev": true, "engines": { - "node": ">=0.12" + "node": ">=20.19.0" }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" @@ -1779,12 +1760,13 @@ } }, "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "dev": true, - "engines": { - "node": ">=16" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/peberminta": { @@ -1797,13 +1779,13 @@ } }, "node_modules/pg": { - "version": "8.16.3", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", - "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", + "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", "dependencies": { - "pg-connection-string": "^2.9.1", - "pg-pool": "^3.10.1", - "pg-protocol": "^1.10.3", + "pg-connection-string": "^2.13.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.14.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, @@ -1811,7 +1793,7 @@ "node": ">= 16.0.0" }, "optionalDependencies": { - "pg-cloudflare": "^1.2.7" + "pg-cloudflare": "^1.4.0" }, "peerDependencies": { "pg-native": ">=3.0.1" @@ -1823,15 +1805,15 @@ } }, "node_modules/pg-cloudflare": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", - "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", "optional": true }, "node_modules/pg-connection-string": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", - "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==" + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", + "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==" }, "node_modules/pg-int8": { "version": "1.0.1", @@ -1842,17 +1824,17 @@ } }, "node_modules/pg-pool": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", - "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", - "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==" + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", + "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==" }, "node_modules/pg-types": { "version": "2.2.0", @@ -1878,12 +1860,12 @@ } }, "node_modules/playwright": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", - "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", "dev": true, "dependencies": { - "playwright-core": "1.56.1" + "playwright-core": "1.60.0" }, "bin": { "playwright": "cli.js" @@ -1896,9 +1878,9 @@ } }, "node_modules/playwright-core": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", - "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -1974,9 +1956,9 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "dev": true, "dependencies": { "side-channel": "^1.1.0" @@ -1998,18 +1980,34 @@ } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "dev": true, "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/require-from-string": { @@ -2038,9 +2036,9 @@ } }, "node_modules/router/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -2060,12 +2058,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2116,31 +2108,35 @@ } }, "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "dev": true, "dependencies": { - "debug": "^4.3.5", + "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "statuses": "^2.0.2" }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/send/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -2160,15 +2156,10 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" - }, "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "dev": true, "dependencies": { "encodeurl": "^2.0.0", @@ -2178,6 +2169,10 @@ }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { @@ -2206,13 +2201,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -2259,30 +2254,38 @@ } }, "node_modules/smtp-server": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/smtp-server/-/smtp-server-3.15.0.tgz", - "integrity": "sha512-yv945vk0/xcukSKAoIhGz6GOlcXoCyGQH2w9IlLrTKk3SJiOBH9bcO6tD0ILTZYJsMqRa6OTRZAyqeuLXkv59Q==", + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/smtp-server/-/smtp-server-3.18.4.tgz", + "integrity": "sha512-9EnXPG4Tv+2P/TSEUdFTduYn9IxtxNRsOq/ryVj8ZlT+6MU2um9gn2Td2hHlgH1n+saagMWtici3hn5J5PhU+g==", "dev": true, "dependencies": { - "base32.js": "0.1.0", "ipv6-normalize": "1.0.1", - "nodemailer": "7.0.9", + "nodemailer": "8.0.5", "punycode.js": "2.3.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.18.0" + } + }, + "node_modules/smtp-server/node_modules/nodemailer": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.5.tgz", + "integrity": "sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==", + "dev": true, + "engines": { + "node": ">=6.0.0" } }, "node_modules/socket.io": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", - "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", "dev": true, "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", - "debug": "~4.3.2", + "debug": "~4.4.1", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" @@ -2292,19 +2295,19 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", - "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.7.tgz", + "integrity": "sha512-e0LyK91f3cUxTmv95/KzoLg47+zF+s/sbxRGDNsyG4dmIP8ZSX8ax6byOxfJXeNNtS/8AZlfD+uP7gBeR7DLlg==", "dev": true, "dependencies": { - "debug": "~4.3.4", - "ws": "~8.17.1" + "debug": "~4.4.1", + "ws": "~8.20.1" } }, "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -2324,44 +2327,23 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/socket.io-adapter/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", "dev": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "debug": "~4.4.1" }, "engines": { "node": ">=10.0.0" } }, "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -2395,9 +2377,9 @@ } }, "node_modules/socket.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -2464,12 +2446,18 @@ "node": ">= 10.x" } }, - "node_modules/sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "node_modules/sql-escaper": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz", + "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==", "engines": { - "node": ">= 0.6" + "bun": ">=1.0.0", + "deno": ">=2.0.0", + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" } }, "node_modules/statuses": { @@ -2488,30 +2476,30 @@ "dev": true }, "node_modules/tlds": { - "version": "1.260.0", - "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.260.0.tgz", - "integrity": "sha512-78+28EWBhCEE7qlyaHA9OR3IPvbCLiDh3Ckla593TksfFc9vfTsgvH7eS+dr3o9qr31gwGbogcI16yN91PoRjQ==", + "version": "1.261.0", + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz", + "integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==", "dev": true, "bin": { "tlds": "bin.js" } }, "node_modules/tldts": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz", - "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==", + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz", + "integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==", "dev": true, "dependencies": { - "tldts-core": "^7.0.17" + "tldts-core": "^7.0.30" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz", - "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==", + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz", + "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", "dev": true }, "node_modules/toidentifier": { @@ -2524,9 +2512,9 @@ } }, "node_modules/tough-cookie": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", - "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", "dev": true, "dependencies": { "tldts": "^7.0.5" @@ -2548,17 +2536,34 @@ } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "dev": true, "dependencies": { - "content-type": "^1.0.5", + "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/uc.micro": { @@ -2567,11 +2572,19 @@ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true }, + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "dev": true, + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", - "dev": true + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==" }, "node_modules/unpipe": { "version": "1.0.0", @@ -2604,46 +2617,35 @@ } }, "node_modules/webidl-conversions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", - "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, "engines": { "node": ">=20" } }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dev": true, - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", "dev": true, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/whatwg-url": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", - "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", "dev": true, "dependencies": { + "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", - "webidl-conversions": "^8.0.0" + "webidl-conversions": "^8.0.1" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/wildstring": { @@ -2659,9 +2661,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "dev": true, "engines": { "node": ">=10.0.0" diff --git a/playwright/package.json b/playwright/package.json index f47ec5dc..a7a35734 100644 --- a/playwright/package.json +++ b/playwright/package.json @@ -8,14 +8,14 @@ "author": "", "license": "ISC", "devDependencies": { - "@playwright/test": "1.56.1", - "dotenv": "17.2.3", - "dotenv-expand": "12.0.3", - "maildev": "npm:@timshel_npm/maildev@3.2.5" + "@playwright/test": "1.60.0", + "dotenv": "17.4.2", + "dotenv-expand": "13.0.0", + "maildev": "npm:@timshel_npm/maildev@3.2.19" }, "dependencies": { - "mysql2": "3.15.3", - "otpauth": "9.4.1", - "pg": "8.16.3" + "mysql2": "3.22.3", + "otpauth": "9.5.1", + "pg": "8.21.0" } } diff --git a/playwright/playwright.config.ts b/playwright/playwright.config.ts index de721aa3..ba5885d9 100644 --- a/playwright/playwright.config.ts +++ b/playwright/playwright.config.ts @@ -25,10 +25,12 @@ export default defineConfig({ /* Long global timeout for complex tests * But short action/nav/expect timeouts to fail on specific step (raise locally if not enough). */ - timeout: 120 * 1000, - actionTimeout: 20 * 1000, - navigationTimeout: 20 * 1000, - expect: { timeout: 20 * 1000 }, + timeout: 240 * 1000, + actionTimeout: 40 * 1000, + navigationTimeout: 40 * 1000, + expect: { timeout: 40 * 1000 }, + + "permissions": ["clipboard-read"], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { @@ -37,6 +39,10 @@ export default defineConfig({ browserName: 'firefox', locale: 'en-GB', timezoneId: 'Europe/London', + ignoreHTTPSErrors: true, + launchOptions: { + args: ['--ignore-certificate-errors'] + }, /* Always collect trace (other values add random test failures) See https://playwright.dev/docs/trace-viewer */ trace: 'on', diff --git a/playwright/test.env b/playwright/test.env index df182ebe..2260f860 100644 --- a/playwright/test.env +++ b/playwright/test.env @@ -10,7 +10,7 @@ DOCKER_BUILDKIT=1 ##################### # Playwright Config # ##################### -PW_KEEP_SERVICE_RUNNNING=${PW_KEEP_SERVICE_RUNNNING:-false} +PW_KEEP_SERVICE_RUNNING=${PW_KEEP_SERVICE_RUNNING:-false} PW_SMTP_FROM=vaultwarden@playwright.test ##################### @@ -38,8 +38,8 @@ TEST_USER3_MAIL=${TEST_USER3}@example.com ################### # Keycloak Config # ################### -KEYCLOAK_ADMIN=admin -KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN} +KC_BOOTSTRAP_ADMIN_USERNAME=admin +KC_BOOTSTRAP_ADMIN_PASSWORD=${KC_BOOTSTRAP_ADMIN_USERNAME} KC_HTTP_HOST=127.0.0.1 KC_HTTP_PORT=8081 @@ -52,10 +52,12 @@ DUMMY_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${DUMMY_REALM} # Vaultwarden Config # ###################### ROCKET_PORT=8003 -DOMAIN=http://localhost:${ROCKET_PORT} +ROCKET_TLS={certs="/data/ssl/localhost.crt",key="/data/ssl/localhost.key"} +DOMAIN=https://127.0.0.1:${ROCKET_PORT} LOG_LEVEL=info,oidcwarden::sso=debug LOGIN_RATELIMIT_MAX_BURST=100 ADMIN_TOKEN=admin +CLIENT_SUPPRESS_ONBOARDING=true SMTP_SECURITY=off SMTP_PORT=${MAILDEV_SMTP_PORT} diff --git a/playwright/tests/collection.spec.ts b/playwright/tests/collection.spec.ts index 786a4644..867386a5 100644 --- a/playwright/tests/collection.spec.ts +++ b/playwright/tests/collection.spec.ts @@ -1,6 +1,8 @@ import { test, expect, type TestInfo } from '@playwright/test'; import * as utils from "../global-utils"; + +import * as orgs from './setups/orgs'; import { createAccount } from './setups/user'; let users = utils.loadEnv(); @@ -16,20 +18,12 @@ test.afterAll('Teardown', async ({}) => { test('Create', async ({ page }) => { await createAccount(test, page, users.user1); - await test.step('Create Org', async () => { - await page.getByRole('link', { name: 'New organisation' }).click(); - await page.getByLabel('Organisation name (required)').fill('Test'); - await page.getByRole('button', { name: 'Submit' }).click(); - await page.locator('div').filter({ hasText: 'Members' }).nth(2).click(); - - await utils.checkNotification(page, 'Organisation created'); - }); + await orgs.create(test, page, 'New organisation'); await test.step('Create Collection', async () => { - await page.getByRole('link', { name: 'Collections' }).click(); - await page.getByRole('button', { name: 'New' }).click(); + await page.getByRole('button', { name: 'New', exact: true }).click(); await page.getByRole('menuitem', { name: 'Collection' }).click(); - await page.getByLabel('Name (required)').fill('RandomCollec'); + await page.getByRole('textbox', { name: 'Name * (required)', exact: true }).fill('RandomCollec'); await page.getByRole('button', { name: 'Save' }).click(); await utils.checkNotification(page, 'Created collection RandomCollec'); await expect(page.getByRole('button', { name: 'RandomCollec' })).toBeVisible(); diff --git a/playwright/tests/cyphers.spec.ts b/playwright/tests/cyphers.spec.ts new file mode 100644 index 00000000..679874de --- /dev/null +++ b/playwright/tests/cyphers.spec.ts @@ -0,0 +1,56 @@ +import { test, expect, type Page, type TestInfo } from '@playwright/test'; +import * as OTPAuth from "otpauth"; + +import * as utils from "../global-utils"; +import { createAccount, logUser } from './setups/user'; +import { activateTOTP, disableTOTP } from './setups/2fa'; + +let users = utils.loadEnv(); +let totp; + +test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => { + await utils.startVault(browser, testInfo, {}); +}); + +test.afterAll('Teardown', async ({}) => { + utils.stopVault(); +}); + +test('Change Key settings', async ({ page }) => { + await createAccount(test, page, users.user1); + + await test.step('Change SHA-256 Iterations', async () => { + await page.getByRole('button', { name: 'Toggle collapse Settings' }).click(); + await page.getByRole('link', { name: 'Security' }).click(); + await page.getByRole('link', { name: 'Keys' }).click(); + + await page.getByRole('spinbutton', { name: 'KDF iterations * (required)'}).fill('700000'); + + await page.getByRole('button', { name: 'Update encryption settings' }).click(); + await page.getByRole('textbox', { name: 'Master password * (required)' }).fill(users.user1.password); + await page.getByRole('button', { name: 'Update settings' }).click(); + await page.getByRole('heading', { name: 'Log in' }).click(); + }); + + await logUser(test, page, users.user1); + + await test.step('Switch to Argon2', async () => { + await page.getByRole('button', { name: 'Toggle collapse Settings' }).click(); + await page.getByRole('link', { name: 'Security' }).click(); + await page.getByRole('link', { name: 'Keys' }).click(); + + await page.locator('.ng-arrow-wrapper').click(); + await page.getByText('Argon2id').click(); + + await page.getByRole('spinbutton', { name: 'KDF memory (MB) * (required)'}).fill('16'); + await page.getByRole('spinbutton', { name: 'KDF iterations * (required)'}).fill('2'); + await page.getByRole('spinbutton', { name: 'KDF parallelism * (required)'}).fill('1'); + + await page.getByRole('button', { name: 'Update encryption settings' }).click(); + await page.getByRole('textbox', { name: 'Master password * (required)' }).fill(users.user1.password); + await page.getByRole('button', { name: 'Update settings' }).click(); + await page.getByRole('heading', { name: 'Log in' }).click(); + }); + + await logUser(test, page, users.user1); +}); diff --git a/playwright/tests/login.smtp.spec.ts b/playwright/tests/login.smtp.spec.ts index 87474b79..c5c4d9ba 100644 --- a/playwright/tests/login.smtp.spec.ts +++ b/playwright/tests/login.smtp.spec.ts @@ -41,13 +41,10 @@ test('Account creation', async ({ page }) => { test('Login', async ({ context, page }) => { const mailBuffer = mailserver.buffer(users.user1.email); - await logUser(test, page, users.user1, mailBuffer); + await logUser(test, page, users.user1, { mailBuffer }); await test.step('verify email', async () => { - await page.getByText('Verify your account\'s email').click(); - await expect(page.getByText('Verify your account\'s email')).toBeVisible(); - await page.getByRole('button', { name: 'Send email' }).click(); - + await page.getByRole('button', { name: "Send email" }).click(); await utils.checkNotification(page, 'Check your email inbox for a verification link'); const verify = await mailBuffer.expect((m) => m.subject === "Verify Your Email"); @@ -78,26 +75,10 @@ test('Activate 2fa', async ({ page }) => { test('2fa', async ({ page }) => { const emails = mailserver.buffer(users.user1.email); - await test.step('login', async () => { - await page.goto('/'); - - await page.getByLabel(/Email address/).fill(users.user1.email); - await page.getByRole('button', { name: 'Continue' }).click(); - await page.getByLabel('Master password').fill(users.user1.password); - await page.getByRole('button', { name: 'Log in with master password' }).click(); - - await expect(page.getByRole('heading', { name: 'Verify your Identity' })).toBeVisible(); - const code = await retrieveEmailCode(test, page, emails); - await page.getByLabel(/Verification code/).fill(code); - await page.getByRole('button', { name: 'Continue' }).click(); - - await page.getByRole('button', { name: 'Add it later' }).click(); - await page.getByRole('link', { name: 'Skip to web app' }).click(); - - await expect(page).toHaveTitle(/Vaults/); - }) - - await disableEmail(test, page, users.user1); + await logUser(test, page, users.user1, { + mailBuffer: emails, + mail2fa: true, + }); emails.close(); }); diff --git a/playwright/tests/login.spec.ts b/playwright/tests/login.spec.ts index aaac4708..194976ea 100644 --- a/playwright/tests/login.spec.ts +++ b/playwright/tests/login.spec.ts @@ -37,8 +37,8 @@ test('Authenticator 2fa', async ({ page }) => { await page.getByLabel(/Email address/).fill(users.user1.email); await page.getByRole('button', { name: 'Continue' }).click(); - await page.getByLabel('Master password').fill(users.user1.password); - await page.getByRole('button', { name: 'Log in with master password' }).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})); diff --git a/playwright/tests/organization.smtp.spec.ts b/playwright/tests/organization.smtp.spec.ts index 2be5fec1..6d0eb859 100644 --- a/playwright/tests/organization.smtp.spec.ts +++ b/playwright/tests/organization.smtp.spec.ts @@ -4,6 +4,7 @@ import { MailDev } from 'maildev'; import * as utils from '../global-utils'; import * as orgs from './setups/orgs'; import { createAccount, logUser } from './setups/user'; +import { activateTOTP } from './setups/2fa'; let users = utils.loadEnv(); @@ -20,6 +21,7 @@ test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => { await utils.startVault(browser, testInfo, { SMTP_HOST: process.env.MAILDEV_HOST, SMTP_FROM: process.env.PW_SMTP_FROM, + EMAIL_2FA_AUTO_FALLBACK: "true", }); mail1Buffer = mailServer.buffer(users.user1.email); @@ -45,7 +47,7 @@ test('Invite users', async ({ page }) => { await orgs.policies(test, page, 'Test'); await page.getByRole('button', { name: 'Account recovery' }).click(); await page.getByRole('checkbox', { name: 'Turn on' }).check(); - await page.getByRole('checkbox', { name: 'Require new members' }).check(); + await page.getByRole('checkbox', { name: 'Automatically enroll new' }).check(); await page.getByRole('button', { name: 'Save' }).click(); await utils.checkNotification(page, 'Edited policy Account recovery'); }); @@ -66,18 +68,16 @@ test('invited with new account', async ({ page }) => { await page.goto(link); await expect(page).toHaveTitle(/Create account | Vaultwarden Web/); - //await page.getByLabel('Name').fill(users.user2.name); - await page.getByLabel('Master password (required)', { exact: true }).fill(users.user2.password); - await page.getByLabel('Confirm master password (').fill(users.user2.password); + // await page.getByLabel('Name').fill(users.user2.name); + await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(users.user2.password); + await page.getByRole('textbox', { name: 'Confirm master password * (' }).fill(users.user2.password); await page.getByRole('button', { name: 'Create account' }).click(); await utils.checkNotification(page, 'Your new account has been created'); - await utils.checkNotification(page, 'Invitation accepted'); - await utils.ignoreExtension(page); - // Redirected to the vault await expect(page).toHaveTitle('Vaults | Vaultwarden Web'); // await utils.checkNotification(page, 'You have been logged in!'); + await utils.checkNotification(page, 'Successfully accepted your invitation'); }); await test.step('Check mails', async () => { @@ -100,21 +100,19 @@ test('invited with existing account', async ({ page }) => { await page.getByRole('button', { name: 'Continue' }).click(); // Unlock page - await page.getByLabel('Master password').fill(users.user3.password); - await page.getByRole('button', { name: 'Log in with master password' }).click(); - - await utils.checkNotification(page, 'Invitation accepted'); - await utils.ignoreExtension(page); + await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(users.user3.password); + await page.getByRole('button', { name: 'Log in', exact: true }).click(); // We are now in the default vault page await expect(page).toHaveTitle(/Vaultwarden Web/); + await utils.checkNotification(page, 'Successfully accepted your invitation'); await mail3Buffer.expect((m) => m.subject === 'New Device Logged In From Firefox'); await mail1Buffer.expect((m) => m.subject.includes('Invitation to Test accepted')); }); test('Confirm invited user', async ({ page }) => { - await logUser(test, page, users.user1, mail1Buffer); + await logUser(test, page, users.user1, { mailBuffer: mail1Buffer }); await orgs.members(test, page, 'Test'); await orgs.confirm(test, page, 'Test', users.user2.email); @@ -123,25 +121,26 @@ test('Confirm invited user', async ({ page }) => { }); test('Organization is visible', async ({ page }) => { - await logUser(test, page, users.user2, mail2Buffer); + await logUser(test, page, users.user2, { mailBuffer: mail2Buffer }); await page.getByRole('button', { name: 'vault: Test', exact: true }).click(); await expect(page.getByLabel('Filter: Default collection')).toBeVisible(); }); test('Recover user password', async ({ page }) => { - await logUser(test, page, users.user1, mail1Buffer); + await logUser(test, page, users.user1, { mailBuffer: mail1Buffer }); let newPassword = "TotoNewPassword"; await orgs.members(test, page, 'Test'); - await test.step(`Rrcover ${users.user2.email}`, async () => { + await test.step(`Recover ${users.user2.email}`, async () => { await expect(page.getByRole('heading', { name: 'Members' })).toBeVisible(); await page.getByRole('row').filter({hasText: users.user2.email}).getByLabel('Options').click(); await page.getByRole('menuitem', { name: 'Recover account' }).click(); - await page.getByRole('textbox', { name: 'New master password (required)', exact: true }).fill(newPassword); - await page.getByRole('textbox', { name: 'Confirm new master password (' }).fill(newPassword); - await page.getByRole('button', { name: 'Save' }).click(); - await utils.checkNotification(page, 'Password reset success'); + await page.getByRole('textbox', { name: 'New master password * (required)', exact: true }).fill(newPassword); + await page.getByRole('textbox', { name: 'Confirm new master password * (' }).fill(newPassword); + await page.getByRole('button', { name: 'Save' }).click(); + await utils.checkNotification(page, 'Account recovery success'); + await mail2Buffer.expect((m) => m.subject.includes('Master Password Has Been Changed')); }); let user2 = { @@ -149,5 +148,8 @@ test('Recover user password', async ({ page }) => { name: users.user2.name, password: newPassword, }; - await logUser(test, page, user2, mail2Buffer); + await logUser(test, page, user2, { + mailBuffer: mail2Buffer, + notNewDevice: true, + }); }); diff --git a/playwright/tests/secrets.spec.ts b/playwright/tests/secrets.spec.ts new file mode 100644 index 00000000..e229d400 --- /dev/null +++ b/playwright/tests/secrets.spec.ts @@ -0,0 +1,110 @@ +import { test, expect, type Page, type TestInfo } from '@playwright/test'; +import * as OTPAuth from "otpauth"; + +import * as utils from "../global-utils"; +import { createAccount, logUser } from './setups/user'; + +let users = utils.loadEnv(); +let totp; + +test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => { + await utils.startVault(browser, testInfo, {}); + + const context = await browser.newContext(); + const page = await context.newPage(); + await createAccount(test, page, users.user1); + await context.close(); +}); + +test.afterAll('Teardown', async ({}) => { + utils.stopVault(); +}); + +test('Password', async ({ context, page }, testInfo: TestInfo) => { + const label = 'Test Password'; + + await logUser(test, page, users.user1); + + await test.step('Create password entry', async () => { + await page.getByRole('button', { name: 'New item' }).click(); + await page.getByRole('textbox', { name: 'Item name * (required)' }).fill(label); + await page.getByRole('textbox', { name: 'Username' }).fill(users.user1.name); + await page.getByRole('textbox', { name: 'Password' }).fill(users.user1.password); + await page.getByRole('button', { name: 'Save' }).click(); + await utils.checkNotification(page, 'Item added'); + await page.getByRole('button', { name: 'Close' }).click(); + }); + + // Log again + await logUser(test, page, users.user1); + + await test.step('Check', async () => { + await page.getByRole('row').filter({ hasText: label }).getByRole('button', { name: label }).click(); + await page.getByTestId('copy-username').click(); + await utils.checkNotification(page, 'Username copied'); + expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(users.user1.name) + await page.getByTestId('copy-password').click(); + await utils.checkNotification(page, 'Password copied'); + expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(users.user1.password) + await page.getByRole('button', { name: 'Close' }).click(); + }); + + await test.step('Delete', async () => { + await page.getByRole('row').filter({ hasText: label }).getByLabel('Options').click(); + await page.getByRole('menuitem', { name: 'Delete' }).click(); + await page.getByRole('button', { name: 'Yes' }).click(); + await utils.checkNotification(page, 'Item sent to bin'); + }); + + // Log again + await logUser(test, page, users.user1); + + await test.step('Deleted', async () => { + await expect(page.getByRole('row').filter({ hasText: label })).toHaveCount(0) + }); +}); + + +test('SSH Key', async ({ context, page }, testInfo: TestInfo) => { + const label = 'Test SSH key'; + + await logUser(test, page, users.user1); + + const privateKey = await test.step('Create key entry', async () => { + await page.getByRole('button', { name: 'New', exact: true }).click(); + await page.getByRole('menuitem', { name: 'SSH key' }).click(); + await page.getByRole('textbox', { name: 'Item name * (required)' }).fill('Test SSH key'); + await page.getByRole('button', { name: 'Save' }).click(); + await utils.checkNotification(page, 'Item added'); + + await page.getByRole('button', { name: 'Copy private key' }).click(); + await utils.checkNotification(page, 'Private key copied'); + return await page.evaluate(() => navigator.clipboard.readText()); + }); + + // Log again + await logUser(test, page, users.user1); + + await test.step('Check', async () => { + await page.getByRole('row').filter({ hasText: label }).getByRole('button', { name: label }).click(); + + await page.getByRole('button', { name: 'Copy private key' }).click(); + await utils.checkNotification(page, 'Private key copied'); + expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(privateKey) + await page.getByRole('button', { name: 'Close' }).click(); + }); + + await test.step('Delete', async () => { + await page.getByRole('row').filter({ hasText: label }).getByLabel('Options').click(); + await page.getByRole('menuitem', { name: 'Delete' }).click(); + await page.getByRole('button', { name: 'Yes' }).click(); + await utils.checkNotification(page, 'Item sent to bin'); + }); + + // Log again + await logUser(test, page, users.user1); + + await test.step('Deleted', async () => { + await expect(page.getByRole('row').filter({ hasText: label })).toHaveCount(0) + }) +}); diff --git a/playwright/tests/send.spec.ts b/playwright/tests/send.spec.ts index d27c3ffc..ddb8009d 100644 --- a/playwright/tests/send.spec.ts +++ b/playwright/tests/send.spec.ts @@ -21,11 +21,11 @@ test('Send', async ({ browser, page }) => { await page.getByRole('link', { name: 'Send' }).click(); await expect(page.locator('#main-content').getByText('Send', { exact: true })).toBeVisible(); - await page.getByRole('button', { name: 'New', exact: true }).click(); + await page.getByRole('button', { name: 'New Send', exact: true }).click(); await page.getByRole('menuitem', { name: 'Text' }).click(); - await page.getByRole('textbox', { name: 'Send name (required)' }).fill('Test'); - await page.getByRole('textbox', { name: 'Text to share (required)' }).fill('test'); + await page.getByRole('textbox', { name: 'Send name * (required)' }).fill('Test'); + await page.getByRole('textbox', { name: 'Text to share * (required)' }).fill('test'); await page.getByRole('button', { name: 'Save' }).click(); await page.locator('footer').getByRole('button', { name: 'Copy link' }).click(); @@ -46,14 +46,14 @@ test('Send', async ({ browser, page }) => { await page.getByRole('link', { name: 'Send' }).click(); await expect(page.locator('#main-content').getByText('Send', { exact: true })).toBeVisible(); - await page.getByRole('button', { name: 'New', exact: true }).click(); + await page.getByRole('button', { name: 'New' }).click(); await page.getByRole('menuitem', { name: 'Text' }).click(); - await page.getByRole('textbox', { name: 'Send name (required)' }).fill('Password'); - await page.getByRole('textbox', { name: 'Text to share (required)' }).fill('password'); + await page.getByRole('textbox', { name: 'Send name * (required)' }).fill('Password'); + await page.getByRole('textbox', { name: 'Text to share * (required)' }).fill('password'); await page.getByRole('combobox', { name: 'Who can view' }).click(); await page.getByText('Anyone with a password set by you').click(); - await page.getByRole('textbox', { name: 'Password (required)' }).fill('password'); + await page.getByRole('textbox', { name: 'Password * (required)', exact: true }).fill('password'); await page.getByRole('button', { name: 'Save' }).click(); await page.locator('footer').getByRole('button', { name: 'Copy link' }).click(); @@ -64,7 +64,7 @@ test('Send', async ({ browser, page }) => { await test.step('View with password', async () => { await page2.goto(pwd_url, { waitUntil: 'domcontentloaded' }); await expect(page2.getByRole('heading', { name: 'Enter the password to view' })).toBeVisible(); - await page2.getByRole('textbox', { name: 'Password (required)' }).fill('password'); + await page2.getByRole('textbox', { name: 'Password * (required)' }).fill('password'); await page2.getByRole('button', { name: 'Continue' }).click(); await expect(page2.getByRole('heading', { name: 'View Send' })).toBeVisible(); await expect(await page2.getByRole('paragraph').filter({ hasText: 'Password' })).toBeVisible(); diff --git a/playwright/tests/setups/2fa.ts b/playwright/tests/setups/2fa.ts index d7936420..d430d053 100644 --- a/playwright/tests/setups/2fa.ts +++ b/playwright/tests/setups/2fa.ts @@ -11,10 +11,11 @@ export async function activateTOTP(test: Test, page: Page, user: { name: string, await page.getByRole('link', { name: 'Security' }).click(); await page.getByRole('link', { name: 'Two-step login' }).click(); await page.locator('bit-item').filter({ hasText: /Authenticator app/ }).getByRole('button').click(); - await page.getByLabel('Master password (required)').fill(user.password); + await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(user.password); await page.getByRole('button', { name: 'Continue' }).click(); - const secret = await page.getByLabel('Key').innerText(); + const secret = await page.getByLabel('Key', { exact: true }).innerText(); + let totp = new OTPAuth.TOTP({ secret, period: 30 }); await page.getByLabel(/Verification code/).fill(totp.generate()); @@ -33,8 +34,8 @@ export async function disableTOTP(test: Test, page: Page, user: { password: stri await page.getByRole('link', { name: 'Security' }).click(); await page.getByRole('link', { name: 'Two-step login' }).click(); await page.locator('bit-item').filter({ hasText: /Authenticator app/ }).getByRole('button').click(); - await page.getByLabel('Master password (required)').click(); - await page.getByLabel('Master password (required)').fill(user.password); + await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).click() + await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(user.password); await page.getByRole('button', { name: 'Continue' }).click(); await page.getByRole('button', { name: 'Turn off' }).click(); await page.getByRole('button', { name: 'Yes' }).click(); @@ -49,7 +50,7 @@ export async function activateEmail(test: Test, page: Page, user: { name: string await page.getByRole('link', { name: 'Security' }).click(); await page.getByRole('link', { name: 'Two-step login' }).click(); await page.locator('bit-item').filter({ hasText: 'Enter a code sent to your email' }).getByRole('button').click(); - await page.getByLabel('Master password (required)').fill(user.password); + await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(user.password); await page.getByRole('button', { name: 'Continue' }).click(); await page.getByRole('button', { name: 'Send email' }).click(); }); @@ -81,8 +82,8 @@ export async function disableEmail(test: Test, page: Page, user: { password: str await page.getByRole('link', { name: 'Security' }).click(); await page.getByRole('link', { name: 'Two-step login' }).click(); await page.locator('bit-item').filter({ hasText: 'Email' }).getByRole('button').click(); - await page.getByLabel('Master password (required)').click(); - await page.getByLabel('Master password (required)').fill(user.password); + await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).click() + await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(user.password); await page.getByRole('button', { name: 'Continue' }).click(); await page.getByRole('button', { name: 'Turn off' }).click(); await page.getByRole('button', { name: 'Yes' }).click(); diff --git a/playwright/tests/setups/admin.ts b/playwright/tests/setups/admin.ts new file mode 100644 index 00000000..354c9ee7 --- /dev/null +++ b/playwright/tests/setups/admin.ts @@ -0,0 +1,21 @@ +import { expect, type Browser, Page } from '@playwright/test'; +import * as utils from '../../global-utils'; + +utils.loadEnv(); + +export async function login(test, page: Page) { + await test.step(`Admin login`, async () => { + await page.goto('/admin'); + await page.getByRole('textbox', { name: 'Enter admin token' }).fill(process.env.ADMIN_TOKEN); + await page.getByRole('button', { name: 'Enter' }).click(); + }); +} + +export async function invite(test, page: Page, email: string) { + await test.step(`Invite user with ${email}`, async () => { + await page.getByRole('link', { name: 'Users' }).click(); + await page.getByRole('textbox', { name: 'Enter email' }).fill(email); + await page.getByRole('button', { name: 'Invite' }).click(); + await expect(page.getByRole('row', { name: email })).toHaveText(/Invited/); + }); +} diff --git a/playwright/tests/setups/db-teardown.ts b/playwright/tests/setups/db-teardown.ts index 5f753a9d..86d40ac5 100644 --- a/playwright/tests/setups/db-teardown.ts +++ b/playwright/tests/setups/db-teardown.ts @@ -5,7 +5,7 @@ const utils = require('../../global-utils'); utils.loadEnv(); test('DB teardown ?', async ({ serviceName }) => { - if( process.env.PW_KEEP_SERVICE_RUNNNING !== "true" ) { + if( process.env.PW_KEEP_SERVICE_RUNNING !== "true" ) { utils.stopComposeService(serviceName); } }); diff --git a/playwright/tests/setups/orgs.ts b/playwright/tests/setups/orgs.ts index 04d81b45..ce12c50e 100644 --- a/playwright/tests/setups/orgs.ts +++ b/playwright/tests/setups/orgs.ts @@ -3,11 +3,14 @@ import { expect, type Browser,Page } from '@playwright/test'; import * as utils from '../../global-utils'; export async function create(test, page: Page, name: string) { - await test.step('Create Org', async () => { - await page.locator('a').filter({ hasText: 'Password Manager' }).first().click(); + await test.step(`Create Org ${name}`, async () => { + let pm_locator = page.locator('a').filter({ hasText: 'Password Manager' }); + if( await pm_locator.count() > 0 ){ + pm_locator.first().click(); + } await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible(); await page.getByRole('link', { name: 'New organisation' }).click(); - await page.getByLabel('Organisation name (required)').fill(name); + await page.getByRole('textbox', { name: 'Organisation name * (required)', exact: true }).fill(name); await page.getByRole('button', { name: 'Submit' }).click(); await utils.checkNotification(page, 'Organisation created'); @@ -18,7 +21,7 @@ export async function policies(test, page: Page, name: string) { await test.step(`Navigate to ${name} policies`, async () => { await page.locator('a').filter({ hasText: 'Admin Console' }).first().click(); await page.locator('org-switcher').getByLabel(/Toggle collapse/).click(); - await page.locator('org-switcher').getByRole('link', { name: `${name}` }).first().click(); + await page.locator('org-switcher > bit-nav-group > div > bit-nav-item').filter({ hasText: `${name}` }).first().click(); await expect(page.getByRole('heading', { name: `${name} collections` })).toBeVisible(); await page.getByRole('button', { name: 'Toggle collapse Settings' }).click(); await page.getByRole('link', { name: 'Policies' }).click(); @@ -30,11 +33,11 @@ export async function members(test, page: Page, name: string) { await test.step(`Navigate to ${name} members`, async () => { await page.locator('a').filter({ hasText: 'Admin Console' }).first().click(); await page.locator('org-switcher').getByLabel(/Toggle collapse/).click(); - await page.locator('org-switcher').getByRole('link', { name: `${name}` }).first().click(); + await page.locator('org-switcher > bit-nav-group > div > bit-nav-item').filter({ hasText: `${name}` }).first().click(); await expect(page.getByRole('heading', { name: `${name} collections` })).toBeVisible(); - await page.locator('div').filter({ hasText: 'Members' }).nth(2).click(); + await page.getByRole('link', { name: 'Members' }).click(); await expect(page.getByRole('heading', { name: 'Members' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'All' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Select all' })).toBeVisible(); }); } @@ -42,13 +45,13 @@ export async function invite(test, page: Page, name: string, email: string) { await test.step(`Invite ${email}`, async () => { await expect(page.getByRole('heading', { name: 'Members' })).toBeVisible(); await page.getByRole('button', { name: 'Invite member' }).click(); - await page.getByLabel('Email (required)').fill(email); + await page.getByRole('textbox', { name: 'Email * (required)', exact: true }).fill(email); await page.getByRole('tab', { name: 'Collections' }).click(); await page.getByRole('combobox', { name: 'Permission' }).click(); await page.getByText('Edit items', { exact: true }).click(); - await page.getByLabel('Select collections').click(); - await page.getByText('Default collection').click(); - await page.getByRole('cell', { name: 'Collection', exact: true }).click(); + await page.getByRole('combobox', { name: 'Select collections' }).click(); + await page.getByLabel('Options List').getByText('Default collection').click(); + await page.getByRole('columnheader', { name: 'Collection', exact: true }).click(); await page.getByRole('button', { name: 'Save' }).click(); await utils.checkNotification(page, 'User(s) invited'); }); diff --git a/playwright/tests/setups/sso-teardown.ts b/playwright/tests/setups/sso-teardown.ts index 2899afff..22934b75 100644 --- a/playwright/tests/setups/sso-teardown.ts +++ b/playwright/tests/setups/sso-teardown.ts @@ -6,7 +6,7 @@ const utils = require('../../global-utils'); utils.loadEnv(); test('Keycloak teardown', async () => { - if( process.env.PW_KEEP_SERVICE_RUNNNING === "true" ) { + if( process.env.PW_KEEP_SERVICE_RUNNING === "true" ) { console.log("Keep Keycloak running"); } else { console.log("Keycloak stopping"); diff --git a/playwright/tests/setups/sso.ts b/playwright/tests/setups/sso.ts index 6317f8b0..0ad0cffb 100644 --- a/playwright/tests/setups/sso.ts +++ b/playwright/tests/setups/sso.ts @@ -15,11 +15,8 @@ export async function logNewUser( options: { mailBuffer?: MailBuffer } = {} ) { await test.step(`Create user ${user.name}`, async () => { - await page.context().clearCookies(); - await test.step('Landing page', async () => { await utils.cleanLanding(page); - await page.locator("input[type=email].vw-email-sso").fill(user.email); await page.getByRole('button', { name: /Use single sign-on/ }).click(); }); @@ -33,26 +30,24 @@ export async function logNewUser( await test.step('Create Vault account', async () => { await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible(); - await page.getByLabel('Master password (required)', { exact: true }).fill(user.password); - await page.getByLabel('Confirm master password (').fill(user.password); + await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(user.password); + await page.getByRole('textbox', { name: 'Confirm master password * (' }).fill(user.password); await page.getByRole('button', { name: 'Create account' }).click(); }); - await utils.checkNotification(page, 'Account successfully created!'); - await utils.checkNotification(page, 'Invitation accepted'); - - await utils.ignoreExtension(page); - await test.step('Default vault page', async () => { await expect(page).toHaveTitle(/Vaultwarden Web/); await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible(); }); + await utils.checkNotification(page, 'Account successfully created!'); + await utils.checkNotification(page, 'Invitation accepted'); + if( options.mailBuffer ){ let mailBuffer = options.mailBuffer; await test.step('Check emails', async () => { - await mailBuffer.expect((m) => m.subject === "Welcome"); await mailBuffer.expect((m) => m.subject.includes("New Device Logged")); + await mailBuffer.expect((m) => m.subject === "Welcome"); }); } }); @@ -69,16 +64,14 @@ export async function logUser( mailBuffer ?: MailBuffer, totp?: OTPAuth.TOTP, mail2fa?: boolean, + notNewDevice?: boolean, } = {} ) { let mailBuffer = options.mailBuffer; await test.step(`Log user ${user.email}`, async () => { - await page.context().clearCookies(); - await test.step('Landing page', async () => { await utils.cleanLanding(page); - await page.locator("input[type=email].vw-email-sso").fill(user.email); await page.getByRole('button', { name: /Use single sign-on/ }).click(); }); @@ -117,14 +110,12 @@ export async function logUser( await page.getByRole('button', { name: 'Unlock' }).click(); }); - await utils.ignoreExtension(page); - await test.step('Default vault page', async () => { await expect(page).toHaveTitle(/Vaultwarden Web/); await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible(); }); - if( mailBuffer ){ + if( mailBuffer && !options.notNewDevice ){ await test.step('Check email', async () => { await mailBuffer.expect((m) => m.subject.includes("New Device Logged")); }); diff --git a/playwright/tests/setups/user.ts b/playwright/tests/setups/user.ts index 395196ae..3d3990e9 100644 --- a/playwright/tests/setups/user.ts +++ b/playwright/tests/setups/user.ts @@ -3,6 +3,7 @@ import { expect, type Browser, Page } from '@playwright/test'; import { type MailBuffer } from 'maildev'; import * as utils from '../../global-utils'; +import { retrieveEmailCode } from './2fa'; export async function createAccount(test, page: Page, user: { email: string, name: string, password: string }, mailBuffer?: MailBuffer) { await test.step(`Create user ${user.name}`, async () => { @@ -17,12 +18,11 @@ export async function createAccount(test, page: Page, user: { email: string, nam await page.getByRole('button', { name: 'Continue' }).click(); // Vault finish Creation - await page.getByLabel('Master password (required)', { exact: true }).fill(user.password); - await page.getByLabel('Confirm master password (').fill(user.password); + await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(user.password); + await page.getByRole('textbox', { name: 'Confirm master password * (' }).fill(user.password); await page.getByRole('button', { name: 'Create account' }).click(); await utils.checkNotification(page, 'Your new account has been created') - await utils.ignoreExtension(page); // We are now in the default vault page await expect(page).toHaveTitle('Vaults | Vaultwarden Web'); @@ -35,7 +35,16 @@ export async function createAccount(test, page: Page, user: { email: string, nam }); } -export async function logUser(test, page: Page, user: { email: string, password: string }, mailBuffer?: MailBuffer) { +export async function logUser( + test, + page: Page, + user: { email: string, password: string }, + options: { + mailBuffer ?: MailBuffer, + mail2fa?: boolean, + notNewDevice?: boolean, + } = {} +) { await test.step(`Log user ${user.email}`, async () => { await utils.cleanLanding(page); @@ -43,16 +52,23 @@ export async function logUser(test, page: Page, user: { email: string, password: await page.getByRole('button', { name: 'Continue' }).click(); // Unlock page - await page.getByLabel('Master password').fill(user.password); - await page.getByRole('button', { name: 'Log in with master password' }).click(); + await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(user.password); + await page.getByRole('button', { name: 'Log in', exact: true }).click(); - await utils.ignoreExtension(page); + if( options.mail2fa ){ + await test.step('2FA check', async () => { + await expect(page.getByRole('heading', { name: 'Verify your Identity' })).toBeVisible(); + let code = await retrieveEmailCode(test, page, options.mailBuffer); + await page.getByLabel(/Verification code/).fill(code); + await page.getByRole('button', { name: 'Continue' }).click(); + }); + } // We are now in the default vault page await expect(page).toHaveTitle(/Vaultwarden Web/); - if( mailBuffer ){ - await mailBuffer.expect((m) => m.subject === "New Device Logged In From Firefox"); + if( options.mailBuffer && !options.notNewDevice ){ + await options.mailBuffer.expect((m) => m.subject === "New Device Logged In From Firefox"); } }); } diff --git a/playwright/tests/sso_login.smtp.spec.ts b/playwright/tests/sso_login.smtp.spec.ts index 7a615cd6..1f5c9361 100644 --- a/playwright/tests/sso_login.smtp.spec.ts +++ b/playwright/tests/sso_login.smtp.spec.ts @@ -1,6 +1,7 @@ import { test, expect, type TestInfo } from '@playwright/test'; import { MailDev } from 'maildev'; +import * as admin from "./setups/admin"; import { logNewUser, logUser } from './setups/sso'; import { activateEmail, disableEmail } from './setups/2fa'; import * as utils from "../global-utils"; @@ -19,7 +20,7 @@ test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => { await utils.startVault(browser, testInfo, { SSO_ENABLED: true, - SSO_ONLY: false, + SSO_ONLY: true, SMTP_HOST: process.env.MAILDEV_HOST, SMTP_FROM: process.env.PW_SMTP_FROM, }); @@ -32,22 +33,64 @@ test.afterAll('Teardown', async ({}) => { } }); -test('Create and activate 2FA', async ({ page }) => { +test('2FA email', async ({ page }) => { + const mailBuffer = mailserver.buffer(users.user1.email); await logNewUser(test, page, users.user1, {mailBuffer: mailBuffer}); await activateEmail(test, page, users.user1, mailBuffer); - mailBuffer.close(); -}); - -test('Log and disable', async ({ page }) => { - const mailBuffer = mailserver.buffer(users.user1.email); - - await logUser(test, page, users.user1, {mailBuffer: mailBuffer, mail2fa: true}); + await logUser(test, page, users.user1, {mailBuffer: mailBuffer, mail2fa: true, notNewDevice: true}); await disableEmail(test, page, users.user1); mailBuffer.close(); }); + + +test('Admin invite', async ({ page }) => { + const mailBuffer = mailserver.buffer(users.user2.email); + + await admin.login(test, page); + await admin.invite(test, page, users.user2.email); + + + const link = await test.step('Extract email link', async () => { + const invited = await mailBuffer.expect((m) => m.subject === "Join Vaultwarden"); + await page.setContent(invited.html); + return await page.getByTestId("invite").getAttribute("href"); + }); + + await test.step('Redirect to Keycloak', async () => { + await page.goto(link); + }); + + await test.step('Keycloak login', async () => { + await expect(page.getByRole('heading', { name: 'Sign in to your account' })).toBeVisible(); + await page.getByLabel(/Username/).fill(users.user2.name); + await page.getByLabel('Password', { exact: true }).fill(users.user2.password); + await page.getByRole('button', { name: 'Sign In' }).click(); + }); + + await test.step('Create Vault account', async () => { + await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible(); + await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(users.user2.password); + await page.getByRole('textbox', { name: 'Confirm master password * (' }).fill(users.user2.password); + await page.getByRole('button', { name: 'Create account' }).click(); + }); + + await test.step('Default vault page', async () => { + await expect(page).toHaveTitle('Vaults | Vaultwarden Web'); + + await utils.checkNotification(page, 'Account successfully created!'); + await utils.checkNotification(page, 'Invitation accepted'); + }); + + await test.step('Check mails', async () => { + await mailBuffer.expect((m) => m.subject.includes("New Device Logged")); + await mailBuffer.expect((m) => m.subject === "Welcome"); + }); + + mailBuffer.close(); +}); diff --git a/playwright/tests/sso_login.spec.ts b/playwright/tests/sso_login.spec.ts index 8a1bb9ab..e93aab14 100644 --- a/playwright/tests/sso_login.spec.ts +++ b/playwright/tests/sso_login.spec.ts @@ -33,8 +33,8 @@ test('Non SSO login', async ({ page }) => { await page.getByRole('button', { name: 'Other' }).click(); // Unlock page - await page.getByLabel('Master password').fill(users.user1.password); - await page.getByRole('button', { name: 'Log in with master password' }).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(); // We are now in the default vault page await expect(page).toHaveTitle(/Vaultwarden Web/); @@ -58,6 +58,7 @@ test('Non SSO login impossible', async ({ page, browser }, testInfo: TestInfo) = // Landing page await page.goto('/'); + await page.locator("input[type=email].vw-email-sso").fill(users.user1.email); // Check that SSO login is available await expect(page.getByRole('button', { name: /Use single sign-on/ })).toHaveCount(1); @@ -66,7 +67,6 @@ test('Non SSO login impossible', async ({ page, browser }, testInfo: TestInfo) = await expect(page.getByRole('button', { name: 'Other' })).toHaveCount(0); }); - test('No SSO login', async ({ page }, testInfo: TestInfo) => { await utils.restartVault(page, testInfo, { SSO_ENABLED: false @@ -74,12 +74,14 @@ test('No SSO login', async ({ page }, testInfo: TestInfo) => { // Landing page await page.goto('/'); + await page.getByLabel(/Email address/).fill(users.user1.email); // No SSO button (rely on a correct selector checked in previous test) + await page.getByLabel('Master password'); await expect(page.getByRole('button', { name: /Use single sign-on/ })).toHaveCount(0); // Can continue to Master password await page.getByLabel(/Email address/).fill(users.user1.email); await page.getByRole('button', { name: 'Continue' }).click(); - await expect(page.getByRole('button', { name: 'Log in with master password' })).toHaveCount(1); + await expect(page.getByRole('button', { name: 'Log in' })).toHaveCount(1); }); diff --git a/playwright/tests/sso_organization.smtp.spec.ts b/playwright/tests/sso_organization.smtp.spec.ts index 92813f72..eef4f83d 100644 --- a/playwright/tests/sso_organization.smtp.spec.ts +++ b/playwright/tests/sso_organization.smtp.spec.ts @@ -67,17 +67,16 @@ test('invited with new account', async ({ page }) => { await test.step('Create Vault account', async () => { await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible(); - await page.getByLabel('Master password (required)', { exact: true }).fill(users.user2.password); - await page.getByLabel('Confirm master password (').fill(users.user2.password); + await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(users.user2.password); + await page.getByRole('textbox', { name: 'Confirm master password * (' }).fill(users.user2.password); await page.getByRole('button', { name: 'Create account' }).click(); - - await utils.checkNotification(page, 'Account successfully created!'); - await utils.checkNotification(page, 'Invitation accepted'); - await utils.ignoreExtension(page); }); await test.step('Default vault page', async () => { await expect(page).toHaveTitle(/Vaultwarden Web/); + + await utils.checkNotification(page, 'Account successfully created!'); + await utils.checkNotification(page, 'Invitation accepted'); }); await test.step('Check mails', async () => { @@ -95,6 +94,7 @@ test('invited with existing account', async ({ page }) => { await test.step('Redirect to Keycloak', async () => { await page.goto(link); + await page.getByRole('button', { name: /Use single sign-on/ }).click(); }); await test.step('Keycloak login', async () => { @@ -108,13 +108,11 @@ test('invited with existing account', async ({ page }) => { await expect(page).toHaveTitle('Vaultwarden Web'); await page.getByLabel('Master password').fill(users.user3.password); await page.getByRole('button', { name: 'Unlock' }).click(); - - await utils.checkNotification(page, 'Invitation accepted'); - await utils.ignoreExtension(page); }); await test.step('Default vault page', async () => { await expect(page).toHaveTitle(/Vaultwarden Web/); + await utils.checkNotification(page, 'Successfully accepted your invitation'); }); await test.step('Check mails', async () => { diff --git a/playwright/tests/sso_organization.spec.ts b/playwright/tests/sso_organization.spec.ts index c1238d45..ee7e28f6 100644 --- a/playwright/tests/sso_organization.spec.ts +++ b/playwright/tests/sso_organization.spec.ts @@ -49,7 +49,7 @@ test('Organization is visible', async ({ page }) => { await expect(page.getByLabel('Filter: Default collection')).toBeVisible(); }); -test('Enforce password policy', async ({ page }) => { +test('Activate password policy', async ({ page }) => { await logUser(test, page, users.user1); await orgs.policies(test, page, '/Test'); @@ -61,16 +61,27 @@ test('Enforce password policy', async ({ page }) => { await page.getByRole('button', { name: 'Save' }).click(); await utils.checkNotification(page, 'Edited policy Master password requirements.'); }); - - await utils.logout(test, page, users.user1); - - await test.step(`Unlock trigger policy`, async () => { - await page.locator("input[type=email].vw-email-sso").fill(users.user1.email); - await page.getByRole('button', { name: 'Use single sign-on' }).click(); - - await page.getByRole('textbox', { name: 'Master password (required)' }).fill(users.user1.password); - await page.getByRole('button', { name: 'Unlock' }).click(); - - await expect(page.getByRole('heading', { name: 'Update master password' })).toBeVisible(); - }); +}); + +test('Unlock trigger policyy', async ({ page }) => { + await page.goto('/', { waitUntil: 'domcontentloaded' }); + + await page.locator("input[type=email].vw-email-sso").fill(users.user2.email); + await page.getByRole('button', { name: /Use single sign-on/ }).click(); + + await test.step('Keycloak login', async () => { + await expect(page.getByRole('heading', { name: 'Sign in to your account' })).toBeVisible(); + await page.getByLabel(/Username/).fill(users.user2.name); + await page.getByLabel('Password', { exact: true }).fill(users.user2.password); + await page.getByRole('button', { name: 'Sign In' }).click(); + }); + + await test.step('Unlock vault', async () => { + await expect(page).toHaveTitle('Vaultwarden Web'); + await expect(page.getByRole('heading', { name: 'Your vault is locked' })).toBeVisible(); + await page.getByLabel('Master password').fill(users.user2.password); + await page.getByRole('button', { name: 'Unlock' }).click(); + }); + + await expect(page.getByRole('heading', { name: 'Update master password' })).toBeVisible(); }); diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index a5ae50a4..e6a184dd 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -238,7 +238,7 @@ fn config() -> Json { "disableUserRegistration": CONFIG.is_signup_disabled(), // When enabled, this setting signals to clients that onboarding interstitials // (post-login welcome dialogs, extension install prompts, setup extension redirects, and premium upsell modals) should be suppressed - "suppressOnboardingInterstitials": false + "suppressOnboardingInterstitials": CONFIG.client_suppress_onboarding(), }, "environment": { "vault": domain, diff --git a/src/config.rs b/src/config.rs index c4457478..687e2aaf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -659,6 +659,11 @@ make_config! { events_days_retain: i64, false, option; }, + client { + /// Control whether clients onboarding interstitials are suppressed |> post-login welcome dialogs, extension install prompts, setup extension redirects, and premium upsell modals + client_suppress_onboarding: bool, true, def, false; + }, + /// Advanced settings advanced { /// Client IP header |> If not present, the remote IP is used. From b30cc08562cf59645271e0431284a88fdab6e27a Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Thu, 6 Aug 2026 20:22:12 +0200 Subject: [PATCH 03/26] Misc fixes and updates (#7558) * Update GHA and pre-commit Signed-off-by: BlackDex * Update admin diagnostics Added a check if the templates are overridden and return which specific folder, `admin`, `email` or `scss`. This way we could more quickly point users to possible outdated templates which they are using. Also updated the Support String to use some emojis so we should be able to quicker see if there is something wrong. Just checking `true` or `false` could be difficult sometimes, and sometimes what we had as `false` wasn't bad either. Also adjusted the eslint comments so it will work with the latest version of eslint. Signed-off-by: BlackDex * Fix updating collections for a cipher The newer clients expect a `cipherDetails` response on the `collections-admin` endpoints. Without it, the client will cause an error and stops handling the update correctly. This will fix this by returning the cipher json. Fixes #7545 Fixes #7546 Signed-off-by: BlackDex * Cache CSS file in a different way Currently we set a cache ttl of 24 hours, and users need to do a force refresh if there is anything changed to the CSS file. In the past we have had several issue reported which were related to a still cached CSS file. This commit will change the caching and also cache the generated CSS file in memory. Instead of letting the browser cache it for 24 hours we generate an ETag, this is just a hash of the contents. This ETag is returned by the browser during a request, and we can match this, and if so, just return a `304` `Not Modified`. If the ETag is not known, we return the new content. This should make simple refreshes by clients get updated settings or a new version of Vaultwarden which has other CSS entries get updated instantly. If a user does a hard refresh, we will not receive the ETag and the content will be served. The same goes if someone has the `reload_templates` feature enabled, since then we should not cache anyway. If someone adjust settings via the `/admin` interface, the cache will be invalidated and a new CSS will be generated. Signed-off-by: BlackDex * Fix showing events for a specific user Signed-off-by: BlackDex * Update crates and adjust code. - Updated opendal and adjusted code where needed. - Updated yubico_ng and adjusted code where needed. This version now supports using an own HttpClient and it pulls in no reqwest dependency anymore. Now it will use our own client which uses custom hickory DNS and other features. Signed-off-by: BlackDex * Update web-vault to v2026.7.0 Signed-off-by: BlackDex * Fix hadolint warnings Signed-off-by: BlackDex --------- Signed-off-by: BlackDex --- .github/workflows/hadolint.yml | 4 +- .github/workflows/release.yml | 20 +- .github/workflows/trivy.yml | 2 +- .github/workflows/typos.yml | 2 +- .github/workflows/zizmor.yml | 2 +- .pre-commit-config.yaml | 9 +- Cargo.lock | 338 ++++++++++----------- Cargo.toml | 24 +- docker/DockerSettings.yaml | 4 +- docker/Dockerfile.alpine | 21 +- docker/Dockerfile.debian | 22 +- docker/Dockerfile.j2 | 10 +- src/api/admin.rs | 31 ++ src/api/core/ciphers.rs | 6 +- src/api/core/two_factor/yubikey.rs | 57 +++- src/api/mod.rs | 2 +- src/api/web.rs | 43 ++- src/config.rs | 6 + src/db/models/event.rs | 10 +- src/error.rs | 2 +- src/static/scripts/admin.js | 3 +- src/static/scripts/admin_diagnostics.js | 36 ++- src/static/scripts/admin_organizations.js | 3 +- src/static/scripts/admin_settings.js | 1 - src/static/scripts/admin_users.js | 3 +- src/static/templates/admin/diagnostics.hbs | 10 + src/storage.rs | 4 +- src/util.rs | 38 +++ 28 files changed, 432 insertions(+), 281 deletions(-) diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml index 17151922..3111e20b 100644 --- a/.github/workflows/hadolint.yml +++ b/.github/workflows/hadolint.yml @@ -41,12 +41,12 @@ jobs: # Uses the Docker-based action (hadolint pre-bundled in ghcr.io/hadolint/hadolint:v2.14.0-debian) # so no binary is downloaded at runtime. Pinned by commit SHA for supply-chain safety. - name: Run hadolint on Dockerfile.debian - uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 + uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0 with: dockerfile: docker/Dockerfile.debian - name: Run hadolint on Dockerfile.alpine - uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 + uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0 with: dockerfile: docker/Dockerfile.alpine # End Test Dockerfiles with hadolint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0efd21db..9d15dd88 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@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 + 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@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 + 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@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: quay.io username: ${{ secrets.QUAY_USERNAME }} @@ -237,7 +237,7 @@ jobs: # Upload artifacts to Github Actions and Attest the binaries - name: Attest binaries - uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-path: vaultwarden-${{ env.NORMALIZED_ARCH }} @@ -272,7 +272,7 @@ jobs: # Login to Docker Hub - name: Login to Docker Hub - uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 + 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@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 + 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@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: quay.io username: ${{ secrets.QUAY_USERNAME }} @@ -365,7 +365,7 @@ jobs: # Attest container images - name: Attest - docker.io - ${{ matrix.base_image }} if: ${{ vars.DOCKERHUB_REPO != '' && env.DIGEST_SHA != ''}} - uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-name: ${{ vars.DOCKERHUB_REPO }} subject-digest: ${{ env.DIGEST_SHA }} @@ -373,7 +373,7 @@ jobs: - name: Attest - ghcr.io - ${{ matrix.base_image }} if: ${{ vars.GHCR_REPO != '' && env.DIGEST_SHA != ''}} - uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-name: ${{ vars.GHCR_REPO }} subject-digest: ${{ env.DIGEST_SHA }} @@ -381,7 +381,7 @@ jobs: - name: Attest - quay.io - ${{ matrix.base_image }} if: ${{ vars.QUAY_REPO != '' && env.DIGEST_SHA != ''}} - uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-name: ${{ vars.QUAY_REPO }} subject-digest: ${{ env.DIGEST_SHA }} diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index c1f42c56..942a99e9 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@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3 + uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 7c345e0a..779cd6e3 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@bee27e3a4fd1ea2111cf90ab89cd076c870fce14 # v1.48.0 + uses: crate-ci/typos@8a48f81b6c64dcfea44b3633223084c4be58ac5f # v1.49.0 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 72810c67..e1de58c3 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@6fc4b006235f201fdab3722e17240ab420d580e5 # v0.6.1 + uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 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 35a0140e..f9920696 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,9 +18,10 @@ 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: bee27e3a4fd1ea2111cf90ab89cd076c870fce14 # v1.48.0 + rev: 8a48f81b6c64dcfea44b3633223084c4be58ac5f # v1.49.0 hooks: - id: typos + always_run: true - repo: local hooks: @@ -38,8 +39,7 @@ repos: entry: cargo test language: system args: [ "--features", "sqlite,mysql,postgresql", "--" ] - types_or: [ rust, file ] - files: (Cargo.toml|Cargo.lock|rust-toolchain.toml|rustfmt.toml|.*\.rs$) + types_or: [ rust, toml ] # Cargo.lock matches toml type which is intended pass_filenames: false - id: cargo-clippy name: cargo clippy @@ -47,8 +47,7 @@ repos: entry: cargo clippy language: system args: [ "--features", "sqlite,mysql,postgresql", "--", "-D", "warnings" ] - types_or: [ rust, file ] - files: (Cargo.toml|Cargo.lock|rust-toolchain.toml|rustfmt.toml|.*\.rs$) + types_or: [ rust, toml ] # Cargo.lock matches toml type which is intended pass_filenames: false - id: check-docker-templates name: check docker templates diff --git a/Cargo.lock b/Cargo.lock index 6b20797b..21defe79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,9 +22,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -52,9 +52,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android_system_properties" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" dependencies = [ "libc", ] @@ -150,9 +150,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" +checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8" dependencies = [ "compression-codecs", "compression-core", @@ -213,7 +213,7 @@ version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "event-listener 5.4.1", + "event-listener 5.4.2", "event-listener-strategy", "pin-project-lite", ] @@ -231,7 +231,7 @@ dependencies = [ "async-task", "blocking", "cfg-if", - "event-listener 5.4.1", + "event-listener 5.4.2", "futures-lite", "rustix", ] @@ -349,9 +349,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-config" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "701418aa459dac33e50a0f8e818e5662a16bc018a6ac7423659b70f3799d67a8" +checksum = "1b180a3c8b55960db3426d8964b8745e652466a1a49fe1a2eda828046d30b5e4" dependencies = [ "aws-credential-types", "aws-runtime", @@ -369,7 +369,7 @@ dependencies = [ "bytes", "fastrand", "hex", - "http 1.4.2", + "http 1.5.0", "sha1 0.10.7", "time", "tokio", @@ -392,9 +392,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6b50a43f3ccdf331521c6d6c68b7cc9668b6e09d439ebda9569df5722324d76" +checksum = "c9007227e10b5fed2f3e0a2beff489211e2b5604c400b7a9d5d81ca9d64c24bb" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -407,7 +407,7 @@ dependencies = [ "bytes", "bytes-utils", "fastrand", - "http 1.4.2", + "http 1.5.0", "http-body 1.1.0", "percent-encoding", "pin-project-lite", @@ -417,9 +417,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.104.0" +version = "1.105.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53416d16c278234845392e38d93bd4481d2f09daa0f005a2277f0aa91f59c22" +checksum = "6ffd0fbe7873cb548a7aa60f9573c268fff94155397fd4f14dc9f1ecaaab8516" dependencies = [ "arc-swap", "aws-credential-types", @@ -436,16 +436,16 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.4.2", + "http 1.5.0", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-ssooidc" -version = "1.106.0" +version = "1.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9b706c3305ed0285d5b1b696c747aa34950f830fb03e3e6c76890f99b9f188" +checksum = "175763eb222a46377df7aa257a3bca980ab3e96703fefc8f4d0b8da6ad2e254c" dependencies = [ "arc-swap", "aws-credential-types", @@ -462,16 +462,16 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.4.2", + "http 1.5.0", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sts" -version = "1.109.0" +version = "1.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d214cdfa5bbe17f117e76a7643fadf32a5234fb597322ef8b1fb4b2f17dbbd" +checksum = "dd8b14781dfbff48984017d57167b6ea0b6471c6920ec52b44a2677c7feb3c13" dependencies = [ "arc-swap", "aws-credential-types", @@ -489,7 +489,7 @@ dependencies = [ "aws-types", "fastrand", "http 0.2.12", - "http 1.4.2", + "http 1.5.0", "regex-lite", "tracing", ] @@ -509,7 +509,7 @@ dependencies = [ "hex", "hmac 0.13.0", "http 0.2.12", - "http 1.4.2", + "http 1.5.0", "percent-encoding", "sha2 0.11.0", "time", @@ -539,7 +539,7 @@ dependencies = [ "bytes-utils", "futures-core", "futures-util", - "http 1.4.2", + "http 1.5.0", "http-body 1.1.0", "http-body-util", "percent-encoding", @@ -596,7 +596,7 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.4.2", + "http 1.5.0", "http-body 0.4.6", "http-body 1.1.0", "http-body-util", @@ -617,7 +617,7 @@ dependencies = [ "aws-smithy-types", "bytes", "http 0.2.12", - "http 1.4.2", + "http 1.5.0", "pin-project-lite", "tokio", "tracing", @@ -643,7 +643,7 @@ checksum = "7d56e0a4e53127a632224e43633b0fe045fa9e1e3cfc68b9830f1115e103f910" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", - "http 1.4.2", + "http 1.5.0", ] [[package]] @@ -656,7 +656,7 @@ dependencies = [ "bytes", "bytes-utils", "http 0.2.12", - "http 1.4.2", + "http 1.5.0", "http-body 0.4.6", "http-body 1.1.0", "http-body-util", @@ -714,6 +714,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "base64-simd" version = "0.8.0" @@ -1129,12 +1135,13 @@ dependencies = [ ] [[package]] -name = "crc32c" -version = "0.6.8" +name = "crc-fast" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +checksum = "e75b2483e97a5a7da73ac68a05b629f9c53cff58d8ed1c77866079e18b00dba5" dependencies = [ - "rustc_version", + "digest 0.10.7", + "spin 0.10.1", ] [[package]] @@ -1154,13 +1161,14 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "cron" -version = "0.15.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5877d3fbf742507b66bc2a1945106bd30dd8504019d596901ddd012a4dd01740" +checksum = "a5dcd6f69605c2956916ce24e8af637b754964c9a83f4662d3a2361654cdba09" dependencies = [ "chrono", "once_cell", - "winnow 0.6.26", + "phf 0.11.3", + "winnow 0.7.15", ] [[package]] @@ -1380,9 +1388,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" [[package]] name = "data-url" @@ -1638,13 +1646,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -1737,9 +1745,9 @@ dependencies = [ [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "elliptic-curve" @@ -1764,11 +1772,11 @@ dependencies = [ [[package]] name = "email-encoding" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6" +checksum = "420b9da095f052ea597503e39073b5b3c522f7db933fbac202d91d24492693fd" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "memchr", ] @@ -1814,11 +1822,10 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "5.4.1" +version = "5.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" dependencies = [ - "concurrent-queue", "parking", "pin-project-lite", ] @@ -1829,7 +1836,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.1", + "event-listener 5.4.2", "pin-project-lite", ] @@ -2179,7 +2186,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.2", + "http 1.5.0", "indexmap 2.14.0", "slab", "tokio", @@ -2396,9 +2403,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -2422,7 +2429,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", - "http 1.4.2", + "http 1.5.0", ] [[package]] @@ -2433,7 +2440,7 @@ checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", - "http 1.4.2", + "http 1.5.0", "http-body 1.1.0", "pin-project-lite", ] @@ -2452,9 +2459,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" dependencies = [ "typenum", ] @@ -2493,7 +2500,7 @@ dependencies = [ "futures-channel", "futures-core", "h2", - "http 1.4.2", + "http 1.5.0", "http-body 1.1.0", "httparse", "itoa", @@ -2509,10 +2516,10 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.2", + "http 1.5.0", "hyper 1.11.0", "hyper-util", - "rustls 0.23.42", + "rustls 0.23.43", "tokio", "tokio-rustls 0.26.4", "tower-service", @@ -2528,7 +2535,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.2", + "http 1.5.0", "http-body 1.1.0", "hyper 1.11.0", "ipnet", @@ -2720,9 +2727,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" dependencies = [ "serde", ] @@ -2761,9 +2768,9 @@ checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" [[package]] name = "jiff" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e184d09547b80eb7e20d141ba2fb1fbac843ca53f4cf1b31210adc4c1adc6e16" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" dependencies = [ "defmt", "jiff-core", @@ -2789,9 +2796,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "323da076b7a6faf914dc677cb05a4b907742ff7375c8322c9e7f5061e5e0e9de" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" dependencies = [ "jiff-core", "proc-macro2", @@ -2865,9 +2872,9 @@ dependencies = [ [[package]] name = "job_scheduler_ng" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217723d58ee473953675d15f11e56898a611aca8ea044d5a34eabeade99ef613" +checksum = "576b4255ab9de8ce7b81060ec54b1b7f8499dfd6c16a66c4cd4cb1ad4eba27e3" dependencies = [ "chrono", "cron", @@ -2943,18 +2950,18 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin", + "spin 0.9.9", ] [[package]] name = "lettre" -version = "0.11.22" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da65617f6cb926332d039cb578aad56178da86e128db6a1b09f4c94fa5b3349" +checksum = "f2c646bd5cc763b1087b15493e29a64be6147ba8f19342004fa52048ee596eae" dependencies = [ "async-std", "async-trait", - "base64 0.22.1", + "base64 0.23.1", "email-encoding", "email_address", "fastrand", @@ -2967,7 +2974,7 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "rustls 0.23.42", + "rustls 0.23.43", "rustls-native-certs", "serde", "socket2 0.6.5", @@ -3089,9 +3096,9 @@ dependencies = [ [[package]] name = "mea" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2640d335e7273dacdcf51044026139b2e269c3bb0dfc3f8cb3496b85e3f6a42c" +checksum = "31fc7d159de0085ab6dd7ff145a9819442cfd3d098f783263120503c3f3e58b0" dependencies = [ "slab", ] @@ -3176,7 +3183,7 @@ dependencies = [ "crossbeam-epoch", "crossbeam-utils", "equivalent", - "event-listener 5.4.1", + "event-listener 5.4.2", "futures-util", "parking_lot", "portable-atomic", @@ -3194,11 +3201,11 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.4.2", + "http 1.5.0", "httparse", "memchr", "mime", - "spin", + "spin 0.9.9", "tokio", "tokio-util", "version_check", @@ -3370,7 +3377,7 @@ dependencies = [ "base64 0.22.1", "chrono", "getrandom 0.2.17", - "http 1.4.2", + "http 1.5.0", "rand 0.8.7", "serde", "serde_json", @@ -3401,9 +3408,9 @@ dependencies = [ [[package]] name = "opendal" -version = "0.57.0" +version = "0.58.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c9c85ce253ff87225e7669979d877a20c98a06604ec9d6dd5f4473e08f1ae1" +checksum = "4f20562cc7447fcc915fc5c23df305a412ea80a733c9f2fd9e2d267e2815be6d" dependencies = [ "opendal-core", "opendal-service-fs", @@ -3412,24 +3419,22 @@ dependencies = [ [[package]] name = "opendal-core" -version = "0.57.0" +version = "0.58.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f8607c90e2c963a91467f50fb49fbc7fb3d573f88cea219ca59ccd3740b309" +checksum = "ec75551ff4cf3e57da98979f6a937aaa9ddb3915bf68cc17d03df733be6646ed" dependencies = [ "anyhow", - "base64 0.22.1", + "base64 0.23.1", "bytes", "futures", - "http 1.4.2", - "http-body 1.1.0", + "http 1.5.0", "jiff", "log", "md-5", "mea", "percent-encoding", - "quick-xml 0.39.4", + "quick-xml", "reqsign-core", - "reqwest", "serde", "serde_json", "tokio", @@ -3440,9 +3445,9 @@ dependencies = [ [[package]] name = "opendal-service-fs" -version = "0.57.0" +version = "0.58.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e89a665fef0e6bd249cf5ea47fc174b7ba892159bee4b9382528b1ca873a2c" +checksum = "826c4e17a30643b888fe983897f9a4b23b07066e1d069727a923cc8fb419a702" dependencies = [ "bytes", "log", @@ -3454,18 +3459,18 @@ dependencies = [ [[package]] name = "opendal-service-s3" -version = "0.57.0" +version = "0.58.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "313d46c9f5ae70bca26b7c3e3fbb9b639292625f28af73aa016f47e788af9deb" +checksum = "58e80cdf192d7eff05feed747894d64f81905ac4eaf132edf7ea270abdd2d663" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bytes", - "crc32c", - "http 1.4.2", + "crc-fast", + "http 1.5.0", "log", "md-5", "opendal-core", - "quick-xml 0.39.4", + "quick-xml", "reqsign-aws-v4", "reqsign-core", "reqsign-file-read-tokio", @@ -3484,7 +3489,7 @@ dependencies = [ "dyn-clone", "ed25519-dalek", "hmac 0.12.1", - "http 1.4.2", + "http 1.5.0", "itertools", "log", "oauth2", @@ -4007,16 +4012,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" -[[package]] -name = "quick-xml" -version = "0.39.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "quick-xml" version = "0.41.0" @@ -4193,9 +4188,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -4226,19 +4221,18 @@ dependencies = [ ] [[package]] -name = "reqsign-aws-v4" -version = "3.0.2" +name = "reqsign-aws-core" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9e1168fab3883ec6afed1c2e20c25b2a09f366cdb662ac3e0878ae0332d63e" +checksum = "e4af084e1f3cbf3e67e0c972765399bce54ecec804cceba46b39a8331f3c1bff" dependencies = [ - "anyhow", "bytes", "form_urlencoded", "hex", - "http 1.4.2", + "http 1.5.0", "log", "percent-encoding", - "quick-xml 0.41.0", + "quick-xml", "reqsign-core", "rust-ini", "serde", @@ -4248,19 +4242,33 @@ dependencies = [ ] [[package]] -name = "reqsign-core" +name = "reqsign-aws-v4" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514a1e0b4aa288652a3fdbda4f0a610f379cdf5374e55a37c9edd03d57ed856b" +checksum = "4ac5b3b7cefa28933792b439186459f77f19f9b6edbeab41b8b187150361a206" +dependencies = [ + "bytes", + "http 1.5.0", + "log", + "quick-xml", + "reqsign-aws-core", + "reqsign-core", + "serde", +] + +[[package]] +name = "reqsign-core" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07dd510b1e1b9b241883e483358147fb2ed2d497a7b39b065ba61eb93deceb0" dependencies = [ "anyhow", - "base64 0.22.1", + "base64 0.23.1", "bytes", - "form_urlencoded", "futures", "hex", "hmac 0.13.0", - "http 1.4.2", + "http 1.5.0", "jiff", "log", "percent-encoding", @@ -4271,9 +4279,9 @@ dependencies = [ [[package]] name = "reqsign-file-read-tokio" -version = "3.0.2" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b472a8d1f2e5a4be8ce13bb7bdf4b59e9bee613ce124aca23959ddb42176b39" +checksum = "663d9d55abd0df0830ef0ae43708297cc1371cf4e8ca91f3ac813c309cca8c98" dependencies = [ "anyhow", "reqsign-core", @@ -4291,11 +4299,10 @@ dependencies = [ "cookie", "cookie_store", "encoding_rs", - "futures-channel", "futures-core", "futures-util", "h2", - "http 1.4.2", + "http 1.5.0", "http-body 1.1.0", "http-body-util", "hyper 1.11.0", @@ -4306,7 +4313,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustls 0.23.42", + "rustls 0.23.43", "rustls-pki-types", "rustls-platform-verifier", "serde", @@ -4575,9 +4582,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.42" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "log", "once_cell", @@ -4629,7 +4636,7 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.42", + "rustls 0.23.43", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki 0.103.13", @@ -4719,9 +4726,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" dependencies = [ "dyn-clone", "ref-cast", @@ -4921,7 +4928,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.14.0", "schemars 0.9.0", - "schemars 1.2.1", + "schemars 1.2.2", "serde_core", "serde_json", "serde_with_macros", @@ -5107,6 +5114,12 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" + [[package]] name = "spinning_top" version = "0.3.0" @@ -5330,20 +5343,11 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - [[package]] name = "time" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", "libc", @@ -5424,13 +5428,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -5449,7 +5453,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.42", + "rustls 0.23.43", "tokio", ] @@ -5550,9 +5554,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ "winnow 1.0.4", ] @@ -5601,7 +5605,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.4.2", + "http 1.5.0", "http-body 1.1.0", "http-body-util", "pin-project-lite", @@ -5702,7 +5706,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.4.2", + "http 1.5.0", "httparse", "log", "rand 0.8.7", @@ -5818,9 +5822,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "value-bag" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef73bfbaf3216cb59c205d7176bee1194e0d84348979da31f4a71fefe3c2054e" +checksum = "068e763e8279de7ab94b6afebded2cb701678af094feb1c12ccb061b4783c1be" [[package]] name = "vaultwarden" @@ -5853,7 +5857,7 @@ dependencies = [ "handlebars", "hickory-resolver", "html5gum", - "http 1.4.2", + "http 1.5.0", "ipnet", "job_scheduler_ng", "jsonwebtoken", @@ -5881,7 +5885,7 @@ dependencies = [ "rocket", "rocket_ws", "rpassword", - "rustls 0.23.42", + "rustls 0.23.43", "semver", "serde", "serde_json", @@ -6392,15 +6396,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.6.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.7.15" @@ -6501,18 +6496,15 @@ dependencies = [ [[package]] name = "yubico_ng" -version = "0.15.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "228e2862e3c66f3224102d9a00d9d3646b271a05cc6c4819fea195fa8b5c00e0" +checksum = "563eb0ab41031e758446e3737231541bb4556d6af1e893e94faa09c989f794af" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "form_urlencoded", - "futures", - "hmac 0.12.1", - "rand 0.9.5", - "reqwest", - "sha1 0.10.7", - "threadpool", + "getrandom 0.4.3", + "hmac 0.13.0", + "sha1 0.11.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index db685864..f65ead29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,7 +124,7 @@ libsqlite3-sys = { version = "0.37.0", optional = true } # Crypto-related libraries rand = "0.10.2" ring = "0.17.14" -rustls = { version = "0.23.42", features = ["ring", "std"], default-features = false } +rustls = { version = "0.23.43", features = ["ring", "std"], default-features = false } subtle = "2.6.1" # UUID generation @@ -133,13 +133,13 @@ uuid = { version = "1.24.0", features = ["v4"] } # Date and time libraries chrono = { version = "0.4.45", default-features = false, features = ["clock", "serde"] } chrono-tz = "0.10.4" -time = "0.3.54" +time = "0.3.55" # Job scheduler -job_scheduler_ng = "2.4.0" +job_scheduler_ng = "2.5.0" # Data encoding library Hex/Base32/Base64 -data-encoding = "2.11.0" +data-encoding = "2.11.1" # JWT library jsonwebtoken = { version = "11.0.0", default-features = false, features = ["rust_crypto", "use_pem"] } @@ -148,7 +148,7 @@ jsonwebtoken = { version = "11.0.0", default-features = false, features = ["rust totp-lite = "2.0.1" # Yubico Library -yubico = { package = "yubico_ng", version = "0.15.0", default-features = false, features = ["online-tokio"] } +yubico_ng = { version = "1.0.0", default-features = false } # WebAuthn libraries # danger-allow-state-serialisation is needed to save the state in the db @@ -161,7 +161,7 @@ webauthn-rs-core = "0.5.5" url = "2.5.8" # Email libraries -lettre = { version = "0.11.22", default-features = false, features = [ +lettre = { version = "0.11.23", default-features = false, features = [ # Misc "tracing", "serde", @@ -231,7 +231,7 @@ pastey = "0.2.3" governor = "0.10.4" # CIDR parsing for the trusted proxies of the client IP header -ipnet = "2.12.0" +ipnet = "2.12.1" # OIDC for SSO openidconnect = { version = "4.0.1", default-features = false } @@ -256,10 +256,10 @@ rpassword = "7.5.4" grass_compiler = { version = "0.13.4", default-features = false } # File are accessed through Apache OpenDAL -opendal = { version = "0.57.0", default-features = false, features = ["services-fs"] } +opendal = { version = "0.58.1", default-features = false, features = ["services-fs"] } # For retrieving AWS credentials, including temporary SSO credentials -aws-config = { version = "1.10.0", optional = true, default-features = false, features = [ +aws-config = { version = "1.10.1", optional = true, default-features = false, features = [ "behavior-version-latest", "credentials-process", "rt-tokio", @@ -267,9 +267,9 @@ aws-config = { version = "1.10.0", optional = true, default-features = false, fe ] } aws-credential-types = { version = "1.3.0", optional = true } aws-smithy-runtime-api = { version = "1.14.0", optional = true } -http = { version = "1.4.2", optional = true } -reqsign-aws-v4 = { version = "3.0.2", optional = true } -reqsign-core = { version = "3.1.0", optional = true } +http = { version = "1.5.0", optional = true } +reqsign-aws-v4 = { version = "3.1.0", optional = true } +reqsign-core = { version = "3.2.1", optional = true } # Strip debuginfo from the release builds # The debug symbols are to provide better panic traces diff --git a/docker/DockerSettings.yaml b/docker/DockerSettings.yaml index 4a51a6b2..4c5e851b 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -1,6 +1,6 @@ --- -vault_version: "v2026.6.4" -vault_image_digest: "sha256:e7d3b31ec6a991a6bf447721ea341b4192ce5d3b920929211672fd4f3f891427" +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 diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index baa4c979..7045138d 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -19,15 +19,15 @@ # - 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.6.4 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.6.4 -# [docker.io/vaultwarden/web-vault@sha256:e7d3b31ec6a991a6bf447721ea341b4192ce5d3b920929211672fd4f3f891427] +# $ 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:e7d3b31ec6a991a6bf447721ea341b4192ce5d3b920929211672fd4f3f891427 -# [docker.io/vaultwarden/web-vault:v2026.6.4] +# $ 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:e7d3b31ec6a991a6bf447721ea341b4192ce5d3b920929211672fd4f3f891427 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 @@ -70,7 +70,7 @@ RUN echo "export CARGO_TARGET=${CARGO_BUILD_TARGET}" >> /env-cargo && \ # Output the current contents of the file cat /env-cargo -RUN source /env-cargo && \ +RUN . /env-cargo && \ rustup target add "${CARGO_TARGET}" # Copies over *only* your manifests and build files @@ -86,7 +86,7 @@ ARG DB=sqlite,mysql,postgresql,enable_mimalloc # Builds your dependencies and removes the # dummy project, except the target folder # This folder contains the compiled dependencies -RUN source /env-cargo && \ +RUN . /env-cargo && \ cargo build --features ${DB} --profile "${CARGO_PROFILE}" --target="${CARGO_TARGET}" && \ find . -not -path "./target*" -delete @@ -97,13 +97,13 @@ COPY . . ARG VW_VERSION # Builds again, this time it will be the actual source files being build -RUN source /env-cargo && \ +RUN . /env-cargo && \ # Make sure that we actually build the project by updating the src/main.rs timestamp # Also do this for build.rs to ensure the version is rechecked touch build.rs src/main.rs && \ # Create a symlink to the binary target folder to easy copy the binary in the final stage cargo build --features ${DB} --profile "${CARGO_PROFILE}" --target="${CARGO_TARGET}" && \ - if [[ "${CARGO_PROFILE}" == "dev" ]] ; then \ + if [ "${CARGO_PROFILE}" = "dev" ] ; then \ ln -vfsr "/app/target/${CARGO_TARGET}/debug" /app/target/final ; \ else \ ln -vfsr "/app/target/${CARGO_TARGET}/${CARGO_PROFILE}" /app/target/final ; \ @@ -126,6 +126,7 @@ RUN source /env-cargo && \ # To uninstall: docker run --privileged --rm tonistiigi/binfmt --uninstall 'qemu-*' # # We need to add `--platform` here, because of a podman bug: https://github.com/containers/buildah/issues/4742 +# hadolint ignore=DL3065 FROM --platform=$TARGETPLATFORM docker.io/library/alpine:3.24 ENV ROCKET_PROFILE="release" \ diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 9e2e6016..9ab02568 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -19,15 +19,15 @@ # - 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.6.4 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.6.4 -# [docker.io/vaultwarden/web-vault@sha256:e7d3b31ec6a991a6bf447721ea341b4192ce5d3b920929211672fd4f3f891427] +# $ 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:e7d3b31ec6a991a6bf447721ea341b4192ce5d3b920929211672fd4f3f891427 -# [docker.io/vaultwarden/web-vault:v2026.6.4] +# $ 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:e7d3b31ec6a991a6bf447721ea341b4192ce5d3b920929211672fd4f3f891427 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 @@ -37,6 +37,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.97.1-slim-trixie AS build +# hadolint ignore=DL3067 COPY --from=xx / / ARG TARGETARCH ARG TARGETVARIANT @@ -80,7 +81,7 @@ RUN mkdir -pv "${CARGO_HOME}" && \ RUN USER=root cargo new --bin /app WORKDIR /app -RUN source /env-cargo && \ +RUN . /env-cargo && \ rustup target add "${CARGO_TARGET}" # Copies over *only* your manifests and build files @@ -95,7 +96,7 @@ ARG DB=sqlite,mysql,postgresql # Builds your dependencies and removes the # dummy project, except the target folder # This folder contains the compiled dependencies -RUN source /env-cargo && \ +RUN . /env-cargo && \ # Configure xx-cargo for target pkg-config and Debian transitive library lookup # https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977 # https://github.com/dani-garcia/vaultwarden/discussions/7522 @@ -113,7 +114,7 @@ COPY . . ARG VW_VERSION # Builds again, this time it will be the actual source files being build -RUN source /env-cargo && \ +RUN . /env-cargo && \ # Make sure that we actually build the project by updating the src/main.rs timestamp # Also do this for build.rs to ensure the version is rechecked touch build.rs src/main.rs && \ @@ -126,7 +127,7 @@ RUN source /env-cargo && \ export XX_RUSTFLAGS; \ fi && \ PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}" && \ - if [[ "${CARGO_PROFILE}" == "dev" ]] ; then \ + if [ "${CARGO_PROFILE}" = "dev" ] ; then \ ln -vfsr "/app/target/${CARGO_TARGET}/debug" /app/target/final ; \ else \ ln -vfsr "/app/target/${CARGO_TARGET}/${CARGO_PROFILE}" /app/target/final ; \ @@ -149,6 +150,7 @@ RUN source /env-cargo && \ # To uninstall: docker run --privileged --rm tonistiigi/binfmt --uninstall 'qemu-*' # # We need to add `--platform` here, because of a podman bug: https://github.com/containers/buildah/issues/4742 +# hadolint ignore=DL3065 FROM --platform=$TARGETPLATFORM docker.io/library/debian:trixie-slim ENV ROCKET_PROFILE="release" \ diff --git a/docker/Dockerfile.j2 b/docker/Dockerfile.j2 index d8b9c8c6..633d6955 100644 --- a/docker/Dockerfile.j2 +++ b/docker/Dockerfile.j2 @@ -57,6 +57,7 @@ FROM --platform=$BUILDPLATFORM {{ build_stage_image[base].arch_image[arch] }} AS # hadolint ignore=DL3006 FROM --platform=$BUILDPLATFORM {{ build_stage_image[base].image }} AS build {% if base == "debian" %} +# hadolint ignore=DL3067 COPY --from=xx / / {% endif %} ARG TARGETARCH @@ -116,7 +117,7 @@ RUN echo "export CARGO_TARGET=${CARGO_BUILD_TARGET}" >> /env-cargo && \ cat /env-cargo {% endif %} -RUN source /env-cargo && \ +RUN . /env-cargo && \ rustup target add "${CARGO_TARGET}" # Copies over *only* your manifests and build files @@ -136,7 +137,7 @@ ARG DB=sqlite,mysql,postgresql,enable_mimalloc # Builds your dependencies and removes the # dummy project, except the target folder # This folder contains the compiled dependencies -RUN source /env-cargo && \ +RUN . /env-cargo && \ {% if base == "debian" %} {{ xx_cargo_config() }} && \ {% elif base == "alpine" %} @@ -151,7 +152,7 @@ COPY . . ARG VW_VERSION # Builds again, this time it will be the actual source files being build -RUN source /env-cargo && \ +RUN . /env-cargo && \ # Make sure that we actually build the project by updating the src/main.rs timestamp # Also do this for build.rs to ensure the version is rechecked touch build.rs src/main.rs && \ @@ -161,7 +162,7 @@ RUN source /env-cargo && \ {% elif base == "alpine" %} cargo build --features ${DB} --profile "${CARGO_PROFILE}" --target="${CARGO_TARGET}" && \ {% endif %} - if [[ "${CARGO_PROFILE}" == "dev" ]] ; then \ + if [ "${CARGO_PROFILE}" = "dev" ] ; then \ ln -vfsr "/app/target/${CARGO_TARGET}/debug" /app/target/final ; \ else \ ln -vfsr "/app/target/${CARGO_TARGET}/${CARGO_PROFILE}" /app/target/final ; \ @@ -184,6 +185,7 @@ RUN source /env-cargo && \ # To uninstall: docker run --privileged --rm tonistiigi/binfmt --uninstall 'qemu-*' # # We need to add `--platform` here, because of a podman bug: https://github.com/containers/buildah/issues/4742 +# hadolint ignore=DL3065 FROM --platform=$TARGETPLATFORM {{ runtime_stage_image[base] }} ENV ROCKET_PROFILE="release" \ diff --git a/src/api/admin.rs b/src/api/admin.rs index 7037bfb1..48f36afd 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -716,6 +716,36 @@ fn web_vault_compare(active: &str, latest: &str) -> i8 { } } +fn check_template_overrides() -> Vec<&'static str> { + let template_folder = std::path::PathBuf::from(CONFIG.templates_folder()); + let mut overrides = Vec::new(); + for folder in ["admin", "email", "scss"] { + if folder_has_hbs_files(&template_folder.join(folder)) { + overrides.push(folder); + } + } + + if folder_has_hbs_files(&template_folder) { + overrides.push("other"); + } + + overrides +} + +fn folder_has_hbs_files(dir: &std::path::Path) -> bool { + let Ok(files) = std::fs::read_dir(dir) else { + // No files in this directory at all, so we can return false + return false; + }; + + files.flatten().any(|f| { + // Validate if it is a file and if it has the `.hbs` extension and starts with a-z or 0-9 + f.file_type().is_ok_and(|t| t.is_file()) + && f.path().extension().is_some_and(|e| e.eq_ignore_ascii_case("hbs")) + && f.file_name().to_str().is_some_and(|n| n.starts_with(|c: char| c.is_ascii_alphanumeric())) + }) +} + #[get("/diagnostics")] async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResult> { use chrono::prelude::*; @@ -770,6 +800,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(", "), + "template_overrides": check_template_overrides().join(", "), "invalid_feature_flags": invalid_feature_flags, "host_arch": env::consts::ARCH, "host_os": env::consts::OS, diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 0cdae612..2b51fd0c 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -870,7 +870,7 @@ async fn put_collections_admin( headers: Headers, conn: DbConn, nt: Notify<'_>, -) -> EmptyResult { +) -> JsonResult { post_collections_admin(cipher_id, data, headers, conn, nt).await } @@ -881,7 +881,7 @@ async fn post_collections_admin( headers: Headers, conn: DbConn, nt: Notify<'_>, -) -> EmptyResult { +) -> JsonResult { let data: CollectionsAdminData = data.into_inner(); let Some(cipher) = Cipher::find_by_uuid(&cipher_id, &conn).await else { @@ -940,7 +940,7 @@ async fn post_collections_admin( ) .await; - Ok(()) + Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::Organization, &conn).await?)) } #[derive(Deserialize)] diff --git a/src/api/core/two_factor/yubikey.rs b/src/api/core/two_factor/yubikey.rs index 08e8d269..eb3d6dfd 100644 --- a/src/api/core/two_factor/yubikey.rs +++ b/src/api/core/two_factor/yubikey.rs @@ -1,6 +1,10 @@ use rocket::{Route, serde::json::Json}; use serde_json::Value; -use yubico::{config::Config, verify_async}; +use yubico_ng::{ + Verifier, YubicoError, + config::Config, + transport::{AsyncTransport, Response}, +}; use crate::{ CONFIG, @@ -14,12 +18,39 @@ use crate::{ models::{EventType, TwoFactor, TwoFactorType}, }, error::{Error, MapResult}, + http_client, }; pub fn routes() -> Vec { routes![generate_yubikey, activate_yubikey, activate_yubikey_put,] } +struct HttpClientTransport { + client: reqwest::Client, +} + +impl HttpClientTransport { + fn new() -> Result { + http_client::get_reqwest_client_builder(false).redirect(reqwest::redirect::Policy::none()).build().map( + |client| Self { + client, + }, + ) + } +} + +impl AsyncTransport for HttpClientTransport { + type Error = YubicoError; + + async fn yubico_get(&self, url: &str) -> Result { + let response = self.client.get(url).send().await.map_err(YubicoError::transport)?; + Ok(Response { + status: response.status().as_u16(), + body: response.text().await.map_err(YubicoError::transport)?, + }) + } +} + #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] struct EnableYubikeyData { @@ -44,8 +75,7 @@ pub struct YubikeyMetadata { fn parse_yubikeys(data: &EnableYubikeyData) -> Vec { let data_keys = [&data.key1, &data.key2, &data.key3, &data.key4, &data.key5]; - - data_keys.into_iter().flatten().cloned().collect() + data_keys.into_iter().flatten().filter(|e| !e.is_empty()).cloned().collect() } fn jsonify_yubikeys(yubikeys: Vec) -> Value { @@ -73,13 +103,15 @@ fn get_yubico_credentials() -> Result<(String, String), Error> { async fn verify_yubikey_otp(otp: String) -> EmptyResult { let (yubico_id, yubico_secret) = get_yubico_credentials()?; - let config = Config::default().set_client_id(yubico_id).set_key(yubico_secret); - - match CONFIG.yubico_server() { - Some(server) => verify_async(otp, config.set_api_hosts(vec![server])).await, - None => verify_async(otp, config).await, + let mut config = Config::default().set_client_id(yubico_id).set_key(yubico_secret)?; + if let Some(yubico_server) = CONFIG.yubico_server() { + config = config.set_api_host(yubico_server); } - .map_res("Failed to verify OTP") + + let client = HttpClientTransport::new()?; + let verifier = Verifier::with_client(config, client)?; + + verifier.verify(otp).await.map_res("Failed to verify OTP") } #[post("/two-factor/get-yubikey", data = "")] @@ -137,10 +169,9 @@ async fn activate_yubikey(data: Json, headers: Headers, conn: let yubikeys = parse_yubikeys(&data); if yubikeys.is_empty() { - return Ok(Json(json!({ - "enabled": false, - "object": "twoFactorU2f", - }))); + // Return an error to prevent saving empty keys which would cause users not being able to login anymore. + // To remove all keys users should click the `Deactivate all keys` button + err!("A key is required."); } // Ensure they are valid OTPs diff --git a/src/api/mod.rs b/src/api/mod.rs index 05c4215d..9a79ce95 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -30,7 +30,7 @@ pub use crate::api::{ }, web::catchers as web_catchers, web::routes as web_routes, - web::static_files, + web::{invalidate_css_cache, static_files}, }; use crate::{ CONFIG, diff --git a/src/api/web.rs b/src/api/web.rs index 5bd4c85d..a7eca9fc 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -1,4 +1,7 @@ -use std::path::{Path, PathBuf}; +use std::{ + path::{Path, PathBuf}, + sync::{Arc, RwLock}, +}; use rocket::{ Catcher, Route, @@ -13,12 +16,13 @@ use crate::{ CONFIG, api::{ApiResult, EmptyResult, core::now}, auth::decode_file_download, + crypto::sha256_hex, db::{ DbConn, models::{AttachmentId, CipherId}, }, error::Error, - util::Cached, + util::{Cached, EtagCached}, }; pub fn routes() -> Vec { @@ -63,8 +67,27 @@ fn not_found() -> ApiResult> { Ok(Html(text)) } +struct CssCache { + css: String, + etag: String, +} + +static CSS_CACHE: RwLock>> = RwLock::new(None); + +pub fn invalidate_css_cache() { + *CSS_CACHE.write().unwrap() = None; +} + #[get("/css/vaultwarden.css")] -fn vaultwarden_css() -> Cached> { +fn vaultwarden_css() -> EtagCached> { + // If reload_templates is false, and we already have the CSS Cached, return this + if !CONFIG.reload_templates() + && let Some(cached) = CSS_CACHE.read().unwrap().as_ref() + { + return EtagCached::new(Css(cached.css.clone()), &cached.etag); + } + + // Else, there is either no cache, or reload_templates is true and we need to rebuild the CSS let css_options = json!({ "emergency_access_allowed": CONFIG.emergency_access_allowed(), "load_user_scss": true, @@ -112,8 +135,18 @@ fn vaultwarden_css() -> Cached> { } }; - // Cache for one day should be enough and not too much - Cached::ttl(Css(css), 86_400, false) + let etag = sha256_hex(css.as_bytes()); + let cached = Arc::new(CssCache { + css, + etag, + }); + + if !CONFIG.reload_templates() { + *CSS_CACHE.write().unwrap() = Some(Arc::clone(&cached)); + } + + // Etag Caching will let the browser send us an etag to verify and send new content if needed + EtagCached::new(Css(cached.css.clone()), &cached.etag) } #[get("/")] diff --git a/src/config.rs b/src/config.rs index 687e2aaf..d5b50146 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1506,6 +1506,9 @@ impl Config { let operator = storage::operator_for_path(&CONFIG_FILE_PARENT_DIR)?; operator.write(&CONFIG_FILENAME, config_str).await?; + // Invalidate CSS Cache because several config items might have impact on the rendered CSS + crate::api::invalidate_css_cache(); + Ok(()) } @@ -1588,6 +1591,9 @@ impl Config { writer._overrides = Vec::new(); } + // Invalidate CSS Cache because several config items might have impact on the rendered CSS + crate::api::invalidate_css_cache(); + Ok(()) } diff --git a/src/db/models/event.rs b/src/db/models/event.rs index 3a6b610c..86cbf5d0 100644 --- a/src/db/models/event.rs +++ b/src/db/models/event.rs @@ -298,12 +298,16 @@ impl Event { ) -> Vec { conn.run(move |conn| { event::table - .inner_join(users_organizations::table.on(users_organizations::uuid.eq(member_uuid))) + .inner_join( + users_organizations::table + .on(users_organizations::uuid.eq(member_uuid).and(users_organizations::org_uuid.eq(org_uuid))), + ) .filter(event::org_uuid.eq(org_uuid)) .filter(event::event_date.between(start, end)) .filter( - event::user_uuid - .eq(users_organizations::user_uuid.nullable()) + event::org_user_uuid + .eq(member_uuid) + .or(event::user_uuid.eq(users_organizations::user_uuid.nullable())) .or(event::act_user_uuid.eq(users_organizations::user_uuid.nullable())), ) .select(event::all_columns) diff --git a/src/error.rs b/src/error.rs index ecbc8199..d90c38e3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -58,7 +58,7 @@ use serde_json::{Error as SerdeErr, Value}; use std::io::Error as IoErr; use std::time::SystemTimeError as TimeErr; use webauthn_rs::prelude::WebauthnError as WebauthnErr; -use yubico::yubicoerror::YubicoError as YubiErr; +use yubico_ng::error::YubicoError as YubiErr; #[derive(Serialize)] pub struct Empty {} diff --git a/src/static/scripts/admin.js b/src/static/scripts/admin.js index 3f6bb1df..fa949a40 100644 --- a/src/static/scripts/admin.js +++ b/src/static/scripts/admin.js @@ -1,6 +1,5 @@ "use strict"; -/* eslint-env es2017, browser */ -/* exported BASE_URL, _post _delete */ +/* exported BASE_URL, _post, _delete */ function getBaseUrl() { // If the base URL is `https://vaultwarden.example.com/base/path/admin/`, diff --git a/src/static/scripts/admin_diagnostics.js b/src/static/scripts/admin_diagnostics.js index 2cff4410..ae4d4235 100644 --- a/src/static/scripts/admin_diagnostics.js +++ b/src/static/scripts/admin_diagnostics.js @@ -1,5 +1,4 @@ "use strict"; -/* eslint-env es2017, browser */ /* global BASE_URL:readable, bootstrap:readable */ var dnsCheck = false; @@ -80,37 +79,44 @@ async function generateSupportString(event, dj) { event.preventDefault(); event.stopPropagation(); + // Health check Markdown emoji, if something is a failure or not + const chk = v => v ? "true :white_check_mark:" : "false :x:"; + // Yes/No Markdown emoji, if something is not a failure, but just yes or no + const yn = v => v ? "yes :heavy_plus_sign:" : "no :heavy_minus_sign:"; + + const template_overrides = dj.template_overrides !== "" ? ` (${dj.template_overrides})` : ""; let supportString = "### Your environment (Generated via diagnostics page)\n\n"; supportString += `* Vaultwarden version: v${dj.current_release}\n`; supportString += `* Web-vault version: v${dj.active_web_release}\n`; supportString += `* OS/Arch: ${dj.host_os}/${dj.host_arch}\n`; - supportString += `* Running within a container: ${dj.running_within_container} (Base: ${dj.container_base_image})\n`; + supportString += `* Running within a container: ${yn(dj.running_within_container)} (Base: ${dj.container_base_image})\n`; supportString += `* Database type: ${dj.db_type}\n`; supportString += `* Database version: ${dj.db_version}\n`; - supportString += `* Uses config.json: ${dj.overrides !== ""}\n`; - supportString += `* Uses a reverse proxy: ${dj.ip_header_exists}\n`; + supportString += `* Uses config.json: ${yn(dj.overrides !== "")}\n`; + supportString += `* Uses custom templates: ${yn(dj.template_overrides !== "")}${template_overrides}\n`; + supportString += `* Uses a reverse proxy: ${yn(dj.ip_header_exists)}\n`; if (dj.ip_header_exists) { - supportString += `* IP Header check: ${dj.ip_header_match} (${dj.ip_header_name})\n`; + supportString += `* IP Header check: ${chk(dj.ip_header_match)} (${dj.ip_header_name})\n`; } - supportString += `* Internet access: ${dj.has_http_access}\n`; - supportString += `* Internet access via a proxy: ${dj.uses_proxy}\n`; - supportString += `* DNS Check: ${dnsCheck}\n`; + supportString += `* Internet access: ${chk(dj.has_http_access)}\n`; + supportString += `* Internet access via a proxy: ${yn(dj.uses_proxy)}\n`; + supportString += `* DNS Check: ${chk(dnsCheck)}\n`; if (dj.tz_env !== "") { supportString += `* TZ environment: ${dj.tz_env}\n`; } - supportString += `* Browser/Server Time Check: ${timeCheck}\n`; - supportString += `* Server/NTP Time Check: ${ntpTimeCheck}\n`; - supportString += `* Domain Configuration Check: ${domainCheck}\n`; - supportString += `* HTTPS Check: ${httpsCheck}\n`; + supportString += `* Browser/Server Time Check: ${chk(timeCheck)}\n`; + supportString += `* Server/NTP Time Check: ${chk(ntpTimeCheck)}\n`; + supportString += `* Domain Configuration Check: ${chk(domainCheck)}\n`; + supportString += `* HTTPS Check: ${chk(httpsCheck)}\n`; if (dj.enable_websocket) { - supportString += `* Websocket Check: ${websocketCheck}\n`; + supportString += `* Websocket Check: ${chk(websocketCheck)}\n`; } else { supportString += "* Websocket Check: disabled\n"; } - supportString += `* HTTP Response Checks: ${httpResponseCheck}\n`; + supportString += `* HTTP Response Checks: ${chk(httpResponseCheck)}\n`; if (dj.invalid_feature_flags != "") { - supportString += `* Invalid feature flags: true\n`; + supportString += "* Invalid feature flags: true\n"; } const jsonResponse = await fetch(`${BASE_URL}/admin/diagnostics/config`, { diff --git a/src/static/scripts/admin_organizations.js b/src/static/scripts/admin_organizations.js index c885344e..33314ad7 100644 --- a/src/static/scripts/admin_organizations.js +++ b/src/static/scripts/admin_organizations.js @@ -1,6 +1,5 @@ "use strict"; -/* eslint-env es2017, browser, jquery */ -/* global _post:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ +/* global jQuery, _post:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ function deleteOrganization(event) { event.preventDefault(); diff --git a/src/static/scripts/admin_settings.js b/src/static/scripts/admin_settings.js index 3d61a508..9061719e 100644 --- a/src/static/scripts/admin_settings.js +++ b/src/static/scripts/admin_settings.js @@ -1,5 +1,4 @@ "use strict"; -/* eslint-env es2017, browser */ /* global _post:readable, BASE_URL:readable */ function smtpTest(event) { diff --git a/src/static/scripts/admin_users.js b/src/static/scripts/admin_users.js index 99e39aab..a2a643c3 100644 --- a/src/static/scripts/admin_users.js +++ b/src/static/scripts/admin_users.js @@ -1,6 +1,5 @@ "use strict"; -/* eslint-env es2017, browser, jquery */ -/* global _post:readable, _delete:readable BASE_URL:readable, reload:readable, jdenticon:readable */ +/* global jQuery, _post:readable, _delete:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ function deleteUser(event) { event.preventDefault(); diff --git a/src/static/templates/admin/diagnostics.hbs b/src/static/templates/admin/diagnostics.hbs index ddde389b..0c889353 100644 --- a/src/static/templates/admin/diagnostics.hbs +++ b/src/static/templates/admin/diagnostics.hbs @@ -77,6 +77,16 @@ No {{/unless}} +
Uses custom templates
+
+ {{#if page_data.template_overrides}} + Yes + Details + {{/if}} + {{#unless page_data.template_overrides}} + No + {{/unless}} +
Uses a reverse proxy
{{#if page_data.ip_header_exists}} diff --git a/src/storage.rs b/src/storage.rs index ac88d026..689be302 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -67,7 +67,7 @@ pub(crate) fn operator_for_path(path: &str) -> Result bool { diff --git a/src/util.rs b/src/util.rs index 91f075d1..0e8a93e4 100644 --- a/src/util.rs +++ b/src/util.rs @@ -257,6 +257,44 @@ impl<'r, R: 'r + Responder<'r, 'static> + Send> Responder<'r, 'static> for Cache } } +pub struct EtagCached { + response: R, + etag: String, +} + +impl EtagCached { + /// An `etag` response should always be quoted + pub fn new(response: R, etag: &str) -> Self { + Self { + response, + etag: format!("\"{etag}\""), + } + } +} + +impl<'r, R: 'r + Responder<'r, 'static> + Send> Responder<'r, 'static> for EtagCached { + fn respond_to(self, request: &'r Request<'_>) -> response::Result<'static> { + // Check and validate a `If-None-Match` ETag header + // Multiple tags could be returned for the same URI if the browser has multiple versions cached + // Also, weak tags are prefixed with `W/`, but ETags are always weak, so just strip it too before comparing + let etag_matches = request + .headers() + .get_one("If-None-Match") + .is_some_and(|v| v.split(',').any(|t| t.trim().trim_start_matches("W/") == self.etag)); + + let mut res = if etag_matches { + Response::build().status(Status::NotModified).ok()? + } else { + self.response.respond_to(request)? + }; + + // Both 200 (OK) and 304 (Not Modified) need to return the etag and cache-control + res.set_raw_header("Etag", self.etag); + res.set_raw_header("Cache-Control", "public, no-cache"); + Ok(res) + } +} + // Log all the routes from the main paths list, and the attachments endpoint // Effectively ignores, any static file route, and the alive endpoint const LOGGED_ROUTES: [&str; 7] = ["/api", "/admin", "/identity", "/icons", "/attachments", "/events", "/notifications"]; From 0cefa4cca7c9f2a5579dd290f78193b543818c51 Mon Sep 17 00:00:00 2001 From: lmogthb Date: Fri, 7 Aug 2026 14:09:43 +0200 Subject: [PATCH 04/26] Include user email in successful login logs (#7496) * Include user email in successful login logs * Modified disable account log to display Email instead of Display Name --------- Co-authored-by: Alejandro Olmos --- src/api/identity.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/identity.rs b/src/api/identity.rs index 9212ed8d..23411dc7 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -318,7 +318,7 @@ async fn sso_login( Some((user, _)) if !user.enabled => { err!( "This user has been disabled", - format!("IP: {}. Username: {}.", ip.ip, user.display_name()), + format!("IP: {}. Username: {}.", ip.ip, user.email), ErrorEvent { event: EventType::UserFailedLogIn } @@ -577,7 +577,7 @@ async fn authenticated_response( result["TwoFactorToken"] = Value::String(token); } - info!("User {} logged in successfully. IP: {}", user.display_name(), ip.ip); + info!("User {} logged in successfully. IP: {}", user.email, ip.ip); Ok(Json(result)) } From 9e78911a2f46818cd98582b5006b9836b46ad912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20B=C3=B6nisch?= Date: Thu, 20 Aug 2026 17:34:38 +0200 Subject: [PATCH 05/26] Fix sendmail executable permission check (#7483) * Fix sendmail executable permission check * Use access check for sendmail command --- Cargo.lock | 19 +++++++++++++++++++ Cargo.toml | 1 + src/config.rs | 7 ++----- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21defe79..b0a36edf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -934,6 +934,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + [[package]] name = "chacha20" version = "0.10.1" @@ -3228,6 +3234,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -5867,6 +5885,7 @@ dependencies = [ "macros", "mimalloc", "moka", + "nix", "num-derive", "num-traits", "opendal", diff --git a/Cargo.toml b/Cargo.toml index f65ead29..3e187ff3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ oidc-accept-string-booleans = ["openidconnect/accept-string-booleans"] unstable = [] [target."cfg(unix)".dependencies] +nix = { version = "0.31.3", features = ["fs"] } # Logging syslog = "7.0.0" diff --git a/src/config.rs b/src/config.rs index d5b50146..72b58252 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1162,11 +1162,8 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> { } #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - if !metadata.permissions().mode() & 0o111 != 0 { - err!(format!("sendmail command at `{path:?}` isn't executable")); - } + if nix::unistd::access(&path, nix::unistd::AccessFlags::X_OK).is_err() { + err!(format!("sendmail command at `{path:?}` isn't executable")); } } } From 46d71107f5094460dd5ecbe1dbac6e6c71e5189a Mon Sep 17 00:00:00 2001 From: Stefan Melmuk <509385+stefan0xC@users.noreply.github.com> Date: Thu, 20 Aug 2026 22:03:56 +0200 Subject: [PATCH 06/26] add dummy revisionDate (#7608) --- src/db/models/org_policy.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs index 88b7872c..d501f8b9 100644 --- a/src/db/models/org_policy.rs +++ b/src/db/models/org_policy.rs @@ -91,6 +91,7 @@ impl OrgPolicy { "type": self.atype, "data": data_json, "enabled": self.enabled, + "revisionDate": null, "object": "policy", }); From fa2566d14fc745937ce104011475eca9e6c7a6f6 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Mon, 24 Aug 2026 19:38:23 +0200 Subject: [PATCH 07/26] Fix password change with newer web-vault (#7634) --- src/api/core/accounts.rs | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 0cb4d3c0..626f22bb 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -595,29 +595,52 @@ async fn post_keys(data: Json, headers: Headers, conn: DbConn) -> Json #[serde(rename_all = "camelCase")] struct ChangePassData { master_password_hash: String, - new_master_password_hash: String, master_password_hint: Option, - key: String, + authentication_data: Option, + unlock_data: Option, + + // Outdated values, might still be used by older clients + new_master_password_hash: Option, + key: Option, } #[post("/accounts/password", data = "")] async fn post_password(data: Json, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { let data: ChangePassData = data.into_inner(); - let mut user = headers.user; + let user = headers.user; if !user.check_valid_password(&data.master_password_hash) { err!("Invalid password") } - user.password_hint = clean_password_hint(data.master_password_hint.as_ref()); - enforce_password_hint_setting(user.password_hint.as_ref())?; - log_user_event(EventType::UserChangedPassword as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn) .await; + let (new_master_password_hash, new_key) = + if let (Some(unlock_data), Some(authentication_data)) = (data.unlock_data, data.authentication_data) { + if authentication_data.kdf != unlock_data.kdf { + err!("KDF settings must be equal for authentication and unlock") + } + + if user.email != authentication_data.salt || user.email != unlock_data.salt { + err!("Invalid master password salt") + } + + (authentication_data.master_password_authentication_hash, unlock_data.master_key_wrapped_user_key) + } else if let (Some(new_master_password_hash), Some(new_key)) = (data.new_master_password_hash, data.key) { + (new_master_password_hash, new_key) + } else { + err!("Invalid request!") + }; + + let mut user = user; + + user.password_hint = clean_password_hint(data.master_password_hint.as_ref()); + enforce_password_hint_setting(user.password_hint.as_ref())?; + user.set_password( - &data.new_master_password_hash, - Some(data.key), + &new_master_password_hash, + Some(new_key), true, Some(vec![ String::from("post_rotatekey"), From 10e044f563e6224eb0271419f7b7f4140791dc7f Mon Sep 17 00:00:00 2001 From: Matt Van Horn Date: Sat, 29 Aug 2026 08:01:45 -0700 Subject: [PATCH 08/26] chore: remove duplicate "the" in ciphers.rs comment (#7254) `src/api/core/ciphers.rs:170` comment said "similar to the the userDecryptionOptions" -> "similar to the userDecryptionOptions". Comment-only. Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> --- src/api/core/ciphers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 2b51fd0c..3e94ca7c 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -167,7 +167,7 @@ async fn sync(data: SyncData, headers: Headers, client_version: Option Date: Sat, 29 Aug 2026 11:01:51 -0400 Subject: [PATCH 09/26] Ignore reset-password auto-enroll when mail is disabled (#7585) Account recovery requires SMTP. When mail is off, treat the organization reset-password auto-enroll policy as inactive so invite/accept flows are not forced to supply a reset-password key. Fixes #7459 --- src/db/models/org_policy.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs index d501f8b9..2b45cd86 100644 --- a/src/db/models/org_policy.rs +++ b/src/db/models/org_policy.rs @@ -318,6 +318,13 @@ impl OrgPolicy { } pub async fn org_is_reset_password_auto_enroll(org_uuid: &OrganizationId, conn: &DbConn) -> bool { + // Account recovery depends on outbound mail. When SMTP is disabled, treat the + // auto-enroll policy as inactive so invites/registration are not forced to + // supply a reset-password key (see check_reset_password_applicable). + if !CONFIG.mail_enabled() { + return false; + } + match OrgPolicy::find_by_org_and_type(org_uuid, OrgPolicyType::ResetPassword, conn).await { Some(policy) => match serde_json::from_str::(&policy.data) { Ok(opts) => { From 923f5d0b5eb7e223855031e35ba9606372ff5fa3 Mon Sep 17 00:00:00 2001 From: Timshel Date: Sat, 29 Aug 2026 15:02:01 +0000 Subject: [PATCH 10/26] Fix migration for MariaDB 12.2.2 (#7265) Co-authored-by: Timshel --- .../up.sql | 42 +++++++++++++------ playwright/docker-compose.yml | 2 +- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/migrations/mysql/2024-03-13-170000_sso_users_cascade/up.sql b/migrations/mysql/2024-03-13-170000_sso_users_cascade/up.sql index 9e5e46df..8d1eb178 100644 --- a/migrations/mysql/2024-03-13-170000_sso_users_cascade/up.sql +++ b/migrations/mysql/2024-03-13-170000_sso_users_cascade/up.sql @@ -1,15 +1,31 @@ --- Dynamically create DROP FOREIGN KEY --- Some versions of MySQL or MariaDB might fail if the key doesn't exists --- This checks if the key exists, and if so, will drop it. -SET @drop_sso_fk = IF((SELECT true FROM information_schema.TABLE_CONSTRAINTS WHERE - CONSTRAINT_SCHEMA = DATABASE() AND - TABLE_NAME = 'sso_users' AND - CONSTRAINT_NAME = 'sso_users_ibfk_1' AND - CONSTRAINT_TYPE = 'FOREIGN KEY') = true, - 'ALTER TABLE sso_users DROP FOREIGN KEY sso_users_ibfk_1', - 'SELECT 1'); -PREPARE stmt FROM @drop_sso_fk; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; +SELECT if ( + EXISTS( + SELECT CONSTRAINT_NAME FROM information_schema.table_constraints + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'sso_users' + AND CONSTRAINT_TYPE = 'FOREIGN KEY' + AND CONSTRAINT_NAME = 'sso_users_ibfk_1' + ) + ,'ALTER TABLE sso_users DROP FOREIGN KEY `sso_users_ibfk_1`' + ,'SELECT "info: FK sso_users_ibfk_1 does not exist."' +) INTO @drop_stmt; +PREPARE drop_stmt FROM @drop_stmt; +EXECUTE drop_stmt; + +SELECT if ( + EXISTS( + SELECT CONSTRAINT_NAME FROM information_schema.table_constraints + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'sso_users' + AND CONSTRAINT_TYPE = 'FOREIGN KEY' + AND CONSTRAINT_NAME = '1' + ) + ,'ALTER TABLE sso_users DROP FOREIGN KEY `1`' + ,'SELECT "info: FK sso_users 1 does not exist."' +) INTO @drop_stmt; +PREPARE drop_stmt FROM @drop_stmt; +EXECUTE drop_stmt; + +DEALLOCATE PREPARE drop_stmt; ALTER TABLE sso_users ADD FOREIGN KEY(user_uuid) REFERENCES users(uuid) ON UPDATE CASCADE ON DELETE CASCADE; diff --git a/playwright/docker-compose.yml b/playwright/docker-compose.yml index 5dd04ff4..5bfc47a5 100644 --- a/playwright/docker-compose.yml +++ b/playwright/docker-compose.yml @@ -61,7 +61,7 @@ services: Mariadb: profiles: ["playwright"] container_name: playwright_mariadb - image: mariadb:11.2.4 + image: mariadb:12.2.2 env_file: test.env healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] From 2073c03092d328e4b5fd19882ecfbe491dc8b5c7 Mon Sep 17 00:00:00 2001 From: Timshel Date: Sat, 29 Aug 2026 15:02:22 +0000 Subject: [PATCH 11/26] Add SSO_SIGNUPS_ALLOWED (#7272) * Add SSO_SIGNUPS_ALLOWED * Fix regression with domain_allowed in SSO onboarding --------- Co-authored-by: Timshel --- .env.template | 3 +++ src/api/identity.rs | 28 +++++++++++++++++++++++++++- src/config.rs | 13 +++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/.env.template b/.env.template index 9fc29989..5f6f374c 100644 --- a/.env.template +++ b/.env.template @@ -518,6 +518,9 @@ ## Prevent users from logging in directly without going through SSO # SSO_ONLY=false +## Allow SSO flow to create account. You probably want to disable it when using a public provider. +# SSO_SIGNUPS_ALLOWED=true + ## On SSO Signup if a user with a matching email already exists make the association # SSO_SIGNUPS_MATCH_EMAIL=true diff --git a/src/api/identity.rs b/src/api/identity.rs index 23411dc7..2b1ddfb1 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -234,6 +234,24 @@ async fn sso_login( } ) } + Some((user, None)) + if user.private_key.is_none() + && !CONFIG.sso_signups_allowed() + && !CONFIG.is_email_domain_allowed(&user.email) + && !CONFIG.mail_enabled() + && Invitation::find_by_mail(&user.email, conn).await.is_none() => + { + error!( + "Login failure ({}), no invitation with email ({}) was found", + user_infos.identifier, user.email + ); + err_silent!( + "Missing invitation", + ErrorEvent { + event: EventType::UserFailedLogIn + } + ) + } Some((user, None)) if user.private_key.is_some() && !CONFIG.sso_signups_match_email() => { error!( "Login failure ({}), existing non SSO user ({}) with same email ({}) and association is disabled", @@ -281,7 +299,15 @@ async fn sso_login( // Will trigger 2FA flow if needed let (user, mut device, twofactor_token, sso_user) = match user_with_sso { None => { - if !CONFIG.is_email_domain_allowed(&user_infos.email) { + if !CONFIG.is_sso_signup_allowed(&user_infos.email) { + if CONFIG.signups_domains_whitelist().is_empty() { + err!( + "Signups are disabled. You will need an invitation", + ErrorEvent { + event: EventType::UserFailedLogIn + } + ); + } err!( "Email domain not allowed", ErrorEvent { diff --git a/src/config.rs b/src/config.rs index 72b58252..2502dd02 100644 --- a/src/config.rs +++ b/src/config.rs @@ -817,6 +817,8 @@ make_config! { sso_enabled: bool, true, def, false; /// Only SSO login |> Disable Email+Master Password login sso_only: bool, true, def, false; + /// Allow SSO flow to create account |> You probably want to disable it when using a public provider + sso_signups_allowed: bool, true, def, true; /// Allow email association |> Associate existing non-SSO user based on email sso_signups_match_email: bool, true, def, true; /// Allow unknown email verification status |> Allowing this with `SSO_SIGNUPS_MATCH_EMAIL=true` open potential account takeover. @@ -1544,6 +1546,17 @@ impl Config { } } + /// Tests whether SSO signup is allowed for an email address, taking into + /// account the sso_signups_allowed and signups_domains_whitelist settings. + pub fn is_sso_signup_allowed(&self, email: &str) -> bool { + if self.signups_domains_whitelist().is_empty() { + self.sso_signups_allowed() + } else { + // The whitelist setting overrides the signups_allowed setting. + self.is_email_domain_allowed(email) + } + } + // The registration link should be hidden if // - Signup is not allowed and email whitelist is empty unless mail is disabled and invitations are allowed // - The SSO is activated and password login is disabled. From fdc156b247846ca73f6aa3e9c676a6f2f57577cf Mon Sep 17 00:00:00 2001 From: Timshel Date: Sat, 29 Aug 2026 15:02:26 +0000 Subject: [PATCH 12/26] log_event take enum parameter not i32 (#7656) Co-authored-by: Timshel --- src/api/admin.rs | 6 ++--- src/api/core/ciphers.rs | 28 ++++++++------------- src/api/core/events.rs | 6 ++--- src/api/core/organizations.rs | 46 +++++++++++++++++----------------- src/api/core/two_factor/mod.rs | 14 +++-------- 5 files changed, 42 insertions(+), 58 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index 48f36afd..eaa681dd 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -425,7 +425,7 @@ async fn delete_user(user_id: UserId, token: AdminToken, conn: DbConn) -> EmptyR for membership in memberships { log_event( - EventType::OrganizationUserDeleted as i32, + EventType::OrganizationUserDeleted, &membership.uuid, &membership.org_uuid, &ACTING_ADMIN_USER.into(), @@ -446,7 +446,7 @@ async fn delete_sso_user(user_id: UserId, token: AdminToken, conn: DbConn) -> Em for membership in memberships { log_event( - EventType::OrganizationUserUnlinkedSso as i32, + EventType::OrganizationUserUnlinkedSso, &membership.uuid, &membership.org_uuid, &ACTING_ADMIN_USER.into(), @@ -571,7 +571,7 @@ async fn update_membership_type(data: Json, token: AdminToke OrgPolicy::check_user_allowed(&member_to_edit, "modify", &conn).await?; log_event( - EventType::OrganizationUserUpdated as i32, + EventType::OrganizationUserUpdated, &member_to_edit.uuid, &data.org_uuid, &ACTING_ADMIN_USER.into(), diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 3e94ca7c..13021ca3 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -553,16 +553,8 @@ pub async fn update_cipher_from_data( (_, _) => EventType::CipherUpdated, }; - log_event( - event_type as i32, - &cipher.uuid, - org_id, - &headers.user.uuid, - headers.device.atype, - &headers.ip.ip, - conn, - ) - .await; + log_event(event_type, &cipher.uuid, org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn) + .await; } nt.send_cipher_update( ut, @@ -850,7 +842,7 @@ async fn post_collections_update( .await; log_event( - EventType::CipherUpdatedCollections as i32, + EventType::CipherUpdatedCollections, &cipher.uuid, org_uuid, &headers.user.uuid, @@ -930,7 +922,7 @@ async fn post_collections_admin( .await; log_event( - EventType::CipherUpdatedCollections as i32, + EventType::CipherUpdatedCollections, &cipher.uuid, org_uuid, &headers.user.uuid, @@ -1335,7 +1327,7 @@ async fn save_attachment( if let Some(org_id) = &cipher.organization_uuid { log_event( - EventType::CipherAttachmentCreated as i32, + EventType::CipherAttachmentCreated, &cipher.uuid, org_id, &headers.user.uuid, @@ -1696,7 +1688,7 @@ async fn purge_org_vault( nt.send_user_update(UpdateType::SyncVault, &user, headers.device.push_uuid.as_ref(), &conn).await; log_event( - EventType::OrganizationPurgedVault as i32, + EventType::OrganizationPurgedVault, &organization.org_id, &organization.org_id, &user.uuid, @@ -1824,9 +1816,9 @@ async fn delete_cipher_by_uuid( let event_type = if *delete_options == CipherDeleteOptions::SoftSingle || *delete_options == CipherDeleteOptions::SoftMulti { - EventType::CipherSoftDeleted as i32 + EventType::CipherSoftDeleted } else { - EventType::CipherDeleted as i32 + EventType::CipherDeleted }; log_event(event_type, &cipher.uuid, &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn) @@ -1895,7 +1887,7 @@ async fn restore_cipher_by_uuid( if let Some(org_id) = &cipher.organization_uuid { log_event( - EventType::CipherRestored as i32, + EventType::CipherRestored, &cipher.uuid.clone(), org_id, &headers.user.uuid, @@ -1972,7 +1964,7 @@ async fn delete_cipher_attachment_by_id( if let Some(ref org_id) = cipher.organization_uuid { log_event( - EventType::CipherAttachmentDeleted as i32, + EventType::CipherAttachmentDeleted, &cipher.uuid, org_id, &headers.user.uuid, diff --git a/src/api/core/events.rs b/src/api/core/events.rs index 5518fa3c..2c437a36 100644 --- a/src/api/core/events.rs +++ b/src/api/core/events.rs @@ -10,7 +10,7 @@ use crate::{ auth::{AdminHeaders, Headers}, db::{ DbConn, DbPool, - models::{Cipher, CipherId, Event, Membership, MembershipId, OrganizationId, UserId}, + models::{Cipher, CipherId, Event, EventType, Membership, MembershipId, OrganizationId, UserId}, }, util::parse_date, }; @@ -267,7 +267,7 @@ async fn log_user_event_impl( } pub async fn log_event( - event_type: i32, + event_type: EventType, source_uuid: &str, org_id: &OrganizationId, act_user_id: &UserId, @@ -278,7 +278,7 @@ pub async fn log_event( if !CONFIG.org_events_enabled() { return; } - log_event_impl(event_type, source_uuid, org_id, act_user_id, device_type, None, ip, conn).await; + log_event_impl(event_type as i32, source_uuid, org_id, act_user_id, device_type, None, ip, conn).await; } #[expect(clippy::too_many_arguments)] diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 989ca47d..9082297f 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -269,7 +269,7 @@ async fn leave_organization(org_id: OrganizationId, headers: OrgMemberHeaders, c } log_event( - EventType::OrganizationUserLeft as i32, + EventType::OrganizationUserLeft, &membership.uuid, &org_id, &headers.user.uuid, @@ -327,7 +327,7 @@ async fn post_organization( org.save(&conn).await?; log_event( - EventType::OrganizationUpdated as i32, + EventType::OrganizationUpdated, org_id.as_ref(), &org_id, &headers.user.uuid, @@ -514,7 +514,7 @@ async fn post_organization_collections( collection.save(&conn).await?; log_event( - EventType::CollectionCreated as i32, + EventType::CollectionCreated, &collection.uuid, &org_id, &headers.user.uuid, @@ -597,7 +597,7 @@ async fn post_bulk_access_collections( collection.save(&conn).await?; log_event( - EventType::CollectionUpdated as i32, + EventType::CollectionUpdated, &collection.uuid, &org_id, &headers.user.uuid, @@ -674,7 +674,7 @@ async fn post_organization_collection_update( collection.save(&conn).await?; log_event( - EventType::CollectionUpdated as i32, + EventType::CollectionUpdated, &collection.uuid, &org_id, &headers.user.uuid, @@ -723,7 +723,7 @@ async fn delete_organization_collection_impl( err!("Collection not found", "Collection does not exist or does not belong to this organization") }; log_event( - EventType::CollectionDeleted as i32, + EventType::CollectionDeleted, &collection.uuid, org_id, &headers.user.uuid, @@ -1148,7 +1148,7 @@ async fn send_invite( } log_event( - EventType::OrganizationUserInvited as i32, + EventType::OrganizationUserInvited, &new_member.uuid, &org_id, &headers.user.uuid, @@ -1447,7 +1447,7 @@ async fn confirm_invite_impl( OrgPolicy::check_user_allowed(&member_to_confirm, "confirm", conn).await?; log_event( - EventType::OrganizationUserConfirmed as i32, + EventType::OrganizationUserConfirmed, &member_to_confirm.uuid, org_id, &headers.user.uuid, @@ -1637,7 +1637,7 @@ async fn edit_member( } log_event( - EventType::OrganizationUserUpdated as i32, + EventType::OrganizationUserUpdated, &member_to_edit.uuid, &org_id, &headers.user.uuid, @@ -1724,7 +1724,7 @@ async fn delete_member_impl( } log_event( - EventType::OrganizationUserRemoved as i32, + EventType::OrganizationUserRemoved, &member_to_delete.uuid, org_id, &headers.user.uuid, @@ -2144,7 +2144,7 @@ async fn put_policy( } log_event( - EventType::OrganizationUserRemoved as i32, + EventType::OrganizationUserRemoved, &member.uuid, &org_id, &headers.user.uuid, @@ -2170,7 +2170,7 @@ async fn put_policy( policy.save(&conn).await?; log_event( - EventType::PolicyUpdated as i32, + EventType::PolicyUpdated, policy.uuid.as_ref(), &org_id, &headers.user.uuid, @@ -2339,7 +2339,7 @@ async fn revoke_member_impl( member.save(conn).await?; log_event( - EventType::OrganizationUserRevoked as i32, + EventType::OrganizationUserRevoked, &member.uuid, org_id, &headers.user.uuid, @@ -2437,7 +2437,7 @@ async fn restore_member_impl( member.save(conn).await?; log_event( - EventType::OrganizationUserRestored as i32, + EventType::OrganizationUserRestored, &member.uuid, org_id, &headers.user.uuid, @@ -2605,7 +2605,7 @@ async fn post_groups( let group = group_request.to_group(&org_id); log_event( - EventType::GroupCreated as i32, + EventType::GroupCreated, &group.uuid, &org_id, &headers.user.uuid, @@ -2646,7 +2646,7 @@ async fn put_group( GroupUser::delete_all_by_group(&group_id, &org_id, &conn).await?; log_event( - EventType::GroupUpdated as i32, + EventType::GroupUpdated, &updated_group.uuid, &org_id, &headers.user.uuid, @@ -2679,7 +2679,7 @@ async fn add_update_group( user_entry.save(conn).await?; log_event( - EventType::OrganizationUserUpdatedGroups as i32, + EventType::OrganizationUserUpdatedGroups, &assigned_member, &org_id, &headers.user.uuid, @@ -2754,7 +2754,7 @@ async fn delete_group_impl( }; log_event( - EventType::GroupDeleted as i32, + EventType::GroupDeleted, &group.uuid, org_id, &headers.user.uuid, @@ -2865,7 +2865,7 @@ async fn put_group_members( user_entry.save(&conn).await?; log_event( - EventType::OrganizationUserUpdatedGroups as i32, + EventType::OrganizationUserUpdatedGroups, &assigned_member, &org_id, &headers.user.uuid, @@ -2903,7 +2903,7 @@ async fn post_delete_group_member( } log_event( - EventType::OrganizationUserUpdatedGroups as i32, + EventType::OrganizationUserUpdatedGroups, &member_id, &org_id, &headers.user.uuid, @@ -3039,7 +3039,7 @@ async fn recover_account( nt.send_logout(&user, None, &conn).await; log_event( - EventType::OrganizationUserAdminResetPassword as i32, + EventType::OrganizationUserAdminResetPassword, &member_id, &org_id, &headers.user.uuid, @@ -3166,9 +3166,9 @@ async fn put_reset_password_enrollment( membership.save(&conn).await?; let event_type = if membership.reset_password_key.is_some() { - EventType::OrganizationUserResetPasswordEnroll as i32 + EventType::OrganizationUserResetPasswordEnroll } else { - EventType::OrganizationUserResetPasswordWithdraw as i32 + EventType::OrganizationUserResetPasswordWithdraw }; log_event(event_type, &membership.uuid, &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, &conn) diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs index 8869d23d..c95fb297 100644 --- a/src/api/core/two_factor/mod.rs +++ b/src/api/core/two_factor/mod.rs @@ -190,7 +190,7 @@ pub async fn enforce_2fa_policy( member.save(conn).await?; log_event( - EventType::OrganizationUserRevoked as i32, + EventType::OrganizationUserRevoked, &member.uuid, &member.org_uuid, act_user_id, @@ -224,16 +224,8 @@ pub async fn enforce_2fa_policy_for_org( member.revoke(); member.save(conn).await?; - log_event( - EventType::OrganizationUserRevoked as i32, - &member.uuid, - org_id, - act_user_id, - device_type, - ip, - conn, - ) - .await; + log_event(EventType::OrganizationUserRevoked, &member.uuid, org_id, act_user_id, device_type, ip, conn) + .await; } } From 6729e835218edb29b644a99f65a3b76cde9a341d Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Thu, 3 Sep 2026 00:07:00 +0200 Subject: [PATCH 13/26] Misc Updates (#7676) * Misc Updates - Update Rust to v1.98.0 - Update all the crates and adjusted code where needed - Updated JavaScript libraries Removed jquery as this isn't needed anymore, adjusted code where needed - Updated all GitHub Actions - Fixed nightly clippy lint warnings Signed-off-by: BlackDex * Adjust email validation as suggested Signed-off-by: BlackDex --------- Signed-off-by: BlackDex --- .github/workflows/build.yml | 2 +- .github/workflows/hadolint.yml | 6 +- .github/workflows/release.yml | 4 +- .github/workflows/trivy.yml | 2 +- .github/workflows/typos.yml | 2 +- .github/workflows/zizmor.yml | 2 +- .pre-commit-config.yaml | 2 +- Cargo.lock | 579 +- Cargo.toml | 36 +- macros/Cargo.toml | 2 +- rust-toolchain.toml | 2 +- src/api/admin.rs | 4 +- src/api/web.rs | 3 - src/config.rs | 2 +- src/main.rs | 8 +- src/static/scripts/admin_organizations.js | 11 +- src/static/scripts/admin_users.js | 76 +- src/static/scripts/datatables.css | 160 +- src/static/scripts/datatables.js | 27072 ++++++++--------- src/static/scripts/jquery-4.0.0.slim.js | 6856 ----- src/static/templates/admin/organizations.hbs | 3 +- src/static/templates/admin/users.hbs | 7 +- src/util.rs | 5 +- 23 files changed, 13321 insertions(+), 21525 deletions(-) delete mode 100644 src/static/scripts/jquery-4.0.0.slim.js diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 31a04012..f52bd8e4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -113,7 +113,7 @@ jobs: # Enable Rust Caching - name: Rust Caching - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 with: # Use a custom prefix-key to force a fresh start. This is sometimes needed with bigger changes. # Like changing the build host from Ubuntu 20.04 to 22.04 for example. diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml index 3111e20b..a429d10f 100644 --- a/.github/workflows/hadolint.yml +++ b/.github/workflows/hadolint.yml @@ -20,7 +20,7 @@ jobs: steps: # Start Docker Buildx - name: Setup Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 # https://github.com/moby/buildkit/issues/3969 # Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills with: @@ -41,12 +41,12 @@ jobs: # Uses the Docker-based action (hadolint pre-bundled in ghcr.io/hadolint/hadolint:v2.14.0-debian) # so no binary is downloaded at runtime. Pinned by commit SHA for supply-chain safety. - name: Run hadolint on Dockerfile.debian - uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0 + uses: hadolint/hadolint-action@06be81baf89a55ffd0e24b8f04a4185738dd3387 # v3.5.0 with: dockerfile: docker/Dockerfile.debian - name: Run hadolint on Dockerfile.alpine - uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0 + uses: hadolint/hadolint-action@06be81baf89a55ffd0e24b8f04a4185738dd3387 # v3.5.0 with: dockerfile: docker/Dockerfile.alpine # End Test Dockerfiles with hadolint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d15dd88..311891b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,13 +58,13 @@ jobs: steps: - name: Initialize QEMU binfmt support - uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 + uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0 with: platforms: "arm64,arm" # Start Docker Buildx - name: Setup Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 # https://github.com/moby/buildkit/issues/3969 # Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills with: diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 942a99e9..7f41885d 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -50,6 +50,6 @@ jobs: severity: CRITICAL,HIGH - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 779cd6e3..83cd581b 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -23,4 +23,4 @@ jobs: # When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too - name: Spell Check Repo - uses: crate-ci/typos@8a48f81b6c64dcfea44b3633223084c4be58ac5f # v1.49.0 + uses: crate-ci/typos@4d9c206a77c041268485162b8e2579ad7a5cb9a3 # v1.50.0 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index e1de58c3..5e7100b9 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 + uses: zizmorcore/zizmor-action@70fb788f84895a7701f5643d103d587e460b5c99 # v0.6.3 with: # intentionally not scanning the entire repository, # since it contains integration tests. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f9920696..5269c041 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too - repo: https://github.com/crate-ci/typos - rev: 8a48f81b6c64dcfea44b3633223084c4be58ac5f # v1.49.0 + rev: 4d9c206a77c041268485162b8e2579ad7a5cb9a3 # v1.50.0 hooks: - id: typos always_run: true diff --git a/Cargo.lock b/Cargo.lock index b0a36edf..b8335e5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -76,13 +77,13 @@ dependencies = [ [[package]] name = "argon2" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +checksum = "134c52ddac6d63c576bef8168db10c83c49c26444ecbc68060fef078925a901c" dependencies = [ "base64ct", "blake2", - "cpufeatures 0.2.17", + "cpufeatures 0.3.1", "password-hash", ] @@ -311,13 +312,23 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", +] + +[[package]] +name = "asyncband" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a214ba60d6231afd0e805e3c27c45a1626d9debaa5a5061c45a1ea1b2f1ed0" +dependencies = [ + "hashbrown 0.17.1", + "slab", ] [[package]] @@ -349,9 +360,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-config" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b180a3c8b55960db3426d8964b8745e652466a1a49fe1a2eda828046d30b5e4" +checksum = "a767267da9e2c2e189b2f9df8b5657e850ecf5352644734ba130d4a57095cf1b" dependencies = [ "aws-credential-types", "aws-runtime", @@ -417,9 +428,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.105.0" +version = "1.108.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ffd0fbe7873cb548a7aa60f9573c268fff94155397fd4f14dc9f1ecaaab8516" +checksum = "c15301b04372832947916607983b114b3374b9db0be058a00fb7513800de1f05" dependencies = [ "arc-swap", "aws-credential-types", @@ -443,9 +454,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.107.0" +version = "1.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175763eb222a46377df7aa257a3bca980ab3e96703fefc8f4d0b8da6ad2e254c" +checksum = "72cc2c205cb27108183cf1856333f7d584c2ba0f505421b4209ca5828f9ea899" dependencies = [ "arc-swap", "aws-credential-types", @@ -469,9 +480,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.110.0" +version = "1.113.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b14781dfbff48984017d57167b6ea0b6471c6920ec52b44a2677c7feb3c13" +checksum = "68182ecb449f7537db0f4d5d25917789cf41e32074a9fe47b6a0b847fe1d2032" dependencies = [ "arc-swap", "aws-credential-types", @@ -583,9 +594,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.12.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07505b34e8f4b3591a4fa69e9792b52289b95488dbbc68c3c0075b7bedb245e1" +checksum = "b82e438d30e02a825d363bd639a9efaed68a8089d86101054b0081e7e0d3e606" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -608,9 +619,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b98f2e1fd67ec06618f9c291e5e495a468e60519e44c9c1979cd0521f3affdb" +checksum = "954c563ce84507722d2679f07a35d21b9c6466b3872d513020d0281fc8112ac9" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", @@ -648,9 +659,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6dc683efb34b9e755675b37fedbe0103141e5b6df7bdc9eb6967756a8c167d8" +checksum = "fce83ce9abbb198d25bc7131e468d0f9fe1257125e58c39f3f9fc9f5098c9647" dependencies = [ "base64-simd", "bytes", @@ -780,11 +791,11 @@ checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "blake2" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +checksum = "5b5d4d889834ee8ecfc0f8426ad30faf7cdcb10f741a8e6d7224d95325479f6f" dependencies = [ - "digest 0.10.7", + "digest 0.11.3", ] [[package]] @@ -807,9 +818,9 @@ dependencies = [ [[package]] name = "blocking" -version = "1.6.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" dependencies = [ "async-channel 2.5.0", "async-task", @@ -884,27 +895,28 @@ dependencies = [ [[package]] name = "cached" -version = "2.0.2" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0df7748fe2f601e376916ab19e7bfc2c74461b8abe3bce2ce20036ad8de38f" +checksum = "133b6b7d6a828c24d5055ef51e67457002b4017ae5ea3d1b1552ca35a44b1119" dependencies = [ "ahash", + "async-lock", "cached_proc_macro", "cached_proc_macro_types", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "parking_lot", - "thiserror 2.0.19", - "tokio", + "thiserror 2.0.20", "web-time", ] [[package]] name = "cached_proc_macro" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66e734c52502e6cf54dce2ba07108906b04b8fe57f4f5e3ef7d58267b4abf060" +checksum = "da80977bd46ecf98c593b651e393260f852678283989b8b7c4079304fe5e8936" dependencies = [ "darling 0.20.11", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.119", @@ -912,15 +924,15 @@ dependencies = [ [[package]] name = "cached_proc_macro_types" -version = "1.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26cf465651fa6ad902a2d327ba60c3a6bc61c6a2f4ad70d091cf20dfda0074ef" +checksum = "f5813789573ae815c8b4be58c4428e0e7ae05f0227678ba9de332ded585b9159" [[package]] name = "cc" -version = "1.4.0" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" dependencies = [ "find-msvc-tools", "jobserver", @@ -942,12 +954,12 @@ checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "chacha20" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "rand_core 0.10.1", ] @@ -989,9 +1001,9 @@ checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" [[package]] name = "combine" -version = "4.6.7" +version = "4.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" dependencies = [ "bytes", "memchr", @@ -1069,9 +1081,9 @@ dependencies = [ [[package]] name = "cookie" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" dependencies = [ "percent-encoding", "time", @@ -1133,9 +1145,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ "libc", ] @@ -1152,9 +1164,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" dependencies = [ "cfg-if", ] @@ -1432,7 +1444,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -1558,9 +1570,9 @@ dependencies = [ [[package]] name = "diesel" -version = "2.3.11" +version = "2.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54d1f576cd3a3460f212a4615fd12ce1b6303c095b79a44449ffbe627753dc1" +checksum = "715377c6e464cb44bb89bd8487584240516c8d5052bc645d6babc50bb8be46c3" dependencies = [ "bigdecimal", "bitflags 2.13.1", @@ -1658,7 +1670,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -1751,9 +1763,9 @@ dependencies = [ [[package]] name = "either" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] name = "elliptic-curve" @@ -1896,18 +1908,19 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flate2" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -1948,9 +1961,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -1963,9 +1976,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -1973,15 +1986,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -1990,9 +2003,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-lite" @@ -2009,26 +2022,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-timer" @@ -2038,9 +2051,9 @@ checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -2164,7 +2177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" dependencies = [ "codemap", - "indexmap 2.14.0", + "indexmap 2.14.1", "lasso", "once_cell", "phf 0.11.3", @@ -2183,9 +2196,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.15" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" dependencies = [ "atomic-waker", "bytes", @@ -2193,7 +2206,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.5.0", - "indexmap 2.14.0", + "indexmap 2.14.1", "slab", "tokio", "tokio-util", @@ -2213,9 +2226,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "6.4.3" +version = "6.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4633d16a2350341713c379d6d06a4b9e1845329386026a49ce4fd09c2f3b16f6" +checksum = "75c54236f9045c8004a77942bebc52145b4844639db934a5c70fe08617fbe61a" dependencies = [ "derive_builder", "log", @@ -2224,7 +2237,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.19", + "thiserror 2.0.20", "walkdir", ] @@ -2260,6 +2273,11 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "heck" @@ -2269,9 +2287,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" [[package]] name = "hex" @@ -2296,7 +2314,7 @@ dependencies = [ "ipnet", "jni", "rand 0.10.2", - "thiserror 2.0.19", + "thiserror 2.0.20", "tinyvec", "tokio", "tracing", @@ -2317,7 +2335,7 @@ dependencies = [ "prefix-trie", "rand 0.10.2", "ring", - "thiserror 2.0.19", + "thiserror 2.0.20", "tinyvec", "tracing", "url", @@ -2344,7 +2362,7 @@ dependencies = [ "resolv-conf", "smallvec", "system-configuration", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "tracing", ] @@ -2440,9 +2458,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", "futures-core", @@ -2497,9 +2515,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ "atomic-waker", "bytes", @@ -2523,7 +2541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.5.0", - "hyper 1.11.0", + "hyper 1.11.1", "hyper-util", "rustls 0.23.43", "tokio", @@ -2543,7 +2561,7 @@ dependencies = [ "futures-util", "http 1.5.0", "http-body 1.1.0", - "hyper 1.11.0", + "hyper 1.11.1", "ipnet", "libc", "percent-encoding", @@ -2582,9 +2600,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", @@ -2596,9 +2614,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -2609,9 +2627,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -2623,16 +2641,17 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -2643,15 +2662,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", @@ -2702,9 +2721,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -2839,7 +2858,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.19", + "thiserror 2.0.20", "walkdir", "windows-link", ] @@ -2899,9 +2918,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if", "futures-util", @@ -2922,7 +2941,7 @@ dependencies = [ "p256", "p384", "pem", - "rand 0.8.7", + "rand 0.8.8", "rsa", "serde", "serde_json", @@ -3013,9 +3032,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.37.0" +version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" +checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8" dependencies = [ "cc", "pkg-config", @@ -3030,9 +3049,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "litrs" @@ -3051,9 +3070,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" dependencies = [ "value-bag", ] @@ -3078,7 +3097,7 @@ name = "macros" version = "0.1.0" dependencies = [ "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -3102,10 +3121,11 @@ dependencies = [ [[package]] name = "mea" -version = "0.6.5" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31fc7d159de0085ab6dd7ff145a9819442cfd3d098f783263120503c3f3e58b0" +checksum = "c709842c4ce65cb91e2666ad5319dfc1efc3af0d34f02075eddca9000d9f8afb" dependencies = [ + "hashbrown 0.17.1", "slab", ] @@ -3159,9 +3179,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.9" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" dependencies = [ "adler2", "simd-adler32", @@ -3180,9 +3200,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.15" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +checksum = "4293f18e7567a1caf3c584855554377025c65e0aa445344d04171f5ad63d19b9" dependencies = [ "async-lock", "crossbeam-channel", @@ -3301,7 +3321,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.7", + "rand 0.8.8", "smallvec", "zeroize", ] @@ -3320,14 +3340,14 @@ checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" dependencies = [ "num-traits", ] @@ -3344,9 +3364,9 @@ dependencies = [ [[package]] name = "num-modular" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc41a1374056e9672221567958a66c16be12d0e2c1b408761e14d901c237d5e0" +checksum = "bd8e500409e6cd603b03e477c26a6caecdc27ac58979a53e881c75eafc079f44" [[package]] name = "num-order" @@ -3396,7 +3416,7 @@ dependencies = [ "chrono", "getrandom 0.2.17", "http 1.5.0", - "rand 0.8.7", + "rand 0.8.8", "serde", "serde_json", "serde_path_to_error", @@ -3426,9 +3446,9 @@ dependencies = [ [[package]] name = "opendal" -version = "0.58.1" +version = "0.58.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f20562cc7447fcc915fc5c23df305a412ea80a733c9f2fd9e2d267e2815be6d" +checksum = "33dbff14cc9bb085224256d6a81289d2f3202e85b06f408d42534b42162a4231" dependencies = [ "opendal-core", "opendal-service-fs", @@ -3437,11 +3457,12 @@ dependencies = [ [[package]] name = "opendal-core" -version = "0.58.1" +version = "0.58.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec75551ff4cf3e57da98979f6a937aaa9ddb3915bf68cc17d03df733be6646ed" +checksum = "48dbcef97d3eb7591db2c18d5cae95c836bcce07359b98d98dd6f4e861eb77b7" dependencies = [ "anyhow", + "asyncband", "base64 0.23.1", "bytes", "futures", @@ -3449,7 +3470,6 @@ dependencies = [ "jiff", "log", "md-5", - "mea", "percent-encoding", "quick-xml", "reqsign-core", @@ -3463,9 +3483,9 @@ dependencies = [ [[package]] name = "opendal-service-fs" -version = "0.58.1" +version = "0.58.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826c4e17a30643b888fe983897f9a4b23b07066e1d069727a923cc8fb419a702" +checksum = "c7ef1e1c45f3f89282a59073897e0d685e51385fed0aea771714789525cff996" dependencies = [ "bytes", "log", @@ -3477,9 +3497,9 @@ dependencies = [ [[package]] name = "opendal-service-s3" -version = "0.58.1" +version = "0.58.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e80cdf192d7eff05feed747894d64f81905ac4eaf132edf7ea270abdd2d663" +checksum = "c64335f9f24ccb62ac36f1d976342b48611a75ba61979813a4f78a4ebd94de42" dependencies = [ "base64 0.23.1", "bytes", @@ -3513,7 +3533,7 @@ dependencies = [ "oauth2", "p256", "p384", - "rand 0.8.7", + "rand 0.8.8", "rsa", "serde", "serde-value", @@ -3660,13 +3680,12 @@ dependencies = [ [[package]] name = "password-hash" -version = "0.5.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", + "getrandom 0.4.3", + "phc", ] [[package]] @@ -3731,9 +3750,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" +checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf" dependencies = [ "memchr", "ucd-trie", @@ -3741,9 +3760,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" +checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d" dependencies = [ "pest", "pest_generator", @@ -3751,9 +3770,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" +checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a" dependencies = [ "pest", "pest_meta", @@ -3764,13 +3783,24 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" +checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496" dependencies = [ "pest", ] +[[package]] +name = "phc" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892" +dependencies = [ + "base64ct", + "ctutils", + "getrandom 0.4.3", +] + [[package]] name = "phf" version = "0.11.3" @@ -3797,7 +3827,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared 0.11.3", - "rand 0.8.7", + "rand 0.8.8", ] [[package]] @@ -3883,9 +3913,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "polling" @@ -3903,9 +3933,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "portable-atomic-util" @@ -3918,9 +3948,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] @@ -3971,6 +4001,15 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.13+spec-1.1.0", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -4080,9 +4119,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -4174,22 +4213,22 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -4240,9 +4279,9 @@ dependencies = [ [[package]] name = "reqsign-aws-core" -version = "3.0.3" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4af084e1f3cbf3e67e0c972765399bce54ecec804cceba46b39a8331f3c1bff" +checksum = "bac4749b7dfa7bfaccd01eb03e9dc795ed37e3f20d6f0f38e2c67ee85ad6bc86" dependencies = [ "bytes", "form_urlencoded", @@ -4261,9 +4300,9 @@ dependencies = [ [[package]] name = "reqsign-aws-v4" -version = "3.1.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac5b3b7cefa28933792b439186459f77f19f9b6edbeab41b8b187150361a206" +checksum = "ff250f0fd0b913fbd565e405acc553da0f13bde30bfb5403178c9d0313cdc15f" dependencies = [ "bytes", "http 1.5.0", @@ -4276,9 +4315,9 @@ dependencies = [ [[package]] name = "reqsign-core" -version = "3.2.1" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07dd510b1e1b9b241883e483358147fb2ed2d497a7b39b065ba61eb93deceb0" +checksum = "ff052daffb0599681c50f85c59e7236438976efe991ab864edd9f3b235501a0f" dependencies = [ "anyhow", "base64 0.23.1", @@ -4289,6 +4328,7 @@ dependencies = [ "http 1.5.0", "jiff", "log", + "mea", "percent-encoding", "sha1 0.11.0", "sha2 0.11.0", @@ -4297,9 +4337,9 @@ dependencies = [ [[package]] name = "reqsign-file-read-tokio" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "663d9d55abd0df0830ef0ae43708297cc1371cf4e8ca91f3ac813c309cca8c98" +checksum = "b3235df90a6bca681aa47dd86f2393d122a6d77042aa8a7c81e218cd45c5bfc0" dependencies = [ "anyhow", "reqsign-core", @@ -4323,7 +4363,7 @@ dependencies = [ "http 1.5.0", "http-body 1.1.0", "http-body-util", - "hyper 1.11.0", + "hyper 1.11.1", "hyper-rustls", "hyper-util", "js-sys", @@ -4413,14 +4453,14 @@ dependencies = [ "either", "figment", "futures", - "indexmap 2.14.0", + "indexmap 2.14.1", "log", "memchr", "multer", "num_cpus", "parking_lot", "pin-project-lite", - "rand 0.8.7", + "rand 0.8.8", "ref-cast", "rocket_codegen", "rocket_http", @@ -4445,7 +4485,7 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" dependencies = [ "devise", "glob", - "indexmap 2.14.0", + "indexmap 2.14.1", "proc-macro2", "quote", "rocket_http", @@ -4465,7 +4505,7 @@ dependencies = [ "futures", "http 0.2.12", "hyper 0.14.32", - "indexmap 2.14.0", + "indexmap 2.14.1", "log", "memchr", "pear", @@ -4532,17 +4572,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" dependencies = [ "hashbrown 0.16.1", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] name = "rtoolbox" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" +checksum = "9a1efe12a1469752d0e6ff5ebec0b6ef4924cc5c4c71046b0ec730040535819d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4608,7 +4648,7 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.13", + "rustls-webpki 0.103.15", "subtle", "zeroize", ] @@ -4657,7 +4697,7 @@ dependencies = [ "rustls 0.23.43", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.13", + "rustls-webpki 0.103.15", "security-framework", "security-framework-sys", "webpki-root-certs", @@ -4682,9 +4722,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "ring", "rustls-pki-types", @@ -4866,7 +4906,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -4875,7 +4915,6 @@ version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ - "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -4935,16 +4974,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.21.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" dependencies = [ "base64 0.22.1", "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.14.0", + "indexmap 2.14.1", + "jiff", "schemars 0.9.0", "schemars 1.2.2", "serde_core", @@ -4955,9 +4995,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.21.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -4983,7 +5023,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "digest 0.11.3", ] @@ -5005,7 +5045,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "digest 0.11.3", ] @@ -5084,7 +5124,7 @@ checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.19", + "thiserror 2.0.20", "time", ] @@ -5207,11 +5247,11 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svg-hush" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "929223e80cdcec0482207576ea09692dd71b2b559057fc172e292ecec9a97559" +checksum = "e690409a034dc81758d2986dcc0e01ffe4a77c65902a120777f5182dd40b1f7f" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "data-url", "quick-error", "url", @@ -5231,9 +5271,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" dependencies = [ "proc-macro2", "quote", @@ -5323,11 +5363,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl 2.0.19", + "thiserror-impl 2.0.20", ] [[package]] @@ -5343,13 +5383,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -5404,9 +5444,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", @@ -5452,7 +5492,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -5522,7 +5562,7 @@ dependencies = [ "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", - "toml_edit", + "toml_edit 0.22.27", ] [[package]] @@ -5556,13 +5596,22 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.14.1", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -5570,6 +5619,18 @@ dependencies = [ "winnow 0.7.15", ] +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap 2.14.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + [[package]] name = "toml_parser" version = "1.1.3+spec-1.1.0" @@ -5727,7 +5788,7 @@ dependencies = [ "http 1.5.0", "httparse", "log", - "rand 0.8.7", + "rand 0.8.8", "sha1 0.10.7", "thiserror 1.0.69", "url", @@ -5822,9 +5883,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.24.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -5979,9 +6040,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if", "once_cell", @@ -5992,9 +6053,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.76" +version = "0.4.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" dependencies = [ "js-sys", "wasm-bindgen", @@ -6002,9 +6063,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6012,9 +6073,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", @@ -6025,9 +6086,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] @@ -6047,9 +6108,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" dependencies = [ "js-sys", "wasm-bindgen", @@ -6144,9 +6205,9 @@ dependencies = [ [[package]] name = "which" -version = "8.0.5" +version = "8.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3ef584124b911bcc3875c2f1472e80f24361ceb789bd1c62b3e9a3df9ff43c" +checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" dependencies = [ "libc", ] @@ -6276,15 +6337,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -6429,6 +6481,9 @@ name = "winnow" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] [[package]] name = "wit-bindgen" @@ -6438,9 +6493,9 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "x509-parser" @@ -6471,9 +6526,9 @@ dependencies = [ [[package]] name = "xml" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "636f85e5ca6488e96401b61eb7de54f4e44755c988af0f52cf90230c312a1a89" +checksum = "2f45bb2c13fec6a6cb4c0f76a7e94839e110a14ec803ec2940777a94c347bc52" [[package]] name = "xmlparser" @@ -6528,18 +6583,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", @@ -6589,9 +6644,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -6600,9 +6655,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "yoke", "zerofrom", @@ -6611,15 +6666,21 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + [[package]] name = "zmij" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 3e187ff3..7d711fe1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace.package] edition = "2024" -rust-version = "1.95.0" +rust-version = "1.96.1" license = "AGPL-3.0-only" repository = "https://github.com/dani-garcia/vaultwarden" publish = false @@ -66,7 +66,7 @@ syslog = "7.0.0" macros = { path = "./macros" } # Logging -log = "0.4.33" +log = "0.4.34" fern = { version = "0.7.1", features = ["syslog-7", "reopen-1"] } # We need the `log` feature for `tracing` to enable logging for several crates to work, like lettre or webauthn-rs tracing = { version = "0.1.44", features = ["log"] } @@ -90,7 +90,7 @@ rmpv = "1.3.1" # MessagePack library dashmap = "6.2.1" # Async futures -futures = "0.3.33" +futures = "0.3.34" tokio = { version = "1.53.1", features = [ "fs", "io-util", @@ -107,7 +107,7 @@ serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.151" # A safe, extensible ORM and Query builder -diesel = { version = "2.3.11", features = ["chrono", "r2d2", "numeric"] } +diesel = { version = "2.3.12", features = ["chrono", "r2d2", "numeric"] } diesel_migrations = "2.3.2" derive_more = { version = "2.1.1", features = [ @@ -120,7 +120,7 @@ derive_more = { version = "2.1.1", features = [ diesel-derive-newtype = "2.1.3" # SQLite, statically bundled unless the `sqlite_system` feature is enabled -libsqlite3-sys = { version = "0.37.0", optional = true } +libsqlite3-sys = { version = "0.38.2", optional = true } # Crypto-related libraries rand = "0.10.2" @@ -129,7 +129,7 @@ rustls = { version = "0.23.43", features = ["ring", "std"], default-features = f subtle = "2.6.1" # UUID generation -uuid = { version = "1.24.0", features = ["v4"] } +uuid = { version = "1.26.0", features = ["v4"] } # Date and time libraries chrono = { version = "0.4.45", default-features = false, features = ["clock", "serde"] } @@ -180,7 +180,7 @@ percent-encoding = "2.3.2" # URL encoding library used for URL's in the emails email_address = "0.2.9" # HTML Template library -handlebars = { version = "6.4.3", features = ["dir_source"] } +handlebars = { version = "6.4.4", features = ["dir_source"] } # HTTP client (Used for favicons, version check, DUO and HIBP API) reqwest = { version = "0.13.4", default-features = false, features = [ @@ -212,13 +212,13 @@ regex = { version = "1.13.1", default-features = false, features = [ ] } data-url = "0.3.2" bytes = "1.12.1" -svg-hush = "0.9.6" +svg-hush = "0.9.7" # Cache function results (Used for version check and favicon fetching) -cached = { version = "2.0.2", features = ["async"] } +cached = { version = "3.1.1", features = ["async"] } # Used for custom short lived cookie jar during favicon extraction -cookie = "0.18.1" +cookie = "0.18.2" cookie_store = "0.22.1" # Used by U2F, JWT and PostgreSQL @@ -236,7 +236,7 @@ ipnet = "2.12.1" # OIDC for SSO openidconnect = { version = "4.0.1", default-features = false } -moka = { version = "0.12.15", features = ["future"] } +moka = { version = "0.12.16", features = ["future"] } # Check client versions for specific features. semver = "1.0.28" @@ -245,10 +245,10 @@ semver = "1.0.28" # Mainly used for the musl builds, since the default musl malloc is very slow mimalloc = { version = "0.1.52", optional = true, default-features = false, features = ["secure"] } -which = "8.0.5" +which = "8.0.6" # Argon2 library with support for the PHC format -argon2 = "0.5.3" +argon2 = "0.6.0" # Reading a password from the cli for generating the Argon2id ADMIN_TOKEN rpassword = "7.5.4" @@ -257,20 +257,20 @@ rpassword = "7.5.4" grass_compiler = { version = "0.13.4", default-features = false } # File are accessed through Apache OpenDAL -opendal = { version = "0.58.1", default-features = false, features = ["services-fs"] } +opendal = { version = "0.58.2", default-features = false, features = ["services-fs"] } # For retrieving AWS credentials, including temporary SSO credentials -aws-config = { version = "1.10.1", optional = true, default-features = false, features = [ +aws-config = { version = "1.11.0", optional = true, default-features = false, features = [ "behavior-version-latest", "credentials-process", "rt-tokio", "sso", ] } aws-credential-types = { version = "1.3.0", optional = true } -aws-smithy-runtime-api = { version = "1.14.0", optional = true } +aws-smithy-runtime-api = { version = "1.15.0", optional = true } http = { version = "1.5.0", optional = true } -reqsign-aws-v4 = { version = "3.1.0", optional = true } -reqsign-core = { version = "3.2.1", optional = true } +reqsign-aws-v4 = { version = "3.3.0", optional = true } +reqsign-core = { version = "3.3.1", optional = true } # Strip debuginfo from the release builds # The debug symbols are to provide better panic traces diff --git a/macros/Cargo.toml b/macros/Cargo.toml index f059a214..84d17192 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -14,7 +14,7 @@ proc-macro = true [dependencies] quote = "1.0.47" -syn = "3.0.3" +syn = "3.0.4" [lints] workspace = true diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 9c5862a2..9bfb1d94 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.97.1" +channel = "1.98.0" components = [ "rustfmt", "clippy" ] profile = "minimal" diff --git a/src/api/admin.rs b/src/api/admin.rs index eaa681dd..4bdf8e71 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -231,7 +231,7 @@ fn validate_token(token: &str) -> bool { None => false, Some(t) if t.starts_with("$argon2") => { use argon2::password_hash::PasswordVerifier; - match argon2::password_hash::PasswordHash::new(t) { + match argon2::password_hash::phc::PasswordHash::new(t) { Ok(h) => { // NOTE: hash params from `ADMIN_TOKEN` are used instead of what is configured in the `Argon2` instance. argon2::Argon2::default().verify_password(token.trim().as_ref(), &h).is_ok() @@ -647,7 +647,7 @@ use cached::macros::cached; /// Cache this function to prevent API call rate limit. Github only allows 60 requests per hour, and we use 3 here already /// It will cache this function for 600 seconds (10 minutes) which should prevent the exhaustion of the rate limit /// Any cache will be lost if Vaultwarden is restarted -#[cached(ttl = 600, sync_writes = "default")] +#[cached(ttl_secs = 600, sync_writes = "default")] async fn get_release_info(has_http_access: bool) -> (String, String, String) { // If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway. if has_http_access { diff --git a/src/api/web.rs b/src/api/web.rs index a7eca9fc..d6d8d62c 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -301,9 +301,6 @@ pub fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Erro "jdenticon-3.3.0.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon-3.3.0.js"))), "datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))), "datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))), - "jquery-4.0.0.slim.js" => { - Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-4.0.0.slim.js"))) - } _ => err!(format!("Static file not found: {filename}")), } } diff --git a/src/config.rs b/src/config.rs index 2502dd02..87bea195 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1272,7 +1272,7 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> { if !cfg.disable_admin_token { match cfg.admin_token.as_ref() { Some(t) if t.starts_with("$argon2") => { - if let Err(e) = argon2::password_hash::PasswordHash::new(t) { + if let Err(e) = argon2::password_hash::phc::PasswordHash::new(t) { err!(format!("The configured Argon2 PHC in `ADMIN_TOKEN` is invalid: '{e}'")) } } diff --git a/src/main.rs b/src/main.rs index 28645694..437354af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -137,9 +137,7 @@ fn parse_args() { if let Some(command) = pargs.subcommand().unwrap_or_default() { if command == "hash" { - use argon2::{ - Algorithm::Argon2id, Argon2, ParamsBuilder, PasswordHasher, Version::V0x13, password_hash::SaltString, - }; + use argon2::{Algorithm::Argon2id, Argon2, ParamsBuilder, PasswordHasher, Version::V0x13}; let mut argon2_params = ParamsBuilder::new(); let preset: Option = pargs.opt_value_from_str(["-p", "--preset"]).unwrap_or_default(); @@ -172,10 +170,10 @@ fn parse_args() { } let argon2 = Argon2::new(Argon2id, V0x13, argon2_params.build().unwrap()); - let salt = SaltString::encode_b64(&crypto::get_random_bytes::<32>()).unwrap(); + let salt = crypto::get_random_bytes::<32>(); let argon2_timer = tokio::time::Instant::now(); - if let Ok(password_hash) = argon2.hash_password(password.as_bytes(), &salt) { + if let Ok(password_hash) = argon2.hash_password_with_salt(password.as_bytes(), &salt) { println!( "\n\ ADMIN_TOKEN='{password_hash}'\n\n\ diff --git a/src/static/scripts/admin_organizations.js b/src/static/scripts/admin_organizations.js index 33314ad7..0aa57dcd 100644 --- a/src/static/scripts/admin_organizations.js +++ b/src/static/scripts/admin_organizations.js @@ -1,5 +1,5 @@ "use strict"; -/* global jQuery, _post:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ +/* global DataTable, _post:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ function deleteOrganization(event) { event.preventDefault(); @@ -41,8 +41,9 @@ function initActions() { // onLoad events document.addEventListener("DOMContentLoaded", (/*event*/) => { - jQuery("#orgs-table").DataTable({ - "drawCallback": function() { + const columnCount = document.getElementById("orgs-table").querySelectorAll("thead th").length; + new DataTable("#orgs-table", { + "drawCallback": function () { initActions(); }, "stateSave": true, @@ -53,7 +54,7 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => { ], "pageLength": -1, // Default show all "columnDefs": [{ - "targets": [4,5], + "targets": [columnCount - 2, columnCount - 1], // Do not include the last two columns into the search/order features "searchable": false, "orderable": false }] @@ -66,4 +67,4 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => { if (btnReload) { btnReload.addEventListener("click", reload); } -}); \ No newline at end of file +}); diff --git a/src/static/scripts/admin_users.js b/src/static/scripts/admin_users.js index a2a643c3..63ee2d7b 100644 --- a/src/static/scripts/admin_users.js +++ b/src/static/scripts/admin_users.js @@ -1,5 +1,5 @@ "use strict"; -/* global jQuery, _post:readable, _delete:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ +/* global DataTable, _post:readable, _delete:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ function deleteUser(event) { event.preventDefault(); @@ -141,7 +141,7 @@ function inviteUser(event) { ); } -function resendUserInvite (event) { +function resendUserInvite(event) { event.preventDefault(); event.stopPropagation(); const id = event.target.parentNode.dataset.vwUserUuid; @@ -179,37 +179,9 @@ const ORG_TYPES = { }, }; -// Special sort function to sort dates in ISO format -jQuery.extend(jQuery.fn.dataTableExt.oSort, { - "date-iso-pre": function(a) { - let x; - const sortDate = a.replace(/(<([^>]+)>)/gi, "").trim(); - if (sortDate !== "") { - const dtParts = sortDate.split(" "); - const timeParts = (undefined != dtParts[1]) ? dtParts[1].split(":") : ["00", "00", "00"]; - const dateParts = dtParts[0].split("-"); - x = (dateParts[0] + dateParts[1] + dateParts[2] + timeParts[0] + timeParts[1] + ((undefined != timeParts[2]) ? timeParts[2] : 0)) * 1; - if (isNaN(x)) { - x = 0; - } - } else { - x = Infinity; - } - return x; - }, - - "date-iso-asc": function(a, b) { - return a - b; - }, - - "date-iso-desc": function(a, b) { - return b - a; - } -}); - const userOrgTypeDialog = document.getElementById("userOrgTypeDialog"); // Fill the form and title -userOrgTypeDialog.addEventListener("show.bs.modal", function(event) { +userOrgTypeDialog.addEventListener("show.bs.modal", function (event) { // Get shared values const userEmail = event.relatedTarget.parentNode.dataset.vwUserEmail; const userUuid = event.relatedTarget.parentNode.dataset.vwUserUuid; @@ -227,7 +199,7 @@ userOrgTypeDialog.addEventListener("show.bs.modal", function(event) { }, false); // Prevent accidental submission of the form with valid elements after the modal has been hidden. -userOrgTypeDialog.addEventListener("hide.bs.modal", function() { +userOrgTypeDialog.addEventListener("hide.bs.modal", function () { document.getElementById("userOrgTypeDialogOrgName").textContent = ""; document.getElementById("userOrgTypeDialogUserEmail").textContent = ""; document.getElementById("userOrgTypeUserUuid").value = ""; @@ -249,7 +221,7 @@ function updateUserOrgType(event) { function initUserTable() { // Color all the org buttons per type - document.querySelectorAll("button[data-vw-org-type]").forEach(function(e) { + document.querySelectorAll("button[data-vw-org-type]").forEach(function (e) { const orgType = ORG_TYPES[e.dataset.vwOrgType]; e.style.backgroundColor = orgType.bg; if (orgType.font !== undefined) { @@ -285,12 +257,37 @@ function initUserTable() { } } +// Special sort function to sort dates in ISO format and have anything else as 0 +DataTable.ext.type.order["date-iso-pre"] = function (a) { + let x; + const sortDate = a.replace(/(<([^>]+)>)/gi, "").trim(); + if (sortDate !== "") { + const dtParts = sortDate.split(" "); + const timeParts = (undefined != dtParts[1]) ? dtParts[1].split(":") : ["00", "00", "00"]; + const dateParts = dtParts[0].split("-"); + x = (dateParts[0] + dateParts[1] + dateParts[2] + timeParts[0] + timeParts[1] + ((undefined != timeParts[2]) ? timeParts[2] : 0)) * 1; + if (isNaN(x)) { + x = 0; + } + } else { + x = Infinity; + } + return x; +}; + // onLoad events document.addEventListener("DOMContentLoaded", (/*event*/) => { - const size = jQuery("#users-table > thead th").length; - const ssoOffset = size-7; - jQuery("#users-table").DataTable({ - "drawCallback": function() { + DataTable.ext.type.detect.unshift(function (data) { + if (typeof data !== "string") { return null; } + return data.indexOf("data-sort-type=\"date-iso\"") !== -1 + ? "date-iso" + : null; + }); + + const columnCount = document.getElementById("users-table").querySelectorAll("thead th").length; + new DataTable("#users-table", { + "typeDetect": true, + "drawCallback": function () { initUserTable(); }, "stateSave": true, @@ -301,10 +298,7 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => { ], "pageLength": -1, // Default show all "columnDefs": [{ - "targets": [1 + ssoOffset, 2 + ssoOffset], - "type": "date-iso" - }, { - "targets": size-1, + "targets": columnCount - 1, // Do not include the last column into the search/order features "searchable": false, "orderable": false }] diff --git a/src/static/scripts/datatables.css b/src/static/scripts/datatables.css index e518c143..48b7400c 100644 --- a/src/static/scripts/datatables.css +++ b/src/static/scripts/datatables.css @@ -4,25 +4,32 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#bs5/dt-2.3.8 + * https://datatables.net/download/#bs5/dt-3.0.3 * * Included libraries: - * DataTables 2.3.8 + * DataTables 3.0.3 */ +/*! DataTables Bootstrap 5 integration + * © SpryMedia Ltd - datatables.net/license + */ :root { - --dt-row-selected: 13, 110, 253; - --dt-row-selected-text: 255, 255, 255; - --dt-row-selected-link: 228, 228, 228; - --dt-row-stripe: 0, 0, 0; - --dt-row-hover: 0, 0, 0; - --dt-column-ordering: 0, 0, 0; - --dt-header-align-items: center; - --dt-header-vertical-align: middle; - --dt-html-background: white; + --dt_background-selected: 13, 110, 253; + --dt_color-selected: 255, 255, 255; + --dt_link_color-selected: 228, 228, 228; + --dt-row_background: transparent; + --dt-row_background-selected: var(--dt_background-selected); + --dt-row-text_color-selected: var(--dt_color-selected); + --dt-row-link_color-selected: var(--dt_link_color-selected); + --dt-row_background-stripe: 0, 0, 0; + --dt-row_background-hover: 0, 0, 0; + --dt-column-ordering_background: 0, 0, 0; + --dt-header-cell_align-items: center; + --dt-header-cell_vertical-align: middle; + --dt-html_background: white; } :root.dark { - --dt-html-background: rgb(33, 37, 41); + --dt-html_background: rgb(33, 37, 41); } table.dataTable tbody td.dt-control { @@ -44,16 +51,13 @@ table.dataTable tbody tr.dt-hasChild td.dt-control:before { border-bottom: 0px solid transparent; border-right: 5px solid transparent; } -table.dataTable tfoot:empty { - display: none; -} -html.dark table.dataTable td.dt-control:before, +:root.dark table.dataTable td.dt-control:before, :root[data-bs-theme=dark] table.dataTable td.dt-control:before, :root[data-theme=dark] table.dataTable td.dt-control:before { border-left-color: rgba(255, 255, 255, 0.5); } -html.dark table.dataTable tr.dt-hasChild td.dt-control:before, +:root.dark table.dataTable tr.dt-hasChild td.dt-control:before, :root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before, :root[data-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before { border-top-color: rgba(255, 255, 255, 0.5); @@ -84,6 +88,25 @@ div.dt-scroll-body tfoot tr td div.dt-scroll-sizing { overflow: hidden !important; } +/*! DataTables Bootstrap 5 integration + * © SpryMedia Ltd - datatables.net/license + */ +:root { + --dt-order-arrow_color: rgb(51, 51, 51); + --dt-order-arrow_color-current: rgb(51, 51, 51); + --dt-order-arrow-height: 7px; + --dt-order-arrow_opacity: 0.125; + --dt-order-arrow_opacity-current: 0.65; + --dt-order-arrow-width: 8px; + --dt-order-arrow-gap: 1px; + --dt-order-header_outline-hover: 2px solid rgba(0, 0, 0, 0.05); +} +:root.dark, :root[data-bs-theme=dark], :root[data-theme=dark] { + --dt-order-arrow_color: rgb(229, 233, 238); + --dt-order-arrow_color-current: rgb(229, 233, 238); + --dt-order-header_outline-hover: 2px solid rgba(255, 255, 255, 0.05); +} + table.dataTable thead > tr > th:active, table.dataTable thead > tr > td:active { outline: none; @@ -91,20 +114,18 @@ table.dataTable thead > tr > td:active { table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > td.dt-orderable-asc .dt-column-order:before, table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before { - position: absolute; - display: block; - bottom: 50%; - content: "\25B2"; - content: "\25B2"/""; + bottom: calc(50% + var(--dt-order-arrow-gap)); + border-bottom: var(--dt-order-arrow-height) solid var(--dt-order-arrow_color); + border-left: calc(var(--dt-order-arrow-width) / 2) solid transparent; + border-right: calc(var(--dt-order-arrow-width) / 2) solid transparent; } table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, table.dataTable thead > tr > td.dt-orderable-desc .dt-column-order:after, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:after { - position: absolute; - display: block; - top: 50%; - content: "\25BC"; - content: "\25BC"/""; + top: calc(50% + 1px); + border-top: var(--dt-order-arrow-height) solid var(--dt-order-arrow_color); + border-left: calc(var(--dt-order-arrow-width) / 2) solid transparent; + border-right: calc(var(--dt-order-arrow-width) / 2) solid transparent; } table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order, table.dataTable thead > tr > td.dt-orderable-asc .dt-column-order, @@ -112,8 +133,8 @@ table.dataTable thead > tr > td.dt-orderable-desc .dt-column-order, table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order { position: relative; - width: 12px; - height: 20px; + width: var(--dt-order-arrow-width); + align-self: stretch; } table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, table.dataTable thead > tr > td.dt-orderable-asc .dt-column-order:before, @@ -124,10 +145,14 @@ table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:after, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:before, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:after { + position: absolute; + display: block; + content: " "; + height: 0; + width: 0; left: 0; - opacity: 0.125; - line-height: 9px; - font-size: 0.8em; + color: var(--dt-order-arrow_color); + opacity: var(--dt-order-arrow_opacity); } table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > td.dt-orderable-asc, @@ -137,13 +162,18 @@ table.dataTable thead > tr > td.dt-orderable-desc { table.dataTable thead > tr > th.dt-orderable-asc:hover, table.dataTable thead > tr > th.dt-orderable-desc:hover, table.dataTable thead > tr > td.dt-orderable-asc:hover, table.dataTable thead > tr > td.dt-orderable-desc:hover { - outline: 2px solid rgba(0, 0, 0, 0.05); + outline: var(--dt-order-header_outline-hover); outline-offset: -2px; } -table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, -table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before, +table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before { + border-bottom-color: var(--dt-order-arrow_color-current); + opacity: var(--dt-order-arrow_opacity-current); +} +table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:after { - opacity: 0.6; + border-top-color: var(--dt-order-arrow_color-current); + opacity: var(--dt-order-arrow_opacity-current); } table.dataTable thead > tr > th.dt-orderable-none:not(.dt-ordering-asc, .dt-ordering-desc) .dt-column-order:empty, table.dataTable thead > tr > th.sorting_desc_disabled .dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled .dt-column-order:before, table.dataTable thead > tr > td.dt-orderable-none:not(.dt-ordering-asc, .dt-ordering-desc) .dt-column-order:empty, @@ -166,7 +196,7 @@ table.dataTable tfoot > tr > td div.dt-column-header, table.dataTable tfoot > tr > td div.dt-column-footer { display: flex; justify-content: space-between; - align-items: var(--dt-header-align-items); + align-items: var(--dt-header-cell_align-items); gap: 4px; } table.dataTable thead > tr > th div.dt-column-header .dt-column-title, @@ -202,7 +232,14 @@ div.dt-scroll-body > table.dataTable > thead > tr > td { :root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-desc:hover, :root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-asc:hover, :root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-desc:hover { - outline: 2px solid rgba(255, 255, 255, 0.05); + outline: var(--dt-order-header_outline-hover); +} + +/*! DataTables Bootstrap 5 integration + * © SpryMedia Ltd - datatables.net/license + */ +:root { + --dt-processing-circle_background: var(--dt_background-selected); } div.dt-processing { @@ -228,8 +265,7 @@ div.dt-processing > div:last-child > div { width: 13px; height: 13px; border-radius: 50%; - background: rgb(13, 110, 253); - background: rgb(var(--dt-row-selected)); + background: rgb(var(--dt-processing-circle_background)); animation-timing-function: cubic-bezier(0, 1, 1, 0); } div.dt-processing > div:last-child > div:nth-child(1) { @@ -342,7 +378,7 @@ table.dataTable thead td, table.dataTable tfoot th, table.dataTable tfoot td { text-align: left; - vertical-align: var(--dt-header-vertical-align); + vertical-align: var(--dt-header-cell_vertical-align); } table.dataTable thead th.dt-head-left, table.dataTable thead td.dt-head-left, @@ -425,11 +461,16 @@ table.dataTable tbody td.dt-body-nowrap { white-space: nowrap; } -/*! Bootstrap 5 integration for DataTables - * - * ©2020 SpryMedia Ltd, all rights reserved. - * License: MIT datatables.net/license/mit - */ +:root { + --dt_background-selected: 13, 110, 253; +} + +:root[data-bs-theme=dark] { + --dt-row_background-hover: 255, 255, 255; + --dt-row_background-stripe: 255, 255, 255; + --dt-column-ordering_background: 255, 255, 255; +} + table.table.dataTable { clear: both; margin-bottom: 0; @@ -443,31 +484,26 @@ table.table.dataTable > :not(caption) > * > * { background-color: var(--bs-table-bg); } table.table.dataTable > tbody > tr { - background-color: transparent; + background-color: var(--dt-row_background); } table.table.dataTable > tbody > tr.selected > * { - box-shadow: inset 0 0 0 9999px rgb(13, 110, 253); - box-shadow: inset 0 0 0 9999px rgb(var(--dt-row-selected)); - color: rgb(255, 255, 255); - color: rgb(var(--dt-row-selected-text)); + box-shadow: inset 0 0 0 9999px rgb(var(--dt-row_background-selected)); + color: rgb(var(--dt-row-text_color-selected)); } table.table.dataTable > tbody > tr.selected a { - color: rgb(228, 228, 228); - color: rgb(var(--dt-row-selected-link)); + color: rgb(var(--dt-row-link_color-selected)); } table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * { - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row_background-stripe), 0.05); } table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1).selected > * { - box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row_background-selected), 0.95); } table.table.dataTable.table-hover > tbody > tr:hover > * { - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row_background-hover), 0.075); } table.table.dataTable.table-hover > tbody > tr.selected:hover > * { - box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row_background-selected), 0.975); } div.dt-container div.dt-layout-start > *:not(:last-child) { @@ -616,10 +652,4 @@ div.table-responsive > div.dt-container > div.row > div[class^=col-]:last-child padding-right: 0; } -:root[data-bs-theme=dark] { - --dt-row-hover: 255, 255, 255; - --dt-row-stripe: 255, 255, 255; - --dt-column-ordering: 255, 255, 255; -} - diff --git a/src/static/scripts/datatables.js b/src/static/scripts/datatables.js index c9f9ea56..1ae94cd2 100644 --- a/src/static/scripts/datatables.js +++ b/src/static/scripts/datatables.js @@ -4,14196 +4,12792 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#bs5/dt-2.3.8 + * https://datatables.net/download/#bs5/dt-3.0.3 * * Included libraries: - * DataTables 2.3.8 + * DataTables 3.0.3 */ -/*! DataTables 2.3.8 - * © SpryMedia Ltd - datatables.net/license +/*! DataTables 3.0.3 + * Copyright (c) SpryMedia Ltd - datatables.net/license */ -(function( factory ) { - "use strict"; - - if ( typeof define === 'function' && define.amd ) { +(function(factory){ + if (typeof define === 'function' && define.amd) { // AMD - define( ['jquery'], function ( $ ) { - return factory( $, window, document ); - } ); + define([], function () { + return factory(window, document); + }); } - else if ( typeof exports === 'object' ) { + else if (typeof exports === 'object') { // CommonJS - // jQuery's factory checks for a global window - if it isn't present then it - // returns a factory function that expects the window object - var jq = require('jquery'); + var cjsRequires = function (root) { }; if (typeof window === 'undefined') { - module.exports = function (root, $) { - if ( ! root ) { + module.exports = function (root) { + if (! root) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } - if ( ! $ ) { - $ = jq( root ); - } - - return factory( $, root, root.document ); + cjsRequires(root); + return factory(root, root.document); }; } else { - module.exports = factory( jq, window, window.document ); + cjsRequires(window); + module.exports = factory(window, window.document); } } else { // Browser - window.DataTable = factory( jQuery, window, document ); + window.DataTable = factory(window, document); } -}(function( $, window, document ) { - "use strict"; +}(function(window, document) { +'use strict'; - - var DataTable = function ( selector, options ) - { - // Check if called with a window or jQuery object for DOM less applications - // This is for backwards compatibility - if (DataTable.factory(selector, options)) { - return DataTable; - } - - // When creating with `new`, create a new DataTable, returning the API instance - if (this instanceof DataTable) { - return $(selector).DataTable(options); - } - else { - // Argument switching - options = selector; - } - - var _that = this; - var emptyInit = options === undefined; - var len = this.length; - - if ( emptyInit ) { - options = {}; - } - - // Method to get DT API instance from jQuery object - this.api = function () - { - return new _Api( this ); - }; - - this.each(function() { - // For each initialisation we want to give it a clean initialisation - // object that can be bashed around - var o = {}; - var oInit = len > 1 ? // optimisation for single table case - _fnExtend( o, options, true ) : - options; - - - var i=0, iLen; - var sId = this.getAttribute( 'id' ); - var defaults = DataTable.defaults; - var $this = $(this); - - // Sanity check - if ( this.nodeName.toLowerCase() != 'table' ) - { - _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); - return; - } - - // Special case for options - if (oInit.on && oInit.on.options) { - _fnListener($this, 'options', oInit.on.options); - } - - $this.trigger( 'options.dt', oInit ); - - /* Backwards compatibility for the defaults */ - _fnCompatOpts( defaults ); - _fnCompatCols( defaults.column ); - - /* Convert the camel-case defaults to Hungarian */ - _fnCamelToHungarian( defaults, defaults, true ); - _fnCamelToHungarian( defaults.column, defaults.column, true ); - - /* Setting up the initialisation object */ - _fnCamelToHungarian( defaults, $.extend( oInit, _fnEscapeObject($this.data()) ), true ); - - - - /* Check to see if we are re-initialising a table */ - var allSettings = DataTable.settings; - for ( i=0, iLen=allSettings.length ; i'), - fastData: function (row, column, type) { - return _fnGetCellData(oSettings, row, column, type); - } - } ); - oSettings.nTable = this; - oSettings.oInit = oInit; - - allSettings.push( oSettings ); - - // Make a single API instance available for internal handling - oSettings.api = new _Api( oSettings ); - - // Need to add the instance after the instance after the settings object has been added - // to the settings array, so we can self reference the table instance if more than one - oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable(); - - // Backwards compatibility, before we apply all the defaults - _fnCompatOpts( oInit ); - - // If the length menu is given, but the init display length is not, use the length menu - if ( oInit.aLengthMenu && ! oInit.iDisplayLength ) - { - oInit.iDisplayLength = Array.isArray(oInit.aLengthMenu[0]) - ? oInit.aLengthMenu[0][0] - : $.isPlainObject( oInit.aLengthMenu[0] ) - ? oInit.aLengthMenu[0].value - : oInit.aLengthMenu[0]; - } - - // Apply the defaults and init options to make a single init object will all - // options defined from defaults and instance options. - oInit = _fnExtend( $.extend( true, {}, defaults ), oInit ); - - - // Map the initialisation options onto the settings object - _fnMap( oSettings.oFeatures, oInit, [ - "bPaginate", - "bLengthChange", - "bFilter", - "bSort", - "bSortMulti", - "bInfo", - "bProcessing", - "bAutoWidth", - "bSortClasses", - "bServerSide", - "bDeferRender" - ] ); - _fnMap( oSettings, oInit, [ - "ajax", - "fnFormatNumber", - "sServerMethod", - "aaSorting", - "aaSortingFixed", - "aLengthMenu", - "sPaginationType", - "iStateDuration", - "bSortCellsTop", - "iTabIndex", - "sDom", - "fnStateLoadCallback", - "fnStateSaveCallback", - "renderer", - "searchDelay", - "rowId", - "caption", - "layout", - "orderDescReverse", - "orderIndicators", - "orderHandler", - "titleRow", - "typeDetect", - "columnTitleTag", - [ "iCookieDuration", "iStateDuration" ], // backwards compat - [ "oSearch", "oPreviousSearch" ], - [ "aoSearchCols", "aoPreSearchCols" ], - [ "iDisplayLength", "_iDisplayLength" ] - ] ); - _fnMap( oSettings.oScroll, oInit, [ - [ "sScrollX", "sX" ], - [ "sScrollXInner", "sXInner" ], - [ "sScrollY", "sY" ], - [ "bScrollCollapse", "bCollapse" ] - ] ); - _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" ); - - /* Callback functions which are array driven */ - _fnCallbackReg( oSettings, 'aoDrawCallback', oInit.fnDrawCallback ); - _fnCallbackReg( oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams ); - _fnCallbackReg( oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams ); - _fnCallbackReg( oSettings, 'aoStateLoaded', oInit.fnStateLoaded ); - _fnCallbackReg( oSettings, 'aoRowCallback', oInit.fnRowCallback ); - _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow ); - _fnCallbackReg( oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback ); - _fnCallbackReg( oSettings, 'aoFooterCallback', oInit.fnFooterCallback ); - _fnCallbackReg( oSettings, 'aoInitComplete', oInit.fnInitComplete ); - _fnCallbackReg( oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback ); - - oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId ); - - // Add event listeners - if (oInit.on) { - Object.keys(oInit.on).forEach(function (key) { - _fnListener($this, key, oInit.on[key]); - }); - } - - /* Browser support detection */ - _fnBrowserDetect( oSettings ); - - var oClasses = oSettings.oClasses; - - $.extend( oClasses, DataTable.ext.classes, oInit.oClasses ); - $this.addClass( oClasses.table ); - - if (! oSettings.oFeatures.bPaginate) { - oInit.iDisplayStart = 0; - } - - if ( oSettings.iInitDisplayStart === undefined ) - { - /* Display start point, taking into account the save saving */ - oSettings.iInitDisplayStart = oInit.iDisplayStart; - oSettings._iDisplayStart = oInit.iDisplayStart; - } - - var defer = oInit.iDeferLoading; - if ( defer !== null ) - { - oSettings.deferLoading = true; - - var tmp = Array.isArray(defer); - oSettings._iRecordsDisplay = tmp ? defer[0] : defer; - oSettings._iRecordsTotal = tmp ? defer[1] : defer; - } - - /* - * Columns - * See if we should load columns automatically or use defined ones - */ - var columnsInit = []; - var thead = this.getElementsByTagName('thead'); - var initHeaderLayout = _fnDetectHeader( oSettings, thead[0] ); - - // If we don't have a columns array, then generate one with nulls - if ( oInit.aoColumns ) { - columnsInit = oInit.aoColumns; - } - else if ( initHeaderLayout.length ) { - for ( i=0, iLen=initHeaderLayout[0].length ; i').prependTo( $this ); - } - - caption.html( oSettings.caption ); - } - - // Store the caption side, so we can remove the element from the document - // when creating the element - if (caption.length) { - caption[0]._captionSide = caption.css('caption-side'); - oSettings.captionNode = caption[0]; - } - - // Place the colgroup element in the correct location for the HTML structure - if (caption.length) { - oSettings.colgroup.insertAfter(caption); - } - else { - oSettings.colgroup.prependTo(oSettings.nTable); - } - - if ( thead.length === 0 ) { - thead = $('').appendTo($this); - } - oSettings.nTHead = thead[0]; - - var tbody = $this.children('tbody'); - if ( tbody.length === 0 ) { - tbody = $('').insertAfter(thead); - } - oSettings.nTBody = tbody[0]; - - var tfoot = $this.children('tfoot'); - if ( tfoot.length === 0 ) { - // If we are a scrolling table, and no footer has been given, then we need to create - // a tfoot element for the caption element to be appended to - tfoot = $('').appendTo($this); - } - oSettings.nTFoot = tfoot[0]; - - // Copy the data index array - oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); - - // Initialisation complete - table can be drawn - oSettings.bInitialised = true; - - // Language definitions - var oLanguage = oSettings.oLanguage; - $.extend( true, oLanguage, oInit.oLanguage ); - - if ( oLanguage.sUrl ) { - // Get the language definitions from a file - $.ajax( { - dataType: 'json', - url: oLanguage.sUrl, - success: function ( json ) { - _fnCamelToHungarian( defaults.oLanguage, json ); - $.extend( true, oLanguage, json, oSettings.oInit.oLanguage ); - - _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true); - _fnInitialise( oSettings ); - }, - error: function () { - // Error occurred loading language file - _fnLog( oSettings, 0, 'i18n file loading error', 21 ); - - // Continue on as best we can - _fnInitialise( oSettings ); - } - } ); - } - else { - _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true); - _fnInitialise( oSettings ); - } - } ); - _that = null; - return this; - }; - - - - /** - * DataTables extensions - * - * This namespace acts as a collection area for plug-ins that can be used to - * extend DataTables capabilities. Indeed many of the build in methods - * use this method to provide their own capabilities (sorting methods for - * example). - * - * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy - * reasons - * - * @namespace - */ - DataTable.ext = _ext = { - /** - * DataTables build type (expanded by the download builder) - * - * @type string - */ - builder: "bs5/dt-2.3.8", - - /** - * Buttons. For use with the Buttons extension for DataTables. This is - * defined here so other extensions can define buttons regardless of load - * order. It is _not_ used by DataTables core. - * - * @type object - * @default {} - */ - buttons: {}, - - - /** - * ColumnControl buttons and content - * - * @type object - */ - ccContent: {}, - - - /** - * Element class names - * - * @type object - * @default {} - */ - classes: {}, - - - /** - * Error reporting. - * - * How should DataTables report an error. Can take the value 'alert', - * 'throw', 'none' or a function. - * - * @type string|function - * @default alert - */ - errMode: "alert", - - /** HTML entity escaping */ - escape: { - /** When reading data-* attributes for initialisation options */ - attributes: false - }, - - /** - * Legacy so v1 plug-ins don't throw js errors on load - */ - feature: [], - - /** - * Feature plug-ins. - * - * This is an object of callbacks which provide the features for DataTables - * to be initialised via the `layout` option. - */ - features: {}, - - - /** - * Row searching. - * - * This method of searching is complimentary to the default type based - * searching, and a lot more comprehensive as it allows you complete control - * over the searching logic. Each element in this array is a function - * (parameters described below) that is called for every row in the table, - * and your logic decides if it should be included in the searching data set - * or not. - * - * Searching functions have the following input parameters: - * - * 1. `{object}` DataTables settings object: see - * {@link DataTable.models.oSettings} - * 2. `{array|object}` Data for the row to be processed (same as the - * original format that was passed in as the data source, or an array - * from a DOM data source - * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which - * can be useful to retrieve the `TR` element if you need DOM interaction. - * - * And the following return is expected: - * - * * {boolean} Include the row in the searched result set (true) or not - * (false) - * - * Note that as with the main search ability in DataTables, technically this - * is "filtering", since it is subtractive. However, for consistency in - * naming we call it searching here. - * - * @type array - * @default [] - * - * @example - * // The following example shows custom search being applied to the - * // fourth column (i.e. the data[3] index) based on two input values - * // from the end-user, matching the data in a certain range. - * $.fn.dataTable.ext.search.push( - * function( settings, data, dataIndex ) { - * var min = document.getElementById('min').value * 1; - * var max = document.getElementById('max').value * 1; - * var version = data[3] == "-" ? 0 : data[3]*1; - * - * if ( min == "" && max == "" ) { - * return true; - * } - * else if ( min == "" && version < max ) { - * return true; - * } - * else if ( min < version && "" == max ) { - * return true; - * } - * else if ( min < version && version < max ) { - * return true; - * } - * return false; - * } - * ); - */ - search: [], - - - /** - * Selector extensions - * - * The `selector` option can be used to extend the options available for the - * selector modifier options (`selector-modifier` object data type) that - * each of the three built in selector types offer (row, column and cell + - * their plural counterparts). For example the Select extension uses this - * mechanism to provide an option to select only rows, columns and cells - * that have been marked as selected by the end user (`{selected: true}`), - * which can be used in conjunction with the existing built in selector - * options. - * - * Each property is an array to which functions can be pushed. The functions - * take three attributes: - * - * * Settings object for the host table - * * Options object (`selector-modifier` object type) - * * Array of selected item indexes - * - * The return is an array of the resulting item indexes after the custom - * selector has been applied. - * - * @type object - */ - selector: { - cell: [], - column: [], - row: [] - }, - - - /** - * Legacy configuration options. Enable and disable legacy options that - * are available in DataTables. - * - * @type object - */ - legacy: { - /** - * Enable / disable DataTables 1.9 compatible server-side processing - * requests - * - * @type boolean - * @default null - */ - ajax: null - }, - - - /** - * Pagination plug-in methods. - * - * Each entry in this object is a function and defines which buttons should - * be shown by the pagination rendering method that is used for the table: - * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the - * buttons are displayed in the document, while the functions here tell it - * what buttons to display. This is done by returning an array of button - * descriptions (what each button will do). - * - * Pagination types (the four built in options and any additional plug-in - * options defined here) can be used through the `paginationType` - * initialisation parameter. - * - * The functions defined take two parameters: - * - * 1. `{int} page` The current page index - * 2. `{int} pages` The number of pages in the table - * - * Each function is expected to return an array where each element of the - * array can be one of: - * - * * `first` - Jump to first page when activated - * * `last` - Jump to last page when activated - * * `previous` - Show previous page when activated - * * `next` - Show next page when activated - * * `{int}` - Show page of the index given - * * `{array}` - A nested array containing the above elements to add a - * containing 'DIV' element (might be useful for styling). - * - * Note that DataTables v1.9- used this object slightly differently whereby - * an object with two functions would be defined for each plug-in. That - * ability is still supported by DataTables 1.10+ to provide backwards - * compatibility, but this option of use is now decremented and no longer - * documented in DataTables 1.10+. - * - * @type object - * @default {} - * - * @example - * // Show previous, next and current page buttons only - * $.fn.dataTableExt.oPagination.current = function ( page, pages ) { - * return [ 'previous', page, 'next' ]; - * }; - */ - pager: {}, - - - renderer: { - pageButton: {}, - header: {} - }, - - - /** - * Ordering plug-ins - custom data source - * - * The extension options for ordering of data available here is complimentary - * to the default type based ordering that DataTables typically uses. It - * allows much greater control over the data that is being used to - * order a column, but is necessarily therefore more complex. - * - * This type of ordering is useful if you want to do ordering based on data - * live from the DOM (for example the contents of an 'input' element) rather - * than just the static string that DataTables knows of. - * - * The way these plug-ins work is that you create an array of the values you - * wish to be ordering for the column in question and then return that - * array. The data in the array much be in the index order of the rows in - * the table (not the currently ordering order!). Which order data gathering - * function is run here depends on the `dt-init columns.orderDataType` - * parameter that is used for the column (if any). - * - * The functions defined take two parameters: - * - * 1. `{object}` DataTables settings object: see - * {@link DataTable.models.oSettings} - * 2. `{int}` Target column index - * - * Each function is expected to return an array: - * - * * `{array}` Data for the column to be ordering upon - * - * @type array - * - * @example - * // Ordering using `input` node values - * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col ) - * { - * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { - * return $('input', td).val(); - * } ); - * } - */ - order: {}, - - - /** - * Type based plug-ins. - * - * Each column in DataTables has a type assigned to it, either by automatic - * detection or by direct assignment using the `type` option for the column. - * The type of a column will effect how it is ordering and search (plug-ins - * can also make use of the column type if required). - * - * @namespace - */ - type: { - /** - * Automatic column class assignment - */ - className: {}, - - /** - * Type detection functions. - * - * The functions defined in this object are used to automatically detect - * a column's type, making initialisation of DataTables super easy, even - * when complex data is in the table. - * - * The functions defined take two parameters: - * - * 1. `{*}` Data from the column cell to be analysed - * 2. `{settings}` DataTables settings object. This can be used to - * perform context specific type detection - for example detection - * based on language settings such as using a comma for a decimal - * place. Generally speaking the options from the settings will not - * be required - * - * Each function is expected to return: - * - * * `{string|null}` Data type detected, or null if unknown (and thus - * pass it on to the other type detection functions. - * - * @type array - * - * @example - * // Currency type detection plug-in: - * $.fn.dataTable.ext.type.detect.push( - * function ( data, settings ) { - * // Check the numeric part - * if ( ! data.substring(1).match(/[0-9]/) ) { - * return null; - * } - * - * // Check prefixed by currency - * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) { - * return 'currency'; - * } - * return null; - * } - * ); - */ - detect: [], - - /** - * Automatic renderer assignment - */ - render: {}, - - - /** - * Type based search formatting. - * - * The type based searching functions can be used to pre-format the - * data to be search on. For example, it can be used to strip HTML - * tags or to de-format telephone numbers for numeric only searching. - * - * Note that is a search is not defined for a column of a given type, - * no search formatting will be performed. - * - * Pre-processing of searching data plug-ins - When you assign the sType - * for a column (or have it automatically detected for you by DataTables - * or a type detection plug-in), you will typically be using this for - * custom sorting, but it can also be used to provide custom searching - * by allowing you to pre-processing the data and returning the data in - * the format that should be searched upon. This is done by adding - * functions this object with a parameter name which matches the sType - * for that target column. This is the corollary of afnSortData - * for searching data. - * - * The functions defined take a single parameter: - * - * 1. `{*}` Data from the column cell to be prepared for searching - * - * Each function is expected to return: - * - * * `{string|null}` Formatted string that will be used for the searching. - * - * @type object - * @default {} - * - * @example - * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { - * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); - * } - */ - search: {}, - - - /** - * Type based ordering. - * - * The column type tells DataTables what ordering to apply to the table - * when a column is sorted upon. The order for each type that is defined, - * is defined by the functions available in this object. - * - * Each ordering option can be described by three properties added to - * this object: - * - * * `{type}-pre` - Pre-formatting function - * * `{type}-asc` - Ascending order function - * * `{type}-desc` - Descending order function - * - * All three can be used together, only `{type}-pre` or only - * `{type}-asc` and `{type}-desc` together. It is generally recommended - * that only `{type}-pre` is used, as this provides the optimal - * implementation in terms of speed, although the others are provided - * for compatibility with existing JavaScript sort functions. - * - * `{type}-pre`: Functions defined take a single parameter: - * - * 1. `{*}` Data from the column cell to be prepared for ordering - * - * And return: - * - * * `{*}` Data to be sorted upon - * - * `{type}-asc` and `{type}-desc`: Functions are typical JavaScript sort - * functions, taking two parameters: - * - * 1. `{*}` Data to compare to the second parameter - * 2. `{*}` Data to compare to the first parameter - * - * And returning: - * - * * `{*}` Ordering match: <0 if first parameter should be sorted lower - * than the second parameter, ===0 if the two parameters are equal and - * >0 if the first parameter should be sorted height than the second - * parameter. - * - * @type object - * @default {} - * - * @example - * // Numeric ordering of formatted numbers with a pre-formatter - * $.extend( $.fn.dataTable.ext.type.order, { - * "string-pre": function(x) { - * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); - * return parseFloat( a ); - * } - * } ); - * - * @example - * // Case-sensitive string ordering, with no pre-formatting method - * $.extend( $.fn.dataTable.ext.order, { - * "string-case-asc": function(x,y) { - * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - * }, - * "string-case-desc": function(x,y) { - * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); - * } - * } ); - */ - order: {} - }, - - /** - * Unique DataTables instance counter - * - * @type int - * @private - */ - _unique: 0, - - - // - // Depreciated - // The following properties are retained for backwards compatibility only. - // The should not be used in new projects and will be removed in a future - // version - // - - /** - * Version check function. - * @type function - * @depreciated Since 1.10 - */ - fnVersionCheck: DataTable.fnVersionCheck, - - - /** - * Index for what 'this' index API functions should use - * @type int - * @deprecated Since v1.10 - */ - iApiIndex: 0, - - - /** - * Software version - * @type string - * @deprecated Since v1.10 - */ - sVersion: DataTable.version - }; - - - // - // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts - // - $.extend( _ext, { - afnFiltering: _ext.search, - aTypes: _ext.type.detect, - ofnSearch: _ext.type.search, - oSort: _ext.type.order, - afnSortData: _ext.order, - aoFeatures: _ext.feature, - oStdClasses: _ext.classes, - oPagination: _ext.pager - } ); - - - $.extend( DataTable.ext.classes, { - container: 'dt-container', - empty: { - row: 'dt-empty' - }, - info: { - container: 'dt-info' - }, - layout: { - row: 'dt-layout-row', - cell: 'dt-layout-cell', - tableRow: 'dt-layout-table', - tableCell: '', - start: 'dt-layout-start', - end: 'dt-layout-end', - full: 'dt-layout-full' - }, - length: { - container: 'dt-length', - select: 'dt-input' - }, - order: { - canAsc: 'dt-orderable-asc', - canDesc: 'dt-orderable-desc', - isAsc: 'dt-ordering-asc', - isDesc: 'dt-ordering-desc', - none: 'dt-orderable-none', - position: 'sorting_' - }, - processing: { - container: 'dt-processing' - }, - scrolling: { - body: 'dt-scroll-body', - container: 'dt-scroll', - footer: { - self: 'dt-scroll-foot', - inner: 'dt-scroll-footInner' - }, - header: { - self: 'dt-scroll-head', - inner: 'dt-scroll-headInner' - } - }, - search: { - container: 'dt-search', - input: 'dt-input' - }, - table: 'dataTable', - tbody: { - cell: '', - row: '' - }, - thead: { - cell: '', - row: '' - }, - tfoot: { - cell: '', - row: '' - }, - paging: { - active: 'current', - button: 'dt-paging-button', - container: 'dt-paging', - disabled: 'disabled', - nav: '' - } - } ); - - - /* - * It is useful to have variables which are scoped locally so only the - * DataTables functions can access them and they don't leak into global space. - * At the same time these functions are often useful over multiple files in the - * core and API, so we list, or at least document, all variables which are used - * by DataTables as private variables here. This also ensures that there is no - * clashing of variable names and that they can easily referenced for reuse. - */ - - - // Defined else where - // _selector_run - // _selector_opts - // _selector_row_indexes - - var _ext; // DataTable.ext - var _Api; // DataTable.Api - var _api_register; // DataTable.Api.register - var _api_registerPlural; // DataTable.Api.registerPlural - - var _re_dic = {}; - var _re_new_lines = /[\r\n\u2028]/g; - var _re_html = /<([^>]*>)/g; - var _max_str_len = Math.pow(2, 28); - - // This is not strict ISO8601 - Date.parse() is quite lax, although - // implementations differ between browsers. - var _re_date = /^\d{2,4}[./-]\d{1,2}[./-]\d{1,2}([T ]{1}\d{1,2}[:.]\d{2}([.:]\d{2})?)?$/; - - // Escape regular expression special characters - var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); - - // https://en.wikipedia.org/wiki/Foreign_exchange_market - // - \u20BD - Russian ruble. - // - \u20a9 - South Korean Won - // - \u20BA - Turkish Lira - // - \u20B9 - Indian Rupee - // - R - Brazil (R$) and South Africa - // - fr - Swiss Franc - // - kr - Swedish krona, Norwegian krone and Danish krone - // - \u2009 is thin space and \u202F is narrow no-break space, both used in many - // - Ƀ - Bitcoin - // - Ξ - Ethereum - // standards as thousands separators. - var _re_formatted_numeric = /['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi; - - - var _empty = function ( d ) { - return !d || d === true || d === '-' ? true : false; - }; - - - var _intVal = function ( s ) { - var integer = parseInt( s, 10 ); - return !isNaN(integer) && isFinite(s) ? integer : null; - }; - - // Convert from a formatted number with characters other than `.` as the - // decimal place, to a JavaScript number - var _numToDecimal = function ( num, decimalPoint ) { - // Cache created regular expressions for speed as this function is called often - if ( ! _re_dic[ decimalPoint ] ) { - _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' ); - } - return typeof num === 'string' && decimalPoint !== '.' ? - num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) : - num; - }; - - - var _isNumber = function ( d, decimalPoint, formatted, allowEmpty ) { - var type = typeof d; - var strType = type === 'string'; - - if ( type === 'number' || type === 'bigint') { - return true; - } - - // If empty return immediately so there must be a number if it is a - // formatted string (this stops the string "k", or "kr", etc being detected - // as a formatted number for currency - if ( allowEmpty && _empty( d ) ) { - return true; - } - - if ( decimalPoint && strType ) { - d = _numToDecimal( d, decimalPoint ); - } - - if ( formatted && strType ) { - d = d.replace( _re_formatted_numeric, '' ); - } - - return !isNaN( parseFloat(d) ) && isFinite( d ); - }; - - - // A string without HTML in it can be considered to be HTML still - var _isHtml = function ( d ) { - return _empty( d ) || typeof d === 'string'; - }; - - // Is a string a number surrounded by HTML? - var _htmlNumeric = function ( d, decimalPoint, formatted, allowEmpty ) { - if ( allowEmpty && _empty( d ) ) { - return true; - } - - // input and select strings mean that this isn't just a number - if (typeof d === 'string' && d.match(/<(input|select)/i)) { - return null; - } - - var html = _isHtml( d ); - return ! html ? - null : - _isNumber( _stripHtml( d ), decimalPoint, formatted, allowEmpty ) ? - true : - null; - }; - - - var _pluck = function ( a, prop, prop2 ) { - var out = []; - var i=0, iLen=a.length; - - // Could have the test in the loop for slightly smaller code, but speed - // is essential here - if ( prop2 !== undefined ) { - for ( ; i _max_str_len) { - throw new Error('Exceeded max str len'); - } - - var previous; - - input = input.replace(_re_html, replacement || ''); // Complete tags - - // Safety for incomplete script tag - use do / while to ensure that - // we get all instances - do { - previous = input; - input = input.replace(/ diff --git a/src/static/templates/admin/users.hbs b/src/static/templates/admin/users.hbs index 4c91bc0e..b1dfb17d 100644 --- a/src/static/templates/admin/users.hbs +++ b/src/static/templates/admin/users.hbs @@ -14,7 +14,7 @@ Entries Attachments Organizations - Actions + Actions @@ -47,10 +47,10 @@ {{/if}} - {{created_at}} + {{created_at}} - {{last_active}} + {{last_active}} {{cipher_count}} @@ -153,7 +153,6 @@ - diff --git a/src/util.rs b/src/util.rs index 0e8a93e4..6de2d803 100644 --- a/src/util.rs +++ b/src/util.rs @@ -537,10 +537,7 @@ pub fn is_valid_email(email: &str) -> bool { let Ok(email_url) = url::Url::parse(&format!("https://{}", email.domain())) else { return false; }; - if email_url.path().ne("/") || email_url.domain().is_none() || email_url.query().is_some() { - return false; - } - true + email_url.domain().is_some() && email_url.path() == "/" && email_url.query().is_none() } // From a6c3bd6d1826fb527822df4a2655f34fd440d7d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Thu, 3 Sep 2026 21:17:50 +0200 Subject: [PATCH 14/26] Update rust docker version (#7689) --- docker/DockerSettings.yaml | 2 +- docker/Dockerfile.alpine | 8 ++++---- docker/Dockerfile.debian | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/DockerSettings.yaml b/docker/DockerSettings.yaml index 4c5e851b..fdbf40f2 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -5,7 +5,7 @@ vault_image_digest: "sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10 # We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts # https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707" -rust_version: 1.97.1 # Rust version to be used +rust_version: 1.98.0 # Rust version to be used debian_version: trixie # Debian release name to be used alpine_version: "3.24" # Alpine version to be used # For which platforms/architectures will we try to build images diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index 7045138d..491aa9e0 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -32,10 +32,10 @@ FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330 ########################## ALPINE BUILD IMAGES ########################## ## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64 ## And for Alpine we define all build images here, they will only be loaded when actually used -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.97.1 AS build_amd64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.97.1 AS build_arm64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.97.1 AS build_armv7 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.97.1 AS build_armv6 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.98.0 AS build_amd64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.98.0 AS build_arm64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.98.0 AS build_armv7 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.98.0 AS build_armv6 ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 9ab02568..280559e2 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -36,7 +36,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 -FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.97.1-slim-trixie AS build +FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.98.0-slim-trixie AS build # hadolint ignore=DL3067 COPY --from=xx / / ARG TARGETARCH From 32d85d03bb5ec401d1378f4cd60139be1d8db3f4 Mon Sep 17 00:00:00 2001 From: Tom <83423411+tom27052006@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:13:32 +0200 Subject: [PATCH 15/26] Fix organization import failing with missing field groups (#7699) --- src/api/core/organizations.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 9082297f..c0c90426 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -132,7 +132,6 @@ struct FullCollectionData { name: String, groups: Vec, users: Vec, - id: Option, external_id: Option, } @@ -1793,11 +1792,22 @@ async fn bulk_public_keys( use super::ciphers::CipherData; use super::ciphers::update_cipher_from_data; +// The import endpoint only ever uses the name/id/external_id of a collection. +// Bitwarden's own server ignores `groups`/`users` here too, so do not make them +// mandatory: clients are free to leave them out. +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ImportCollectionData { + name: String, + id: Option, + external_id: Option, +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct ImportData { ciphers: Vec, - collections: Vec, + collections: Vec, collection_relationships: Vec, } From 2ffad8775d8712329aab7d00a05a94f64098170b Mon Sep 17 00:00:00 2001 From: Tom <83423411+tom27052006@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:13:40 +0200 Subject: [PATCH 16/26] Add `pm-32413-multi-client-password-management` feature flag (#7677) --- .env.template | 1 + src/config.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/.env.template b/.env.template index 5f6f374c..d22145b8 100644 --- a/.env.template +++ b/.env.template @@ -390,6 +390,7 @@ ## ## The following flags are available: ## - "pm-5594-safari-account-switching": Enable account switching in Safari. (Safari >= 2026.2.0) +## - "pm-32413-multi-client-password-management": Enable changing the master password directly in the client. (Desktop/Extension >= 2026.4.0) ## - "ssh-agent": Enable SSH agent support on Desktop. (Desktop >= 2024.12.0) ## - "ssh-agent-v2": Enable newer SSH agent support. (Desktop >= 2026.2.1) ## - "ssh-key-vault-item": Enable the creation and use of SSH key vault items. (Clients >= 2024.12.0) diff --git a/src/config.rs b/src/config.rs index 87bea195..7e21ecf1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1425,6 +1425,7 @@ pub const SUPPORTED_FEATURE_FLAGS: &[&str] = &[ "desktop-ui-migration-milestone-4", // Auth Team "pm-5594-safari-account-switching", + "pm-32413-multi-client-password-management", // Autofill Team "ssh-agent", "ssh-agent-v2", From 277e1536ebe426296519f8bd8bf99f5f6c1c5c77 Mon Sep 17 00:00:00 2001 From: The CRahn <5043504+crahn@users.noreply.github.com> Date: Tue, 8 Sep 2026 05:13:51 -0500 Subject: [PATCH 17/26] Log IP/username on two-factor email-login credential failures (#7654) The three "Username or password is incorrect" errors in send_email_login() (email.rs) don't log the client IP or submitted identifier, unlike the equivalent wrong-password error in password_login() (identity.rs), which logs both via format!("IP: {}. Username: {username}.", ip.ip). This makes the two code paths inconsistent for the same underlying error, and means log-based tooling that keys on the identity.rs error's "IP: x.x.x.x" pattern can't do the same for this endpoint. Bring email.rs's three call sites in line with identity.rs's existing format. The two email-present branches log IP+Username (the email submitted); the device-identifier-only branch (SSO path, no email in scope) logs IP+Device instead of fabricating a username. Verified: cargo build/test/clippy/fmt all pass with the sqlite feature (matching one leg of this repo's own CI matrix), including the two existing unit tests in this file. --- src/api/core/two_factor/email.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/api/core/two_factor/email.rs b/src/api/core/two_factor/email.rs index 44ba2e7f..3667b871 100644 --- a/src/api/core/two_factor/email.rs +++ b/src/api/core/two_factor/email.rs @@ -63,13 +63,19 @@ async fn send_email_login(data: Json, client_headers: Client let user = if let Some(email) = email { let Some(user) = User::find_by_mail(email, &conn).await else { - err!("Username or password is incorrect. Try again.") + err!( + "Username or password is incorrect. Try again", + format!("IP: {}. Username: {email}.", client_headers.ip.ip) + ) }; if let Some(master_password_hash) = master_password_hash { // Check password if !user.check_valid_password(master_password_hash) { - err!("Username or password is incorrect. Try again.") + err!( + "Username or password is incorrect. Try again", + format!("IP: {}. Username: {email}.", client_headers.ip.ip) + ) } } else if let Some(auth_request_id) = auth_request_id { let Some(auth_request) = AuthRequest::find_by_uuid(auth_request_id, &conn).await else { @@ -96,7 +102,10 @@ async fn send_email_login(data: Json, client_headers: Client }; // SSO login only sends device id, so we get the user by the most recently used device let Some(user) = User::find_by_device_for_email2fa(device_identifier, &conn).await else { - err!("Username or password is incorrect. Try again.") + err!( + "Username or password is incorrect. Try again", + format!("IP: {}. Device: {device_identifier}.", client_headers.ip.ip) + ) }; user From 57fbed1bed2e42b540cb790dd536e02633c4f445 Mon Sep 17 00:00:00 2001 From: Timshel Date: Tue, 8 Sep 2026 10:14:01 +0000 Subject: [PATCH 18/26] Support admin reset 2fa (#7435) * Support admin reset 2fa * Fix recovery email --------- Co-authored-by: Timshel --- playwright/tests/organization.smtp.spec.ts | 7 +- src/api/core/organizations.rs | 89 ++++++++++++------- src/auth.rs | 12 ++- src/config.rs | 2 +- src/db/models/event.rs | 8 +- src/mail.rs | 14 ++- .../email/admin_account_recovery.hbs | 12 +++ ...ml.hbs => admin_account_recovery.html.hbs} | 11 ++- .../templates/email/admin_reset_password.hbs | 4 - 9 files changed, 112 insertions(+), 47 deletions(-) create mode 100644 src/static/templates/email/admin_account_recovery.hbs rename src/static/templates/email/{admin_reset_password.html.hbs => admin_account_recovery.html.hbs} (55%) delete mode 100644 src/static/templates/email/admin_reset_password.hbs diff --git a/playwright/tests/organization.smtp.spec.ts b/playwright/tests/organization.smtp.spec.ts index 6d0eb859..1e97ed5d 100644 --- a/playwright/tests/organization.smtp.spec.ts +++ b/playwright/tests/organization.smtp.spec.ts @@ -127,6 +127,9 @@ test('Organization is visible', async ({ page }) => { }); test('Recover user password', async ({ page }) => { + await logUser(test, page, users.user2, { mailBuffer: mail2Buffer }); + await activateTOTP(test, page, users.user2); + await logUser(test, page, users.user1, { mailBuffer: mail1Buffer }); let newPassword = "TotoNewPassword"; @@ -138,9 +141,10 @@ test('Recover user password', async ({ page }) => { await page.getByRole('menuitem', { name: 'Recover account' }).click(); await page.getByRole('textbox', { name: 'New master password * (required)', exact: true }).fill(newPassword); await page.getByRole('textbox', { name: 'Confirm new master password * (' }).fill(newPassword); + await page.getByRole('checkbox', { name: 'Reset two-step login' }).check(); await page.getByRole('button', { name: 'Save' }).click(); await utils.checkNotification(page, 'Account recovery success'); - await mail2Buffer.expect((m) => m.subject.includes('Master Password Has Been Changed')); + await mail2Buffer.expect((m) => m.subject.includes('Admin account recovery from Test organization')); }); let user2 = { @@ -150,6 +154,7 @@ test('Recover user password', async ({ page }) => { }; await logUser(test, page, user2, { mailBuffer: mail2Buffer, + mail2fa: true, notNewDevice: true, }); }); diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index c0c90426..4f490854 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use num_traits::FromPrimitive; -use rocket::{Route, serde::json::Json}; +use rocket::{Route, http::Status, serde::json::Json}; use serde_json::Value; use crate::{ @@ -17,7 +17,8 @@ use crate::{ models::{ Cipher, CipherId, Collection, CollectionCipher, CollectionGroup, CollectionId, CollectionUser, EventType, Group, GroupId, GroupUser, Invitation, Membership, MembershipId, MembershipStatus, MembershipType, - OrgPolicy, OrgPolicyType, Organization, OrganizationApiKey, OrganizationId, User, UserId, + OrgPolicy, OrgPolicyType, Organization, OrganizationApiKey, OrganizationId, TwoFactor, TwoFactorType, User, + UserId, }, }, mail, @@ -390,7 +391,7 @@ async fn get_org_collections(org_id: OrganizationId, headers: ManagerHeadersLoos } if !headers.membership.has_full_access() { - err_code!("Resource not found.", "User does not have full access", rocket::http::Status::NotFound.code); + err_code!("Resource not found.", "User does not have full access", Status::NotFound.code); } Ok(Json(json!({ @@ -886,11 +887,11 @@ struct OrgIdData { #[get("/ciphers/organization-details?")] async fn get_org_details(data: OrgIdData, headers: ManagerHeadersLoose, conn: DbConn) -> JsonResult { if data.organization_id != headers.membership.org_uuid { - err_code!("Resource not found.", "Organization id's do not match", rocket::http::Status::NotFound.code); + err_code!("Resource not found.", "Organization id's do not match", Status::NotFound.code); } if !headers.membership.has_full_access() { - err_code!("Resource not found.", "User does not have full access", rocket::http::Status::NotFound.code); + err_code!("Resource not found.", "User does not have full access", Status::NotFound.code); } Ok(Json(json!({ @@ -954,7 +955,7 @@ async fn get_members( } if !headers.membership.has_full_access() { - err_code!("Resource not found.", "User does not have full access", rocket::http::Status::NotFound.code); + err_code!("Resource not found.", "User does not have full access", Status::NotFound.code); } let mut users_json = Vec::new(); @@ -2486,7 +2487,7 @@ async fn get_groups_data( || Collection::has_manageable_collection_by_user(&org_id, &headers.membership.user_uuid, &conn).await }; if !allowed { - err_code!("Resource not found.", "User does not have access", rocket::http::Status::NotFound.code); + err_code!("Resource not found.", "User does not have access", Status::NotFound.code); } let groups: Vec = if CONFIG.org_groups_enabled() { @@ -2937,8 +2938,8 @@ struct OrganizationUserResetPasswordEnrollmentRequest { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct OrganizationUserRecoverAccountRequest { - new_master_password_hash: String, - key: String, + new_master_password_hash: Option, + key: Option, #[serde(default)] reset_master_password: bool, @@ -2982,12 +2983,7 @@ async fn put_recover_account( conn: DbConn, nt: Notify<'_>, ) -> EmptyResult { - let req = data.into_inner(); - if req.reset_master_password && !req.reset_two_factor { - recover_account(org_id, member_id, headers, req, conn, nt).await - } else { - err!("Unsupported operation") - } + recover_account(org_id, member_id, headers, data.into_inner(), conn, nt).await } // Deprecated since `v2026.4.2` @@ -3007,7 +3003,7 @@ async fn recover_account( org_id: OrganizationId, member_id: MembershipId, headers: AdminHeaders, - reset_request: OrganizationUserRecoverAccountRequest, + req: OrganizationUserRecoverAccountRequest, conn: DbConn, nt: Notify<'_>, ) -> EmptyResult { @@ -3022,7 +3018,7 @@ async fn recover_account( err!("User to reset isn't member of required organization") }; - let Some(user) = User::find_by_uuid(&member.user_uuid, &conn).await else { + let Some(mut user) = User::find_by_uuid(&member.user_uuid, &conn).await else { err!("User not found") }; @@ -3035,29 +3031,56 @@ async fn recover_account( err!("Organization user must be confirmed for password reset functionality"); } - // Sending email before resetting password to ensure working email configuration and the resulting - // user notification. Also this might add some protection against security flaws and misuse - if let Err(e) = mail::send_admin_reset_password(&user.email, user.display_name(), &org.name).await { + let fallback_2fa_email = if req.reset_two_factor && CONFIG.email_2fa_auto_fallback() { + TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email as i32, &conn).await.is_none() + } else { + false + }; + + // Sending email first ensure working email configuration and the resulting user notification. + // Also this might add some protection against security flaws and misuse + if let Err(e) = mail::send_admin_account_recovery( + &user.email, + user.display_name(), + &org.name, + req.reset_master_password, + req.reset_two_factor, + fallback_2fa_email, + ) + .await + { err!(format!("Error sending user reset password email: {e:#?}")); } - let mut user = user; - user.set_password(reset_request.new_master_password_hash.as_str(), Some(reset_request.key), true, None, &conn) - .await?; + if req.reset_master_password { + if let Some(key) = req.key + && let Some(hash) = req.new_master_password_hash + { + user.set_password(hash.as_str(), Some(key), true, None, &conn).await?; + } else { + err_code!("Unprocessable request", "Missing fields to reset password", Status::UnprocessableEntity.code); + } + } + + if req.reset_two_factor { + TwoFactor::delete_all_by_user(&user.uuid, &conn).await?; + if !fallback_2fa_email || two_factor::email::find_and_activate_email_2fa(&user.uuid, &conn).await.is_err() { + two_factor::enforce_2fa_policy(&user, &headers.user.uuid, headers.device.atype, &headers.ip.ip, &conn) + .await?; + } + } + user.save(&conn).await?; nt.send_logout(&user, None, &conn).await; - log_event( - EventType::OrganizationUserAdminResetPassword, - &member_id, - &org_id, - &headers.user.uuid, - headers.device.atype, - &headers.ip.ip, - &conn, - ) - .await; + if req.reset_master_password { + headers.log_event(EventType::OrganizationUserAdminResetPassword, &member_id, &org_id, &conn).await; + } + + if req.reset_two_factor { + headers.log_event(EventType::OrganizationUserAdminResetTwoFactor, &member_id, &org_id, &conn).await; + } Ok(()) } diff --git a/src/auth.rs b/src/auth.rs index 762088e5..07373389 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -23,14 +23,14 @@ use rocket::{ use crate::{ CONFIG, - api::ApiResult, + api::{ApiResult, core::log_event}, config::PathType, db::{ DbConn, models::{ AttachmentId, CipherId, Collection, CollectionId, Device, DeviceId, DeviceType, EmergencyAccessId, - Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, OrganizationId, SendFileId, - SendId, User, UserId, UserStampException, + EventType, Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, OrganizationId, + SendFileId, SendId, User, UserId, UserStampException, }, }, error::Error, @@ -822,6 +822,12 @@ pub struct AdminHeaders { pub org_id: OrganizationId, } +impl AdminHeaders { + pub async fn log_event(&self, event_type: EventType, source_uuid: &str, org_id: &OrganizationId, conn: &DbConn) { + log_event(event_type, source_uuid, org_id, &self.user.uuid, self.device.atype, &self.ip.ip, conn).await; + } +} + #[rocket::async_trait] impl<'r> FromRequest<'r> for AdminHeaders { type Error = &'static str; diff --git a/src/config.rs b/src/config.rs index 7e21ecf1..37fc3e85 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1745,7 +1745,7 @@ where reg!("email/email_footer"); reg!("email/email_footer_text"); - reg!("email/admin_reset_password", ".html"); + reg!("email/admin_account_recovery", ".html"); reg!("email/change_email_existing", ".html"); reg!("email/change_email_invited", ".html"); reg!("email/change_email", ".html"); diff --git a/src/db/models/event.rs b/src/db/models/event.rs index 86cbf5d0..1f307979 100644 --- a/src/db/models/event.rs +++ b/src/db/models/event.rs @@ -43,7 +43,7 @@ pub struct Event { pub provider_org_uuid: Option, } -// Upstream enum: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Enums/EventType.cs +// Upstream enum: https://github.com/bitwarden/server/blob/v2026.6.2/src/Core/Dirt/Enums/EventType.cs #[derive(Debug, Copy, Clone)] pub enum EventType { // User @@ -108,6 +108,12 @@ pub enum EventType { OrganizationUserRejectedAuthRequest = 1514, OrganizationUserDeleted = 1515, // Both user and organization user data were deleted OrganizationUserLeft = 1516, // User voluntarily left the organization + // OrganizationUserAutomaticallyConfirmed = 1517, + // OrganizationUserSelfRevoked = 1518, // User self-revoked due to declining organization data ownership policy + OrganizationUserAdminResetTwoFactor = 1519, + // OrganizationUserRevoked_TwoFactorNonCompliance = 1520, + // OrganizationUserRevoked_SingleOrganizationNonCompliance = 1521, + // OrganizationUserNotificationBannerActionClicked = 1522, // Organization OrganizationUpdated = 1600, diff --git a/src/mail.rs b/src/mail.rs index a7e5e5ae..b20f2853 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -633,14 +633,24 @@ pub async fn send_test(address: &str) -> EmptyResult { send_email(address, &subject, body_html, body_text).await } -pub async fn send_admin_reset_password(address: &str, user_name: &str, org_name: &str) -> EmptyResult { +pub async fn send_admin_account_recovery( + address: &str, + user_name: &str, + org_name: &str, + reset_password: bool, + reset_2fa: bool, + fallback_2fa_email: bool, +) -> EmptyResult { let (subject, body_html, body_text) = get_text( - "email/admin_reset_password", + "email/admin_account_recovery", json!({ "url": CONFIG.domain(), "img_src": CONFIG._smtp_img_src(), "user_name": user_name, "org_name": org_name, + "reset_password": reset_password, + "reset_2fa": reset_2fa, + "fallback_2fa_email": fallback_2fa_email, }), )?; send_email(address, &subject, body_html, body_text).await diff --git a/src/static/templates/email/admin_account_recovery.hbs b/src/static/templates/email/admin_account_recovery.hbs new file mode 100644 index 00000000..a35a1d05 --- /dev/null +++ b/src/static/templates/email/admin_account_recovery.hbs @@ -0,0 +1,12 @@ +Admin account recovery from {{org_name}} organization + +{{#if reset_password}} +The master password for {{user_name}} has been changed. +{{/if}} +{{#if reset_2fa}} +Your two-step verification providers have been reset.{{#if fallback_2fa_email}} Email two factor has been activated as a fallback.{{/if}} +{{/if}} + +If you did not initiate this request, please reach out to your administrator immediately. + +{{> email/email_footer_text }} diff --git a/src/static/templates/email/admin_reset_password.html.hbs b/src/static/templates/email/admin_account_recovery.html.hbs similarity index 55% rename from src/static/templates/email/admin_reset_password.html.hbs rename to src/static/templates/email/admin_account_recovery.html.hbs index d9749d22..cf8eebed 100644 --- a/src/static/templates/email/admin_reset_password.html.hbs +++ b/src/static/templates/email/admin_account_recovery.html.hbs @@ -1,10 +1,17 @@ -Master Password Has Been Changed +Admin account recovery from {{org_name}} organization {{> email/email_header }}
- The master password for {{user_name}} has been changed by an administrator in your {{org_name}} organization. If you did not initiate this request, please reach out to your administrator immediately. + {{#if reset_password}} + The master password for {{user_name}} has been changed. + {{/if}} + {{#if reset_2fa}} + Your two-step verification providers have been reset.{{#if fallback_2fa_email}} Email two factor has been activated as a fallback.{{/if}} + {{/if}} +
+ If you did not initiate this request, please reach out to your administrator immediately.
diff --git a/src/static/templates/email/admin_reset_password.hbs b/src/static/templates/email/admin_reset_password.hbs deleted file mode 100644 index f70423f1..00000000 --- a/src/static/templates/email/admin_reset_password.hbs +++ /dev/null @@ -1,4 +0,0 @@ -Master Password Has Been Changed - -The master password for {{user_name}} has been changed by an administrator in your {{org_name}} organization. If you did not initiate this request, please reach out to your administrator immediately. -{{> email/email_footer_text }} From f1ff61300844b0664907393c0fe93f5092654784 Mon Sep 17 00:00:00 2001 From: Bryan Date: Tue, 8 Sep 2026 12:14:07 +0200 Subject: [PATCH 19/26] fix(security): revoke 2FA remember tokens when credentials or 2FA change (#7682) --- src/api/core/two_factor/mod.rs | 5 +++-- src/api/identity.rs | 6 ++++++ src/db/models/device.rs | 12 ++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs index c95fb297..0eb6563e 100644 --- a/src/api/core/two_factor/mod.rs +++ b/src/api/core/two_factor/mod.rs @@ -16,8 +16,8 @@ use crate::{ db::{ DbConn, DbPool, models::{ - DeviceType, EventType, Membership, MembershipType, OrgPolicyType, Organization, OrganizationId, TwoFactor, - TwoFactorIncomplete, TwoFactorType, User, UserId, + Device, DeviceType, EventType, Membership, MembershipType, OrgPolicyType, Organization, OrganizationId, + TwoFactor, TwoFactorIncomplete, TwoFactorType, User, UserId, }, }, mail, @@ -151,6 +151,7 @@ async fn disable_twofactor(data: Json, headers: Headers, c if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await { twofactor.delete(&conn).await?; + Device::clear_twofactor_remember_by_user(&user.uuid, &conn).await?; log_user_event(EventType::UserDisabled2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn) .await; } diff --git a/src/api/identity.rs b/src/api/identity.rs index 2b1ddfb1..a2525d9b 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -905,6 +905,12 @@ async fn twofactor_auth( // Remove all twofactors from the user TwoFactor::delete_all_by_user(&user.uuid, conn).await?; + + // No device may keep skipping 2FA once every second factor is gone. + // `device` is cleared in memory too, since saving it later would restore its token. + Device::clear_twofactor_remember_by_user(&user.uuid, conn).await?; + device.delete_twofactor_remember(); + enforce_2fa_policy(user, &user.uuid, device.atype, &ip.ip, conn).await?; log_user_event(EventType::UserRecovered2fa as i32, &user.uuid, device.atype, &ip.ip, conn).await; diff --git a/src/db/models/device.rs b/src/db/models/device.rs index 6c1b686a..cc8f1cec 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -266,10 +266,22 @@ impl Device { let devices = Self::find_by_user(user_uuid, conn).await; for mut device in devices { device.refresh_token = Device::generate_refresh_token(); + device.twofactor_remember = None; device.save(false, conn).await?; } Ok(()) } + + pub async fn clear_twofactor_remember_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult { + conn.run(move |conn| { + diesel::update(devices::table) + .filter(devices::user_uuid.eq(user_uuid)) + .set(devices::twofactor_remember.eq::>(None)) + .execute(conn) + .map_res("Error removing two factor remember tokens") + }) + .await + } } #[derive(Display)] From f1c36b8c1d9b2cdd0f1cf6f1c4062f81c3a70302 Mon Sep 17 00:00:00 2001 From: Bryan Date: Tue, 8 Sep 2026 12:14:16 +0200 Subject: [PATCH 20/26] fix(security): rate limit prelogin and auth request endpoints (#7681) --- src/api/core/accounts.rs | 16 +++++++++++----- src/api/identity.rs | 8 ++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 626f22bb..3ea6eada 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -1340,11 +1340,13 @@ pub struct PreloginData { } #[post("/accounts/prelogin", data = "")] -async fn post_prelogin(data: Json, conn: DbConn) -> Json { - prelogin(data, conn).await +async fn post_prelogin(data: Json, ip: ClientIp, conn: DbConn) -> JsonResult { + prelogin(data, ip, conn).await } -pub async fn prelogin(data: Json, conn: DbConn) -> Json { +pub async fn prelogin(data: Json, ip: ClientIp, conn: DbConn) -> JsonResult { + crate::ratelimit::check_limit_unauthenticated(&ip.ip)?; + let data: PreloginData = data.into_inner(); let (kdf_type, kdf_iter, kdf_mem, kdf_para) = match User::find_by_mail(&data.email, &conn).await { @@ -1352,7 +1354,7 @@ pub async fn prelogin(data: Json, conn: DbConn) -> Json { None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT, None, None), }; - Json(json!({ + Ok(Json(json!({ "kdf": kdf_type, "kdfIterations": kdf_iter, "kdfMemory": kdf_mem, @@ -1364,7 +1366,7 @@ pub async fn prelogin(data: Json, conn: DbConn) -> Json { "parallelism": kdf_para }, "salt": null, - })) + }))) } // https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Auth/Models/Request/Accounts/SecretVerificationRequestModel.cs @@ -1595,6 +1597,8 @@ async fn post_auth_request( conn: DbConn, nt: Notify<'_>, ) -> JsonResult { + crate::ratelimit::check_limit_unauthenticated(&client_headers.ip.ip)?; + let data = data.into_inner(); let Some(user) = User::find_by_mail(&data.email, &conn).await else { @@ -1756,6 +1760,8 @@ async fn get_auth_request_response( client_headers: ClientHeaders, conn: DbConn, ) -> JsonResult { + crate::ratelimit::check_limit_unauthenticated(&client_headers.ip.ip)?; + let Some(auth_request) = AuthRequest::find_by_uuid(&auth_request_id, &conn).await else { err!("AuthRequest doesn't exist", "User not found") }; diff --git a/src/api/identity.rs b/src/api/identity.rs index a2525d9b..7bd12a78 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -1056,13 +1056,13 @@ async fn json_err_twofactor( } #[post("/accounts/prelogin", data = "")] -async fn post_prelogin(data: Json, conn: DbConn) -> Json { - prelogin(data, conn).await +async fn post_prelogin(data: Json, ip: ClientIp, conn: DbConn) -> JsonResult { + prelogin(data, ip, conn).await } #[post("/accounts/prelogin/password", data = "")] -async fn prelogin_password(data: Json, conn: DbConn) -> Json { - prelogin(data, conn).await +async fn prelogin_password(data: Json, ip: ClientIp, conn: DbConn) -> JsonResult { + prelogin(data, ip, conn).await } #[post("/accounts/register", data = "")] From b7667e27bf3500a2446d39446b1a7b10b8b25991 Mon Sep 17 00:00:00 2001 From: niniconi <112842746+niniconi@users.noreply.github.com> Date: Tue, 8 Sep 2026 19:42:58 +0800 Subject: [PATCH 21/26] fix: Correct invalid comment syntax in .dockerignore (#7274) Docker only recognizes `#` as a valid comment indicator in .dockerignore files. Using `//` causes the lines to be incorrectly parsed as glob patterns rather than comments. While this may not cause fatal errors if no matching files exist, it is syntactically invalid and could lead to unexpected behavior. Corrected the syntax to use `#`. --- .dockerignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.dockerignore b/.dockerignore index a9a358a3..d6ac6b9b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,7 @@ -// Ignore everything +# Ignore everything * -// Allow what is needed +# Allow what is needed !.git !docker/healthcheck.sh !docker/start.sh From de7abaaafa5ce6627e43efa52840f6df6f43da23 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Wed, 9 Sep 2026 11:51:23 +0200 Subject: [PATCH 22/26] Update Rust and adjust DockerSettings (#7690) - Update Rust to v1.98.1 which resolves a build issues with strange outcomes - Adjusted the DockerSettings and render_template to extract the `rust_version` from the `rust-toolchain.toml` file. This should prevent mismatches and forgetting to update DockerSettings. - Updated typos in GHA and Pre-Commit - Updated all possible crates including hickory which has several CVE's fixed. Signed-off-by: BlackDex --- .github/workflows/typos.yml | 2 +- .github/workflows/zizmor.yml | 2 +- .pre-commit-config.yaml | 2 +- Cargo.lock | 357 +++++++++++++++++++---------------- Cargo.toml | 19 +- docker/DockerSettings.yaml | 3 +- docker/Dockerfile.alpine | 8 +- docker/Dockerfile.debian | 2 +- docker/render_template | 10 +- macros/Cargo.toml | 2 +- rust-toolchain.toml | 2 +- 11 files changed, 227 insertions(+), 182 deletions(-) diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 83cd581b..00fcbab4 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -23,4 +23,4 @@ jobs: # When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too - name: Spell Check Repo - uses: crate-ci/typos@4d9c206a77c041268485162b8e2579ad7a5cb9a3 # v1.50.0 + uses: crate-ci/typos@d43b6c087ac471e2ea7b8af622ff15f05c0c365b # v1.50.1 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 5e7100b9..31153b07 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@70fb788f84895a7701f5643d103d587e460b5c99 # v0.6.3 + uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4 with: # intentionally not scanning the entire repository, # since it contains integration tests. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5269c041..e8319414 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too - repo: https://github.com/crate-ci/typos - rev: 4d9c206a77c041268485162b8e2579ad7a5cb9a3 # v1.50.0 + rev: d43b6c087ac471e2ea7b8af622ff15f05c0c365b # v1.50.1 hooks: - id: typos always_run: true diff --git a/Cargo.lock b/Cargo.lock index b8335e5b..f9c763d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,9 +151,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8" +checksum = "24a8ec73eb862508b7041723c89386365894b4e7d9f6998bf1b8529e5b0ee254" dependencies = [ "compression-codecs", "compression-core", @@ -318,7 +318,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -360,9 +360,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-config" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a767267da9e2c2e189b2f9df8b5657e850ecf5352644734ba130d4a57095cf1b" +checksum = "b8d7b388a9fc3a6db15a5ec778c38b354eff1364882c94d08e0252f7a47dcaa4" dependencies = [ "aws-credential-types", "aws-runtime", @@ -403,9 +403,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9007227e10b5fed2f3e0a2beff489211e2b5604c400b7a9d5d81ca9d64c24bb" +checksum = "ef47857a1d4488b528f4a5d5715fa7c3300820897824152234d3fa22b1426657" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -428,9 +428,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.108.0" +version = "1.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15301b04372832947916607983b114b3374b9db0be058a00fb7513800de1f05" +checksum = "c3cfe74df5d9ad2fedd691973ad3521ebf4f27a3c68c792556686aedb5519bab" dependencies = [ "arc-swap", "aws-credential-types", @@ -454,9 +454,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.110.0" +version = "1.111.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72cc2c205cb27108183cf1856333f7d584c2ba0f505421b4209ca5828f9ea899" +checksum = "81b0ec31ed6191bd11350aae4b2004198f2db21350cb0a20c57e0a92e55dd161" dependencies = [ "arc-swap", "aws-credential-types", @@ -480,9 +480,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.113.0" +version = "1.114.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68182ecb449f7537db0f4d5d25917789cf41e32074a9fe47b6a0b847fe1d2032" +checksum = "ef45745026107ec30c4ef86bd8ae4b002e7e5f6a86e4225240bdf6b06a0b944a" dependencies = [ "arc-swap", "aws-credential-types", @@ -619,9 +619,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "954c563ce84507722d2679f07a35d21b9c6466b3872d513020d0281fc8112ac9" +checksum = "9c054752dd9e4dc73d0b75748c99ac2d0feafbf2f25c7b0516f03a3534161223" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", @@ -659,9 +659,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.6.2" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce83ce9abbb198d25bc7131e468d0f9fe1257125e58c39f3f9fc9f5098c9647" +checksum = "8f94d16e797ec62cd999fc9d5942b48fa7050c3093ddadff48e4d7528d16fcb9" dependencies = [ "base64-simd", "bytes", @@ -694,9 +694,9 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eec1cd5469f328c782dc3e33d4153cf118a54e33cbb3356d60d16f89883e1f94" +checksum = "209f3a6d82a6e9e5f94abbed94c7a26e1c052341002bf57a5fb5481f625896fc" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -895,9 +895,9 @@ dependencies = [ [[package]] name = "cached" -version = "3.1.1" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133b6b7d6a828c24d5055ef51e67457002b4017ae5ea3d1b1552ca35a44b1119" +checksum = "c5a6cf8262820194a1488ece477f5fb5ba9256ef25229d525470e71d6e9a5835" dependencies = [ "ahash", "async-lock", @@ -911,9 +911,9 @@ dependencies = [ [[package]] name = "cached_proc_macro" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da80977bd46ecf98c593b651e393260f852678283989b8b7c4079304fe5e8936" +checksum = "ece0579b43cf6e927b3370c7d44359c7122e7e52b3ea635bc8484da571442edb" dependencies = [ "darling 0.20.11", "proc-macro-crate", @@ -930,9 +930,9 @@ checksum = "f5813789573ae815c8b4be58c4428e0e7ae05f0227678ba9de332ded585b9159" [[package]] name = "cc" -version = "1.4.4" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" dependencies = [ "find-msvc-tools", "jobserver", @@ -1011,9 +1011,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.38" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" +checksum = "100590da849306918656ffbb22576bbdfc1382b50ad10d1d50ec177d3b205fb2" dependencies = [ "brotli", "compression-core", @@ -1025,9 +1025,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" +checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" [[package]] name = "concurrent-queue" @@ -1134,6 +1134,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1191,27 +1197,27 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.20" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crunchy" @@ -1308,12 +1314,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", + "darling_core 0.24.1", + "darling_macro 0.24.1", ] [[package]] @@ -1346,15 +1352,15 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" dependencies = [ "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] @@ -1381,13 +1387,13 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ - "darling_core 0.23.0", + "darling_core 0.24.1", "quote", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] @@ -1570,9 +1576,9 @@ dependencies = [ [[package]] name = "diesel" -version = "2.3.12" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715377c6e464cb44bb89bd8487584240516c8d5052bc645d6babc50bb8be46c3" +checksum = "e3b934ddbdcb2abb9f9fc9c30bd47bcc5618b615eea1d334cda5fdf8ff9b072a" dependencies = [ "bigdecimal", "bitflags 2.13.1", @@ -1607,9 +1613,9 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.3.9" +version = "2.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1817b7f4279b947fc4cafddec12b0e5f8727141706561ce3ac94a60bddd1cf5" +checksum = "ecbd51fb6c020672543641167efa4e6417ff7ad76849ed556ace3595e72de03a" dependencies = [ "diesel_table_macro_syntax", "dsl_auto_type", @@ -1670,7 +1676,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -1809,11 +1815,17 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.35" +version = "0.8.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a" dependencies = [ "cfg-if", + "core_detect", + "multiversion", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", ] [[package]] @@ -1908,9 +1920,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" [[package]] name = "flate2" @@ -2028,7 +2040,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -2177,7 +2189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" dependencies = [ "codemap", - "indexmap 2.14.1", + "indexmap 2.14.2", "lasso", "once_cell", "phf 0.11.3", @@ -2206,7 +2218,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.5.0", - "indexmap 2.14.1", + "indexmap 2.14.2", "slab", "tokio", "tokio-util", @@ -2299,9 +2311,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hickory-net" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" +checksum = "084e7bd6a377435d568f652153e571b50970d7ccc1d1eeec0519f834632287e1" dependencies = [ "async-trait", "cfg-if", @@ -2323,9 +2335,9 @@ dependencies = [ [[package]] name = "hickory-proto" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" +checksum = "7e2da0694c15b44c6f68a6b05e0233617008c54080e31d6eb848d858a9c5b38d" dependencies = [ "data-encoding", "idna", @@ -2343,9 +2355,9 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" +checksum = "0e4f9f4603319422d482738f3f6fe5aac03157fdbfed1cd85a3ff45adb09072f" dependencies = [ "cfg-if", "futures-util", @@ -2483,9 +2495,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ "typenum", ] @@ -2543,9 +2555,9 @@ dependencies = [ "http 1.5.0", "hyper 1.11.1", "hyper-util", - "rustls 0.23.43", + "rustls 0.23.44", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls 0.26.5", "tower-service", ] @@ -2721,9 +2733,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.1" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -2752,9 +2764,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.1" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" dependencies = [ "serde", ] @@ -2918,9 +2930,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.104" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" dependencies = [ "cfg-if", "futures-util", @@ -2999,12 +3011,12 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "rustls 0.23.43", + "rustls 0.23.44", "rustls-native-certs", "serde", "socket2 0.6.5", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls 0.26.5", "tracing", "url", ] @@ -3097,7 +3109,7 @@ name = "macros" version = "0.1.0" dependencies = [ "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -3189,9 +3201,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", "wasi", @@ -3237,6 +3249,33 @@ dependencies = [ "version_check", ] +[[package]] +name = "multiversion" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c" +dependencies = [ + "multiversion-macros", +] + +[[package]] +name = "multiversion-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83" +dependencies = [ + "proc-macro2", + "quote", + "rustversion", + "syn 3.0.5", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + [[package]] name = "mysqlclient-sys" version = "0.5.2" @@ -3340,7 +3379,7 @@ checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -3446,9 +3485,9 @@ dependencies = [ [[package]] name = "opendal" -version = "0.58.2" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33dbff14cc9bb085224256d6a81289d2f3202e85b06f408d42534b42162a4231" +checksum = "f950151f9587a51a7bed70a15fa0cff464eae96e41ae7499f97067bdafdf43eb" dependencies = [ "opendal-core", "opendal-service-fs", @@ -3457,9 +3496,9 @@ dependencies = [ [[package]] name = "opendal-core" -version = "0.58.2" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dbcef97d3eb7591db2c18d5cae95c836bcce07359b98d98dd6f4e861eb77b7" +checksum = "a43405d217dfdfb543f58847336d3af672897dd1939bb7dcf314b63cf364f1c9" dependencies = [ "anyhow", "asyncband", @@ -3483,9 +3522,9 @@ dependencies = [ [[package]] name = "opendal-service-fs" -version = "0.58.2" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7ef1e1c45f3f89282a59073897e0d685e51385fed0aea771714789525cff996" +checksum = "fb9caf04d6d38713299dd4abac984b95ab16ee20e1ff09160f495c5a64644083" dependencies = [ "bytes", "log", @@ -3497,9 +3536,9 @@ dependencies = [ [[package]] name = "opendal-service-s3" -version = "0.58.2" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64335f9f24ccb62ac36f1d976342b48611a75ba61979813a4f78a4ebd94de42" +checksum = "388b1d39b62535c62803754ebef89808859558697366dbedd0299345887ba461" dependencies = [ "base64 0.23.1", "bytes", @@ -3750,9 +3789,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf" +checksum = "6d45aeb61b4bf818e12d4205f2466f8c4748f85f4fce0146d1c03d69d753f0ad" dependencies = [ "memchr", "ucd-trie", @@ -3760,9 +3799,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d" +checksum = "89cc5a242e25ed4e7704d0be240f2cfbe20a8c27e7e252d94835be93d92dc39f" dependencies = [ "pest", "pest_generator", @@ -3770,9 +3809,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a" +checksum = "7abf21475cc3820fe4b2ca2dc2142902f67a02189f3b5b3a229f4febc01a43e5" dependencies = [ "pest", "pest_meta", @@ -3783,9 +3822,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496" +checksum = "adba4db388f687393c18c51348d44a41d870ca9df71a2c98172ea3035dc6936e" dependencies = [ "pest", ] @@ -3939,9 +3978,9 @@ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "portable-atomic-util" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" dependencies = [ "portable-atomic", ] @@ -4228,7 +4267,7 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -4348,11 +4387,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bytes", "cookie", "cookie_store", @@ -4371,7 +4410,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustls 0.23.43", + "rustls 0.23.44", "rustls-pki-types", "rustls-platform-verifier", "serde", @@ -4379,7 +4418,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls 0.26.5", "tokio-util", "tower", "tower-http", @@ -4453,7 +4492,7 @@ dependencies = [ "either", "figment", "futures", - "indexmap 2.14.1", + "indexmap 2.14.2", "log", "memchr", "multer", @@ -4485,7 +4524,7 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" dependencies = [ "devise", "glob", - "indexmap 2.14.1", + "indexmap 2.14.2", "proc-macro2", "quote", "rocket_http", @@ -4505,7 +4544,7 @@ dependencies = [ "futures", "http 0.2.12", "hyper 0.14.32", - "indexmap 2.14.1", + "indexmap 2.14.2", "log", "memchr", "pear", @@ -4640,9 +4679,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.43" +version = "0.23.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" dependencies = [ "log", "once_cell", @@ -4694,7 +4733,7 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.43", + "rustls 0.23.44", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki 0.103.15", @@ -4906,7 +4945,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -4974,16 +5013,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" +checksum = "935177bb8c0cd8ca1a4e6d1a2ac8988bea69cab4f9d3a31311e012ad27868ea4" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.14.1", + "indexmap 2.14.2", "jiff", "schemars 0.9.0", "schemars 1.2.2", @@ -4995,14 +5034,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" +checksum = "1d607aa01a3cb0ad757d6fd216136910db3c97b102fe686585689615a02dbcdc" dependencies = [ - "darling 0.23.0", + "darling 0.24.1", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] @@ -5142,9 +5181,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.2" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" [[package]] name = "socket2" @@ -5271,9 +5310,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.4" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" dependencies = [ "proc-macro2", "quote", @@ -5389,7 +5428,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -5454,9 +5493,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" dependencies = [ "tinyvec_macros", ] @@ -5492,7 +5531,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -5507,11 +5546,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.4" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" dependencies = [ - "rustls 0.23.43", + "rustls 0.23.44", "tokio", ] @@ -5611,7 +5650,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.14.1", + "indexmap 2.14.2", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -5625,7 +5664,7 @@ version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ - "indexmap 2.14.1", + "indexmap 2.14.2", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "winnow 1.0.4", @@ -5901,9 +5940,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "value-bag" -version = "1.13.2" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068e763e8279de7ab94b6afebded2cb701678af094feb1c12ccb061b4783c1be" +checksum = "2799ffb329a792ecfd902b71306c8a815a6ef1c0470fa9953a6aa4d4cecbe511" [[package]] name = "vaultwarden" @@ -5965,7 +6004,7 @@ dependencies = [ "rocket", "rocket_ws", "rpassword", - "rustls 0.23.43", + "rustls 0.23.44", "semver", "serde", "serde_json", @@ -6040,9 +6079,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" dependencies = [ "cfg-if", "once_cell", @@ -6053,9 +6092,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.77" +version = "0.4.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" dependencies = [ "js-sys", "wasm-bindgen", @@ -6063,9 +6102,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6073,22 +6112,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" dependencies = [ "unicode-ident", ] @@ -6108,9 +6147,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.104" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" dependencies = [ "js-sys", "wasm-bindgen", @@ -6583,18 +6622,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.56" +version = "0.8.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.56" +version = "0.8.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" dependencies = [ "proc-macro2", "quote", @@ -6672,7 +6711,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -6689,27 +6728,27 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zstd" -version = "0.13.3" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +checksum = "bf06bd8162af0734b344780deb55b42a2429ae430870d13fcc12f238e880fe6e" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.4" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +checksum = "ae42c0555055784c70058d19ba8e275528e8a99a706684868ace5da4e716a4ab" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" +version = "2.1.0+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 7d711fe1..cc6dff02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ members = ["macros"] name = "vaultwarden" version = "1.0.0" authors = ["Daniel García "] -readme = "README.md" build = "build.rs" repository.workspace = true edition.workspace = true @@ -107,7 +106,7 @@ serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.151" # A safe, extensible ORM and Query builder -diesel = { version = "2.3.12", features = ["chrono", "r2d2", "numeric"] } +diesel = { version = "2.3.13", features = ["chrono", "r2d2", "numeric"] } diesel_migrations = "2.3.2" derive_more = { version = "2.1.1", features = [ @@ -125,7 +124,7 @@ libsqlite3-sys = { version = "0.38.2", optional = true } # Crypto-related libraries rand = "0.10.2" ring = "0.17.14" -rustls = { version = "0.23.43", features = ["ring", "std"], default-features = false } +rustls = { version = "0.23.44", features = ["ring", "std"], default-features = false } subtle = "2.6.1" # UUID generation @@ -183,7 +182,7 @@ email_address = "0.2.9" handlebars = { version = "6.4.4", features = ["dir_source"] } # HTTP client (Used for favicons, version check, DUO and HIBP API) -reqwest = { version = "0.13.4", default-features = false, features = [ +reqwest = { version = "0.13.5", default-features = false, features = [ # Misc "charset", "cookies", @@ -201,7 +200,7 @@ reqwest = { version = "0.13.4", default-features = false, features = [ "socks", "system-proxy", ] } -hickory-resolver = "0.26.1" +hickory-resolver = "0.26.2" # Favicon extraction libraries html5gum = "0.8.4" @@ -215,7 +214,7 @@ bytes = "1.12.1" svg-hush = "0.9.7" # Cache function results (Used for version check and favicon fetching) -cached = { version = "3.1.1", features = ["async"] } +cached = { version = "4.0.0", features = ["async"] } # Used for custom short lived cookie jar during favicon extraction cookie = "0.18.2" @@ -232,7 +231,7 @@ pastey = "0.2.3" governor = "0.10.4" # CIDR parsing for the trusted proxies of the client IP header -ipnet = "2.12.1" +ipnet = "2.12.2" # OIDC for SSO openidconnect = { version = "4.0.1", default-features = false } @@ -257,17 +256,17 @@ rpassword = "7.5.4" grass_compiler = { version = "0.13.4", default-features = false } # File are accessed through Apache OpenDAL -opendal = { version = "0.58.2", default-features = false, features = ["services-fs"] } +opendal = { version = "0.59.1", default-features = false, features = ["services-fs"] } # For retrieving AWS credentials, including temporary SSO credentials -aws-config = { version = "1.11.0", optional = true, default-features = false, features = [ +aws-config = { version = "1.12.0", optional = true, default-features = false, features = [ "behavior-version-latest", "credentials-process", "rt-tokio", "sso", ] } aws-credential-types = { version = "1.3.0", optional = true } -aws-smithy-runtime-api = { version = "1.15.0", optional = true } +aws-smithy-runtime-api = { version = "1.16.0", optional = true } http = { version = "1.5.0", optional = true } reqsign-aws-v4 = { version = "3.3.0", optional = true } reqsign-core = { version = "3.3.1", optional = true } diff --git a/docker/DockerSettings.yaml b/docker/DockerSettings.yaml index fdbf40f2..6ae6e9a8 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -5,7 +5,8 @@ vault_image_digest: "sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10 # We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts # https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707" -rust_version: 1.98.0 # Rust version to be used +# The `rust_version` variable is extracted from `rust-toolchain.toml` +# rust_version: x.yy.z # Rust version to be used debian_version: trixie # Debian release name to be used alpine_version: "3.24" # Alpine version to be used # For which platforms/architectures will we try to build images diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index 491aa9e0..a91deb07 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -32,10 +32,10 @@ FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330 ########################## ALPINE BUILD IMAGES ########################## ## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64 ## And for Alpine we define all build images here, they will only be loaded when actually used -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.98.0 AS build_amd64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.98.0 AS build_arm64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.98.0 AS build_armv7 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.98.0 AS build_armv6 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.98.1 AS build_amd64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.98.1 AS build_arm64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.98.1 AS build_armv7 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.98.1 AS build_armv6 ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 280559e2..b8490609 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -36,7 +36,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 -FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.98.0-slim-trixie AS build +FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.98.1-slim-trixie AS build # hadolint ignore=DL3067 COPY --from=xx / / ARG TARGETARCH diff --git a/docker/render_template b/docker/render_template index 401e0ad0..84ca8ead 100755 --- a/docker/render_template +++ b/docker/render_template @@ -3,17 +3,23 @@ import os import argparse import json +import tomllib import yaml import jinja2 # Load settings file -with open("DockerSettings.yaml", 'r') as yaml_file: +with open('DockerSettings.yaml', 'r', encoding='utf-8') as yaml_file: yaml_data = yaml.safe_load(yaml_file) +# Extract the rust_version from the rust-toolchain.toml file +script_dir = os.path.dirname(os.path.abspath(__file__)) +with open(os.path.join(script_dir, '..', 'rust-toolchain.toml'), 'rb') as toolchain_file: + yaml_data["rust_version"] = tomllib.load(toolchain_file)["toolchain"]["channel"] + settings_env = jinja2.Environment( loader=jinja2.FileSystemLoader(os.getcwd()), ) -settings_yaml = yaml.safe_load(settings_env.get_template("DockerSettings.yaml").render(yaml_data)) +settings_yaml = yaml.safe_load(settings_env.get_template('DockerSettings.yaml').render(yaml_data)) args_parser = argparse.ArgumentParser() args_parser.add_argument('template_file', help='Jinja2 template file to render.') diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 84d17192..34cb913d 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -14,7 +14,7 @@ proc-macro = true [dependencies] quote = "1.0.47" -syn = "3.0.4" +syn = "3.0.5" [lints] workspace = true diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 9bfb1d94..2be20926 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.98.0" +channel = "1.98.1" components = [ "rustfmt", "clippy" ] profile = "minimal" From 5b51b60f9407bc4e088eb1dcb035a5d395178aab Mon Sep 17 00:00:00 2001 From: Chase Douglas Date: Wed, 9 Sep 2026 05:30:25 -0700 Subject: [PATCH 23/26] Route service clients through shared HTTP setup (#7639) * storage: route OpenDAL through HTTP client OpenDAL 0.58 requires applications to provide an HTTP transport. Its default installer creates a standalone client, bypassing Vaultwarden DNS, redirect, proxy, timeout, and request configuration. Build the client through the internal HTTP interface and inject it into OpenDAL's public reqwest transport. * http: honor block setting on redirects Clients can disable host blocking for administrator-configured private services. DNS resolution honors this setting, but the redirect policy still performs config-backed host checks. Capture the setting in the redirect policy and skip those checks when blocking is disabled. This also avoids re-entering CONFIG when a remote configuration request is redirected during startup. * http: make DNS setup bootstrap-safe Remote configuration can require an HTTP client while CONFIG is still initializing. Building the DNS resolver currently reads CONFIG.dns_prefer_ipv6(), so loading an S3-backed config can deadlock. Build one resolver without consulting CONFIG. Order addresses for each lookup using the merged setting when available, falling back to the environment and then IPv4-first during bootstrap. * aws: use internal HTTP client The AWS SDK connector builds a raw reqwest client, bypassing Vaultwarden TLS, DNS, redirect, proxy, timeout, and request setup. Construct it through the internal HTTP client interface and retain the standard ten-second request deadline. Permit private AWS metadata and service endpoints by disabling non-global IP blocking. Preserve timeout errors when adapting reqwest failures to the AWS SDK so the runtime receives the correct connector error category. * Added comment for the prefer IPv function Signed-off-by: BlackDex --------- Signed-off-by: BlackDex Co-authored-by: BlackDex --- Cargo.lock | 15 ++++++++ Cargo.toml | 2 ++ src/http_client.rs | 87 ++++++++++++++++++++++++++++++++++++++-------- src/storage.rs | 19 ++++++---- 4 files changed, 102 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9c763d1..55a7233b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3520,6 +3520,20 @@ dependencies = [ "web-time", ] +[[package]] +name = "opendal-http-transport-reqwest" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "401999057db611e592f883fcf2cbd6754ff37af587deaadd07b8c1398b2b6b06" +dependencies = [ + "bytes", + "futures", + "http 1.5.0", + "http-body 1.1.0", + "opendal-core", + "reqwest", +] + [[package]] name = "opendal-service-fs" version = "0.59.1" @@ -5989,6 +6003,7 @@ dependencies = [ "num-derive", "num-traits", "opendal", + "opendal-http-transport-reqwest", "openidconnect", "openssl", "pastey 0.2.3", diff --git a/Cargo.toml b/Cargo.toml index cc6dff02..d3a3d5e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ vendored_openssl = ["openssl/vendored"] enable_mimalloc = ["dep:mimalloc"] s3 = [ "opendal/services-s3", + "dep:opendal-http-transport-reqwest", "dep:aws-config", "dep:aws-credential-types", "dep:aws-smithy-runtime-api", @@ -257,6 +258,7 @@ grass_compiler = { version = "0.13.4", default-features = false } # File are accessed through Apache OpenDAL opendal = { version = "0.59.1", default-features = false, features = ["services-fs"] } +opendal-http-transport-reqwest = { version = "0.59.1", default-features = false, features = ["rustls-no-provider"], optional = true } # For retrieving AWS credentials, including temporary SSO credentials aws-config = { version = "1.12.0", optional = true, default-features = false, features = [ diff --git a/src/http_client.rs b/src/http_client.rs index 0831d990..5ef293fc 100644 --- a/src/http_client.rs +++ b/src/http_client.rs @@ -14,7 +14,10 @@ use reqwest::{ }; use url::Host; -use crate::{CONFIG, util::is_global}; +use crate::{ + CONFIG, + util::{get_env_bool, is_global}, +}; pub fn make_http_request(method: reqwest::Method, url: &str) -> Result { static INSTANCE: LazyLock = @@ -36,7 +39,7 @@ pub fn get_reqwest_client_builder(enforce_block: bool) -> ClientBuilder { let mut headers = header::HeaderMap::new(); headers.insert(header::USER_AGENT, header::HeaderValue::from_static("Vaultwarden")); - let redirect_policy = reqwest::redirect::Policy::custom(|attempt| { + let redirect_policy = reqwest::redirect::Policy::custom(move |attempt| { if attempt.previous().len() >= 5 { return attempt.error("Too many redirects"); } @@ -45,7 +48,7 @@ pub fn get_reqwest_client_builder(enforce_block: bool) -> ClientBuilder { return attempt.error("Invalid host"); }; - if let Err(e) = should_block_host(&host) { + if enforce_block && let Err(e) = should_block_host(&host) { return attempt.error(e); } @@ -59,6 +62,14 @@ pub fn get_reqwest_client_builder(enforce_block: bool) -> ClientBuilder { .timeout(Duration::from_secs(10)) } +fn dns_prefer_ipv6() -> bool { + // CONFIG may require DNS to initialize, so avoid forcing it during bootstrap. + match LazyLock::get(&CONFIG) { + Some(config) => config.dns_prefer_ipv6(), + None => get_env_bool("DNS_PREFER_IPV6").unwrap_or(false), + } +} + fn should_block_ip(ip: IpAddr) -> bool { if !CONFIG.http_request_block_non_global_ips() { return false; @@ -258,12 +269,8 @@ impl CustomDnsResolver { fn new() -> Arc { TokioResolver::builder(TokioRuntimeProvider::default()) .and_then(|mut builder| { - // Hickory's default since v0.26 is `Ipv6AndIpv4`, which sorts IPv6 first - // This might cause issues on IPv4 only systems or containers - // Unless someone enabled DNS_PREFER_IPV6, use Ipv4AndIpv6, which returns IPv4 first which was our previous default - if !CONFIG.dns_prefer_ipv6() { - builder.options_mut().ip_strategy = hickory_resolver::config::LookupIpStrategy::Ipv4AndIpv6; - } + // Query both families; the preferred order is applied per lookup below. + builder.options_mut().ip_strategy = hickory_resolver::config::LookupIpStrategy::Ipv4AndIpv6; builder.build() }) .inspect_err(|e| warn!("Error creating Hickory resolver, falling back to default: {e:?}")) @@ -289,6 +296,17 @@ impl CustomDnsResolver { } } +fn sort_addresses(addresses: &mut [SocketAddr], prefer_ipv6: bool) { + // `sort_by_key` orders `false` before `true`. + // When IPv6 is preferred, IPv6 addresses return `false` for `is_ipv4()` and sort first. + // When IPv4 is preferred, IPv4 addresses return `false` for `is_ipv6()` and sort first. + if prefer_ipv6 { + addresses.sort_by_key(SocketAddr::is_ipv4); + } else { + addresses.sort_by_key(SocketAddr::is_ipv6); + } +} + fn pre_resolve(name: &str, enforce_block: bool) -> Result<(), CustomHttpClientError> { let Ok(host) = get_valid_host(name) else { return Err(CustomHttpClientError::Invalid { @@ -320,7 +338,9 @@ impl Resolve for CustomDns { let this = Arc::clone(&self.resolver); Box::pin(async move { let name = name.as_str(); - let results = this.resolve_domain(name, enforce_block).await?; + let mut results = this.resolve_domain(name, enforce_block).await?; + // Recheck after bootstrap so long-lived clients adopt the loaded config. + sort_addresses(&mut results, dns_prefer_ipv6()); if results.is_empty() { warn!("Unable to resolve {name} to any valid IP address"); } @@ -339,10 +359,29 @@ pub(crate) mod aws { }; use reqwest::Client; + use super::get_reqwest_client_builder; + // Adapter that wraps reqwest to be compatible with the AWS SDK #[derive(Debug)] pub(crate) struct AwsReqwestConnector { - pub(crate) client: Client, + client: Client, + } + + impl AwsReqwestConnector { + pub(crate) fn new() -> Self { + let client = get_reqwest_client_builder(false).build().expect("Failed to build AWS HTTP client"); + Self { + client, + } + } + } + + fn connector_error(error: reqwest::Error) -> ConnectorError { + if error.is_timeout() { + ConnectorError::timeout(Box::new(error)) + } else { + ConnectorError::io(Box::new(error)) + } } impl HttpConnector for AwsReqwestConnector { @@ -362,10 +401,10 @@ pub(crate) mod aws { req_builder = req_builder.body(body_bytes.to_vec()); } - let response = req_builder.send().await.map_err(|e| ConnectorError::io(Box::new(e)))?; + let response = req_builder.send().await.map_err(connector_error)?; let status = response.status().into(); - let bytes = response.bytes().await.map_err(|e| ConnectorError::io(Box::new(e)))?; + let bytes = response.bytes().await.map_err(connector_error)?; Ok(HttpResponse::new(status, bytes.into())) }; @@ -391,7 +430,7 @@ pub(crate) mod aws { mod tests { use super::*; use crate::util::is_global_hardcoded; - use std::net::Ipv4Addr; + use std::net::{Ipv4Addr, Ipv6Addr}; use url::Host; // === @@ -404,6 +443,26 @@ mod tests { } } + #[test] + fn dns_setup_does_not_initialize_config() { + assert!(LazyLock::get(&CONFIG).is_none()); + drop(CustomDns::instance(false)); + assert!(LazyLock::get(&CONFIG).is_none()); + } + + #[test] + fn dns_preference_orders_addresses() { + let ipv4 = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0); + let ipv6 = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 0); + let mut addresses = [ipv6, ipv4]; + + sort_addresses(&mut addresses, false); + assert_eq!(addresses, [ipv4, ipv6]); + + sort_addresses(&mut addresses, true); + assert_eq!(addresses, [ipv6, ipv4]); + } + #[test] fn dotted_decimal_loopback_normalizes() { let ip = parse_to_ip("127.0.0.1").unwrap(); diff --git a/src/storage.rs b/src/storage.rs index 689be302..32562a0d 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -77,10 +77,18 @@ pub(crate) fn operator_for_path(path: &str) -> Result = LazyLock::new(|| { + // Storage endpoints are administrator-configured and may be private. + crate::http_client::get_reqwest_client_builder(false).build().expect("Failed to build OpenDAL HTTP client") + }); + pub(super) fn is_uri(path: &str) -> bool { path.starts_with("s3://") } @@ -177,12 +185,7 @@ mod s3 { let chain = DEFAULT_CREDENTIAL_CHAIN .get_or_init(|| { - let reqwest_client = reqwest::Client::builder().build().unwrap(); - let connector = AwsReqwestConnector { - client: reqwest_client, - }; - - let conf = ProviderConfig::default().with_http_client(connector); + let conf = ProviderConfig::default().with_http_client(AwsReqwestConnector::new()); DefaultCredentialsChain::builder().configure(conf).build() }) @@ -236,7 +239,9 @@ mod s3 { builder.credential_provider_chain(ProvideCredentialChain::new().push(OpenDALS3CredentialProvider)); } - Ok(opendal::Operator::new(builder)?) + let http_transport = opendal::HttpTransporter::new(ReqwestTransport::new(HTTP_CLIENT.clone())); + let context = opendal::OperationContext::new().with_http_transport(http_transport); + Ok(opendal::Operator::new(builder)?.with_context(context)) } fn uri_has_option(uri: &opendal::OperatorUri, names: &[&str]) -> bool { From e992cbb4f520c53d50b22fa3cc1f2f77c4a95a81 Mon Sep 17 00:00:00 2001 From: Tom <83423411+tom27052006@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:31:03 +0200 Subject: [PATCH 24/26] Fix iOS registration token response (#7714) * Return registration token as text/plain for Accept: */* * fix register verification response content negotiation --- src/api/identity.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/api/identity.rs b/src/api/identity.rs index 7bd12a78..6808ddde 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -3,7 +3,7 @@ use num_traits::FromPrimitive; use rocket::{ Route, form::{Form, FromForm}, - http::{Cookie, CookieJar, SameSite}, + http::{Accept, Cookie, CookieJar, MediaType, SameSite}, response::Redirect, serde::json::Json, }; @@ -1083,11 +1083,18 @@ enum RegisterVerificationResponse { #[response(status = 204)] NoContent(()), Token(Json), + PlainToken(String), +} + +// Return JSON only when the client explicitly requests it, otherwise return plain text. +fn accepts_json(accept: Option<&Accept>) -> bool { + accept.is_some_and(|accept| accept.preferred().media_type() == &MediaType::JSON) } #[post("/accounts/register/send-verification-email", data = "")] async fn register_verification_email( data: Json, + accept: Option<&Accept>, ip: ClientIp, conn: DbConn, ) -> ApiResult { @@ -1125,7 +1132,11 @@ async fn register_verification_email( } else { // If email verification is not required, return the token directly // the clients will use this token to finish the registration - Ok(RegisterVerificationResponse::Token(Json(token))) + Ok(if accepts_json(accept) { + RegisterVerificationResponse::Token(Json(token)) + } else { + RegisterVerificationResponse::PlainToken(token) + }) } } From 25dfedafd73cdeee47a50c1dfa7a451afe1ae1bd Mon Sep 17 00:00:00 2001 From: Timshel Date: Wed, 9 Sep 2026 14:43:47 +0000 Subject: [PATCH 25/26] Use insert_into when possible (#6437) Co-authored-by: Timshel --- src/api/core/accounts.rs | 2 +- src/db/models/archive.rs | 9 +- src/db/models/attachment.rs | 22 ++--- src/db/models/auth_request.rs | 34 +++----- src/db/models/cipher.rs | 22 ++--- src/db/models/collection.rs | 96 +++++++-------------- src/db/models/device.rs | 9 +- src/db/models/emergency_access.rs | 22 ++--- src/db/models/event.rs | 29 ++++--- src/db/models/folder.rs | 33 +++----- src/db/models/group.rs | 135 ++++++++++-------------------- src/db/models/organization.rs | 68 +++++---------- src/db/models/send.rs | 22 ++--- src/db/models/user.rs | 18 ++-- 14 files changed, 188 insertions(+), 333 deletions(-) diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 3ea6eada..69be1334 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -1611,7 +1611,7 @@ async fn post_auth_request( _ => err!("AuthRequest doesn't exist", "Device verification failed"), }; - let mut auth_request = AuthRequest::new( + let auth_request = AuthRequest::new( user.uuid.clone(), data.device_identifier.clone(), client_headers.device_type, diff --git a/src/db/models/archive.rs b/src/db/models/archive.rs index 83d547f2..2330fac4 100644 --- a/src/db/models/archive.rs +++ b/src/db/models/archive.rs @@ -41,17 +41,20 @@ impl Archive { ) -> EmptyResult { User::update_uuid_revision(user_uuid, conn).await; db_run! { conn: - sqlite, mysql { - diesel::replace_into(archives::table) + mysql { + diesel::insert_into(archives::table) .values(( archives::user_uuid.eq(user_uuid), archives::cipher_uuid.eq(cipher_uuid), archives::archived_at.eq(archived_at), )) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(archives::archived_at.eq(archived_at)) .execute(conn) .map_res("Error saving archive") } - postgresql { + postgresql, sqlite { diesel::insert_into(archives::table) .values(( archives::user_uuid.eq(user_uuid), diff --git a/src/db/models/attachment.rs b/src/db/models/attachment.rs index 244f8c27..0536dde5 100644 --- a/src/db/models/attachment.rs +++ b/src/db/models/attachment.rs @@ -82,24 +82,16 @@ impl Attachment { impl Attachment { pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - sqlite, mysql { - match diesel::replace_into(attachments::table) + mysql { + diesel::insert_into(attachments::table) .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(attachments::table) - .filter(attachments::id.eq(&self.id)) - .set(self) - .execute(conn) - .map_res("Error saving attachment") - } - Err(e) => Err(e.into()), - }.map_res("Error saving attachment") + .map_res("Error saving attachment") } - postgresql { + postgresql, sqlite { diesel::insert_into(attachments::table) .values(self) .on_conflict(attachments::id) diff --git a/src/db/models/auth_request.rs b/src/db/models/auth_request.rs index a3876661..cc4b60fd 100644 --- a/src/db/models/auth_request.rs +++ b/src/db/models/auth_request.rs @@ -82,31 +82,23 @@ impl AuthRequest { } impl AuthRequest { - pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { + pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - sqlite, mysql { - match diesel::replace_into(auth_requests::table) - .values(&*self) - .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(auth_requests::table) - .filter(auth_requests::uuid.eq(&self.uuid)) - .set(&*self) - .execute(conn) - .map_res("Error auth_request") - } - Err(e) => Err(e.into()), - }.map_res("Error auth_request") - } - postgresql { + mysql { diesel::insert_into(auth_requests::table) - .values(&*self) + .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(self) + .execute(conn) + .map_res("Error saving auth_request") + } + postgresql, sqlite { + diesel::insert_into(auth_requests::table) + .values(self) .on_conflict(auth_requests::uuid) .do_update() - .set(&*self) + .set(self) .execute(conn) .map_res("Error saving auth_request") } diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index eed5041d..721d9790 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -440,24 +440,16 @@ impl Cipher { self.updated_at = Utc::now().naive_utc(); db_run! { conn: - sqlite, mysql { - match diesel::replace_into(ciphers::table) + mysql { + diesel::insert_into(ciphers::table) .values(&*self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(&*self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(ciphers::table) - .filter(ciphers::uuid.eq(&self.uuid)) - .set(&*self) - .execute(conn) - .map_res("Error saving cipher") - } - Err(e) => Err(e.into()), - }.map_res("Error saving cipher") + .map_res("Error saving cipher") } - postgresql { + postgresql, sqlite { diesel::insert_into(ciphers::table) .values(&*self) .on_conflict(ciphers::uuid) diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs index 8aec90ea..be108f13 100644 --- a/src/db/models/collection.rs +++ b/src/db/models/collection.rs @@ -168,24 +168,16 @@ impl Collection { self.update_users_revision(conn).await; db_run! { conn: - sqlite, mysql { - match diesel::replace_into(collections::table) + mysql { + diesel::insert_into(collections::table) .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(collections::table) - .filter(collections::uuid.eq(&self.uuid)) - .set(self) - .execute(conn) - .map_res("Error saving collection") - } - Err(e) => Err(e.into()), - }.map_res("Error saving collection") + .map_res("Error saving collection") } - postgresql { + postgresql, sqlite { diesel::insert_into(collections::table) .values(self) .on_conflict(collections::uuid) @@ -728,53 +720,30 @@ impl CollectionUser { ) -> EmptyResult { User::update_uuid_revision(user_uuid, conn).await; + let values = ( + users_collections::user_uuid.eq(user_uuid), + users_collections::collection_uuid.eq(collection_uuid), + users_collections::read_only.eq(read_only), + users_collections::hide_passwords.eq(hide_passwords), + users_collections::manage.eq(manage), + ); + db_run! { conn: - sqlite, mysql { - match diesel::replace_into(users_collections::table) - .values(( - users_collections::user_uuid.eq(user_uuid), - users_collections::collection_uuid.eq(collection_uuid), - users_collections::read_only.eq(read_only), - users_collections::hide_passwords.eq(hide_passwords), - users_collections::manage.eq(manage), - )) - .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(users_collections::table) - .filter(users_collections::user_uuid.eq(user_uuid)) - .filter(users_collections::collection_uuid.eq(collection_uuid)) - .set(( - users_collections::user_uuid.eq(user_uuid), - users_collections::collection_uuid.eq(collection_uuid), - users_collections::read_only.eq(read_only), - users_collections::hide_passwords.eq(hide_passwords), - users_collections::manage.eq(manage), - )) - .execute(conn) - .map_res("Error adding user to collection") - } - Err(e) => Err(e.into()), - }.map_res("Error adding user to collection") - } - postgresql { + mysql { diesel::insert_into(users_collections::table) - .values(( - users_collections::user_uuid.eq(user_uuid), - users_collections::collection_uuid.eq(collection_uuid), - users_collections::read_only.eq(read_only), - users_collections::hide_passwords.eq(hide_passwords), - users_collections::manage.eq(manage), - )) + .values(values) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(values) + .execute(conn) + .map_res("Error adding user to collection") + } + postgresql, sqlite { + diesel::insert_into(users_collections::table) + .values(values) .on_conflict((users_collections::user_uuid, users_collections::collection_uuid)) .do_update() - .set(( - users_collections::read_only.eq(read_only), - users_collections::hide_passwords.eq(hide_passwords), - users_collections::manage.eq(manage), - )) + .set(values) .execute(conn) .map_res("Error adding user to collection") } @@ -909,19 +878,18 @@ impl CollectionCipher { Self::update_users_revision(collection_uuid, conn).await; db_run! { conn: - sqlite, mysql { - // Not checking for ForeignKey Constraints here. - // Table ciphers_collections does not have ForeignKey Constraints which would cause conflicts. - // This table has no constraints pointing to itself, but only to others. - diesel::replace_into(ciphers_collections::table) + mysql { + diesel::insert_into(ciphers_collections::table) .values(( ciphers_collections::cipher_uuid.eq(cipher_uuid), ciphers_collections::collection_uuid.eq(collection_uuid), )) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_nothing() .execute(conn) .map_res("Error adding cipher to collection") } - postgresql { + postgresql, sqlite { diesel::insert_into(ciphers_collections::table) .values(( ciphers_collections::cipher_uuid.eq(cipher_uuid), diff --git a/src/db/models/device.rs b/src/db/models/device.rs index cc8f1cec..5e5f1f97 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -146,15 +146,18 @@ impl Device { } db_run! { conn: - sqlite, mysql { + mysql { crate::util::retry(|| - diesel::replace_into(devices::table) + diesel::insert_into(devices::table) .values(&*self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(&*self) .execute(conn), 10, ).map_res("Error saving device") } - postgresql { + postgresql, sqlite { crate::util::retry(|| diesel::insert_into(devices::table) .values(&*self) diff --git a/src/db/models/emergency_access.rs b/src/db/models/emergency_access.rs index 45fad91f..09783061 100644 --- a/src/db/models/emergency_access.rs +++ b/src/db/models/emergency_access.rs @@ -146,24 +146,16 @@ impl EmergencyAccess { self.updated_at = Utc::now().naive_utc(); db_run! { conn: - sqlite, mysql { - match diesel::replace_into(emergency_access::table) + mysql { + diesel::insert_into(emergency_access::table) .values(&*self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(&*self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(emergency_access::table) - .filter(emergency_access::uuid.eq(&self.uuid)) - .set(&*self) - .execute(conn) - .map_res("Error updating emergency access") - } - Err(e) => Err(e.into()), - }.map_res("Error saving emergency access") + .map_res("Error saving emergency access") } - postgresql { + postgresql, sqlite { diesel::insert_into(emergency_access::table) .values(&*self) .on_conflict(emergency_access::uuid) diff --git a/src/db/models/event.rs b/src/db/models/event.rs index 1f307979..2d9ed8b2 100644 --- a/src/db/models/event.rs +++ b/src/db/models/event.rs @@ -208,20 +208,23 @@ impl Event { /// Basic Queries pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - sqlite, mysql { - diesel::replace_into(event::table) - .values(self) - .execute(conn) - .map_res("Error saving event") - } - postgresql { + mysql { diesel::insert_into(event::table) - .values(self) - .on_conflict(event::uuid) - .do_update() - .set(self) - .execute(conn) - .map_res("Error saving event") + .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(self) + .execute(conn) + .map_res("Error saving event") + } + postgresql, sqlite { + diesel::insert_into(event::table) + .values(self) + .on_conflict(event::uuid) + .do_update() + .set(self) + .execute(conn) + .map_res("Error saving event") } } } diff --git a/src/db/models/folder.rs b/src/db/models/folder.rs index 745608e3..adbe993f 100644 --- a/src/db/models/folder.rs +++ b/src/db/models/folder.rs @@ -77,24 +77,16 @@ impl Folder { self.updated_at = Utc::now().naive_utc(); db_run! { conn: - sqlite, mysql { - match diesel::replace_into(folders::table) + mysql { + diesel::insert_into(folders::table) .values(&*self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(&*self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(folders::table) - .filter(folders::uuid.eq(&self.uuid)) - .set(&*self) - .execute(conn) - .map_res("Error saving folder") - } - Err(e) => Err(e.into()), - }.map_res("Error saving folder") + .map_res("Error saving folder") } - postgresql { + postgresql, sqlite { diesel::insert_into(folders::table) .values(&*self) .on_conflict(folders::uuid) @@ -147,16 +139,15 @@ impl Folder { impl FolderCipher { pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - sqlite, mysql { - // Not checking for ForeignKey Constraints here. - // Table folders_ciphers does not have ForeignKey Constraints which would cause conflicts. - // This table has no constraints pointing to itself, but only to others. - diesel::replace_into(folders_ciphers::table) + mysql { + diesel::insert_into(folders_ciphers::table) .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_nothing() .execute(conn) .map_res("Error adding cipher to folder") } - postgresql { + postgresql, sqlite { diesel::insert_into(folders_ciphers::table) .values(self) .on_conflict((folders_ciphers::cipher_uuid, folders_ciphers::folder_uuid)) diff --git a/src/db/models/group.rs b/src/db/models/group.rs index 37037de6..32e9333f 100644 --- a/src/db/models/group.rs +++ b/src/db/models/group.rs @@ -166,24 +166,16 @@ impl Group { self.revision_date = Utc::now().naive_utc(); db_run! { conn: - sqlite, mysql { - match diesel::replace_into(groups::table) + mysql { + diesel::insert_into(groups::table) .values(&*self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(&*self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(groups::table) - .filter(groups::uuid.eq(&self.uuid)) - .set(&*self) - .execute(conn) - .map_res("Error saving group") - } - Err(e) => Err(e.into()), - }.map_res("Error saving group") + .map_res("Error saving group") } - postgresql { + postgresql, sqlite { diesel::insert_into(groups::table) .values(&*self) .on_conflict(groups::uuid) @@ -326,53 +318,30 @@ impl CollectionGroup { group_user.update_user_revision(conn).await; } + let values = ( + collections_groups::collections_uuid.eq(&self.collections_uuid), + collections_groups::groups_uuid.eq(&self.groups_uuid), + collections_groups::read_only.eq(&self.read_only), + collections_groups::hide_passwords.eq(&self.hide_passwords), + collections_groups::manage.eq(&self.manage), + ); + db_run! { conn: - sqlite, mysql { - match diesel::replace_into(collections_groups::table) - .values(( - collections_groups::collections_uuid.eq(&self.collections_uuid), - collections_groups::groups_uuid.eq(&self.groups_uuid), - collections_groups::read_only.eq(&self.read_only), - collections_groups::hide_passwords.eq(&self.hide_passwords), - collections_groups::manage.eq(&self.manage), - )) - .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(collections_groups::table) - .filter(collections_groups::collections_uuid.eq(&self.collections_uuid)) - .filter(collections_groups::groups_uuid.eq(&self.groups_uuid)) - .set(( - collections_groups::collections_uuid.eq(&self.collections_uuid), - collections_groups::groups_uuid.eq(&self.groups_uuid), - collections_groups::read_only.eq(&self.read_only), - collections_groups::hide_passwords.eq(&self.hide_passwords), - collections_groups::manage.eq(&self.manage), - )) - .execute(conn) - .map_res("Error adding group to collection") - } - Err(e) => Err(e.into()), - }.map_res("Error adding group to collection") - } - postgresql { + mysql { diesel::insert_into(collections_groups::table) - .values(( - collections_groups::collections_uuid.eq(&self.collections_uuid), - collections_groups::groups_uuid.eq(&self.groups_uuid), - collections_groups::read_only.eq(self.read_only), - collections_groups::hide_passwords.eq(self.hide_passwords), - collections_groups::manage.eq(self.manage), - )) + .values(values) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(values) + .execute(conn) + .map_res("Error adding group to collection") + } + postgresql, sqlite { + diesel::insert_into(collections_groups::table) + .values(values) .on_conflict((collections_groups::collections_uuid, collections_groups::groups_uuid)) .do_update() - .set(( - collections_groups::read_only.eq(self.read_only), - collections_groups::hide_passwords.eq(self.hide_passwords), - collections_groups::manage.eq(self.manage), - )) + .set(values) .execute(conn) .map_res("Error adding group to collection") } @@ -497,43 +466,25 @@ impl GroupUser { pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { self.update_user_revision(conn).await; + let values = ( + groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), + groups_users::groups_uuid.eq(&self.groups_uuid), + ); + db_run! { conn: - sqlite, mysql { - match diesel::replace_into(groups_users::table) - .values(( - groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), - groups_users::groups_uuid.eq(&self.groups_uuid), - )) - .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(groups_users::table) - .filter(groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid)) - .filter(groups_users::groups_uuid.eq(&self.groups_uuid)) - .set(( - groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), - groups_users::groups_uuid.eq(&self.groups_uuid), - )) - .execute(conn) - .map_res("Error adding user to group") - } - Err(e) => Err(e.into()), - }.map_res("Error adding user to group") - } - postgresql { + mysql { diesel::insert_into(groups_users::table) - .values(( - groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), - groups_users::groups_uuid.eq(&self.groups_uuid), - )) + .values(values) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_nothing() + .execute(conn) + .map_res("Error adding user to group") + } + postgresql, sqlite { + diesel::insert_into(groups_users::table) + .values(values) .on_conflict((groups_users::users_organizations_uuid, groups_users::groups_uuid)) - .do_update() - .set(( - groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), - groups_users::groups_uuid.eq(&self.groups_uuid), - )) + .do_nothing() .execute(conn) .map_res("Error adding user to group") } diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index bdb69864..29016865 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -353,25 +353,16 @@ impl Organization { } db_run! { conn: - sqlite, mysql { - match diesel::replace_into(organizations::table) + mysql { + diesel::insert_into(organizations::table) .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(organizations::table) - .filter(organizations::uuid.eq(&self.uuid)) - .set(self) - .execute(conn) - .map_res("Error saving organization") - } - Err(e) => Err(e.into()), - }.map_res("Error saving organization") - + .map_res("Error saving organization") } - postgresql { + postgresql, sqlite { diesel::insert_into(organizations::table) .values(self) .on_conflict(organizations::uuid) @@ -753,24 +744,16 @@ impl Membership { User::update_uuid_revision(&self.user_uuid, conn).await; db_run! { conn: - sqlite, mysql { - match diesel::replace_into(users_organizations::table) + mysql { + diesel::insert_into(users_organizations::table) .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(users_organizations::table) - .filter(users_organizations::uuid.eq(&self.uuid)) - .set(self) - .execute(conn) - .map_res("Error adding user to organization") - }, - Err(e) => Err(e.into()), - }.map_res("Error adding user to organization") + .map_res("Error adding user to organization") } - postgresql { + postgresql, sqlite { diesel::insert_into(users_organizations::table) .values(self) .on_conflict(users_organizations::uuid) @@ -1186,25 +1169,16 @@ impl Membership { impl OrganizationApiKey { pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - sqlite, mysql { - match diesel::replace_into(organization_api_key::table) + mysql { + diesel::insert_into(organization_api_key::table) .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(organization_api_key::table) - .filter(organization_api_key::uuid.eq(&self.uuid)) - .set(self) - .execute(conn) - .map_res("Error saving organization") - } - Err(e) => Err(e.into()), - }.map_res("Error saving organization") - + .map_res("Error saving organization") } - postgresql { + postgresql, sqlite { diesel::insert_into(organization_api_key::table) .values(self) .on_conflict((organization_api_key::uuid, organization_api_key::org_uuid)) diff --git a/src/db/models/send.rs b/src/db/models/send.rs index c5bc98c4..d7de7749 100644 --- a/src/db/models/send.rs +++ b/src/db/models/send.rs @@ -202,24 +202,16 @@ impl Send { self.revision_date = Utc::now().naive_utc(); db_run! { conn: - sqlite, mysql { - match diesel::replace_into(sends::table) + mysql { + diesel::insert_into(sends::table) .values(&*self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_update() + .set(&*self) .execute(conn) - { - Ok(_) => Ok(()), - // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { - diesel::update(sends::table) - .filter(sends::uuid.eq(&self.uuid)) - .set(&*self) - .execute(conn) - .map_res("Error saving send") - } - Err(e) => Err(e.into()), - }.map_res("Error saving send") + .map_res("Error saving send") } - postgresql { + postgresql, sqlite { diesel::insert_into(sends::table) .values(&*self) .on_conflict(sends::uuid) diff --git a/src/db/models/user.rs b/src/db/models/user.rs index 93d750d5..81cb8d84 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -463,15 +463,17 @@ impl Invitation { } db_run! { conn: - sqlite, mysql { - // Not checking for ForeignKey Constraints here - // Table invitations does not have any ForeignKey Constraints. - diesel::replace_into(invitations::table) + // Not checking for ForeignKey Constraints here + // Table invitations does not have any ForeignKey Constraints. + mysql { + diesel::insert_into(invitations::table) .values(self) + .on_conflict(diesel::dsl::DuplicatedKeys) + .do_nothing() .execute(conn) .map_res("Error saving invitation") } - postgresql { + postgresql, sqlite { diesel::insert_into(invitations::table) .values(self) .on_conflict(invitations::email) @@ -528,13 +530,13 @@ pub struct UserId(String); impl SsoUser { pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - sqlite, mysql { - diesel::replace_into(sso_users::table) + mysql { + diesel::insert_into(sso_users::table) .values(self) .execute(conn) .map_res("Error saving SSO user") } - postgresql { + postgresql, sqlite { diesel::insert_into(sso_users::table) .values(self) .execute(conn) From eb212e23fad88e6136723f43e5b73543fa7026d3 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Wed, 9 Sep 2026 18:24:33 +0200 Subject: [PATCH 26/26] Fix archiveDate update (#7722) When `archiveDate` is set to `null` it should unarchive it for that specific user, which is what Bitwarden does. This should fix this by checking and validating if it is `null` Fixes #7581 Signed-off-by: BlackDex --- src/api/core/ciphers.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 13021ca3..50be6732 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -537,11 +537,12 @@ pub async fn update_cipher_from_data( cipher.move_to_folder(data.folder_id, &headers.user.uuid, conn).await?; cipher.set_favorite(data.favorite, &headers.user.uuid, conn).await?; - if let Some(dt_str) = data.archived_date { - match NaiveDateTime::parse_from_str(&dt_str, "%+") { + match data.archived_date { + Some(dt_str) => match NaiveDateTime::parse_from_str(&dt_str, "%+") { Ok(dt) => cipher.set_archived_at(dt, &headers.user.uuid, conn).await?, Err(err) => warn!("Error parsing ArchivedDate '{dt_str}': {err}"), - } + }, + None => cipher.unarchive(&headers.user.uuid, conn).await?, } if ut != UpdateType::None {