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 1/3] 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 2/3] 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 3/3] 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"];