diff --git a/.env.template b/.env.template index 70bfe771..27e17abe 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 ### ######################### @@ -324,6 +332,14 @@ ## Set to the string "none" (without quotes), to disable any headers and just use the remote IP # IP_HEADER=X-Real-IP +## Which addresses the header above is accepted from, defaults to "local". +## Anyone able to reach Vaultwarden can set the header, and the client IP is used for the login and +## admin rate limits, so it is only trusted when the request comes from a proxy listed here. +## "local" accepts it from any non global address, which covers a reverse proxy running on the same +## host or container network. Use "all" to accept it from anywhere, or list the addresses of your +## proxy as IPs and CIDR ranges if it connects from a public address. +# IP_HEADER_TRUSTED_PROXIES=local + ## Icon service ## The predefined icon services are: internal, bitwarden, duckduckgo, google. ## To specify a custom icon service, set a URL template with exactly one instance of `{}`, @@ -461,6 +477,13 @@ ## Note that this applies to both the login and the 2FA, so it's recommended to allow a burst size of at least 2. # LOGIN_RATELIMIT_MAX_BURST=10 +## Number of seconds, on average, between requests from the same IP address to one of the rate limited +## unauthenticated endpoints, like the password hint, the account recovery mails or accessing a Send. +# UNAUTHENTICATED_RATELIMIT_SECONDS=60 +## Allow a burst of requests of up to this size, while maintaining the average indicated by `UNAUTHENTICATED_RATELIMIT_SECONDS`. +## This budget is shared between all of those endpoints, so it is more lenient than the login one. +# UNAUTHENTICATED_RATELIMIT_MAX_BURST=50 + ## BETA FEATURE: Groups ## Controls whether group support is enabled for organizations ## This setting applies to organizations. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c9e8442e..31a04012 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,7 +62,7 @@ jobs: # Checkout the repo - name: "Checkout" - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 0 diff --git a/.github/workflows/check-templates.yml b/.github/workflows/check-templates.yml index d9e139db..da4d90fd 100644 --- a/.github/workflows/check-templates.yml +++ b/.github/workflows/check-templates.yml @@ -20,7 +20,7 @@ jobs: steps: # Checkout the repo - name: "Checkout" - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false # End Checkout the repo diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml index 60fafe9e..3111e20b 100644 --- a/.github/workflows/hadolint.yml +++ b/.github/workflows/hadolint.yml @@ -32,7 +32,7 @@ jobs: # Checkout the repo - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false # End Checkout the repo @@ -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 cbd32451..9d15dd88 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -77,7 +77,7 @@ jobs: # Checkout the repo - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # We need fetch-depth of 0 so we also get all the tag metadata with: persist-credentials: false @@ -106,7 +106,7 @@ jobs: # Login to Docker Hub - name: Login to Docker Hub - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -121,7 +121,7 @@ jobs: # Login to GitHub Container Registry - name: Login to GitHub Container Registry - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -137,7 +137,7 @@ jobs: # Login to Quay.io - name: Login to Quay.io - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + 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@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1 + 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@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -287,7 +287,7 @@ jobs: # Login to GitHub Container Registry - name: Login to GitHub Container Registry - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -303,7 +303,7 @@ jobs: # Login to Quay.io - name: Login to Quay.io - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + 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@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1 + 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@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1 + 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@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1 + 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 eaf89350..942a99e9 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -50,6 +50,6 @@ jobs: severity: CRITICAL,HIGH - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + 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 e906b5bd..779cd6e3 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -16,11 +16,11 @@ jobs: steps: # Checkout the repo - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false # End Checkout the repo # 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 b8f66cbe..e1de58c3 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -19,12 +19,12 @@ jobs: security-events: write # To write the security report steps: - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7 + 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 0715098c..21defe79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,17 +8,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures 0.2.17", -] - [[package]] name = "ahash" version = "0.8.12" @@ -33,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", ] @@ -63,18 +52,18 @@ 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", ] [[package]] name = "anyhow" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arc-swap" @@ -121,7 +110,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -133,7 +122,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -161,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", @@ -224,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", ] @@ -242,7 +231,7 @@ dependencies = [ "async-task", "blocking", "cfg-if", - "event-listener 5.4.1", + "event-listener 5.4.2", "futures-lite", "rustix", ] @@ -311,7 +300,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -322,13 +311,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -360,9 +349,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-config" -version = "1.8.18" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33f815b73a3899c03b380d543532e5865f230dce9678d108dc10732a8682275" +checksum = "1b180a3c8b55960db3426d8964b8745e652466a1a49fe1a2eda828046d30b5e4" dependencies = [ "aws-credential-types", "aws-runtime", @@ -380,8 +369,8 @@ dependencies = [ "bytes", "fastrand", "hex", - "http 1.4.2", - "sha1 0.10.6", + "http 1.5.0", + "sha1 0.10.7", "time", "tokio", "tracing", @@ -391,9 +380,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.14" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7" +checksum = "e93964ffdaf57857f544be3666a5f57570bb699e934700f11b49708f61bb556e" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -403,9 +392,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.7.5" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c9b9de216a988dd54b754a82a7660cfe14cee4f6782ae4524470972fa0ccb39" +checksum = "c9007227e10b5fed2f3e0a2beff489211e2b5604c400b7a9d5d81ca9d64c24bb" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -418,8 +407,8 @@ dependencies = [ "bytes", "bytes-utils", "fastrand", - "http 1.4.2", - "http-body 1.0.1", + "http 1.5.0", + "http-body 1.1.0", "percent-encoding", "pin-project-lite", "tracing", @@ -428,9 +417,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.102.0" +version = "1.105.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c82b3ac19f1431854f7ace3a7531674633e286bfdde21976893bfee36fd493b" +checksum = "6ffd0fbe7873cb548a7aa60f9573c268fff94155397fd4f14dc9f1ecaaab8516" dependencies = [ "arc-swap", "aws-credential-types", @@ -441,21 +430,22 @@ dependencies = [ "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-types", "bytes", "fastrand", "http 0.2.12", - "http 1.4.2", + "http 1.5.0", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-ssooidc" -version = "1.104.0" +version = "1.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321000d2b4c5519ee573f73167f612efd7329322d9b26969ad1979f0427f1913" +checksum = "175763eb222a46377df7aa257a3bca980ab3e96703fefc8f4d0b8da6ad2e254c" dependencies = [ "arc-swap", "aws-credential-types", @@ -466,21 +456,22 @@ dependencies = [ "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-types", "bytes", "fastrand", "http 0.2.12", - "http 1.4.2", + "http 1.5.0", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sts" -version = "1.107.0" +version = "1.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0d328ba962af23ecfa3c9f23b98d3d35e325fa218d7f13d17a6bf522f8a560" +checksum = "dd8b14781dfbff48984017d57167b6ea0b6471c6920ec52b44a2677c7feb3c13" dependencies = [ "arc-swap", "aws-credential-types", @@ -492,21 +483,22 @@ dependencies = [ "aws-smithy-query", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-smithy-xml", "aws-types", "fastrand", "http 0.2.12", - "http 1.4.2", + "http 1.5.0", "regex-lite", "tracing", ] [[package]] name = "aws-sigv4" -version = "1.4.5" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae38512beae0ffee7010fc24e7a8a123c53efdfef42a61e80fda4882418dc71" +checksum = "723c2234ad7511ceef63eab016b7ba6ff7c55590fefb96fa8467af014a07309f" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -517,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", @@ -537,9 +529,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.63.6" +version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231" +checksum = "37843d9add67c3aff5856f409c6dc315d3cdff60f9c0cb5b670dab1e9920306d" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -547,8 +539,8 @@ dependencies = [ "bytes-utils", "futures-core", "futures-util", - "http 1.4.2", - "http-body 1.0.1", + "http 1.5.0", + "http-body 1.1.0", "http-body-util", "percent-encoding", "pin-project-lite", @@ -558,9 +550,9 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.62.7" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "701a947f4797e52a911e114a898667c746c39feea467bbd1abd7b3721f702ffa" +checksum = "3dc65a121adb4b33729919fcfa14fa36fb33c1555a8f06bb0e2188dbfdc1d9ef" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-schema", @@ -569,28 +561,31 @@ dependencies = [ [[package]] name = "aws-smithy-observability" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c" +checksum = "8e86338c869539a581bf161247762a6e87f92c5c075060057b5ed6d06632ed0c" dependencies = [ "aws-smithy-runtime-api", ] [[package]] name = "aws-smithy-query" -version = "0.60.15" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a56d79744fb3edb5d722ef79d86081e121d3b9422cb209eb03aea6aa4f21ebd" +checksum = "512346c7212ab7436df2d77a16d976a468ae44a418835511d2a69269810aaf62" dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", + "aws-smithy-xml", "urlencoding", ] [[package]] name = "aws-smithy-runtime" -version = "1.11.3" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e6f5caf6fea86f8c2206541ab5857cfcda9013426cdbe8fa0098b9e2d32182" +checksum = "07505b34e8f4b3591a4fa69e9792b52289b95488dbbc68c3c0075b7bedb245e1" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -601,9 +596,9 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.4.2", + "http 1.5.0", "http-body 0.4.6", - "http-body 1.0.1", + "http-body 1.1.0", "http-body-util", "pin-project-lite", "pin-utils", @@ -613,16 +608,16 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ed1ebe6e0a95ea84570225f5a8208dec4b8f77e61a9b0d6f51773fcb4612f0" +checksum = "3b98f2e1fd67ec06618f9c291e5e495a468e60519e44c9c1979cd0521f3affdb" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", "aws-smithy-types", "bytes", "http 0.2.12", - "http 1.4.2", + "http 1.5.0", "pin-project-lite", "tokio", "tracing", @@ -637,18 +632,18 @@ checksum = "221eaa237ddf1ca79b60d1372aad77e47f9c0ea5b3ce5099da8c61d027dc77b3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "aws-smithy-schema" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7442cb268338f0eb8278140a107c046756aa01093d8ef5e99628d34ae09c94f5" +checksum = "7d56e0a4e53127a632224e43633b0fe045fa9e1e3cfc68b9830f1115e103f910" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", - "http 1.4.2", + "http 1.5.0", ] [[package]] @@ -661,9 +656,9 @@ dependencies = [ "bytes", "bytes-utils", "http 0.2.12", - "http 1.4.2", + "http 1.5.0", "http-body 0.4.6", - "http-body 1.0.1", + "http-body 1.1.0", "http-body-util", "itoa", "num-integer", @@ -676,18 +671,21 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.15" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" +checksum = "ce84f71c72fee2cbbadde6e7d082f5fb466e3a84733855295fa7aafd1b31b7d8" dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", "xmlparser", ] [[package]] name = "aws-types" -version = "1.3.16" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16bf10b03a3c01e6b3b7d47cd964e873ffe9e7d4e80fad16bd4c077cb068531" +checksum = "eec1cd5469f328c782dc3e33d4153cf118a54e33cbb3356d60d16f89883e1f94" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -716,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" @@ -770,9 +774,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "blake2" @@ -801,15 +805,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - [[package]] name = "blocking" version = "1.6.2" @@ -861,9 +856,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" -version = "1.25.0" +version = "1.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" [[package]] name = "byteorder" @@ -898,7 +893,7 @@ dependencies = [ "cached_proc_macro_types", "hashbrown 0.16.1", "parking_lot", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "web-time", ] @@ -912,7 +907,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -921,20 +916,11 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26cf465651fa6ad902a2d327ba60c3a6bc61c6a2f4ad70d091cf20dfda0074ef" -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", -] - [[package]] name = "cc" -version = "1.2.66" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -983,16 +969,6 @@ dependencies = [ "phf 0.12.1", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common 0.1.6", - "inout", -] - [[package]] name = "cmov" version = "0.5.4" @@ -1159,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]] @@ -1184,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]] @@ -1287,7 +1265,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1331,7 +1309,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.119", ] [[package]] @@ -1345,7 +1323,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.119", ] [[package]] @@ -1358,7 +1336,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.119", ] [[package]] @@ -1369,7 +1347,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1380,7 +1358,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1391,7 +1369,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core 0.23.0", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1410,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" @@ -1439,7 +1417,7 @@ dependencies = [ "defmt-parser", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1448,7 +1426,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] @@ -1503,7 +1481,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1513,7 +1491,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn", + "syn 2.0.119", ] [[package]] @@ -1535,7 +1513,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.119", "unicode-xid", ] @@ -1565,21 +1543,21 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "diesel" -version = "2.3.10" +version = "2.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29fe29a87fb84c631ffb3ba21798c4b1f3a964701ba78f0dce4bf8668562ec88" +checksum = "e54d1f576cd3a3460f212a4615fd12ce1b6303c095b79a44449ffbe627753dc1" dependencies = [ "bigdecimal", - "bitflags 2.13.0", + "bitflags 2.13.1", "byteorder", "chrono", "diesel_derives", @@ -1606,7 +1584,7 @@ checksum = "4c9c687e77914afc18b1e797d523ace0e5f08dc7805285bcdabd8646ea8d4de7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1619,7 +1597,7 @@ dependencies = [ "dsl_auto_type", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1639,7 +1617,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe2444076b48641147115697648dc743c2c00b61adade0f01ce67133c7babe8c" dependencies = [ - "syn", + "syn 2.0.119", ] [[package]] @@ -1668,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", + "syn 3.0.3", ] [[package]] @@ -1718,7 +1696,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1767,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" @@ -1794,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", ] @@ -1844,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", ] @@ -1859,15 +1836,15 @@ 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", ] [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "fern" @@ -1965,9 +1942,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" dependencies = [ "futures-channel", "futures-core", @@ -1980,9 +1957,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", "futures-sink", @@ -1990,15 +1967,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", @@ -2007,9 +1984,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-lite" @@ -2026,26 +2003,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-timer" @@ -2055,9 +2032,9 @@ checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-channel", "futures-core", @@ -2135,9 +2112,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "gloo-timers" @@ -2168,7 +2145,7 @@ dependencies = [ "parking_lot", "portable-atomic", "quanta", - "rand 0.9.4", + "rand 0.9.5", "smallvec", "spinning_top", "web-time", @@ -2209,7 +2186,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.2", + "http 1.5.0", "indexmap 2.14.0", "slab", "tokio", @@ -2230,9 +2207,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "6.4.2" +version = "6.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26569a2763497b7bd3fbd19374b774ea6038c5293678771259cd534d49740ff" +checksum = "4633d16a2350341713c379d6d06a4b9e1845329386026a49ce4fd09c2f3b16f6" dependencies = [ "derive_builder", "log", @@ -2241,7 +2218,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.19", "walkdir", ] @@ -2313,7 +2290,7 @@ dependencies = [ "ipnet", "jni", "rand 0.10.2", - "thiserror 2.0.18", + "thiserror 2.0.19", "tinyvec", "tokio", "tracing", @@ -2334,7 +2311,7 @@ dependencies = [ "prefix-trie", "rand 0.10.2", "ring", - "thiserror 2.0.18", + "thiserror 2.0.19", "tinyvec", "tracing", "url", @@ -2361,7 +2338,7 @@ dependencies = [ "resolv-conf", "smallvec", "system-configuration", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "tracing", ] @@ -2426,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", @@ -2447,24 +2424,24 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", - "http 1.4.2", + "http 1.5.0", ] [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", - "http 1.4.2", - "http-body 1.0.1", + "http 1.5.0", + "http-body 1.1.0", "pin-project-lite", ] @@ -2482,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", ] @@ -2514,17 +2491,17 @@ dependencies = [ [[package]] name = "hyper" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2", - "http 1.4.2", - "http-body 1.0.1", + "http 1.5.0", + "http-body 1.1.0", "httparse", "itoa", "pin-project-lite", @@ -2539,10 +2516,10 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.2", - "hyper 1.10.1", + "http 1.5.0", + "hyper 1.11.0", "hyper-util", - "rustls 0.23.41", + "rustls 0.23.43", "tokio", "tokio-rustls 0.26.4", "tower-service", @@ -2558,14 +2535,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.2", - "http-body 1.0.1", - "hyper 1.10.1", + "http 1.5.0", + "http-body 1.1.0", + "hyper 1.11.0", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.4", + "socket2 0.6.5", "system-configuration", "tokio", "tower-service", @@ -2735,23 +2712,13 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "block-padding", - "generic-array", -] - [[package]] name = "ipconfig" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" dependencies = [ - "socket2 0.6.4", + "socket2 0.6.5", "widestring", "windows-registry", "windows-result", @@ -2760,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", ] @@ -2801,11 +2768,12 @@ checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" [[package]] name = "jiff" -version = "0.2.31" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" dependencies = [ "defmt", + "jiff-core", "jiff-static", "jiff-tzdb-platform", "js-sys", @@ -2817,22 +2785,32 @@ dependencies = [ "windows-link", ] +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + [[package]] name = "jiff-static" -version = "0.2.31" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" dependencies = [ + "jiff-core", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "jiff-tzdb" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6142247df1a93c2b3587402a19710be3e6e942f1581a1702e76408f2c21d6590" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" [[package]] name = "jiff-tzdb-platform" @@ -2855,7 +2833,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.18", + "thiserror 2.0.19", "walkdir", "windows-link", ] @@ -2870,7 +2848,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn", + "syn 2.0.119", ] [[package]] @@ -2889,14 +2867,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn", + "syn 2.0.119", ] [[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", @@ -2926,9 +2904,9 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "10.4.0" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" +checksum = "881733cbc631fc9e472e24447ce32a64bedf2da498d6d8570b08edc87de71f65" dependencies = [ "base64 0.22.1", "ed25519-dalek", @@ -2938,7 +2916,7 @@ dependencies = [ "p256", "p384", "pem", - "rand 0.8.6", + "rand 0.8.7", "rsa", "serde", "serde_json", @@ -2972,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", @@ -2996,10 +2974,10 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "rustls 0.23.41", + "rustls 0.23.43", "rustls-native-certs", "serde", - "socket2 0.6.4", + "socket2 0.6.5", "tokio", "tokio-rustls 0.26.4", "tracing", @@ -3008,9 +2986,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libm" @@ -3094,7 +3072,7 @@ name = "macros" version = "0.1.0" dependencies = [ "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -3118,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", ] @@ -3185,9 +3163,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", @@ -3205,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", @@ -3223,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", @@ -3305,7 +3283,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.6", + "rand 0.8.7", "smallvec", "zeroize", ] @@ -3318,13 +3296,13 @@ checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-derive" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -3399,8 +3377,8 @@ dependencies = [ "base64 0.22.1", "chrono", "getrandom 0.2.17", - "http 1.4.2", - "rand 0.8.6", + "http 1.5.0", + "rand 0.8.7", "serde", "serde_json", "serde_path_to_error", @@ -3430,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", @@ -3441,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.0.1", + "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", @@ -3469,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", @@ -3483,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", @@ -3513,13 +3489,13 @@ dependencies = [ "dyn-clone", "ed25519-dalek", "hmac 0.12.1", - "http 1.4.2", + "http 1.5.0", "itertools", "log", "oauth2", "p256", "p384", - "rand 0.8.6", + "rand 0.8.7", "rsa", "serde", "serde-value", @@ -3539,7 +3515,7 @@ version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "cfg-if", "foreign-types", "libc", @@ -3555,7 +3531,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3687,16 +3663,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest 0.10.7", - "hmac 0.12.1", -] - [[package]] name = "pear" version = "0.2.9" @@ -3717,7 +3683,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3747,9 +3713,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47627dd7305c6a2d6c8c6bcd24c5a4c17dbbf425f4f9c5313e724b38fc9782e9" +checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" dependencies = [ "memchr", "ucd-trie", @@ -3757,9 +3723,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b4254325ecad416ab689e27ba51da03ba01a9632bc6e108f5fe7c3c4ad29d58" +checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" dependencies = [ "pest", "pest_generator", @@ -3767,22 +3733,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c4c0e91ead7a8f7acecbca6f003fc2e8282b1dbe2dd9c9d2f16aba42995e0a7" +checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "pest_meta" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9744bc48116fee06334924bb5f2bad41eed5e89bd26e29b0b799f9a3f82c210" +checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" dependencies = [ "pest", ] @@ -3813,7 +3779,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared 0.11.3", - "rand 0.8.6", + "rand 0.8.7", ] [[package]] @@ -3826,7 +3792,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3887,21 +3853,6 @@ dependencies = [ "spki", ] -[[package]] -name = "pkcs5" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" -dependencies = [ - "aes", - "cbc", - "der", - "pbkdf2", - "scrypt", - "sha2 0.10.9", - "spki", -] - [[package]] name = "pkcs8" version = "0.10.2" @@ -3909,8 +3860,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", - "pkcs5", - "rand_core 0.6.4", "spki", ] @@ -3936,9 +3885,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" [[package]] name = "portable-atomic-util" @@ -4006,9 +3955,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -4021,7 +3970,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "version_check", "yansi", ] @@ -4065,19 +4014,9 @@ 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.40.1" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2474bd2e5029e7ccb6abb2ba48cf2383a333851dedf495901544281590c7da7f" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" dependencies = [ "memchr", "serde", @@ -4085,9 +4024,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -4123,9 +4062,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -4134,9 +4073,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -4203,7 +4142,7 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", ] [[package]] @@ -4212,34 +4151,34 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", ] [[package]] name = "ref-cast" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "regex" -version = "1.12.4" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -4249,9 +4188,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -4282,19 +4221,18 @@ dependencies = [ ] [[package]] -name = "reqsign-aws-v4" -version = "3.0.1" +name = "reqsign-aws-core" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75624bd8a466e37ddc0a7b6c33ac859a85347c153a916e1dd9d0b68338f74a" +checksum = "e4af084e1f3cbf3e67e0c972765399bce54ecec804cceba46b39a8331f3c1bff" dependencies = [ - "anyhow", "bytes", "form_urlencoded", "hex", - "http 1.4.2", + "http 1.5.0", "log", "percent-encoding", - "quick-xml 0.40.1", + "quick-xml", "reqsign-core", "rust-ini", "serde", @@ -4303,26 +4241,37 @@ dependencies = [ "sha1 0.11.0", ] +[[package]] +name = "reqsign-aws-v4" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac5b3b7cefa28933792b439186459f77f19f9b6edbeab41b8b187150361a206" +dependencies = [ + "bytes", + "http 1.5.0", + "log", + "quick-xml", + "reqsign-aws-core", + "reqsign-core", + "serde", +] + [[package]] name = "reqsign-core" -version = "3.0.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5fa5cb48808693614d1701fcd3db0b30fa292e0f18e122ae068b6d32eaeed3f" +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", - "rsa", - "serde", - "serde_json", "sha1 0.11.0", "sha2 0.11.0", "windows-sys 0.61.2", @@ -4330,9 +4279,9 @@ dependencies = [ [[package]] name = "reqsign-file-read-tokio" -version = "3.0.1" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a4b6f3a3fd29ffcc99a90aec585a65217783badfd73acddf847b63ae683bda9" +checksum = "663d9d55abd0df0830ef0ae43708297cc1371cf4e8ca91f3ac813c309cca8c98" dependencies = [ "anyhow", "reqsign-core", @@ -4350,14 +4299,13 @@ dependencies = [ "cookie", "cookie_store", "encoding_rs", - "futures-channel", "futures-core", "futures-util", "h2", - "http 1.4.2", - "http-body 1.0.1", + "http 1.5.0", + "http-body 1.1.0", "http-body-util", - "hyper 1.10.1", + "hyper 1.11.0", "hyper-rustls", "hyper-util", "js-sys", @@ -4365,7 +4313,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustls 0.23.41", + "rustls 0.23.43", "rustls-pki-types", "rustls-platform-verifier", "serde", @@ -4454,7 +4402,7 @@ dependencies = [ "num_cpus", "parking_lot", "pin-project-lite", - "rand 0.8.6", + "rand 0.8.7", "ref-cast", "rocket_codegen", "rocket_http", @@ -4483,7 +4431,7 @@ dependencies = [ "proc-macro2", "quote", "rocket_http", - "syn", + "syn 2.0.119", "unicode-xid", "version_check", ] @@ -4553,7 +4501,6 @@ dependencies = [ "pkcs1", "pkcs8", "rand_core 0.6.4", - "sha2 0.10.9", "signature", "spki", "subtle", @@ -4567,7 +4514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" dependencies = [ "hashbrown 0.16.1", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] @@ -4614,7 +4561,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys", @@ -4635,9 +4582,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.41" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "log", "once_cell", @@ -4671,9 +4618,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "zeroize", ] @@ -4689,7 +4636,7 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.41", + "rustls 0.23.43", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki 0.103.13", @@ -4738,15 +4685,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher", -] - [[package]] name = "same-file" version = "1.0.6" @@ -4788,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", @@ -4810,17 +4748,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scrypt" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" -dependencies = [ - "pbkdf2", - "salsa20", - "sha2 0.10.9", -] - [[package]] name = "sct" version = "0.7.1" @@ -4851,7 +4778,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -4876,9 +4803,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -4906,29 +4833,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "indexmap 2.14.0", "itoa", @@ -5001,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", @@ -5017,14 +4944,14 @@ dependencies = [ "darling 0.23.0", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -5111,15 +5038,15 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "simd_cesu8" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" dependencies = [ "rustc_version", "simdutf8", @@ -5139,7 +5066,7 @@ checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.18", + "thiserror 2.0.19", "time", ] @@ -5173,9 +5100,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -5183,9 +5110,15 @@ dependencies = [ [[package]] name = "spin" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +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" @@ -5269,9 +5202,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.118" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -5295,7 +5239,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -5316,7 +5260,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -5361,11 +5305,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.19", ] [[package]] @@ -5376,43 +5320,34 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" 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.53" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", "libc", @@ -5432,9 +5367,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -5461,9 +5396,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -5476,9 +5411,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -5486,20 +5421,20 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.4", + "socket2 0.6.5", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -5518,15 +5453,15 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.41", + "rustls 0.23.43", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" dependencies = [ "futures-core", "pin-project-lite", @@ -5547,14 +5482,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", + "libc", "pin-project-lite", "tokio", ] @@ -5618,11 +5554,11 @@ 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.3", + "winnow 1.0.4", ] [[package]] @@ -5639,7 +5575,7 @@ checksum = "f8e43134db17199f7f721803383ac5854edd0d3d523cc34dba321d6acfbe76c3" dependencies = [ "digest 0.10.7", "hmac 0.12.1", - "sha1 0.10.6", + "sha1 0.10.7", "sha2 0.10.9", ] @@ -5665,12 +5601,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "async-compression", - "bitflags 2.13.0", + "bitflags 2.13.1", "bytes", "futures-core", "futures-util", - "http 1.4.2", - "http-body 1.0.1", + "http 1.5.0", + "http-body 1.1.0", "http-body-util", "pin-project-lite", "tokio", @@ -5713,7 +5649,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -5770,11 +5706,11 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.4.2", + "http 1.5.0", "httparse", "log", - "rand 0.8.6", - "sha1 0.10.6", + "rand 0.8.7", + "sha1 0.10.7", "thiserror 1.0.69", "url", "utf-8", @@ -5868,9 +5804,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.4" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -5886,9 +5822,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "value-bag" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" +checksum = "068e763e8279de7ab94b6afebded2cb701678af094feb1c12ccb061b4783c1be" [[package]] name = "vaultwarden" @@ -5921,7 +5857,8 @@ dependencies = [ "handlebars", "hickory-resolver", "html5gum", - "http 1.4.2", + "http 1.5.0", + "ipnet", "job_scheduler_ng", "jsonwebtoken", "lettre", @@ -5948,7 +5885,7 @@ dependencies = [ "rocket", "rocket_ws", "rpassword", - "rustls 0.23.41", + "rustls 0.23.43", "semver", "serde", "serde_json", @@ -6063,7 +6000,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -6150,7 +6087,7 @@ dependencies = [ "nom 7.1.3", "openssl", "openssl-sys", - "rand 0.9.4", + "rand 0.9.5", "rand_chacha 0.9.0", "serde", "serde_cbor_2", @@ -6179,18 +6116,18 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" dependencies = [ "rustls-pki-types", ] [[package]] name = "which" -version = "8.0.4" +version = "8.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d7cd18d4acb58fb3cdfe9ea54e6cd96a4e7d4cc45c56338b236e82dad47248" +checksum = "8f3ef584124b911bcc3875c2f1472e80f24361ceb789bd1c62b3e9a3df9ff43c" dependencies = [ "libc", ] @@ -6262,7 +6199,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -6273,7 +6210,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -6459,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" @@ -6479,9 +6407,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" [[package]] name = "wit-bindgen" @@ -6562,44 +6490,41 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] [[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.4", - "reqwest", - "sha1 0.10.6", - "threadpool", + "getrandom 0.4.3", + "hmac 0.13.0", + "sha1 0.11.0", ] [[package]] name = "zerocopy" -version = "0.8.53" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.53" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -6619,7 +6544,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -6640,7 +6565,7 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -6673,14 +6598,14 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 909570e7..f65ead29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace.package] edition = "2024" -rust-version = "1.94.1" +rust-version = "1.95.0" license = "AGPL-3.0-only" repository = "https://github.com/dani-garcia/vaultwarden" publish = false @@ -75,7 +75,7 @@ dotenvy = { version = "0.15.7", default-features = false } # Numerical libraries num-traits = "0.2.19" -num-derive = "0.4.2" +num-derive = "0.5.1" bigdecimal = "0.4.10" # Web framework @@ -89,8 +89,8 @@ rmpv = "1.3.1" # MessagePack library dashmap = "6.2.1" # Async futures -futures = "0.3.32" -tokio = { version = "1.52.3", features = [ +futures = "0.3.33" +tokio = { version = "1.53.1", features = [ "fs", "io-util", "net", @@ -99,14 +99,14 @@ tokio = { version = "1.52.3", features = [ "signal", "time", ] } -tokio-util = { version = "0.7.18", features = ["compat"] } +tokio-util = { version = "0.7.19", features = ["compat"] } # A generic serialization/deserialization framework -serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.150" +serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" # A safe, extensible ORM and Query builder -diesel = { version = "2.3.10", features = ["chrono", "r2d2", "numeric"] } +diesel = { version = "2.3.11", features = ["chrono", "r2d2", "numeric"] } diesel_migrations = "2.3.2" derive_more = { version = "2.1.1", features = [ @@ -124,31 +124,31 @@ libsqlite3-sys = { version = "0.37.0", optional = true } # Crypto-related libraries rand = "0.10.2" ring = "0.17.14" -rustls = { version = "0.23.41", features = ["ring", "std"], default-features = false } +rustls = { version = "0.23.43", features = ["ring", "std"], default-features = false } subtle = "2.6.1" # UUID generation -uuid = { version = "1.23.4", features = ["v4"] } +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.53" +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 = "10.4.0", default-features = false, features = ["rust_crypto", "use_pem"] } +jsonwebtoken = { version = "11.0.0", default-features = false, features = ["rust_crypto", "use_pem"] } # TOTP library 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", @@ -179,7 +179,7 @@ percent-encoding = "2.3.2" # URL encoding library used for URL's in the emails email_address = "0.2.9" # HTML Template library -handlebars = { version = "6.4.2", features = ["dir_source"] } +handlebars = { version = "6.4.3", features = ["dir_source"] } # HTTP client (Used for favicons, version check, DUO and HIBP API) reqwest = { version = "0.13.4", default-features = false, features = [ @@ -204,7 +204,7 @@ hickory-resolver = "0.26.1" # Favicon extraction libraries html5gum = "0.8.4" -regex = { version = "1.12.4", default-features = false, features = [ +regex = { version = "1.13.1", default-features = false, features = [ "perf", "std", "unicode-perl", @@ -230,6 +230,9 @@ pico-args = "0.5.0" pastey = "0.2.3" governor = "0.10.4" +# CIDR parsing for the trusted proxies of the client IP header +ipnet = "2.12.1" + # OIDC for SSO openidconnect = { version = "4.0.1", default-features = false } moka = { version = "0.12.15", features = ["future"] } @@ -241,7 +244,7 @@ semver = "1.0.28" # Mainly used for the musl builds, since the default musl malloc is very slow mimalloc = { version = "0.1.52", optional = true, default-features = false, features = ["secure"] } -which = "8.0.4" +which = "8.0.5" # Argon2 library with support for the PHC format argon2 = "0.5.3" @@ -253,20 +256,20 @@ 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.8.18", 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", "sso", ] } -aws-credential-types = { version = "1.2.14", optional = true } -aws-smithy-runtime-api = { version = "1.13.0", optional = true } -http = { version = "1.4.2", optional = true } -reqsign-aws-v4 = { version = "3.0.1", optional = true } -reqsign-core = { version = "3.0.1", optional = true } +aws-credential-types = { version = "1.3.0", optional = true } +aws-smithy-runtime-api = { version = "1.14.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 1d765305..4c5e851b 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -1,11 +1,11 @@ --- -vault_version: "v2026.6.2" -vault_image_digest: "sha256:f004f72a5d357b87483839500a517da3d1b4ea0a57b9731989d298cccea7d02a" +vault_version: "v2026.7.0" +vault_image_digest: "sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c" # Cross Compile Docker Helper Scripts v1.9.0 # We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts # https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707" -rust_version: 1.96.1 # Rust version to be used +rust_version: 1.97.1 # Rust version to be used debian_version: trixie # Debian release name to be used alpine_version: "3.24" # Alpine version to be used # For which platforms/architectures will we try to build images diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index 7bea0d0d..7045138d 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -19,23 +19,23 @@ # - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # click the tag name to view the digest of the image it currently points to. # - From the command line: -# $ docker pull docker.io/vaultwarden/web-vault:v2026.6.2 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.6.2 -# [docker.io/vaultwarden/web-vault@sha256:f004f72a5d357b87483839500a517da3d1b4ea0a57b9731989d298cccea7d02a] +# $ 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:f004f72a5d357b87483839500a517da3d1b4ea0a57b9731989d298cccea7d02a -# [docker.io/vaultwarden/web-vault:v2026.6.2] +# $ 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:f004f72a5d357b87483839500a517da3d1b4ea0a57b9731989d298cccea7d02a AS vault +FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c AS vault ########################## ALPINE BUILD IMAGES ########################## ## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64 ## And for Alpine we define all build images here, they will only be loaded when actually used -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.96.1 AS build_amd64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.96.1 AS build_arm64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.96.1 AS build_armv7 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.96.1 AS build_armv6 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.97.1 AS build_amd64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.97.1 AS build_arm64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.97.1 AS build_armv7 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.97.1 AS build_armv6 ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 @@ -66,11 +66,11 @@ RUN USER=root cargo new --bin /app WORKDIR /app # Environment variables for Cargo on Alpine based builds -RUN echo "export CARGO_TARGET=${RUST_MUSL_CROSS_TARGET}" >> /env-cargo && \ +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 5de9fdb5..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.2 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.6.2 -# [docker.io/vaultwarden/web-vault@sha256:f004f72a5d357b87483839500a517da3d1b4ea0a57b9731989d298cccea7d02a] +# $ 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:f004f72a5d357b87483839500a517da3d1b4ea0a57b9731989d298cccea7d02a -# [docker.io/vaultwarden/web-vault:v2026.6.2] +# $ 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:f004f72a5d357b87483839500a517da3d1b4ea0a57b9731989d298cccea7d02a 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 @@ -36,7 +36,8 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 -FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.96.1-slim-trixie AS build +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,9 +96,14 @@ 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 && \ - # Workaround for xx related build issues +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 + 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 @@ -108,15 +114,20 @@ 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 - # 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 \ + 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 ; \ @@ -139,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 f7a056ff..633d6955 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 @@ -52,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 @@ -106,12 +112,12 @@ WORKDIR /app {% if base == "alpine" %} # Environment variables for Cargo on Alpine based builds -RUN echo "export CARGO_TARGET=${RUST_MUSL_CROSS_TARGET}" >> /env-cargo && \ +RUN echo "export CARGO_TARGET=${CARGO_BUILD_TARGET}" >> /env-cargo && \ # Output the current contents of the file cat /env-cargo {% endif %} -RUN source /env-cargo && \ +RUN . /env-cargo && \ rustup target add "${CARGO_TARGET}" # Copies over *only* your manifests and build files @@ -131,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" %} @@ -146,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 && \ @@ -156,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 ; \ @@ -179,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/macros/Cargo.toml b/macros/Cargo.toml index d36b3e46..f059a214 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -13,8 +13,8 @@ path = "src/lib.rs" proc-macro = true [dependencies] -quote = "1.0.46" -syn = "2.0.118" +quote = "1.0.47" +syn = "3.0.3" [lints] workspace = true 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==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "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==", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "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" + "node": ">= 0.8" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "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==", - "dev": true, - "dependencies": { - "@asamuzakjp/dom-selector": "^6.7.2", - "cssstyle": "^5.3.1", - "data-urls": "^6.0.0", + "version": "29.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.0.tgz", + "integrity": "sha512-YNUc7fB9QuvSSQWfrH0xF+TyABkxUwx8sswgIDaCrw4Hol8BghdZDkITtZheRJeMtzWlnTfsM3bBBusRvpO1wg==", + "dev": true, + "dependencies": { + "@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); + await logUser(test, page, users.user1, {mailBuffer: mailBuffer, mail2fa: true, notNewDevice: true}); + + await disableEmail(test, page, users.user1); + 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}); +test('Admin invite', async ({ page }) => { + const mailBuffer = mailserver.buffer(users.user2.email); - await disableEmail(test, page, users.user1); + 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); +test('Unlock trigger policyy', async ({ page }) => { + await page.goto('/', { waitUntil: 'domcontentloaded' }); - 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.locator("input[type=email].vw-email-sso").fill(users.user2.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 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 expect(page.getByRole('heading', { name: 'Update master password' })).toBeVisible(); + 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/rust-toolchain.toml b/rust-toolchain.toml index 6c32b3e0..9c5862a2 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.96.1" +channel = "1.97.1" components = [ "rustfmt", "clippy" ] profile = "minimal" 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/accounts.rs b/src/api/core/accounts.rs index 623edf24..0cb4d3c0 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -15,7 +15,7 @@ use crate::{ core::{accept_org_invite, log_user_event, two_factor::email}, master_password_policy, register_push_device, unregister_push_device, }, - auth::{ClientHeaders, Headers, decode_delete, decode_invite, decode_verify_email}, + auth::{ClientHeaders, ClientIp, Headers, decode_delete, decode_invite, decode_verify_email}, crypto, db::{ DbConn, DbPool, @@ -693,10 +693,6 @@ struct UnlockData { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct ChangeKdfData { - #[allow(dead_code)] - new_master_password_hash: String, - #[allow(dead_code)] - key: String, authentication_data: AuthenticationData, unlock_data: UnlockData, master_password_hash: String, @@ -1197,7 +1193,9 @@ struct DeleteRecoverData { } #[post("/accounts/delete-recover", data = "")] -async fn post_delete_recover(data: Json, conn: DbConn) -> EmptyResult { +async fn post_delete_recover(data: Json, ip: ClientIp, conn: DbConn) -> EmptyResult { + crate::ratelimit::check_limit_unauthenticated(&ip.ip)?; + let data: DeleteRecoverData = data.into_inner(); if CONFIG.mail_enabled() { @@ -1270,9 +1268,11 @@ struct PasswordHintData { } #[post("/accounts/password-hint", data = "")] -async fn password_hint(data: Json, conn: DbConn) -> EmptyResult { +async fn password_hint(data: Json, ip: ClientIp, conn: DbConn) -> EmptyResult { const NO_HINT: &str = "Sorry, you have no password hint..."; + crate::ratelimit::check_limit_unauthenticated(&ip.ip)?; + if !CONFIG.password_hints_allowed() || (!CONFIG.mail_enabled() && !CONFIG.show_password_hint()) { err!("This server is not configured to provide password hints."); } @@ -1334,6 +1334,13 @@ pub async fn prelogin(data: Json, conn: DbConn) -> Json { "kdfIterations": kdf_iter, "kdfMemory": kdf_mem, "kdfParallelism": kdf_para, + "kdfSettings": { + "iterations": kdf_iter, + "kdfType": kdf_type, + "memory": kdf_mem, + "parallelism": kdf_para + }, + "salt": null, })) } @@ -1510,7 +1517,9 @@ async fn put_device_token(device_id: DeviceId, data: Json, headers: H } #[put("/devices/identifier//clear-token")] -async fn put_clear_device_token(device_id: DeviceId, conn: DbConn) -> EmptyResult { +async fn put_clear_device_token(device_id: DeviceId, ip: ClientIp, conn: DbConn) -> EmptyResult { + crate::ratelimit::check_limit_unauthenticated(&ip.ip)?; + // This only clears push token // https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Controllers/DevicesController.cs#L215 // https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/Services/Implementations/DeviceService.cs#L37 @@ -1532,8 +1541,8 @@ async fn put_clear_device_token(device_id: DeviceId, conn: DbConn) -> EmptyResul // On upstream server, both PUT and POST are declared. Implementing the POST method in case it would be useful somewhere #[post("/devices/identifier//clear-token")] -async fn post_clear_device_token(device_id: DeviceId, conn: DbConn) -> EmptyResult { - put_clear_device_token(device_id, conn).await +async fn post_clear_device_token(device_id: DeviceId, ip: ClientIp, conn: DbConn) -> EmptyResult { + put_clear_device_token(device_id, ip, conn).await } #[get("/tasks")] diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 14e9f72f..2b51fd0c 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -450,7 +450,9 @@ pub async fn update_cipher_from_data( match Membership::find_confirmed_by_user_and_org(&headers.user.uuid, &org_id, conn).await { None => err!("You don't have permission to add item to organization"), Some(member) => { - if shared_to_collections.is_some() + // A non-empty list of collections implies the caller already validated the user's write + // access to them, so we can move the cipher into the organization on that basis. + if shared_to_collections.as_ref().is_some_and(|cols| !cols.is_empty()) || member.has_full_access() || cipher.is_write_accessible_to_user(&headers.user.uuid, conn).await { @@ -629,7 +631,7 @@ async fn post_ciphers_import(data: Json, headers: Headers, conn: DbC // Read and create the ciphers for (index, mut cipher_data) in data.ciphers.into_iter().enumerate() { - let folder_id = relations_map.get(&index).map(|i| folders[*i].clone()); + let folder_id = relations_map.get(&index).and_then(|i| folders.get(*i).cloned()); cipher_data.folder_id = folder_id; let mut cipher = Cipher::new(cipher_data.r#type, cipher_data.name.clone()); @@ -868,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 } @@ -879,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 { @@ -938,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)] @@ -1043,6 +1045,13 @@ async fn share_cipher_by_uuid( err!("Cipher doesn't exist") }; + // `update_cipher_from_data()` rejects this too, but only after the collections below were + // already linked. There are no transactions, so that would leave the cipher linked to a + // collection of another organization. + if cipher.organization_uuid.is_some() && cipher.organization_uuid != data.cipher.organization_id { + err!("Organization mismatch. Please resync the client before updating the cipher") + } + let mut shared_to_collections = vec![]; if let Some(organization_id) = &data.cipher.organization_id { diff --git a/src/api/core/events.rs b/src/api/core/events.rs index 698a890f..5518fa3c 100644 --- a/src/api/core/events.rs +++ b/src/api/core/events.rs @@ -182,7 +182,10 @@ async fn post_events_collect(data: Json>, headers: Headers, .await; } 1600..=1699 => { - if let Some(org_id) = &event.organization_id { + // Only allow logging events for an organization the user is actually a member of. + if let Some(org_id) = &event.organization_id + && Membership::find_confirmed_by_user_and_org(&headers.user.uuid, org_id, &conn).await.is_some() + { log_event_impl( event.r#type, org_id, @@ -197,8 +200,11 @@ async fn post_events_collect(data: Json>, headers: Headers, } } _ => { + // The cipher determines the organization the event is logged to, so make sure the + // user can actually access it instead of trusting the provided cipher uuid. if let Some(cipher_uuid) = &event.cipher_id && let Some(cipher) = Cipher::find_by_uuid(cipher_uuid, &conn).await + && cipher.is_accessible_to_user(&headers.user.uuid, &conn).await && let Some(org_id) = cipher.organization_uuid { log_event_impl( diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index 1be032b7..032fb403 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -246,14 +246,17 @@ fn config() -> Json { // Version history: // - Individual cipher key encryption: 2024.2.0 // - Mobile app support for MasterPasswordUnlockData: 2025.8.0 - "version": "2025.12.0", + "version": "2026.6.0", "gitHash": option_env!("GIT_REV"), "server": { "name": "Vaultwarden", "url": "https://github.com/dani-garcia/vaultwarden" }, "settings": { - "disableUserRegistration": CONFIG.is_signup_disabled() + "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": CONFIG.client_suppress_onboarding(), }, "environment": { "vault": domain, @@ -270,6 +273,10 @@ fn config() -> Json { "vapidPublicKey": null }, "featureStates": feature_states, + // Not supported right now + // Used for by clients to learn if the server requires extra work to establish a connection. + // See: https://github.com/bitwarden/server/pull/6892 | https://github.com/bitwarden/server/commit/52955d1860b4dfb905f67bbe39d9b10bbd61ded0 + "communication": null, "object": "config", })) } diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index c7e79aed..989ca47d 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -577,6 +577,13 @@ async fn post_bulk_access_collections( err!("Can't find organization details") } + // The collections and members are checked below, the groups only here. + let org_groups = Group::find_by_organization(&org_id, &conn).await; + let org_group_ids: HashSet<&GroupId> = org_groups.iter().map(|g| &g.uuid).collect(); + if let Some(g) = data.groups.iter().find(|g| !org_group_ids.contains(&g.id)) { + err!("Invalid group", format!("Group {} does not belong to organization {}!", g.id, org_id)) + } + for col_id in data.collection_ids { let Some(collection) = Collection::find_by_uuid_and_org(&col_id, &org_id, &conn).await else { err!("Collection not found") @@ -946,6 +953,11 @@ async fn get_members( if org_id != headers.membership.org_uuid { err!("Organization not found", "Organization id's do not match"); } + + if !headers.membership.has_full_access() { + err_code!("Resource not found.", "User does not have full access", rocket::http::Status::NotFound.code); + } + let mut users_json = Vec::new(); for u in Membership::find_by_org(&org_id, &conn).await { users_json.push( @@ -1167,6 +1179,9 @@ async fn send_invite( } for group_id in &data.groups { + if Group::find_by_uuid_and_org(group_id, &org_id, &conn).await.is_none() { + err!("Group not found in Organization") + } let mut group_entry = GroupUser::new(group_id.clone(), new_member.uuid.clone()); group_entry.save(&conn).await?; } @@ -1614,6 +1629,9 @@ async fn edit_member( GroupUser::delete_all_by_member(&member_to_edit.uuid, &conn).await?; for group_id in data.groups.iter().flatten() { + if Group::find_by_uuid_and_org(group_id, &org_id, &conn).await.is_none() { + err!("Group not found in Organization") + } let mut group_entry = GroupUser::new(group_id.clone(), member_to_edit.uuid.clone()); group_entry.save(&conn).await?; } @@ -1813,19 +1831,19 @@ async fn post_org_import( // TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks. Cipher::validate_cipher_data(&data.ciphers)?; - let existing_collections: HashSet> = - Collection::find_by_organization(&org_id, &conn).await.into_iter().map(|c| Some(c.uuid)).collect(); + let existing_collections: HashMap = + Collection::find_by_organization(&org_id, &conn).await.into_iter().map(|c| (c.uuid.clone(), c)).collect(); let mut collections: Vec = Vec::with_capacity(data.collections.len()); for col in data.collections { - let collection_uuid = if existing_collections.contains(&col.id) { - let col_id = col.id.unwrap(); - // When not an Owner or Admin, check if the member is allowed to access the collection. + let existing = col.id.as_ref().and_then(|col_id| existing_collections.get(col_id)); + let collection_uuid = if let Some(collection) = existing { + // When not an Owner or Admin, check if the member is allowed to write to the collection. if headers.membership.atype < MembershipType::Admin - && !Collection::can_access_collection(&headers.membership, &col_id, &conn).await + && !collection.is_writable_by_user(&headers.membership.user_uuid, &conn).await { err!(Compact, "The current user isn't allowed to manage this collection") } - col_id + collection.uuid.clone() } else { // We do not allow users or managers which can not manage all collections to create new collections // If there is any collection other than an existing import collection, abort the import. @@ -1853,6 +1871,8 @@ async fn post_org_import( for mut cipher_data in data.ciphers { // Always clear folder_id's via an organization import cipher_data.folder_id = None; + // Replace the client-provided, unvalidated organizationId with the real target org + cipher_data.organization_id = Some(org_id.clone()); let mut cipher = Cipher::new(cipher_data.r#type, cipher_data.name.clone()); update_cipher_from_data( &mut cipher, @@ -1870,8 +1890,9 @@ async fn post_org_import( // Assign the collections for (cipher_index, col_index) in relations { - let cipher_id = &ciphers[cipher_index]; - let col_id = &collections[col_index]; + let (Some(cipher_id), Some(col_id)) = (ciphers.get(cipher_index), collections.get(col_index)) else { + err!(Compact, "Invalid collection relationship") + }; CollectionCipher::save(cipher_id, col_id, &conn).await?; } @@ -2441,6 +2462,23 @@ async fn get_groups_data( if org_id != headers.membership.org_uuid { err!("Organization not found", "Organization id's do not match"); } + + // The details view (group→collection/user mappings) needs full org access; the plain list only + // needs manage access to a collection, so a manager of a collection (directly or via a group) + // can load it to assign groups. + let has_full_access = headers.membership.has_full_access() + || (CONFIG.org_groups_enabled() + && GroupUser::has_full_access_by_member(&org_id, &headers.membership.uuid, &conn).await); + let allowed = if details { + has_full_access + } else { + has_full_access + || Collection::has_manageable_collection_by_user(&org_id, &headers.membership.user_uuid, &conn).await + }; + if !allowed { + err_code!("Resource not found.", "User does not have access", rocket::http::Status::NotFound.code); + } + let groups: Vec = if CONFIG.org_groups_enabled() { let groups = Group::find_by_organization(&org_id, &conn).await; let mut groups_json = Vec::with_capacity(groups.len()); diff --git a/src/api/core/public.rs b/src/api/core/public.rs index 33189e78..3db25df9 100644 --- a/src/api/core/public.rs +++ b/src/api/core/public.rs @@ -14,7 +14,7 @@ use crate::{ db::{ DbConn, models::{ - Group, GroupUser, Invitation, Membership, MembershipStatus, MembershipType, Organization, + Group, GroupUser, Invitation, Membership, MembershipStatus, MembershipType, OrgPolicy, Organization, OrganizationApiKey, OrganizationId, User, }, }, @@ -84,8 +84,15 @@ async fn ldap_import(data: Json, token: PublicToken, conn: DbConn } // If user is part of the organization, restore it } else if let Some(mut member) = Membership::find_by_email_and_org(&user_data.email, &org_id, &conn).await { - let restored = member.restore(); + let mut restored = member.restore(); let ext_modified = member.set_external_id(Some(user_data.external_id.clone())); + // Enforce org policies as every other restore path does. + // If the user is not allowed, we revoke again and continue so the external_id is still updated. + if restored && let Err(e) = OrgPolicy::check_user_allowed(&member, "restore", &conn).await { + warn!("Not restoring {}: {e:?}", user_data.email); + member.revoke(); + restored = false; + } if restored || ext_modified { member.save(&conn).await?; } diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs index fb3ee48f..042ce95b 100644 --- a/src/api/core/sends.rs +++ b/src/api/core/sends.rs @@ -453,6 +453,9 @@ async fn post_access(headers: SendHeaders, conn: DbConn, nt: Notify<'_>) -> Json let Some(send) = Send::find_by_uuid(&headers.send_id, &conn).await else { err_code!(SEND_INACCESSIBLE_MSG, 404) }; + if !send.is_accessible() { + err_code!(SEND_INACCESSIBLE_MSG, 404) + } process_access(send, conn, nt).await } @@ -471,6 +474,8 @@ async fn post_access_legacy( ip: ClientIp, nt: Notify<'_>, ) -> JsonResult { + crate::ratelimit::check_limit_unauthenticated(&ip.ip)?; + let Some(mut send) = Send::find_by_access_id(access_id, &conn).await else { err_code!(SEND_INACCESSIBLE_MSG, 404) }; @@ -481,17 +486,7 @@ async fn post_access_legacy( err_code!(SEND_INACCESSIBLE_MSG, 404); } - if let Some(expiration) = send.expiration_date - && Utc::now().naive_utc() >= expiration - { - err_code!(SEND_INACCESSIBLE_MSG, 404) - } - - if Utc::now().naive_utc() >= send.deletion_date { - err_code!(SEND_INACCESSIBLE_MSG, 404) - } - - if send.disabled { + if !send.is_accessible() { err_code!(SEND_INACCESSIBLE_MSG, 404) } @@ -505,11 +500,13 @@ async fn post_access_legacy( // Files are incremented during the download if send.atype == SendType::Text as i32 { - send.access_count += 1; + if !send.register_access(&conn).await? { + err_code!(SEND_INACCESSIBLE_MSG, 404) + } + } else { + send.save(&conn).await?; } - send.save(&conn).await?; - process_access(send, conn, nt).await } @@ -537,6 +534,9 @@ async fn post_access_file( let Some(send) = Send::find_by_uuid(&headers.send_id, &conn).await else { err_code!(SEND_INACCESSIBLE_MSG, 404) }; + if !send.is_accessible() { + err_code!(SEND_INACCESSIBLE_MSG, 404) + } process_access_file(send, file_id, host, conn, nt).await } @@ -548,8 +548,11 @@ async fn post_access_file_legacy( data: Json, host: Host, conn: DbConn, + ip: ClientIp, nt: Notify<'_>, ) -> JsonResult { + crate::ratelimit::check_limit_unauthenticated(&ip.ip)?; + let Some(mut send) = Send::find_by_uuid(&send_id, &conn).await else { err_code!(SEND_INACCESSIBLE_MSG, 404) }; @@ -560,17 +563,7 @@ async fn post_access_file_legacy( err_code!(SEND_INACCESSIBLE_MSG, 404) } - if let Some(expiration) = send.expiration_date - && Utc::now().naive_utc() >= expiration - { - err_code!(SEND_INACCESSIBLE_MSG, 404) - } - - if Utc::now().naive_utc() >= send.deletion_date { - err_code!(SEND_INACCESSIBLE_MSG, 404) - } - - if send.disabled { + if !send.is_accessible() { err_code!(SEND_INACCESSIBLE_MSG, 404) } @@ -582,9 +575,9 @@ async fn post_access_file_legacy( } } - send.access_count += 1; - - send.save(&conn).await?; + if !send.register_access(&conn).await? { + err_code!(SEND_INACCESSIBLE_MSG, 404) + } process_access_file(send, file_id, host, conn, nt).await } 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/icons.rs b/src/api/icons.rs index 81191e38..8f3e730b 100644 --- a/src/api/icons.rs +++ b/src/api/icons.rs @@ -405,6 +405,22 @@ async fn get_page(url: &str) -> Result { } async fn get_page_with_referer(url: &str, referer: &str) -> Result { + // The resolver only sees hosts needing name resolution, so IP-literal hrefs from + // attacker-controlled HTML never reach `post_resolve()`. Check them here. + let Ok(parsed_url) = url::Url::parse(url) else { + err_silent!("Invalid URL", url) + }; + + if !matches!(parsed_url.scheme(), "http" | "https") { + err_silent!("Invalid scheme", url) + } + + let Some(host) = parsed_url.host() else { + err_silent!("Invalid host", url) + }; + + should_block_host(&host)?; + let mut client = CLIENT.get(url); if !referer.is_empty() { client = client.header("Referer", referer); diff --git a/src/api/identity.rs b/src/api/identity.rs index 1597698f..23411dc7 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -109,6 +109,7 @@ async fn login( } "authorization_code" => err!("SSO sign-in is not available"), "send_access" => { + crate::ratelimit::check_limit_unauthenticated(&client_header.ip.ip)?; check_is_some(data.client_id.as_ref(), "client_id cannot be blank")?; check_is_some(data.send_id.as_ref(), "send_id cannot be blank")?; @@ -317,7 +318,7 @@ async fn sso_login( Some((user, _)) if !user.enabled => { err!( "This user has been disabled", - format!("IP: {}. Username: {}.", ip.ip, user.display_name()), + format!("IP: {}. Username: {}.", ip.ip, user.email), ErrorEvent { event: EventType::UserFailedLogIn } @@ -576,7 +577,7 @@ async fn authenticated_response( result["TwoFactorToken"] = Value::String(token); } - info!("User {} logged in successfully. IP: {}", user.display_name(), ip.ip); + info!("User {} logged in successfully. IP: {}", user.email, ip.ip); Ok(Json(result)) } @@ -1055,8 +1056,11 @@ enum RegisterVerificationResponse { #[post("/accounts/register/send-verification-email", data = "")] async fn register_verification_email( data: Json, + ip: ClientIp, conn: DbConn, ) -> ApiResult { + crate::ratelimit::check_limit_unauthenticated(&ip.ip)?; + let data = data.into_inner(); // the registration can only continue if signup is allowed or there exists an invitation 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/notifications.rs b/src/api/notifications.rs index 80067433..8bfcd518 100644 --- a/src/api/notifications.rs +++ b/src/api/notifications.rs @@ -33,9 +33,14 @@ pub static WS_USERS: LazyLock> = LazyLock::new(|| { pub static WS_ANONYMOUS_SUBSCRIPTIONS: LazyLock> = LazyLock::new(|| { Arc::new(AnonymousWebSocketSubscriptions { map: Arc::new(dashmap::DashMap::new()), + connections: Arc::new(dashmap::DashMap::new()), }) }); +/// The anonymous hub needs no authentication, so bound how much a single client can hold open. +/// One connection is needed per pending login request, several at once are only expected behind NAT. +const MAX_ANONYMOUS_CONNECTIONS_PER_IP: u32 = 25; + static NOTIFICATIONS_DISABLED: LazyLock = LazyLock::new(|| !CONFIG.enable_websocket() && !CONFIG.push_enabled()); pub fn routes() -> Vec { @@ -82,14 +87,21 @@ impl Drop for WSEntryMapGuard { struct WSAnonymousEntryMapGuard { subscriptions: Arc, token: String, + entry_uuid: uuid::Uuid, addr: IpAddr, } impl WSAnonymousEntryMapGuard { - fn new(subscriptions: Arc, token: String, addr: IpAddr) -> Self { + fn new( + subscriptions: Arc, + token: String, + entry_uuid: uuid::Uuid, + addr: IpAddr, + ) -> Self { Self { subscriptions, token, + entry_uuid, addr, } } @@ -98,7 +110,11 @@ impl WSAnonymousEntryMapGuard { impl Drop for WSAnonymousEntryMapGuard { fn drop(&mut self) { info!("Closing WS connection from {}", self.addr); - self.subscriptions.map.remove(&self.token); + if let Some(mut entry) = self.subscriptions.map.get_mut(&self.token) { + entry.retain(|(uuid, _)| uuid != &self.entry_uuid); + } + self.subscriptions.map.remove_if(&self.token, |_, senders| senders.is_empty()); + self.subscriptions.release(self.addr); } } @@ -194,12 +210,19 @@ fn anonymous_websockets_hub<'r>(ws: WebSocket, token: String, ip: ClientIp) -> R let (mut rx, guard) = { let subscriptions = Arc::clone(&WS_ANONYMOUS_SUBSCRIPTIONS); - // Add a channel to send messages to this client to the map + if !subscriptions.try_reserve(ip.ip) { + err_code!("Too many connections", 429) + } + + // Add a channel to send messages to this client to the map. + // Clients reconnect with the same token while a login request is still pending, so keep + // every subscriber instead of replacing, otherwise the older one takes the newer one down. let (tx, rx) = tokio::sync::mpsc::channel::(100); - subscriptions.map.insert(token.clone(), tx); + let entry_uuid = uuid::Uuid::new_v4(); + subscriptions.map.entry(token.clone()).or_default().push((entry_uuid, tx)); // Once the guard goes out of scope, the connection will have been closed and the entry will be deleted from the map - (rx, WSAnonymousEntryMapGuard::new(subscriptions, token, ip.ip)) + (rx, WSAnonymousEntryMapGuard::new(subscriptions, token, entry_uuid, ip.ip)) }; Ok({ @@ -534,15 +557,42 @@ impl WebSocketUsers { #[derive(Clone)] pub struct AnonymousWebSocketSubscriptions { - map: Arc>>, + map: Arc>>, + connections: Arc>, } impl AnonymousWebSocketSubscriptions { + /// Takes a connection slot for this address, returns false when it already reached the limit. + fn try_reserve(&self, addr: IpAddr) -> bool { + let mut count = self.connections.entry(addr).or_insert(0); + if *count >= MAX_ANONYMOUS_CONNECTIONS_PER_IP { + return false; + } + *count += 1; + true + } + + /// Releases a slot taken by `try_reserve`. + fn release(&self, addr: IpAddr) { + let empty = if let Some(mut count) = self.connections.get_mut(&addr) { + *count = count.saturating_sub(1); + *count == 0 + } else { + false + }; + // Only remove once the guard above is dropped, otherwise this deadlocks. + if empty { + self.connections.remove_if(&addr, |_, count| *count == 0); + } + } + async fn send_update(&self, token: &str, data: &[u8]) { - if let Some(sender) = self.map.get(token).map(|v| v.clone()) - && let Err(e) = sender.send(Message::binary(data)).await - { - error!("Error sending WS update {e}"); + // Clone the senders so the map isn't kept locked while sending. + let senders = self.map.get(token).map(|v| v.clone()).unwrap_or_default(); + for (_, sender) in senders { + if let Err(e) = sender.send(Message::binary(data)).await { + error!("Error sending WS update {e}"); + } } } 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/auth.rs b/src/auth.rs index 88a59b4b..762088e5 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -10,6 +10,7 @@ use std::{ }; use chrono::{DateTime, TimeDelta, Utc}; +use ipnet::IpNet; use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, errors::ErrorKind}; use num_traits::FromPrimitive; use openssl::rsa::Rsa; @@ -1054,12 +1055,44 @@ pub struct ClientIp { pub ip: IpAddr, } +/// Parses a single entry of `ip_header_trusted_proxies`, which can be a CIDR range or a plain IP. +pub fn parse_trusted_proxy(entry: &str) -> Option { + let entry = entry.trim(); + match entry.parse::() { + Ok(net) => Some(net), + // Without a prefix length it is a single address, which is a valid way to write this. + Err(_) => entry.parse::().ok().map(IpNet::from), + } +} + +/// The client IP header can be set by anyone able to reach us, so only accept it from a proxy we trust. +fn ip_header_is_trusted(remote: Option) -> bool { + let trusted = CONFIG.ip_header_trusted_proxies(); + let trusted = trusted.trim(); + if trusted.eq_ignore_ascii_case("all") { + return true; + } + + let Some(remote) = remote else { + return false; + }; + // A dual stack listener reports IPv4 clients as IPv4-mapped IPv6, which `is_global()` reports as + // non global. That is what we want when blocking outgoing requests, but here it would trust them. + let remote = remote.to_canonical(); + if trusted.eq_ignore_ascii_case("local") { + return !crate::util::is_global(remote); + } + trusted.split(',').filter_map(parse_trusted_proxy).any(|net| net.contains(&remote)) +} + #[rocket::async_trait] impl<'r> FromRequest<'r> for ClientIp { type Error = (); async fn from_request(req: &'r Request<'_>) -> Outcome { - let ip = if CONFIG._ip_header_enabled() { + let remote = req.remote().map(|r| r.ip()); + + let ip = if CONFIG._ip_header_enabled() && ip_header_is_trusted(remote) { req.headers().get_one(&CONFIG.ip_header()).and_then(|ip| { match ip.find(',') { Some(idx) => &ip[..idx], @@ -1070,10 +1103,15 @@ impl<'r> FromRequest<'r> for ClientIp { .ok() }) } else { + if CONFIG._ip_header_enabled() && req.headers().get_one(&CONFIG.ip_header()).is_some() { + // Log the canonical IP, which is what the user filter will need to match against + let remote = remote.map(|ip| ip.to_canonical()); + debug!("Ignoring the '{}' header, {remote:?} is not a trusted proxy", CONFIG.ip_header()); + } None }; - let ip = ip.or_else(|| req.remote().map(|r| r.ip())).unwrap_or_else(|| "0.0.0.0".parse().unwrap()); + let ip = ip.or(remote).unwrap_or_else(|| "0.0.0.0".parse().unwrap()); Outcome::Success(ClientIp { ip, @@ -1268,20 +1306,8 @@ pub async fn refresh_tokens( ) -> ApiResult<(Device, AuthTokens)> { let refresh_claims = match decode_refresh(refresh_token) { Err(err) => { - error!("Failed to decode {} refresh_token: {refresh_token}: {err:?}", ip.ip); - //err_silent!(format!("Impossible to read refresh_token: {}", err.message())) - - // If the token failed to decode, it was probably one of the old style tokens that was just a Base64 string. - // We can generate a claim for them for backwards compatibility. Note that the password refresh claims don't - // check expiration or issuer, so they're not included here. - RefreshJwtClaims { - nbf: 0, - exp: 0, - iss: String::new(), - sub: AuthMethod::Password, - device_token: refresh_token.into(), - token: None, - } + error!("Failed to decode refresh_token from {}: {err:?}", ip.ip); + err_silent!("Invalid refresh token") } Ok(claims) => claims, }; diff --git a/src/auth/send.rs b/src/auth/send.rs index 84500b6a..2554488a 100644 --- a/src/auth/send.rs +++ b/src/auth/send.rs @@ -85,18 +85,8 @@ impl SendTokens { return Self::invalid_error(&format!("Send {send_id}, max access reached"), "send_id_invalid", true); } - if let Some(expiration) = send.expiration_date - && Utc::now().naive_utc() >= expiration - { - return Self::invalid_error(&format!("Send {send_id}, expired"), "send_id_invalid", true); - } - - if Utc::now().naive_utc() >= send.deletion_date { - return Self::invalid_error(&format!("Send {send_id}, past deletion"), "send_id_invalid", true); - } - - if send.disabled { - return Self::invalid_error(&format!("Send {send_id}, disabled"), "send_id_invalid", true); + if !send.is_accessible() { + return Self::invalid_error(&format!("Send {send_id}, not accessible"), "send_id_invalid", true); } if send.password_hash.is_some() { @@ -113,8 +103,9 @@ impl SendTokens { } } - send.access_count += 1; - send.save(conn).await?; + if !send.register_access(conn).await? { + return Self::invalid_error(&format!("Send {send_id}, max access reached"), "send_id_invalid", true); + } Ok(Self { access_claims: generate_send_access_claims(&send_id), diff --git a/src/config.rs b/src/config.rs index f09643de..6f0848e0 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. @@ -666,6 +671,12 @@ make_config! { ip_header: String, true, def, "X-Real-IP".to_owned(); /// Internal IP header property, used to avoid recomputing each time _ip_header_enabled: bool, false, generated, |c| &c.ip_header.trim().to_lowercase() != "none"; + /// Trusted proxies |> Which addresses the client IP header is accepted from. Requests from any + /// other address use the remote IP instead, so a client can't spoof the header. + /// Either the string "local" (the default, any non-global address, which covers a reverse proxy + /// running on the same host or container network), the string "all" to accept it from anywhere, + /// or a comma separated list of IPs and CIDR ranges. + ip_header_trusted_proxies: String, true, def, "local".to_owned(); /// Icon service |> The predefined icon services are: internal, bitwarden, duckduckgo, google. /// To specify a custom icon service, set a URL template with exactly one instance of `{}`, /// which is replaced with the domain. For example: `https://icon.example.com/domain/{}`. @@ -768,6 +779,11 @@ make_config! { /// Max burst size for login requests |> Allow a burst of requests of up to this size, while maintaining the average indicated by `login_ratelimit_seconds`. Note that this applies to both the login and the 2FA, so it's recommended to allow a burst size of at least 2 login_ratelimit_max_burst: u32, false, def, 10; + /// Seconds between unauthenticated requests |> Number of seconds, on average, between requests from the same IP address to any of the rate limited unauthenticated endpoints + unauthenticated_ratelimit_seconds: u64, false, def, 60; + /// Max burst size for unauthenticated requests |> Allow a burst of requests of up to this size, while maintaining the average indicated by `unauthenticated_ratelimit_seconds`. This is shared between several endpoints, so it needs to be more lenient than the login one + unauthenticated_ratelimit_max_burst: u32, false, def, 50; + /// Seconds between admin login requests |> Number of seconds, on average, between admin requests from the same IP address before rate limiting kicks in admin_ratelimit_seconds: u64, false, def, 300; /// Max burst size for admin login requests |> Allow a burst of requests of up to this size, while maintaining the average indicated by `admin_ratelimit_seconds` @@ -954,6 +970,18 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> { } } + let trusted_proxies = cfg.ip_header_trusted_proxies.trim(); + if !trusted_proxies.eq_ignore_ascii_case("all") && !trusted_proxies.eq_ignore_ascii_case("local") { + for entry in trusted_proxies.split(',').filter(|e| !e.trim().is_empty()) { + if crate::auth::parse_trusted_proxy(entry).is_none() { + err!(format!( + "Invalid IP_HEADER_TRUSTED_PROXIES entry `{}`, expected an IP or CIDR range", + entry.trim() + )); + } + } + } + if cfg.password_iterations < 100_000 { err!("PASSWORD_ITERATIONS should be at least 100000 or higher. The default is 600000!"); } @@ -1500,6 +1528,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(()) } @@ -1582,6 +1613,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/cipher.rs b/src/db/models/cipher.rs index 8357c9eb..eed5041d 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -42,13 +42,15 @@ pub struct Cipher { pub key: Option, - /* - Login = 1, - SecureNote = 2, - Card = 3, - Identity = 4, - SshKey = 5 - */ + // See (v2026.7.0): https://github.com/bitwarden/server/blob/5d4461aa42cadbacfef8fe2166c5453a5c52773a/src/Core/Vault/Enums/CipherType.cs + // Login = 1, + // SecureNote = 2, + // Card = 3, + // Identity = 4, + // SSHKey = 5 + // BankAccount = 6, + // DriversLicense = 7, + // Passport = 8, pub atype: i32, pub name: String, pub notes: Option, @@ -306,16 +308,6 @@ impl Cipher { type_data_json = Value::Null; } - // Clone the type_data and add some default value. - let mut data_json = type_data_json.clone(); - - // NOTE: This was marked as *Backwards Compatibility Code*, but as of January 2021 this is still being used by upstream - // data_json should always contain the following keys with every atype - data_json["fields"] = json!(fields_json); - data_json["name"] = json!(self.name); - data_json["notes"] = json!(self.notes); - data_json["passwordHistory"] = Value::Array(password_history_json.clone()); - let collection_ids = if let Some(cipher_sync_data) = cipher_sync_data { if let Some(cipher_collections) = cipher_sync_data.cipher_collections.get(&self.uuid) { Cow::from(cipher_collections) @@ -355,8 +347,6 @@ impl Cipher { "notes": self.notes, "fields": fields_json, - "data": data_json, - "passwordHistory": password_history_json, // All Cipher types are included by default as null, but only the matching one will be populated @@ -365,6 +355,9 @@ impl Cipher { "card": null, "identity": null, "sshKey": null, + "bankAccount": null, + "driversLicense": null, + "passport": null, }); // These values are only needed for user/default syncs @@ -404,6 +397,9 @@ impl Cipher { 3 => "card", 4 => "identity", 5 => "sshKey", + 6 => "bankAccount", + 7 => "driversLicense", + 8 => "passport", _ => err!(format!("Cipher {} has an invalid type {}", self.uuid, self.atype)), }; diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs index f29843f7..8aec90ea 100644 --- a/src/db/models/collection.rs +++ b/src/db/models/collection.rs @@ -21,6 +21,7 @@ use super::{ User, UserId, }; +// See (v2026.7.0): https://github.com/bitwarden/server/blob/5d4461aa42cadbacfef8fe2166c5453a5c52773a/src/Core/AdminConsole/Entities/Collection.cs #[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[diesel(table_name = collections)] #[diesel(treat_none_as_null = true)] @@ -71,6 +72,11 @@ impl Collection { "id": self.uuid, "organizationId": self.org_uuid, "name": self.name, + // Collection types are either 0: SharedCollection or 1: DefaultUserCollection, of which we do not yet support DefaultUserCollection. + // See (v2026.7.0): https://github.com/bitwarden/server/blob/5d4461aa42cadbacfef8fe2166c5453a5c52773a/src/Core/AdminConsole/Enums/CollectionType.cs + "type": 0, + // This is only used together with MyItems/DefaultUserCollection, which we do not yet support. + "defaultUserCollectionEmail": null, "object": "collection", }) } @@ -623,6 +629,47 @@ impl Collection { pub async fn is_manageable_by_user(&self, user_uuid: &UserId, conn: &DbConn) -> bool { Self::is_coll_manageable_by_user(&self.uuid, user_uuid, conn).await } + + // Whether the user has manage access to at least one collection in the org, directly or via a + // group. Org-scoped counterpart of is_coll_manageable_by_user. + pub async fn has_manageable_collection_by_user( + org_uuid: &OrganizationId, + user_uuid: &UserId, + conn: &DbConn, + ) -> bool { + let org_uuid = org_uuid.to_string(); + let user_uuid = user_uuid.to_string(); + conn.run(move |conn| { + collections::table + .left_join( + users_collections::table.on(users_collections::collection_uuid + .eq(collections::uuid) + .and(users_collections::user_uuid.eq(user_uuid.clone()))), + ) + .left_join( + users_organizations::table.on(collections::org_uuid + .eq(users_organizations::org_uuid) + .and(users_organizations::user_uuid.eq(user_uuid))), + ) + .left_join(groups_users::table.on(groups_users::users_organizations_uuid.eq(users_organizations::uuid))) + .left_join( + collections_groups::table.on(collections_groups::groups_uuid + .eq(groups_users::groups_uuid) + .and(collections_groups::collections_uuid.eq(collections::uuid))), + ) + .filter(collections::org_uuid.eq(&org_uuid)) + .filter( + // Manage permission on a collection assigned directly or via a group. + users_collections::manage.eq(true).or(collections_groups::manage.eq(true)), + ) + .count() + .first::(conn) + .ok() + .unwrap_or(0) + != 0 + }) + .await + } } /// Database methods 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/db/models/group.rs b/src/db/models/group.rs index 820d3700..37037de6 100644 --- a/src/db/models/group.rs +++ b/src/db/models/group.rs @@ -271,7 +271,9 @@ impl Group { groups::table .inner_join(groups_users::table.on(groups_users::groups_uuid.eq(groups::uuid))) .inner_join( - users_organizations::table.on(users_organizations::uuid.eq(groups_users::users_organizations_uuid)), + users_organizations::table.on(users_organizations::uuid + .eq(groups_users::users_organizations_uuid) + .and(users_organizations::org_uuid.eq(groups::organizations_uuid))), ) .filter(users_organizations::user_uuid.eq(user_uuid)) .filter(groups::organizations_uuid.eq(org_uuid)) diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 72b1df0b..bdb69864 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -217,11 +217,18 @@ impl Organization { "useSecretsManager": false, // Not supported (Not AGPLv3 Licensed) "selfHost": true, "useApi": true, + "useDisableSMAdsForUsers": true, // Hide Secrets Manager ads + "useInviteLinks": false, // Not (yet) supported + "useMyItems": false, // Not (yet) supported + "useOrganizationDomains": false, // Not supported (Linked to SSO) + "usePam": false, // Not supported + "usePhishingBlocker": false, "hasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(), "useResetPassword": CONFIG.mail_enabled(), "allowAdminAccessToAllCollectionItems": true, "limitCollectionCreation": true, "limitCollectionDeletion": true, + "limitItemDeletion": false, "businessName": self.name, "businessAddress1": null, @@ -495,6 +502,12 @@ impl Membership { "useActivateAutofillPolicy": false, "useAdminSponsoredFamilies": false, "useRiskInsights": false, // Not supported (Not AGPLv3 Licensed) + "useDisableSMAdsForUsers": true, // Hide Secrets Manager ads + "useInviteLinks": false, // Not (yet) supported + "useMyItems": false, // Not (yet) supported + "useOrganizationDomains": false, // Not supported (Linked to SSO) + "usePam": false, // Not supported + "usePhishingBlocker": false, "organizationUserId": self.uuid, "providerId": null, diff --git a/src/db/models/send.rs b/src/db/models/send.rs index 48159e8a..c5bc98c4 100644 --- a/src/db/models/send.rs +++ b/src/db/models/send.rs @@ -231,6 +231,53 @@ impl Send { } } + /// Registers an access, incrementing `access_count` only while below `max_access_count`. + /// Returns false when the limit was already reached. The check and the increment are a single + /// statement, otherwise concurrent accesses can both pass the check and exceed the limit. + pub async fn register_access(&mut self, conn: &DbConn) -> Result { + self.update_users_revision(conn).await; + + let revision_date = Utc::now().naive_utc(); + let uuid = self.uuid.clone(); + let updated = conn + .run(move |conn| { + diesel::update(sends::table) + .filter(sends::uuid.eq(uuid)) + .filter( + sends::max_access_count + .is_null() + .or(sends::access_count.nullable().lt(sends::max_access_count)), + ) + .set((sends::access_count.eq(sends::access_count + 1), sends::revision_date.eq(revision_date))) + .execute(conn) + }) + .await?; + + if updated == 0 { + return Ok(false); + } + + self.access_count += 1; + self.revision_date = revision_date; + Ok(true) + } + + /// Whether the Send is currently within its validity window: not disabled, not past its + /// expiration date, and not past its deletion date. Does not consider `max_access_count` + /// (consumed at token issuance) or the password. + pub fn is_accessible(&self) -> bool { + let now = Utc::now().naive_utc(); + if self.disabled { + return false; + } + if let Some(expiration) = self.expiration_date + && now >= expiration + { + return false; + } + now < self.deletion_date + } + pub async fn delete(&self, conn: &DbConn) -> EmptyResult { self.update_users_revision(conn).await; diff --git a/src/db/models/user.rs b/src/db/models/user.rs index 24bee751..93d750d5 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -268,8 +268,25 @@ impl User { UserStatus::Enabled }; + let account_keys = if self.private_key.is_some() { + json!({ + "publicKeyEncryptionKeyPair": { + "wrappedPrivateKey": self.private_key, + "publicKey": self.public_key, + "signedPublicKey": null, + "object": "publicKeyEncryptionKeyPair", + }, + "securityState": null, + "signatureKeyPair": null, + "object": "privateKeys" + }) + } else { + Value::Null + }; + json!({ "_status": status as i32, + "accountKeys": account_keys, "id": self.uuid, "name": self.name, "email": self.email, 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/http_client.rs b/src/http_client.rs index 205b1cc3..0831d990 100644 --- a/src/http_client.rs +++ b/src/http_client.rs @@ -174,6 +174,27 @@ pub enum CustomHttpClientError { } impl CustomHttpClientError { + /// Attach the domain that resolved to this address, which `should_block_host()` can't know. + fn with_domain(self, name: &str) -> Self { + match self { + Self::NonGlobalIp { + ip, + .. + } => Self::NonGlobalIp { + domain: Some(name.to_owned()), + ip, + }, + Self::Blocked { + domain, + } => Self::Blocked { + domain: format!("{name} ({domain})"), + }, + other @ Self::Invalid { + .. + } => other, + } + } + pub fn downcast_ref(e: &dyn std::error::Error) -> Option<&Self> { let mut source = e.source(); @@ -285,14 +306,12 @@ fn pre_resolve(name: &str, enforce_block: bool) -> Result<(), CustomHttpClientEr } fn post_resolve(name: &str, ip: IpAddr) -> Result<(), CustomHttpClientError> { - if should_block_ip(ip) { - Err(CustomHttpClientError::NonGlobalIp { - domain: Some(name.to_owned()), - ip, - }) - } else { - Ok(()) - } + let host: Host<&str> = match ip { + IpAddr::V4(ip) => Host::Ipv4(ip), + IpAddr::V6(ip) => Host::Ipv6(ip), + }; + + should_block_host(&host).map_err(|e| e.with_domain(name)) } impl Resolve for CustomDns { diff --git a/src/mail.rs b/src/mail.rs index f31234d7..a7e5e5ae 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -307,9 +307,18 @@ pub async fn send_invite( if CONFIG.sso_enabled() && CONFIG.sso_only() { query_params.append_pair("orgSsoIdentifier", &org_id); } - if user.private_key.is_some() { - query_params.append_pair("orgUserHasExistingUser", "true"); - } + + // The web vault requires both of these parameters to be present. + // If either is missing it rejects the invite client-side, before any + // request reaches the server, showing only "Unable to accept invitation". + query_params.append_pair("initOrganization", "false"); + + let org_user_has_existing_user = if user.private_key.is_some() { + "true" + } else { + "false" + }; + query_params.append_pair("orgUserHasExistingUser", org_user_has_existing_user); } let Some(query_string) = query.query() else { diff --git a/src/main.rs b/src/main.rs index 15467ea4..28645694 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ // The recursion_limit is mainly triggered by the json!() macro. // The more key/value pairs there are the more recursion occurs. // We want to keep this as low as possible! -#![recursion_limit = "165"] +#![recursion_limit = "192"] // When enabled use MiMalloc as malloc instead of the default malloc #[cfg(feature = "enable_mimalloc")] diff --git a/src/ratelimit.rs b/src/ratelimit.rs index 2b422924..70217957 100644 --- a/src/ratelimit.rs +++ b/src/ratelimit.rs @@ -18,6 +18,24 @@ static LIMITER_ADMIN: LazyLock = LazyLock::new(|| { RateLimiter::keyed(Quota::with_period(seconds).expect("Non-zero admin ratelimit seconds").allow_burst(burst)) }); +static LIMITER_UNAUTHENTICATED: LazyLock = LazyLock::new(|| { + let seconds = Duration::from_secs(CONFIG.unauthenticated_ratelimit_seconds()); + let burst = NonZeroU32::new(CONFIG.unauthenticated_ratelimit_max_burst()) + .expect("Non-zero unauthenticated ratelimit burst"); + RateLimiter::keyed( + Quota::with_period(seconds).expect("Non-zero unauthenticated ratelimit seconds").allow_burst(burst), + ) +}); + +pub fn check_limit_unauthenticated(ip: &IpAddr) -> Result<(), Error> { + match LIMITER_UNAUTHENTICATED.check_key(ip) { + Ok(()) => Ok(()), + Err(_e) => { + err_code!("Too many requests", 429); + } + } +} + pub fn check_limit_login(ip: &IpAddr) -> Result<(), Error> { match LIMITER_LOGIN.check_key(ip) { Ok(()) => Ok(()), diff --git a/src/sso_client.rs b/src/sso_client.rs index ff39b0b0..bc766586 100644 --- a/src/sso_client.rs +++ b/src/sso_client.rs @@ -95,7 +95,9 @@ impl<'c> AsyncHttpClient<'c> for OidcHttpClient { } let body = response.bytes().await.map_err(Box::new)?; - debug!("Response body {}", String::from_utf8_lossy(&body)); + if CONFIG.sso_debug_tokens() { + debug!("Response body {}", String::from_utf8_lossy(&body)); + } builder.body(body.to_vec()).map_err(HttpClientError::Http) }) } 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/static/templates/scss/vaultwarden.scss.hbs b/src/static/templates/scss/vaultwarden.scss.hbs index 477cdd34..5bbe5db2 100644 --- a/src/static/templates/scss/vaultwarden.scss.hbs +++ b/src/static/templates/scss/vaultwarden.scss.hbs @@ -116,8 +116,8 @@ app-security > app-two-factor-setup > form { } /* Hide unsupported Custom Role options */ -bit-dialog div.tw-ml-4:has(bit-form-control input), -bit-dialog div.tw-col-span-4:has(input[formcontrolname*="access"], input[formcontrolname*="manage"]) { +:is(bit-dialog, [bit-dialog]) div.tw-ml-4:has(bit-form-control input), +:is(bit-dialog, [bit-dialog]) div.tw-col-span-4:has(input[formcontrolname*="access"], input[formcontrolname*="manage"]) { @extend %vw-hide; } 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"];