diff --git a/.dockerignore b/.dockerignore index d6ac6b9b..a9a358a3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,7 @@ -# Ignore everything +// Ignore everything * -# Allow what is needed +// Allow what is needed !.git !docker/healthcheck.sh !docker/start.sh diff --git a/.env.template b/.env.template index d22145b8..fd7c2fd2 100644 --- a/.env.template +++ b/.env.template @@ -316,14 +316,6 @@ ## 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 ### ######################### @@ -390,7 +382,6 @@ ## ## The following flags are available: ## - "pm-5594-safari-account-switching": Enable account switching in Safari. (Safari >= 2026.2.0) -## - "pm-32413-multi-client-password-management": Enable changing the master password directly in the client. (Desktop/Extension >= 2026.4.0) ## - "ssh-agent": Enable SSH agent support on Desktop. (Desktop >= 2024.12.0) ## - "ssh-agent-v2": Enable newer SSH agent support. (Desktop >= 2026.2.1) ## - "ssh-key-vault-item": Enable the creation and use of SSH key vault items. (Clients >= 2024.12.0) @@ -519,9 +510,6 @@ ## Prevent users from logging in directly without going through SSO # SSO_ONLY=false -## Allow SSO flow to create account. You probably want to disable it when using a public provider. -# SSO_SIGNUPS_ALLOWED=true - ## On SSO Signup if a user with a matching email already exists make the association # SSO_SIGNUPS_MATCH_EMAIL=true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f52bd8e4..31a04012 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -113,7 +113,7 @@ jobs: # Enable Rust Caching - name: Rust Caching - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 with: # Use a custom prefix-key to force a fresh start. This is sometimes needed with bigger changes. # Like changing the build host from Ubuntu 20.04 to 22.04 for example. diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml index a429d10f..17151922 100644 --- a/.github/workflows/hadolint.yml +++ b/.github/workflows/hadolint.yml @@ -20,7 +20,7 @@ jobs: steps: # Start Docker Buildx - name: Setup Docker Buildx - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 # https://github.com/moby/buildkit/issues/3969 # Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills with: @@ -41,12 +41,12 @@ jobs: # Uses the Docker-based action (hadolint pre-bundled in ghcr.io/hadolint/hadolint:v2.14.0-debian) # so no binary is downloaded at runtime. Pinned by commit SHA for supply-chain safety. - name: Run hadolint on Dockerfile.debian - uses: hadolint/hadolint-action@06be81baf89a55ffd0e24b8f04a4185738dd3387 # v3.5.0 + uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 with: dockerfile: docker/Dockerfile.debian - name: Run hadolint on Dockerfile.alpine - uses: hadolint/hadolint-action@06be81baf89a55ffd0e24b8f04a4185738dd3387 # v3.5.0 + uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 with: dockerfile: docker/Dockerfile.alpine # End Test Dockerfiles with hadolint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 311891b7..0efd21db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,13 +58,13 @@ jobs: steps: - name: Initialize QEMU binfmt support - uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0 + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 with: platforms: "arm64,arm" # Start Docker Buildx - name: Setup Docker Buildx - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 # https://github.com/moby/buildkit/issues/3969 # Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills with: @@ -106,7 +106,7 @@ jobs: # Login to Docker Hub - name: Login to Docker Hub - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 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@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 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@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 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@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 + uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0 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@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 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@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 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@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 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@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 + uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0 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@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 + uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0 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@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 + uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0 with: subject-name: ${{ vars.QUAY_REPO }} subject-digest: ${{ env.DIGEST_SHA }} diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 7f41885d..c1f42c56 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -50,6 +50,6 @@ jobs: severity: CRITICAL,HIGH - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 + uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3 with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 00fcbab4..7c345e0a 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -23,4 +23,4 @@ jobs: # When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too - name: Spell Check Repo - uses: crate-ci/typos@d43b6c087ac471e2ea7b8af622ff15f05c0c365b # v1.50.1 + uses: crate-ci/typos@bee27e3a4fd1ea2111cf90ab89cd076c870fce14 # v1.48.0 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 31153b07..72810c67 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4 + uses: zizmorcore/zizmor-action@6fc4b006235f201fdab3722e17240ab420d580e5 # v0.6.1 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 e8319414..35a0140e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,10 +18,9 @@ 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: d43b6c087ac471e2ea7b8af622ff15f05c0c365b # v1.50.1 + rev: bee27e3a4fd1ea2111cf90ab89cd076c870fce14 # v1.48.0 hooks: - id: typos - always_run: true - repo: local hooks: @@ -39,7 +38,8 @@ repos: entry: cargo test language: system args: [ "--features", "sqlite,mysql,postgresql", "--" ] - types_or: [ rust, toml ] # Cargo.lock matches toml type which is intended + types_or: [ rust, file ] + files: (Cargo.toml|Cargo.lock|rust-toolchain.toml|rustfmt.toml|.*\.rs$) pass_filenames: false - id: cargo-clippy name: cargo clippy @@ -47,7 +47,8 @@ repos: entry: cargo clippy language: system args: [ "--features", "sqlite,mysql,postgresql", "--", "-D", "warnings" ] - types_or: [ rust, toml ] # Cargo.lock matches toml type which is intended + types_or: [ rust, file ] + files: (Cargo.toml|Cargo.lock|rust-toolchain.toml|rustfmt.toml|.*\.rs$) pass_filenames: false - id: check-docker-templates name: check docker templates diff --git a/Cargo.lock b/Cargo.lock index 55a7233b..6b20797b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,7 +15,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -23,9 +22,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.5" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -53,9 +52,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android_system_properties" -version = "0.1.6" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] @@ -77,13 +76,13 @@ dependencies = [ [[package]] name = "argon2" -version = "0.6.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c52ddac6d63c576bef8168db10c83c49c26444ecbc68060fef078925a901c" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", - "cpufeatures 0.3.1", + "cpufeatures 0.2.17", "password-hash", ] @@ -151,9 +150,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.45" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a8ec73eb862508b7041723c89386365894b4e7d9f6998bf1b8529e5b0ee254" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" dependencies = [ "compression-codecs", "compression-core", @@ -214,7 +213,7 @@ version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "event-listener 5.4.2", + "event-listener 5.4.1", "event-listener-strategy", "pin-project-lite", ] @@ -232,7 +231,7 @@ dependencies = [ "async-task", "blocking", "cfg-if", - "event-listener 5.4.2", + "event-listener 5.4.1", "futures-lite", "rustix", ] @@ -312,23 +311,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.92" +version = "0.1.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", -] - -[[package]] -name = "asyncband" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a214ba60d6231afd0e805e3c27c45a1626d9debaa5a5061c45a1ea1b2f1ed0" -dependencies = [ - "hashbrown 0.17.1", - "slab", + "syn 3.0.3", ] [[package]] @@ -360,9 +349,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-config" -version = "1.12.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d7b388a9fc3a6db15a5ec778c38b354eff1364882c94d08e0252f7a47dcaa4" +checksum = "701418aa459dac33e50a0f8e818e5662a16bc018a6ac7423659b70f3799d67a8" dependencies = [ "aws-credential-types", "aws-runtime", @@ -380,7 +369,7 @@ dependencies = [ "bytes", "fastrand", "hex", - "http 1.5.0", + "http 1.4.2", "sha1 0.10.7", "time", "tokio", @@ -403,9 +392,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.9.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef47857a1d4488b528f4a5d5715fa7c3300820897824152234d3fa22b1426657" +checksum = "a6b50a43f3ccdf331521c6d6c68b7cc9668b6e09d439ebda9569df5722324d76" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -418,7 +407,7 @@ dependencies = [ "bytes", "bytes-utils", "fastrand", - "http 1.5.0", + "http 1.4.2", "http-body 1.1.0", "percent-encoding", "pin-project-lite", @@ -428,9 +417,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.109.0" +version = "1.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3cfe74df5d9ad2fedd691973ad3521ebf4f27a3c68c792556686aedb5519bab" +checksum = "b53416d16c278234845392e38d93bd4481d2f09daa0f005a2277f0aa91f59c22" dependencies = [ "arc-swap", "aws-credential-types", @@ -447,16 +436,16 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.5.0", + "http 1.4.2", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-ssooidc" -version = "1.111.0" +version = "1.106.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b0ec31ed6191bd11350aae4b2004198f2db21350cb0a20c57e0a92e55dd161" +checksum = "cc9b706c3305ed0285d5b1b696c747aa34950f830fb03e3e6c76890f99b9f188" dependencies = [ "arc-swap", "aws-credential-types", @@ -473,16 +462,16 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.5.0", + "http 1.4.2", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sts" -version = "1.114.0" +version = "1.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef45745026107ec30c4ef86bd8ae4b002e7e5f6a86e4225240bdf6b06a0b944a" +checksum = "32d214cdfa5bbe17f117e76a7643fadf32a5234fb597322ef8b1fb4b2f17dbbd" dependencies = [ "arc-swap", "aws-credential-types", @@ -500,7 +489,7 @@ dependencies = [ "aws-types", "fastrand", "http 0.2.12", - "http 1.5.0", + "http 1.4.2", "regex-lite", "tracing", ] @@ -520,7 +509,7 @@ dependencies = [ "hex", "hmac 0.13.0", "http 0.2.12", - "http 1.5.0", + "http 1.4.2", "percent-encoding", "sha2 0.11.0", "time", @@ -550,7 +539,7 @@ dependencies = [ "bytes-utils", "futures-core", "futures-util", - "http 1.5.0", + "http 1.4.2", "http-body 1.1.0", "http-body-util", "percent-encoding", @@ -594,9 +583,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.14.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b82e438d30e02a825d363bd639a9efaed68a8089d86101054b0081e7e0d3e606" +checksum = "07505b34e8f4b3591a4fa69e9792b52289b95488dbbc68c3c0075b7bedb245e1" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -607,7 +596,7 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.5.0", + "http 1.4.2", "http-body 0.4.6", "http-body 1.1.0", "http-body-util", @@ -619,16 +608,16 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.16.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c054752dd9e4dc73d0b75748c99ac2d0feafbf2f25c7b0516f03a3534161223" +checksum = "3b98f2e1fd67ec06618f9c291e5e495a468e60519e44c9c1979cd0521f3affdb" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", "aws-smithy-types", "bytes", "http 0.2.12", - "http 1.5.0", + "http 1.4.2", "pin-project-lite", "tokio", "tracing", @@ -654,20 +643,20 @@ checksum = "7d56e0a4e53127a632224e43633b0fe045fa9e1e3cfc68b9830f1115e103f910" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", - "http 1.5.0", + "http 1.4.2", ] [[package]] name = "aws-smithy-types" -version = "1.6.3" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f94d16e797ec62cd999fc9d5942b48fa7050c3093ddadff48e4d7528d16fcb9" +checksum = "d6dc683efb34b9e755675b37fedbe0103141e5b6df7bdc9eb6967756a8c167d8" dependencies = [ "base64-simd", "bytes", "bytes-utils", "http 0.2.12", - "http 1.5.0", + "http 1.4.2", "http-body 0.4.6", "http-body 1.1.0", "http-body-util", @@ -694,9 +683,9 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.6.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "209f3a6d82a6e9e5f94abbed94c7a26e1c052341002bf57a5fb5481f625896fc" +checksum = "eec1cd5469f328c782dc3e33d4153cf118a54e33cbb3356d60d16f89883e1f94" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -725,12 +714,6 @@ 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" @@ -791,11 +774,11 @@ checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "blake2" -version = "0.11.0" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5d4d889834ee8ecfc0f8426ad30faf7cdcb10f741a8e6d7224d95325479f6f" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.11.3", + "digest 0.10.7", ] [[package]] @@ -818,9 +801,9 @@ dependencies = [ [[package]] name = "blocking" -version = "1.7.0" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" dependencies = [ "async-channel 2.5.0", "async-task", @@ -895,28 +878,27 @@ dependencies = [ [[package]] name = "cached" -version = "4.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a6cf8262820194a1488ece477f5fb5ba9256ef25229d525470e71d6e9a5835" +checksum = "dc0df7748fe2f601e376916ab19e7bfc2c74461b8abe3bce2ce20036ad8de38f" dependencies = [ "ahash", - "async-lock", "cached_proc_macro", "cached_proc_macro_types", - "hashbrown 0.17.1", + "hashbrown 0.16.1", "parking_lot", - "thiserror 2.0.20", + "thiserror 2.0.19", + "tokio", "web-time", ] [[package]] name = "cached_proc_macro" -version = "3.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece0579b43cf6e927b3370c7d44359c7122e7e52b3ea635bc8484da571442edb" +checksum = "66e734c52502e6cf54dce2ba07108906b04b8fe57f4f5e3ef7d58267b4abf060" dependencies = [ "darling 0.20.11", - "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.119", @@ -924,15 +906,15 @@ dependencies = [ [[package]] name = "cached_proc_macro_types" -version = "3.0.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5813789573ae815c8b4be58c4428e0e7ae05f0227678ba9de332ded585b9159" +checksum = "26cf465651fa6ad902a2d327ba60c3a6bc61c6a2f4ad70d091cf20dfda0074ef" [[package]] name = "cc" -version = "1.4.5" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -946,20 +928,14 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - [[package]] name = "chacha20" -version = "0.10.2" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", - "cpufeatures 0.3.1", + "cpufeatures 0.3.0", "rand_core 0.10.1", ] @@ -1001,9 +977,9 @@ checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" [[package]] name = "combine" -version = "4.6.8" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", @@ -1011,9 +987,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.40" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "100590da849306918656ffbb22576bbdfc1382b50ad10d1d50ec177d3b205fb2" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ "brotli", "compression-core", @@ -1025,9 +1001,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.33" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" [[package]] name = "concurrent-queue" @@ -1081,9 +1057,9 @@ dependencies = [ [[package]] name = "cookie" -version = "0.18.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "percent-encoding", "time", @@ -1134,12 +1110,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core_detect" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" - [[package]] name = "cpufeatures" version = "0.2.17" @@ -1151,28 +1121,27 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" dependencies = [ "libc", ] [[package]] -name = "crc-fast" -version = "1.10.0" +name = "crc32c" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75b2483e97a5a7da73ac68a05b629f9c53cff58d8ed1c77866079e18b00dba5" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" dependencies = [ - "digest 0.10.7", - "spin 0.10.1", + "rustc_version", ] [[package]] name = "crc32fast" -version = "1.5.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -1185,39 +1154,38 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "cron" -version = "0.17.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5dcd6f69605c2956916ce24e8af637b754964c9a83f4662d3a2361654cdba09" +checksum = "5877d3fbf742507b66bc2a1945106bd30dd8504019d596901ddd012a4dd01740" dependencies = [ "chrono", "once_cell", - "phf 0.11.3", - "winnow 0.7.15", + "winnow 0.6.26", ] [[package]] name = "crossbeam-channel" -version = "0.5.17" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.21" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.23" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crunchy" @@ -1314,12 +1282,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.24.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "darling_core 0.24.1", - "darling_macro 0.24.1", + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] @@ -1352,15 +1320,15 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.24.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ "ident_case", "proc-macro2", "quote", "strsim", - "syn 3.0.5", + "syn 2.0.119", ] [[package]] @@ -1387,13 +1355,13 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.24.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "darling_core 0.24.1", + "darling_core 0.23.0", "quote", - "syn 3.0.5", + "syn 2.0.119", ] [[package]] @@ -1412,9 +1380,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.11.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "data-url" @@ -1450,7 +1418,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.20", + "thiserror 2.0.19", ] [[package]] @@ -1576,9 +1544,9 @@ dependencies = [ [[package]] name = "diesel" -version = "2.3.13" +version = "2.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b934ddbdcb2abb9f9fc9c30bd47bcc5618b615eea1d334cda5fdf8ff9b072a" +checksum = "e54d1f576cd3a3460f212a4615fd12ce1b6303c095b79a44449ffbe627753dc1" dependencies = [ "bigdecimal", "bitflags 2.13.1", @@ -1613,9 +1581,9 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.3.10" +version = "2.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecbd51fb6c020672543641167efa4e6417ff7ad76849ed556ace3595e72de03a" +checksum = "d1817b7f4279b947fc4cafddec12b0e5f8727141706561ce3ac94a60bddd1cf5" dependencies = [ "diesel_table_macro_syntax", "dsl_auto_type", @@ -1670,13 +1638,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.7" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 2.0.119", ] [[package]] @@ -1769,9 +1737,9 @@ dependencies = [ [[package]] name = "either" -version = "1.18.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elliptic-curve" @@ -1796,11 +1764,11 @@ dependencies = [ [[package]] name = "email-encoding" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420b9da095f052ea597503e39073b5b3c522f7db933fbac202d91d24492693fd" +checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6" dependencies = [ - "base64 0.23.1", + "base64 0.22.1", "memchr", ] @@ -1815,17 +1783,11 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.41" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", - "core_detect", - "multiversion", - "multiversion_no_op", - "rustversion", - "scopeguard", - "simdutf8", ] [[package]] @@ -1852,10 +1814,11 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "5.4.2" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ + "concurrent-queue", "parking", "pin-project-lite", ] @@ -1866,7 +1829,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.2", + "event-listener 5.4.1", "pin-project-lite", ] @@ -1920,19 +1883,18 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.12" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.10" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", - "zlib-rs", ] [[package]] @@ -1973,9 +1935,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" dependencies = [ "futures-channel", "futures-core", @@ -1988,9 +1950,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", "futures-sink", @@ -1998,15 +1960,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", @@ -2015,9 +1977,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-lite" @@ -2034,26 +1996,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 2.0.119", ] [[package]] name = "futures-sink" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-timer" @@ -2063,9 +2025,9 @@ checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" [[package]] name = "futures-util" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-channel", "futures-core", @@ -2189,7 +2151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" dependencies = [ "codemap", - "indexmap 2.14.2", + "indexmap 2.14.0", "lasso", "once_cell", "phf 0.11.3", @@ -2208,17 +2170,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.19" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.5.0", - "indexmap 2.14.2", + "http 1.4.2", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -2238,9 +2200,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "6.4.4" +version = "6.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75c54236f9045c8004a77942bebc52145b4844639db934a5c70fe08617fbe61a" +checksum = "4633d16a2350341713c379d6d06a4b9e1845329386026a49ce4fd09c2f3b16f6" dependencies = [ "derive_builder", "log", @@ -2249,7 +2211,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.20", + "thiserror 2.0.19", "walkdir", ] @@ -2285,11 +2247,6 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] [[package]] name = "heck" @@ -2299,9 +2256,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -2311,9 +2268,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hickory-net" -version = "0.26.2" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084e7bd6a377435d568f652153e571b50970d7ccc1d1eeec0519f834632287e1" +checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" dependencies = [ "async-trait", "cfg-if", @@ -2326,7 +2283,7 @@ dependencies = [ "ipnet", "jni", "rand 0.10.2", - "thiserror 2.0.20", + "thiserror 2.0.19", "tinyvec", "tokio", "tracing", @@ -2335,9 +2292,9 @@ dependencies = [ [[package]] name = "hickory-proto" -version = "0.26.2" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2da0694c15b44c6f68a6b05e0233617008c54080e31d6eb848d858a9c5b38d" +checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" dependencies = [ "data-encoding", "idna", @@ -2347,7 +2304,7 @@ dependencies = [ "prefix-trie", "rand 0.10.2", "ring", - "thiserror 2.0.20", + "thiserror 2.0.19", "tinyvec", "tracing", "url", @@ -2355,9 +2312,9 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.26.2" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4f9f4603319422d482738f3f6fe5aac03157fdbfed1cd85a3ff45adb09072f" +checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" dependencies = [ "cfg-if", "futures-util", @@ -2374,7 +2331,7 @@ dependencies = [ "resolv-conf", "smallvec", "system-configuration", - "thiserror 2.0.20", + "thiserror 2.0.19", "tokio", "tracing", ] @@ -2439,9 +2396,9 @@ dependencies = [ [[package]] name = "http" -version = "1.5.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -2465,18 +2422,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", - "http 1.5.0", + "http 1.4.2", ] [[package]] name = "http-body-util" -version = "0.1.5" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", - "http 1.5.0", + "http 1.4.2", "http-body 1.1.0", "pin-project-lite", ] @@ -2495,9 +2452,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.15" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ "typenum", ] @@ -2527,16 +2484,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.11.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2", - "http 1.5.0", + "http 1.4.2", "http-body 1.1.0", "httparse", "itoa", @@ -2552,12 +2509,12 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.5.0", - "hyper 1.11.1", + "http 1.4.2", + "hyper 1.11.0", "hyper-util", - "rustls 0.23.44", + "rustls 0.23.42", "tokio", - "tokio-rustls 0.26.5", + "tokio-rustls 0.26.4", "tower-service", ] @@ -2571,9 +2528,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.5.0", + "http 1.4.2", "http-body 1.1.0", - "hyper 1.11.1", + "hyper 1.11.0", "ipnet", "libc", "percent-encoding", @@ -2612,9 +2569,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", @@ -2626,9 +2583,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -2639,9 +2596,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -2653,17 +2610,16 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -2674,15 +2630,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.3.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -2733,9 +2689,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.2" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -2764,9 +2720,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.2" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" dependencies = [ "serde", ] @@ -2805,9 +2761,9 @@ checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" [[package]] name = "jiff" -version = "0.2.35" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +checksum = "e184d09547b80eb7e20d141ba2fb1fbac843ca53f4cf1b31210adc4c1adc6e16" dependencies = [ "defmt", "jiff-core", @@ -2833,9 +2789,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.35" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +checksum = "323da076b7a6faf914dc677cb05a4b907742ff7375c8322c9e7f5061e5e0e9de" dependencies = [ "jiff-core", "proc-macro2", @@ -2870,7 +2826,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.20", + "thiserror 2.0.19", "walkdir", "windows-link", ] @@ -2909,9 +2865,9 @@ dependencies = [ [[package]] name = "job_scheduler_ng" -version = "2.5.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576b4255ab9de8ce7b81060ec54b1b7f8499dfd6c16a66c4cd4cb1ad4eba27e3" +checksum = "217723d58ee473953675d15f11e56898a611aca8ea044d5a34eabeade99ef613" dependencies = [ "chrono", "cron", @@ -2930,9 +2886,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.105" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", @@ -2953,7 +2909,7 @@ dependencies = [ "p256", "p384", "pem", - "rand 0.8.8", + "rand 0.8.7", "rsa", "serde", "serde_json", @@ -2987,18 +2943,18 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.9.9", + "spin", ] [[package]] name = "lettre" -version = "0.11.23" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c646bd5cc763b1087b15493e29a64be6147ba8f19342004fa52048ee596eae" +checksum = "0da65617f6cb926332d039cb578aad56178da86e128db6a1b09f4c94fa5b3349" dependencies = [ "async-std", "async-trait", - "base64 0.23.1", + "base64 0.22.1", "email-encoding", "email_address", "fastrand", @@ -3011,12 +2967,12 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "rustls 0.23.44", + "rustls 0.23.42", "rustls-native-certs", "serde", "socket2 0.6.5", "tokio", - "tokio-rustls 0.26.5", + "tokio-rustls 0.26.4", "tracing", "url", ] @@ -3044,9 +3000,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.38.2" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" dependencies = [ "cc", "pkg-config", @@ -3061,9 +3017,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "litrs" @@ -3082,9 +3038,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.34" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" dependencies = [ "value-bag", ] @@ -3109,7 +3065,7 @@ name = "macros" version = "0.1.0" dependencies = [ "quote", - "syn 3.0.5", + "syn 3.0.3", ] [[package]] @@ -3133,11 +3089,10 @@ dependencies = [ [[package]] name = "mea" -version = "0.6.7" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c709842c4ce65cb91e2666ad5319dfc1efc3af0d34f02075eddca9000d9f8afb" +checksum = "2640d335e7273dacdcf51044026139b2e269c3bb0dfc3f8cb3496b85e3f6a42c" dependencies = [ - "hashbrown 0.17.1", "slab", ] @@ -3191,9 +3146,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.9.1" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", @@ -3201,9 +3156,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.3" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", @@ -3212,16 +3167,16 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.16" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4293f18e7567a1caf3c584855554377025c65e0aa445344d04171f5ad63d19b9" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ "async-lock", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "equivalent", - "event-listener 5.4.2", + "event-listener 5.4.1", "futures-util", "parking_lot", "portable-atomic", @@ -3239,43 +3194,16 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.5.0", + "http 1.4.2", "httparse", "memchr", "mime", - "spin 0.9.9", + "spin", "tokio", "tokio-util", "version_check", ] -[[package]] -name = "multiversion" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c" -dependencies = [ - "multiversion-macros", -] - -[[package]] -name = "multiversion-macros" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83" -dependencies = [ - "proc-macro2", - "quote", - "rustversion", - "syn 3.0.5", -] - -[[package]] -name = "multiversion_no_op" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" - [[package]] name = "mysqlclient-sys" version = "0.5.2" @@ -3293,18 +3221,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" -[[package]] -name = "nix" -version = "0.31.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" -dependencies = [ - "bitflags 2.13.1", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "nom" version = "7.1.3" @@ -3360,7 +3276,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.8", + "rand 0.8.7", "smallvec", "zeroize", ] @@ -3379,14 +3295,14 @@ checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.3", ] [[package]] name = "num-integer" -version = "0.1.47" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] @@ -3403,9 +3319,9 @@ dependencies = [ [[package]] name = "num-modular" -version = "0.6.5" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8e500409e6cd603b03e477c26a6caecdc27ac58979a53e881c75eafc079f44" +checksum = "fc41a1374056e9672221567958a66c16be12d0e2c1b408761e14d901c237d5e0" [[package]] name = "num-order" @@ -3454,8 +3370,8 @@ dependencies = [ "base64 0.22.1", "chrono", "getrandom 0.2.17", - "http 1.5.0", - "rand 0.8.8", + "http 1.4.2", + "rand 0.8.7", "serde", "serde_json", "serde_path_to_error", @@ -3485,9 +3401,9 @@ dependencies = [ [[package]] name = "opendal" -version = "0.59.1" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950151f9587a51a7bed70a15fa0cff464eae96e41ae7499f97067bdafdf43eb" +checksum = "96c9c85ce253ff87225e7669979d877a20c98a06604ec9d6dd5f4473e08f1ae1" dependencies = [ "opendal-core", "opendal-service-fs", @@ -3496,22 +3412,24 @@ dependencies = [ [[package]] name = "opendal-core" -version = "0.59.1" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a43405d217dfdfb543f58847336d3af672897dd1939bb7dcf314b63cf364f1c9" +checksum = "c4f8607c90e2c963a91467f50fb49fbc7fb3d573f88cea219ca59ccd3740b309" dependencies = [ "anyhow", - "asyncband", - "base64 0.23.1", + "base64 0.22.1", "bytes", "futures", - "http 1.5.0", + "http 1.4.2", + "http-body 1.1.0", "jiff", "log", "md-5", + "mea", "percent-encoding", - "quick-xml", + "quick-xml 0.39.4", "reqsign-core", + "reqwest", "serde", "serde_json", "tokio", @@ -3520,25 +3438,11 @@ dependencies = [ "web-time", ] -[[package]] -name = "opendal-http-transport-reqwest" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "401999057db611e592f883fcf2cbd6754ff37af587deaadd07b8c1398b2b6b06" -dependencies = [ - "bytes", - "futures", - "http 1.5.0", - "http-body 1.1.0", - "opendal-core", - "reqwest", -] - [[package]] name = "opendal-service-fs" -version = "0.59.1" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb9caf04d6d38713299dd4abac984b95ab16ee20e1ff09160f495c5a64644083" +checksum = "22e89a665fef0e6bd249cf5ea47fc174b7ba892159bee4b9382528b1ca873a2c" dependencies = [ "bytes", "log", @@ -3550,18 +3454,18 @@ dependencies = [ [[package]] name = "opendal-service-s3" -version = "0.59.1" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388b1d39b62535c62803754ebef89808859558697366dbedd0299345887ba461" +checksum = "313d46c9f5ae70bca26b7c3e3fbb9b639292625f28af73aa016f47e788af9deb" dependencies = [ - "base64 0.23.1", + "base64 0.22.1", "bytes", - "crc-fast", - "http 1.5.0", + "crc32c", + "http 1.4.2", "log", "md-5", "opendal-core", - "quick-xml", + "quick-xml 0.39.4", "reqsign-aws-v4", "reqsign-core", "reqsign-file-read-tokio", @@ -3580,13 +3484,13 @@ dependencies = [ "dyn-clone", "ed25519-dalek", "hmac 0.12.1", - "http 1.5.0", + "http 1.4.2", "itertools", "log", "oauth2", "p256", "p384", - "rand 0.8.8", + "rand 0.8.7", "rsa", "serde", "serde-value", @@ -3733,12 +3637,13 @@ dependencies = [ [[package]] name = "password-hash" -version = "0.6.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ - "getrandom 0.4.3", - "phc", + "base64ct", + "rand_core 0.6.4", + "subtle", ] [[package]] @@ -3803,9 +3708,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.9.1" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d45aeb61b4bf818e12d4205f2466f8c4748f85f4fce0146d1c03d69d753f0ad" +checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" dependencies = [ "memchr", "ucd-trie", @@ -3813,9 +3718,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.9.1" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc5a242e25ed4e7704d0be240f2cfbe20a8c27e7e252d94835be93d92dc39f" +checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" dependencies = [ "pest", "pest_generator", @@ -3823,9 +3728,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.9.1" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abf21475cc3820fe4b2ca2dc2142902f67a02189f3b5b3a229f4febc01a43e5" +checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" dependencies = [ "pest", "pest_meta", @@ -3836,24 +3741,13 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.9.1" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adba4db388f687393c18c51348d44a41d870ca9df71a2c98172ea3035dc6936e" +checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" dependencies = [ "pest", ] -[[package]] -name = "phc" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892" -dependencies = [ - "base64ct", - "ctutils", - "getrandom 0.4.3", -] - [[package]] name = "phf" version = "0.11.3" @@ -3880,7 +3774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared 0.11.3", - "rand 0.8.8", + "rand 0.8.7", ] [[package]] @@ -3966,9 +3860,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "polling" @@ -3986,24 +3880,24 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.15.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" [[package]] name = "portable-atomic-util" -version = "0.2.8" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] [[package]] name = "potential_utf" -version = "0.1.6" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -4054,15 +3948,6 @@ dependencies = [ "elliptic-curve", ] -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit 0.25.13+spec-1.1.0", -] - [[package]] name = "proc-macro2" version = "1.0.107" @@ -4122,6 +4007,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +[[package]] +name = "quick-xml" +version = "0.39.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quick-xml" version = "0.41.0" @@ -4172,9 +4067,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.8" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -4266,22 +4161,22 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.27" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.27" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.3", ] [[package]] @@ -4298,9 +4193,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.18" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -4331,18 +4226,19 @@ dependencies = [ ] [[package]] -name = "reqsign-aws-core" -version = "3.1.1" +name = "reqsign-aws-v4" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac4749b7dfa7bfaccd01eb03e9dc795ed37e3f20d6f0f38e2c67ee85ad6bc86" +checksum = "4e9e1168fab3883ec6afed1c2e20c25b2a09f366cdb662ac3e0878ae0332d63e" dependencies = [ + "anyhow", "bytes", "form_urlencoded", "hex", - "http 1.5.0", + "http 1.4.2", "log", "percent-encoding", - "quick-xml", + "quick-xml 0.41.0", "reqsign-core", "rust-ini", "serde", @@ -4351,37 +4247,22 @@ dependencies = [ "sha1 0.11.0", ] -[[package]] -name = "reqsign-aws-v4" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff250f0fd0b913fbd565e405acc553da0f13bde30bfb5403178c9d0313cdc15f" -dependencies = [ - "bytes", - "http 1.5.0", - "log", - "quick-xml", - "reqsign-aws-core", - "reqsign-core", - "serde", -] - [[package]] name = "reqsign-core" -version = "3.3.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff052daffb0599681c50f85c59e7236438976efe991ab864edd9f3b235501a0f" +checksum = "514a1e0b4aa288652a3fdbda4f0a610f379cdf5374e55a37c9edd03d57ed856b" dependencies = [ "anyhow", - "base64 0.23.1", + "base64 0.22.1", "bytes", + "form_urlencoded", "futures", "hex", "hmac 0.13.0", - "http 1.5.0", + "http 1.4.2", "jiff", "log", - "mea", "percent-encoding", "sha1 0.11.0", "sha2 0.11.0", @@ -4390,9 +4271,9 @@ dependencies = [ [[package]] name = "reqsign-file-read-tokio" -version = "3.0.6" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3235df90a6bca681aa47dd86f2393d122a6d77042aa8a7c81e218cd45c5bfc0" +checksum = "2b472a8d1f2e5a4be8ce13bb7bdf4b59e9bee613ce124aca23959ddb42176b39" dependencies = [ "anyhow", "reqsign-core", @@ -4401,22 +4282,23 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.5" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ - "base64 0.23.1", + "base64 0.22.1", "bytes", "cookie", "cookie_store", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2", - "http 1.5.0", + "http 1.4.2", "http-body 1.1.0", "http-body-util", - "hyper 1.11.1", + "hyper 1.11.0", "hyper-rustls", "hyper-util", "js-sys", @@ -4424,7 +4306,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustls 0.23.44", + "rustls 0.23.42", "rustls-pki-types", "rustls-platform-verifier", "serde", @@ -4432,7 +4314,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-rustls 0.26.5", + "tokio-rustls 0.26.4", "tokio-util", "tower", "tower-http", @@ -4506,14 +4388,14 @@ dependencies = [ "either", "figment", "futures", - "indexmap 2.14.2", + "indexmap 2.14.0", "log", "memchr", "multer", "num_cpus", "parking_lot", "pin-project-lite", - "rand 0.8.8", + "rand 0.8.7", "ref-cast", "rocket_codegen", "rocket_http", @@ -4538,7 +4420,7 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" dependencies = [ "devise", "glob", - "indexmap 2.14.2", + "indexmap 2.14.0", "proc-macro2", "quote", "rocket_http", @@ -4558,7 +4440,7 @@ dependencies = [ "futures", "http 0.2.12", "hyper 0.14.32", - "indexmap 2.14.2", + "indexmap 2.14.0", "log", "memchr", "pear", @@ -4625,17 +4507,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" dependencies = [ "hashbrown 0.16.1", - "thiserror 2.0.20", + "thiserror 2.0.19", ] [[package]] name = "rtoolbox" -version = "0.0.6" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1efe12a1469752d0e6ff5ebec0b6ef4924cc5c4c71046b0ec730040535819d" +checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4693,15 +4575,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.44" +version = "0.23.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.15", + "rustls-webpki 0.103.13", "subtle", "zeroize", ] @@ -4747,10 +4629,10 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.44", + "rustls 0.23.42", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.15", + "rustls-webpki 0.103.13", "security-framework", "security-framework-sys", "webpki-root-certs", @@ -4775,9 +4657,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.15" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -4837,9 +4719,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.2.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "dyn-clone", "ref-cast", @@ -4959,7 +4841,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.3", ] [[package]] @@ -4968,6 +4850,7 @@ version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -5027,19 +4910,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.23.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "935177bb8c0cd8ca1a4e6d1a2ac8988bea69cab4f9d3a31311e012ad27868ea4" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" dependencies = [ - "base64 0.23.1", + "base64 0.22.1", "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.14.2", - "jiff", + "indexmap 2.14.0", "schemars 0.9.0", - "schemars 1.2.2", + "schemars 1.2.1", "serde_core", "serde_json", "serde_with_macros", @@ -5048,14 +4930,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.23.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d607aa01a3cb0ad757d6fd216136910db3c97b102fe686585689615a02dbcdc" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" dependencies = [ - "darling 0.24.1", + "darling 0.23.0", "proc-macro2", "quote", - "syn 3.0.5", + "syn 2.0.119", ] [[package]] @@ -5076,7 +4958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures 0.3.1", + "cpufeatures 0.3.0", "digest 0.11.3", ] @@ -5098,7 +4980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures 0.3.1", + "cpufeatures 0.3.0", "digest 0.11.3", ] @@ -5177,7 +5059,7 @@ checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.20", + "thiserror 2.0.19", "time", ] @@ -5195,9 +5077,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.16.0" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" @@ -5225,12 +5107,6 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" -[[package]] -name = "spin" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" - [[package]] name = "spinning_top" version = "0.3.0" @@ -5300,11 +5176,11 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svg-hush" -version = "0.9.7" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e690409a034dc81758d2986dcc0e01ffe4a77c65902a120777f5182dd40b1f7f" +checksum = "929223e80cdcec0482207576ea09692dd71b2b559057fc172e292ecec9a97559" dependencies = [ - "base64 0.23.1", + "base64 0.22.1", "data-url", "quick-error", "url", @@ -5324,9 +5200,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.5" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -5416,11 +5292,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.20", + "thiserror-impl 2.0.19", ] [[package]] @@ -5436,13 +5312,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.3", ] [[package]] @@ -5455,10 +5331,19 @@ dependencies = [ ] [[package]] -name = "time" -version = "0.3.55" +name = "threadpool" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" dependencies = [ "deranged", "libc", @@ -5497,9 +5382,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -5507,9 +5392,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.13.2" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -5539,13 +5424,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.2" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 2.0.119", ] [[package]] @@ -5560,11 +5445,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.5" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.44", + "rustls 0.23.42", "tokio", ] @@ -5615,7 +5500,7 @@ dependencies = [ "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", - "toml_edit 0.22.27", + "toml_edit", ] [[package]] @@ -5649,22 +5534,13 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.14.2", + "indexmap 2.14.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -5672,23 +5548,11 @@ dependencies = [ "winnow 0.7.15", ] -[[package]] -name = "toml_edit" -version = "0.25.13+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" -dependencies = [ - "indexmap 2.14.2", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "winnow 1.0.4", -] - [[package]] name = "toml_parser" -version = "1.1.3+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow 1.0.4", ] @@ -5737,7 +5601,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.5.0", + "http 1.4.2", "http-body 1.1.0", "http-body-util", "pin-project-lite", @@ -5838,10 +5702,10 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.5.0", + "http 1.4.2", "httparse", "log", - "rand 0.8.8", + "rand 0.8.7", "sha1 0.10.7", "thiserror 1.0.69", "url", @@ -5936,9 +5800,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.26.0" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -5954,9 +5818,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "value-bag" -version = "1.14.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2799ffb329a792ecfd902b71306c8a815a6ef1c0470fa9953a6aa4d4cecbe511" +checksum = "ef73bfbaf3216cb59c205d7176bee1194e0d84348979da31f4a71fefe3c2054e" [[package]] name = "vaultwarden" @@ -5989,7 +5853,7 @@ dependencies = [ "handlebars", "hickory-resolver", "html5gum", - "http 1.5.0", + "http 1.4.2", "ipnet", "job_scheduler_ng", "jsonwebtoken", @@ -5999,11 +5863,9 @@ dependencies = [ "macros", "mimalloc", "moka", - "nix", "num-derive", "num-traits", "opendal", - "opendal-http-transport-reqwest", "openidconnect", "openssl", "pastey 0.2.3", @@ -6019,7 +5881,7 @@ dependencies = [ "rocket", "rocket_ws", "rpassword", - "rustls 0.23.44", + "rustls 0.23.42", "semver", "serde", "serde_json", @@ -6094,9 +5956,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.128" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -6107,9 +5969,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.78" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -6117,9 +5979,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.128" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6127,22 +5989,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.128" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 3.0.5", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.128" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] @@ -6162,9 +6024,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.105" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -6259,9 +6121,9 @@ dependencies = [ [[package]] name = "which" -version = "8.0.6" +version = "8.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" +checksum = "8f3ef584124b911bcc3875c2f1472e80f24361ceb789bd1c62b3e9a3df9ff43c" dependencies = [ "libc", ] @@ -6391,6 +6253,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -6521,6 +6392,15 @@ 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" @@ -6535,9 +6415,6 @@ name = "winnow" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" -dependencies = [ - "memchr", -] [[package]] name = "wit-bindgen" @@ -6547,9 +6424,9 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" -version = "0.6.4" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "x509-parser" @@ -6580,9 +6457,9 @@ dependencies = [ [[package]] name = "xml" -version = "1.4.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f45bb2c13fec6a6cb4c0f76a7e94839e110a14ec803ec2940777a94c347bc52" +checksum = "636f85e5ca6488e96401b61eb7de54f4e44755c988af0f52cf90230c312a1a89" [[package]] name = "xmlparser" @@ -6624,31 +6501,34 @@ dependencies = [ [[package]] name = "yubico_ng" -version = "1.0.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563eb0ab41031e758446e3737231541bb4556d6af1e893e94faa09c989f794af" +checksum = "228e2862e3c66f3224102d9a00d9d3646b271a05cc6c4819fea195fa8b5c00e0" dependencies = [ - "base64 0.23.1", + "base64 0.22.1", "form_urlencoded", - "getrandom 0.4.3", - "hmac 0.13.0", - "sha1 0.11.0", + "futures", + "hmac 0.12.1", + "rand 0.9.5", + "reqwest", + "sha1 0.10.7", + "threadpool", ] [[package]] name = "zerocopy" -version = "0.8.57" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.57" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", @@ -6698,9 +6578,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -6709,9 +6589,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.8" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -6720,21 +6600,15 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.6" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 2.0.119", ] -[[package]] -name = "zlib-rs" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" - [[package]] name = "zmij" version = "1.0.23" @@ -6743,27 +6617,27 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zstd" -version = "0.14.0" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf06bd8162af0734b344780deb55b42a2429ae430870d13fcc12f238e880fe6e" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "8.0.0" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae42c0555055784c70058d19ba8e275528e8a99a706684868ace5da4e716a4ab" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.1.0+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index d3a3d5e9..db685864 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace.package] edition = "2024" -rust-version = "1.96.1" +rust-version = "1.95.0" license = "AGPL-3.0-only" repository = "https://github.com/dani-garcia/vaultwarden" publish = false @@ -12,6 +12,7 @@ members = ["macros"] name = "vaultwarden" version = "1.0.0" authors = ["Daniel García "] +readme = "README.md" build = "build.rs" repository.workspace = true edition.workspace = true @@ -40,7 +41,6 @@ vendored_openssl = ["openssl/vendored"] enable_mimalloc = ["dep:mimalloc"] s3 = [ "opendal/services-s3", - "dep:opendal-http-transport-reqwest", "dep:aws-config", "dep:aws-credential-types", "dep:aws-smithy-runtime-api", @@ -58,7 +58,6 @@ oidc-accept-string-booleans = ["openidconnect/accept-string-booleans"] unstable = [] [target."cfg(unix)".dependencies] -nix = { version = "0.31.3", features = ["fs"] } # Logging syslog = "7.0.0" @@ -66,7 +65,7 @@ syslog = "7.0.0" macros = { path = "./macros" } # Logging -log = "0.4.34" +log = "0.4.33" fern = { version = "0.7.1", features = ["syslog-7", "reopen-1"] } # We need the `log` feature for `tracing` to enable logging for several crates to work, like lettre or webauthn-rs tracing = { version = "0.1.44", features = ["log"] } @@ -90,7 +89,7 @@ rmpv = "1.3.1" # MessagePack library dashmap = "6.2.1" # Async futures -futures = "0.3.34" +futures = "0.3.33" tokio = { version = "1.53.1", features = [ "fs", "io-util", @@ -107,7 +106,7 @@ serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.151" # A safe, extensible ORM and Query builder -diesel = { version = "2.3.13", 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 = [ @@ -120,27 +119,27 @@ derive_more = { version = "2.1.1", features = [ diesel-derive-newtype = "2.1.3" # SQLite, statically bundled unless the `sqlite_system` feature is enabled -libsqlite3-sys = { version = "0.38.2", optional = true } +libsqlite3-sys = { version = "0.37.0", optional = true } # Crypto-related libraries rand = "0.10.2" ring = "0.17.14" -rustls = { version = "0.23.44", features = ["ring", "std"], default-features = false } +rustls = { version = "0.23.42", features = ["ring", "std"], default-features = false } subtle = "2.6.1" # UUID generation -uuid = { version = "1.26.0", 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.55" +time = "0.3.54" # Job scheduler -job_scheduler_ng = "2.5.0" +job_scheduler_ng = "2.4.0" # Data encoding library Hex/Base32/Base64 -data-encoding = "2.11.1" +data-encoding = "2.11.0" # JWT library jsonwebtoken = { version = "11.0.0", default-features = false, features = ["rust_crypto", "use_pem"] } @@ -149,7 +148,7 @@ jsonwebtoken = { version = "11.0.0", default-features = false, features = ["rust totp-lite = "2.0.1" # Yubico Library -yubico_ng = { version = "1.0.0", default-features = false } +yubico = { package = "yubico_ng", version = "0.15.0", default-features = false, features = ["online-tokio"] } # WebAuthn libraries # danger-allow-state-serialisation is needed to save the state in the db @@ -162,7 +161,7 @@ webauthn-rs-core = "0.5.5" url = "2.5.8" # Email libraries -lettre = { version = "0.11.23", default-features = false, features = [ +lettre = { version = "0.11.22", default-features = false, features = [ # Misc "tracing", "serde", @@ -180,10 +179,10 @@ 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.4", 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.5", default-features = false, features = [ +reqwest = { version = "0.13.4", default-features = false, features = [ # Misc "charset", "cookies", @@ -201,7 +200,7 @@ reqwest = { version = "0.13.5", default-features = false, features = [ "socks", "system-proxy", ] } -hickory-resolver = "0.26.2" +hickory-resolver = "0.26.1" # Favicon extraction libraries html5gum = "0.8.4" @@ -212,13 +211,13 @@ regex = { version = "1.13.1", default-features = false, features = [ ] } data-url = "0.3.2" bytes = "1.12.1" -svg-hush = "0.9.7" +svg-hush = "0.9.6" # Cache function results (Used for version check and favicon fetching) -cached = { version = "4.0.0", features = ["async"] } +cached = { version = "2.0.2", features = ["async"] } # Used for custom short lived cookie jar during favicon extraction -cookie = "0.18.2" +cookie = "0.18.1" cookie_store = "0.22.1" # Used by U2F, JWT and PostgreSQL @@ -232,11 +231,11 @@ pastey = "0.2.3" governor = "0.10.4" # CIDR parsing for the trusted proxies of the client IP header -ipnet = "2.12.2" +ipnet = "2.12.0" # OIDC for SSO openidconnect = { version = "4.0.1", default-features = false } -moka = { version = "0.12.16", features = ["future"] } +moka = { version = "0.12.15", features = ["future"] } # Check client versions for specific features. semver = "1.0.28" @@ -245,10 +244,10 @@ semver = "1.0.28" # Mainly used for the musl builds, since the default musl malloc is very slow mimalloc = { version = "0.1.52", optional = true, default-features = false, features = ["secure"] } -which = "8.0.6" +which = "8.0.5" # Argon2 library with support for the PHC format -argon2 = "0.6.0" +argon2 = "0.5.3" # Reading a password from the cli for generating the Argon2id ADMIN_TOKEN rpassword = "7.5.4" @@ -257,21 +256,20 @@ rpassword = "7.5.4" grass_compiler = { version = "0.13.4", default-features = false } # File are accessed through Apache OpenDAL -opendal = { version = "0.59.1", default-features = false, features = ["services-fs"] } -opendal-http-transport-reqwest = { version = "0.59.1", default-features = false, features = ["rustls-no-provider"], optional = true } +opendal = { version = "0.57.0", default-features = false, features = ["services-fs"] } # For retrieving AWS credentials, including temporary SSO credentials -aws-config = { version = "1.12.0", optional = true, default-features = false, features = [ +aws-config = { version = "1.10.0", optional = true, default-features = false, features = [ "behavior-version-latest", "credentials-process", "rt-tokio", "sso", ] } aws-credential-types = { version = "1.3.0", optional = true } -aws-smithy-runtime-api = { version = "1.16.0", optional = true } -http = { version = "1.5.0", optional = true } -reqsign-aws-v4 = { version = "3.3.0", optional = true } -reqsign-core = { version = "3.3.1", optional = true } +aws-smithy-runtime-api = { version = "1.14.0", optional = true } +http = { version = "1.4.2", optional = true } +reqsign-aws-v4 = { version = "3.0.2", optional = true } +reqsign-core = { version = "3.1.0", optional = true } # 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 6ae6e9a8..4a51a6b2 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -1,12 +1,11 @@ --- -vault_version: "v2026.7.0" -vault_image_digest: "sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c" +vault_version: "v2026.6.4" +vault_image_digest: "sha256:e7d3b31ec6a991a6bf447721ea341b4192ce5d3b920929211672fd4f3f891427" # 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" -# The `rust_version` variable is extracted from `rust-toolchain.toml` -# rust_version: x.yy.z # 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 a91deb07..baa4c979 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.7.0 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.7.0 -# [docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c] +# $ docker pull docker.io/vaultwarden/web-vault:v2026.6.4 +# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.6.4 +# [docker.io/vaultwarden/web-vault@sha256:e7d3b31ec6a991a6bf447721ea341b4192ce5d3b920929211672fd4f3f891427] # # - Conversely, to get the tag name from the digest: -# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c -# [docker.io/vaultwarden/web-vault:v2026.7.0] +# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e7d3b31ec6a991a6bf447721ea341b4192ce5d3b920929211672fd4f3f891427 +# [docker.io/vaultwarden/web-vault:v2026.6.4] # -FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c AS vault +FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:e7d3b31ec6a991a6bf447721ea341b4192ce5d3b920929211672fd4f3f891427 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.98.1 AS build_amd64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.98.1 AS build_arm64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.98.1 AS build_armv7 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.98.1 AS build_armv6 +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 @@ -70,7 +70,7 @@ RUN echo "export CARGO_TARGET=${CARGO_BUILD_TARGET}" >> /env-cargo && \ # Output the current contents of the file cat /env-cargo -RUN . /env-cargo && \ +RUN source /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 . /env-cargo && \ +RUN source /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 . /env-cargo && \ +RUN source /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,7 +126,6 @@ RUN . /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 b8490609..7ebb07bd 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.7.0 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.7.0 -# [docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c] +# $ docker pull docker.io/vaultwarden/web-vault:v2026.6.4 +# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.6.4 +# [docker.io/vaultwarden/web-vault@sha256:e7d3b31ec6a991a6bf447721ea341b4192ce5d3b920929211672fd4f3f891427] # # - Conversely, to get the tag name from the digest: -# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c -# [docker.io/vaultwarden/web-vault:v2026.7.0] +# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e7d3b31ec6a991a6bf447721ea341b4192ce5d3b920929211672fd4f3f891427 +# [docker.io/vaultwarden/web-vault:v2026.6.4] # -FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10e9c49658451c AS vault +FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:e7d3b31ec6a991a6bf447721ea341b4192ce5d3b920929211672fd4f3f891427 AS vault ########################## Cross Compile Docker Helper Scripts ########################## ## We use the linux/amd64 no matter which Build Platform, since these are all bash scripts @@ -36,8 +36,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 -FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.98.1-slim-trixie AS build -# hadolint ignore=DL3067 +FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.97.1-slim-trixie AS build COPY --from=xx / / ARG TARGETARCH ARG TARGETVARIANT @@ -81,7 +80,7 @@ RUN mkdir -pv "${CARGO_HOME}" && \ RUN USER=root cargo new --bin /app WORKDIR /app -RUN . /env-cargo && \ +RUN source /env-cargo && \ rustup target add "${CARGO_TARGET}" # Copies over *only* your manifests and build files @@ -96,14 +95,9 @@ ARG DB=sqlite,mysql,postgresql # Builds your dependencies and removes the # dummy project, except the target folder # This folder contains the compiled dependencies -RUN . /env-cargo && \ - # Configure xx-cargo for target pkg-config and Debian transitive library lookup +RUN source /env-cargo && \ + # Workaround for xx related build issues # 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 @@ -114,20 +108,15 @@ COPY . . ARG VW_VERSION # Builds again, this time it will be the actual source files being build -RUN . /env-cargo && \ +RUN source /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 - # Configure xx-cargo for target pkg-config and Debian transitive library lookup + # Workaround for xx related build issues # 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 ; \ @@ -150,7 +139,6 @@ RUN . /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 633d6955..5e33d512 100644 --- a/docker/Dockerfile.j2 +++ b/docker/Dockerfile.j2 @@ -28,13 +28,8 @@ # [docker.io/vaultwarden/web-vault:{{ vault_version | replace('+', '_') }}] # {% macro xx_cargo_config() -%} -# Configure xx-cargo for target pkg-config and Debian transitive library lookup +# Workaround for xx related build issues # 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 @@ -57,7 +52,6 @@ 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 @@ -117,7 +111,7 @@ RUN echo "export CARGO_TARGET=${CARGO_BUILD_TARGET}" >> /env-cargo && \ cat /env-cargo {% endif %} -RUN . /env-cargo && \ +RUN source /env-cargo && \ rustup target add "${CARGO_TARGET}" # Copies over *only* your manifests and build files @@ -137,7 +131,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 . /env-cargo && \ +RUN source /env-cargo && \ {% if base == "debian" %} {{ xx_cargo_config() }} && \ {% elif base == "alpine" %} @@ -152,7 +146,7 @@ COPY . . ARG VW_VERSION # Builds again, this time it will be the actual source files being build -RUN . /env-cargo && \ +RUN source /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 && \ @@ -162,7 +156,7 @@ RUN . /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 ; \ @@ -185,7 +179,6 @@ RUN . /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/docker/render_template b/docker/render_template index 84ca8ead..401e0ad0 100755 --- a/docker/render_template +++ b/docker/render_template @@ -3,23 +3,17 @@ import os import argparse import json -import tomllib import yaml import jinja2 # Load settings file -with open('DockerSettings.yaml', 'r', encoding='utf-8') as yaml_file: +with open("DockerSettings.yaml", 'r') as yaml_file: yaml_data = yaml.safe_load(yaml_file) -# Extract the rust_version from the rust-toolchain.toml file -script_dir = os.path.dirname(os.path.abspath(__file__)) -with open(os.path.join(script_dir, '..', 'rust-toolchain.toml'), 'rb') as toolchain_file: - yaml_data["rust_version"] = tomllib.load(toolchain_file)["toolchain"]["channel"] - settings_env = jinja2.Environment( loader=jinja2.FileSystemLoader(os.getcwd()), ) -settings_yaml = yaml.safe_load(settings_env.get_template('DockerSettings.yaml').render(yaml_data)) +settings_yaml = yaml.safe_load(settings_env.get_template("DockerSettings.yaml").render(yaml_data)) args_parser = argparse.ArgumentParser() args_parser.add_argument('template_file', help='Jinja2 template file to render.') diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 34cb913d..f059a214 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -14,7 +14,7 @@ proc-macro = true [dependencies] quote = "1.0.47" -syn = "3.0.5" +syn = "3.0.3" [lints] workspace = true diff --git a/migrations/mysql/2024-03-13-170000_sso_users_cascade/up.sql b/migrations/mysql/2024-03-13-170000_sso_users_cascade/up.sql index 8d1eb178..9e5e46df 100644 --- a/migrations/mysql/2024-03-13-170000_sso_users_cascade/up.sql +++ b/migrations/mysql/2024-03-13-170000_sso_users_cascade/up.sql @@ -1,31 +1,15 @@ -SELECT if ( - EXISTS( - SELECT CONSTRAINT_NAME FROM information_schema.table_constraints - WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'sso_users' - AND CONSTRAINT_TYPE = 'FOREIGN KEY' - AND CONSTRAINT_NAME = 'sso_users_ibfk_1' - ) - ,'ALTER TABLE sso_users DROP FOREIGN KEY `sso_users_ibfk_1`' - ,'SELECT "info: FK sso_users_ibfk_1 does not exist."' -) INTO @drop_stmt; -PREPARE drop_stmt FROM @drop_stmt; -EXECUTE drop_stmt; - -SELECT if ( - EXISTS( - SELECT CONSTRAINT_NAME FROM information_schema.table_constraints - WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'sso_users' - AND CONSTRAINT_TYPE = 'FOREIGN KEY' - AND CONSTRAINT_NAME = '1' - ) - ,'ALTER TABLE sso_users DROP FOREIGN KEY `1`' - ,'SELECT "info: FK sso_users 1 does not exist."' -) INTO @drop_stmt; -PREPARE drop_stmt FROM @drop_stmt; -EXECUTE drop_stmt; - -DEALLOCATE PREPARE drop_stmt; +-- Dynamically create DROP FOREIGN KEY +-- Some versions of MySQL or MariaDB might fail if the key doesn't exists +-- This checks if the key exists, and if so, will drop it. +SET @drop_sso_fk = IF((SELECT true FROM information_schema.TABLE_CONSTRAINTS WHERE + CONSTRAINT_SCHEMA = DATABASE() AND + TABLE_NAME = 'sso_users' AND + CONSTRAINT_NAME = 'sso_users_ibfk_1' AND + CONSTRAINT_TYPE = 'FOREIGN KEY') = true, + 'ALTER TABLE sso_users DROP FOREIGN KEY sso_users_ibfk_1', + 'SELECT 1'); +PREPARE stmt FROM @drop_sso_fk; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; ALTER TABLE sso_users ADD FOREIGN KEY(user_uuid) REFERENCES users(uuid) ON UPDATE CASCADE ON DELETE CASCADE; diff --git a/playwright/.env.template b/playwright/.env.template index 4ead281d..a6696aab 100644 --- a/playwright/.env.template +++ b/playwright/.env.template @@ -21,19 +21,11 @@ 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 # ################### -KC_BOOTSTRAP_ADMIN_USERNAME=admin -KC_BOOTSTRAP_ADMIN_PASSWORD=${KC_BOOTSTRAP_ADMIN_USERNAME} +KEYCLOAK_ADMIN=admin +KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN} KC_HTTP_HOST=127.0.0.1 KC_HTTP_PORT=8080 @@ -47,10 +39,8 @@ DUMMY_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${DUMMY_REALM} ###################### ROCKET_ADDRESS=0.0.0.0 ROCKET_PORT=8000 -ROCKET_TLS={certs="/data/ssl/localhost.crt",key="/data/ssl/localhost.key"} -DOMAIN=https://127.0.0.1:${ROCKET_PORT} +DOMAIN=http://localhost:${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 000725d7..a27e6105 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 usse its own [test.env](/test/scenarios/test.env) with different ports to not collide with a running dev instance. + +It uses its own `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'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: +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: ```bash -npm ci --ignore-scripts --allow-git=none --allow-remote=none +npm ci --ignore-scripts 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_RUNNING=true npx playwright test +PW_KEEP_SERVICE_RUNNNING=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 specific test (It might fail if it has dependency): +To run only a specifc 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 "https://127.0.0.1:8000" --ignore-https-errors +npx playwright codegen "http://127.0.0.1:8003" ``` ## Override web-vault @@ -112,11 +112,12 @@ You can check the result running: DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env up Vaultwarden ``` -Then check `https://127.0.0.1:8003/admin/diagnostics` with `admin`. +Then check `http://127.0.0.1:8003/admin/diagnostics` with `admin`. # OpenID Connect test setup -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. +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. ## Setup @@ -130,17 +131,18 @@ Then start the stack (the `profile` is required to run `Vaultwarden`) : ```bash > docker compose --profile vaultwarden --env-file .env up .... -keycloakSetup_1 | Logging into https://127.0.0.1:8080 as user admin of realm master +keycloakSetup_1 | Logging into http://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 indicate the correct setup of the Keycloak realm, client and user (It's normal for this container to stop once the configuration is done). +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). Then you can access : -- `Vaultwarden` on https://0.0.0.0:8000 with the default user `test@yopmail.com/test`. +- `Vaultwarden` on http://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 @@ -169,7 +171,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) 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)). +- `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)). - All `Vaultwarden` configuration can be set (EX: `SMTP_*`) ## Cleanup diff --git a/playwright/compose/keycloak/setup.sh b/playwright/compose/keycloak/setup.sh index f1d8a303..a27caaff 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 "$KC_BOOTSTRAP_ADMIN_USERNAME" --password "$KC_BOOTSTRAP_ADMIN_PASSWORD" --client admin-cli +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 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 "$KC_BOOTSTRAP_ADMIN_USERNAME" --password "$KC_BOOTSTRAP_ADMIN_PASSWORD" --client admin-cli +# kcadm.sh config credentials --server "http://${KC_HTTP_HOST}:${KC_HTTP_PORT}" --realm master --user "$KEYCLOAK_ADMIN" --password "$KEYCLOAK_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 6b48c7dc..4dae1ae4 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 --allow-git=none --allow-remote=none && npx playwright install-deps && npx playwright install firefox +RUN npm ci --ignore-scripts && 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 9a369dab..e472d207 100644 --- a/playwright/compose/warden/Dockerfile +++ b/playwright/compose/warden/Dockerfile @@ -35,7 +35,6 @@ 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 ee8b47fe..37e9a25e 100755 --- a/playwright/compose/warden/build.sh +++ b/playwright/compose/warden/build.sh @@ -22,14 +22,3 @@ 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 5bfc47a5..f4402326 100644 --- a/playwright/docker-compose.yml +++ b/playwright/docker-compose.yml @@ -24,15 +24,12 @@ 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 @@ -61,7 +58,7 @@ services: Mariadb: profiles: ["playwright"] container_name: playwright_mariadb - image: mariadb:12.2.2 + image: mariadb:11.2.4 env_file: test.env healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] @@ -73,7 +70,7 @@ services: Mysql: profiles: ["playwright"] container_name: playwright_mysql - image: mysql:9.7.0 + image: mysql:8.4.1 env_file: test.env healthcheck: test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] @@ -85,7 +82,7 @@ services: Postgres: profiles: ["playwright"] container_name: playwright_postgres - image: postgres:18.4 + image: postgres:16.3 env_file: test.env healthcheck: test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"] @@ -97,7 +94,7 @@ services: Maildev: profiles: ["vaultwarden", "maildev"] container_name: maildev - image: timshel/maildev:3.2.19 + image: timshel/maildev:3.0.4 ports: - ${SMTP_PORT}:1025 - 1080:1080 @@ -105,7 +102,7 @@ services: Keycloak: profiles: ["keycloak", "vaultwarden"] container_name: keycloak-${ENV:-dev} - image: quay.io/keycloak/keycloak:26.6.2 + image: quay.io/keycloak/keycloak:26.3.4 network_mode: "host" command: - start-dev @@ -115,12 +112,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.6.2 + KEYCLOAK_VERSION: 26.3.4 + network_mode: "host" depends_on: - Keycloak restart: "no" diff --git a/playwright/global-setup.ts b/playwright/global-setup.ts index 9959d247..89405f12 100644 --- a/playwright/global-setup.ts +++ b/playwright/global-setup.ts @@ -1,4 +1,4 @@ -import { type FullConfig } from '@playwright/test'; +import { firefox, 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 937de651..224bb4b8 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_RUNNING === "true" ) { + if( force === false && process.env.PW_KEEP_SERVICE_RUNNNING === "true" ) { console.log(`Keep vaultwarden running on: ${process.env.DOMAIN}`); } else { console.log(`Vaultwarden stopping`); @@ -231,7 +231,6 @@ 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(); @@ -249,3 +248,15 @@ 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 57f5bcaf..2f4cd0c1 100644 --- a/playwright/package-lock.json +++ b/playwright/package-lock.json @@ -9,56 +9,41 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "mysql2": "3.22.3", - "otpauth": "9.5.1", - "pg": "8.21.0" + "mysql2": "3.15.3", + "otpauth": "9.4.1", + "pg": "8.16.3" }, "devDependencies": { - "@playwright/test": "1.60.0", - "dotenv": "17.4.2", - "dotenv-expand": "13.0.0", - "maildev": "npm:@timshel_npm/maildev@3.2.19" + "@playwright/test": "1.56.1", + "dotenv": "17.2.3", + "dotenv-expand": "12.0.3", + "maildev": "npm:@timshel_npm/maildev@3.2.5" } }, "node_modules/@asamuzakjp/css-color": { - "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==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", + "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==", "dev": true, "dependencies": { - "@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" + "@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" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", - "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.3.tgz", + "integrity": "sha512-kiGFeY+Hxf5KbPpjRLf+ffWbkos1aGo8MBfd91oxS3O57RgU3XhZrt/6UzoVF9VMpWbC3v87SRc9jxGrc9qHtQ==", "dev": true, "dependencies": { - "@asamuzakjp/generational-cache": "^1.0.1", "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", - "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" + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.2" } }, "node_modules/@asamuzakjp/nwsapi": { @@ -67,22 +52,10 @@ "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": "6.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", - "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "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==", "dev": true, "funding": [ { @@ -95,13 +68,13 @@ } ], "engines": { - "node": ">=20.19.0" + "node": ">=18" } }, "node_modules/@csstools/css-calc": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", - "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", "dev": true, "funding": [ { @@ -114,17 +87,17 @@ } ], "engines": { - "node": ">=20.19.0" + "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, "node_modules/@csstools/css-color-parser": { - "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==", + "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==", "dev": true, "funding": [ { @@ -137,21 +110,21 @@ } ], "dependencies": { - "@csstools/color-helpers": "^6.0.2", - "@csstools/css-calc": "^3.2.1" + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" }, "engines": { - "node": ">=20.19.0" + "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, "node_modules/@csstools/css-parser-algorithms": { - "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==", + "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==", "dev": true, "funding": [ { @@ -164,16 +137,16 @@ } ], "engines": { - "node": ">=20.19.0" + "node": ">=18" }, "peerDependencies": { - "@csstools/css-tokenizer": "^4.0.0" + "@csstools/css-tokenizer": "^3.0.4" } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "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==", + "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==", "dev": true, "funding": [ { @@ -185,19 +158,14 @@ "url": "https://opencollective.com/csstools" } ], - "peerDependencies": { - "css-tree": "^3.2.1" - }, - "peerDependenciesMeta": { - "css-tree": { - "optional": true - } + "engines": { + "node": ">=18" } }, "node_modules/@csstools/css-tokenizer": { - "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==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", "dev": true, "funding": [ { @@ -210,44 +178,27 @@ } ], "engines": { - "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": ">=18" } }, "node_modules/@noble/hashes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", - "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "engines": { - "node": ">= 20.19.0" + "node": "^14.21.3 || >=16" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@playwright/test": { - "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==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", + "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", "dev": true, "dependencies": { - "playwright": "1.60.0" + "playwright": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -295,11 +246,12 @@ } }, "node_modules/@types/node": { - "version": "24.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", - "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", + "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, "dependencies": { - "undici-types": "~7.12.0" + "undici-types": "~7.10.0" } }, "node_modules/@types/trusted-types": { @@ -309,38 +261,6 @@ "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", @@ -369,6 +289,15 @@ "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", @@ -383,6 +312,15 @@ "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", @@ -402,33 +340,29 @@ } }, "node_modules/body-parser": { - "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==", + "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==", "dev": true, "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.3", + "debug": "^4.4.0", "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" }, "engines": { "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/body-parser/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -442,22 +376,6 @@ } } }, - "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", @@ -503,9 +421,9 @@ } }, "node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", + "integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==", "dev": true, "engines": { "node": ">=20" @@ -542,16 +460,15 @@ } }, "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "dev": true, - "engines": { - "node": ">=18" + "dependencies": { + "safe-buffer": "5.2.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">= 0.6" } }, "node_modules/content-type": { @@ -582,9 +499,9 @@ } }, "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "dev": true, "dependencies": { "object-assign": "^4", @@ -592,36 +509,46 @@ }, "engines": { "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/css-tree": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", "dev": true, "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/data-urls": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", - "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "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": { - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0" + "@asamuzakjp/css-color": "^4.0.3", + "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "css-tree": "^3.1.0" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "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==", + "dev": true, + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.0.0" + }, + "engines": { + "node": ">=20" } }, "node_modules/debug": { @@ -707,9 +634,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.1.tgz", - "integrity": "sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", + "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", "dev": true, "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -730,9 +657,9 @@ } }, "node_modules/dotenv": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", - "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", "dev": true, "engines": { "node": ">=12" @@ -742,12 +669,12 @@ } }, "node_modules/dotenv-expand": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-13.0.0.tgz", - "integrity": "sha512-aBfBS8eYIeXmpHI9ThIlA7/WLq+SLt18iXUZhb52rW89QLKQFoIpPG1bPeewoPZsTyjSSO3T7234FBVUM1V2rA==", + "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==", "dev": true, "dependencies": { - "dotenv": "^17.4.2" + "dotenv": "^16.4.5" }, "engines": { "node": ">=12" @@ -756,6 +683,18 @@ "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", @@ -795,21 +734,20 @@ } }, "node_modules/engine.io": { - "version": "6.6.8", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.8.tgz", - "integrity": "sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g==", + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", "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.4.1", + "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.20.1" + "ws": "~8.17.1" }, "engines": { "node": ">=10.2.0" @@ -838,9 +776,9 @@ } }, "node_modules/engine.io/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -890,6 +828,27 @@ "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", @@ -948,19 +907,18 @@ } }, "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "dev": true, "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.1", + "body-parser": "^2.2.0", "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", @@ -991,9 +949,9 @@ } }, "node_modules/express/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -1014,9 +972,9 @@ "dev": true }, "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "dev": true, "dependencies": { "debug": "^4.4.0", @@ -1027,17 +985,13 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.8" } }, "node_modules/finalhandler/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -1168,9 +1122,9 @@ } }, "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -1189,15 +1143,15 @@ } }, "node_modules/html-encoding-sniffer": { - "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==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dev": true, "dependencies": { - "@exodus/bytes": "^1.6.0" + "whatwg-encoding": "^3.1.1" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=18" } }, "node_modules/html-to-text": { @@ -1236,25 +1190,102 @@ } }, "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "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.2", - "toidentifier": "~1.0.1" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, + "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==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -1306,35 +1337,34 @@ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" }, "node_modules/jsdom": { - "version": "29.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.0.tgz", - "integrity": "sha512-YNUc7fB9QuvSSQWfrH0xF+TyABkxUwx8sswgIDaCrw4Hol8BghdZDkITtZheRJeMtzWlnTfsM3bBBusRvpO1wg==", + "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/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", + "@asamuzakjp/dom-selector": "^6.7.2", + "cssstyle": "^5.3.1", + "data-urls": "^6.0.0", "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^6.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.3.5", - "parse5": "^8.0.1", + "parse5": "^8.0.0", + "rrweb-cssom": "^0.8.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.1", - "undici": "^7.25.0", + "tough-cookie": "^6.0.0", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.1", - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.1", + "webidl-conversions": "^8.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", "xml-name-validator": "^5.0.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + "node": ">=20" }, "peerDependencies": { "canvas": "^3.0.0" @@ -1361,33 +1391,17 @@ "dev": true }, "node_modules/libmime": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.8.tgz", - "integrity": "sha512-ZrCY+Q66mPvasAfjsQ/IgahzoBvfE1VdtGRpo1hwRB1oK3wJKxhKA3GOcd2a6j7AH5eMFccxK9fBoCpRZTf8ng==", + "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.7.2", + "iconv-lite": "0.6.3", "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", @@ -1409,18 +1423,18 @@ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" }, "node_modules/lru-cache": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz", - "integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==", + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", "dev": true, "engines": { "node": "20 || >=22" } }, "node_modules/lru.min": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", - "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", + "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", "engines": { "bun": ">=1.0.0", "deno": ">=1.30.0", @@ -1433,25 +1447,25 @@ }, "node_modules/maildev": { "name": "@timshel_npm/maildev", - "version": "3.2.19", - "resolved": "https://registry.npmjs.org/@timshel_npm/maildev/-/maildev-3.2.19.tgz", - "integrity": "sha512-A/f07Fe7hCFy/2cUo0xg2r349RvOAHi4TuqOlZXxPuWShWbiSbvYcWr5wDz7G8aV5RyggNP0m+yB/lwmYx5OQg==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@timshel_npm/maildev/-/maildev-3.2.5.tgz", + "integrity": "sha512-suWQu2s2kmO+MXtNJYW9peklznhd+aorIUb4tSNrfaKoEJjDa3vLXTvWf+3cb67o4Yv4Z6nPeKdMTCDZVn/Nyw==", "dev": true, "dependencies": { "@types/mailparser": "3.4.6", "addressparser": "1.0.1", "async": "3.2.6", - "commander": "14.0.3", + "commander": "14.0.1", "compression": "1.8.1", - "cors": "2.8.6", - "dompurify": "3.4.1", - "express": "5.2.1", - "jsdom": "29.1.0", - "mailparser": "3.9.8", + "cors": "2.8.5", + "dompurify": "3.3.0", + "express": "5.1.0", + "jsdom": "27.0.1", + "mailparser": "3.7.5", "mime": "4.1.0", - "nodemailer": "8.0.7", - "smtp-server": "3.18.4", - "socket.io": "4.8.3", + "nodemailer": "7.0.9", + "smtp-server": "3.15.0", + "socket.io": "4.8.1", "wildstring": "1.0.9" }, "bin": { @@ -1462,27 +1476,27 @@ } }, "node_modules/mailparser": { - "version": "3.9.8", - "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.9.8.tgz", - "integrity": "sha512-7jSlFGXiianVnhnb6wdutJFloD34488nrHY7r6FNqwXAhZ7YiJDYrKKTxZJ0oSrXcAPHm8YoYnh97xyGtrBQ3w==", + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.7.5.tgz", + "integrity": "sha512-o59RgZC+4SyCOn4xRH1mtRiZ1PbEmi6si6Ufnd3tbX/V9zmZN1qcqu8xbXY62H6CwIclOT3ppm5u/wV2nujn4g==", "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.2", - "libmime": "5.3.8", + "iconv-lite": "0.7.0", + "libmime": "5.3.7", "linkify-it": "5.0.0", - "nodemailer": "8.0.5", + "mailsplit": "5.4.6", + "nodemailer": "7.0.9", "punycode.js": "2.3.1", - "tlds": "1.261.0" + "tlds": "1.260.0" } }, "node_modules/mailparser/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==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -1495,13 +1509,16 @@ "url": "https://opencollective.com/express" } }, - "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==", + "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.", "dev": true, - "engines": { - "node": ">=6.0.0" + "dependencies": { + "libbase64": "1.3.0", + "libmime": "5.3.7", + "libqp": "2.1.1" } }, "node_modules/math-intrinsics": { @@ -1514,9 +1531,9 @@ } }, "node_modules/mdn-data": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", "dev": true }, "node_modules/media-typer": { @@ -1565,19 +1582,15 @@ } }, "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "dev": true, "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" } }, "node_modules/ms": { @@ -1587,30 +1600,28 @@ "dev": true }, "node_modules/mysql2": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.3.tgz", - "integrity": "sha512-uWWxvZSRvRhtBdh2CdcuK83YcOfPdmEeEYB069bAmPnV93QApDGVPuvCQOLjlh7tYHEWdgQPrn6kosDxHBVLkA==", + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", + "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", "dependencies": { - "aws-ssl-profiles": "^1.1.2", + "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", - "iconv-lite": "^0.7.2", - "long": "^5.3.2", - "lru.min": "^1.1.4", - "named-placeholders": "^1.1.6", - "sql-escaper": "^1.3.3" + "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" }, "engines": { "node": ">= 8.0" - }, - "peerDependencies": { - "@types/node": ">= 8" } }, "node_modules/mysql2/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==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -1623,14 +1634,22 @@ } }, "node_modules/named-placeholders": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", - "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", "dependencies": { - "lru.min": "^1.1.0" + "lru-cache": "^7.14.1" }, "engines": { - "node": ">=8.0.0" + "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_modules/negotiator": { @@ -1643,9 +1662,9 @@ } }, "node_modules/nodemailer": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.7.tgz", - "integrity": "sha512-pkjE4mkBzQjdJT4/UmlKl3pX0rC9fZmjh7c6C9o7lv66Ac6w9WCnzPzhbPNxwZAzlF4mdq4CSWB5+FbK6FWCow==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.9.tgz", + "integrity": "sha512-9/Qm0qXIByEP8lEV2qOqcAW7bRpL8CR9jcTwk3NBnHJNmP9fIJ86g2fgmIXqHY+nj55ZEMwWqYAT2QTDpRUYiQ==", "dev": true, "engines": { "node": ">=6.0.0" @@ -1703,35 +1722,35 @@ } }, "node_modules/otpauth": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.5.1.tgz", - "integrity": "sha512-fJmDAHc8wImfqqqOXIlBvT1dEKrZK0Cmb2VEgScpNTolCz0PHh6ExUZGv4sLtOsWNaHCQlD+rRqaPgnoxFoZjQ==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.4.1.tgz", + "integrity": "sha512-+iVvys36CFsyXEqfNftQm1II7SW23W1wx9RwNk0Cd97lbvorqAhBDksb/0bYry087QMxjiuBS0wokdoZ0iUeAw==", "dependencies": { - "@noble/hashes": "2.2.0" + "@noble/hashes": "1.8.0" }, "funding": { "url": "https://github.com/hectorm/otpauth?sponsor=1" } }, "node_modules/parse5": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", - "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", "dev": true, "dependencies": { - "entities": "^8.0.0" + "entities": "^6.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/parse5/node_modules/entities": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", - "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, "engines": { - "node": ">=20.19.0" + "node": ">=0.12" }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" @@ -1760,13 +1779,12 @@ } }, "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">=16" } }, "node_modules/peberminta": { @@ -1779,13 +1797,13 @@ } }, "node_modules/pg": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", - "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "dependencies": { - "pg-connection-string": "^2.13.0", - "pg-pool": "^3.14.0", - "pg-protocol": "^1.14.0", + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, @@ -1793,7 +1811,7 @@ "node": ">= 16.0.0" }, "optionalDependencies": { - "pg-cloudflare": "^1.4.0" + "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" @@ -1805,15 +1823,15 @@ } }, "node_modules/pg-cloudflare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", - "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", "optional": true }, "node_modules/pg-connection-string": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", - "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==" + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==" }, "node_modules/pg-int8": { "version": "1.0.1", @@ -1824,17 +1842,17 @@ } }, "node_modules/pg-pool": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", - "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", - "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==" + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==" }, "node_modules/pg-types": { "version": "2.2.0", @@ -1860,12 +1878,12 @@ } }, "node_modules/playwright": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", - "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", + "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", "dev": true, "dependencies": { - "playwright-core": "1.60.0" + "playwright-core": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -1878,9 +1896,9 @@ } }, "node_modules/playwright-core": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", - "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", + "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -1956,9 +1974,9 @@ } }, "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "dev": true, "dependencies": { "side-channel": "^1.1.0" @@ -1980,34 +1998,18 @@ } }, "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", "dev": true, "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" }, "engines": { - "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": ">= 0.8" } }, "node_modules/require-from-string": { @@ -2036,9 +2038,9 @@ } }, "node_modules/router/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -2058,6 +2060,12 @@ "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", @@ -2108,35 +2116,31 @@ } }, "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "dev": true, "dependencies": { - "debug": "^4.4.3", + "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.2" + "statuses": "^2.0.1" }, "engines": { "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/send/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -2156,10 +2160,15 @@ "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.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "dev": true, "dependencies": { "encodeurl": "^2.0.0", @@ -2169,10 +2178,6 @@ }, "engines": { "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { @@ -2201,13 +2206,13 @@ } }, "node_modules/side-channel-list": { - "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==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" + "object-inspect": "^1.13.3" }, "engines": { "node": ">= 0.4" @@ -2254,38 +2259,30 @@ } }, "node_modules/smtp-server": { - "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==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/smtp-server/-/smtp-server-3.15.0.tgz", + "integrity": "sha512-yv945vk0/xcukSKAoIhGz6GOlcXoCyGQH2w9IlLrTKk3SJiOBH9bcO6tD0ILTZYJsMqRa6OTRZAyqeuLXkv59Q==", "dev": true, "dependencies": { + "base32.js": "0.1.0", "ipv6-normalize": "1.0.1", - "nodemailer": "8.0.5", + "nodemailer": "7.0.9", "punycode.js": "2.3.1" }, "engines": { - "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": ">=12.0.0" } }, "node_modules/socket.io": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", - "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", "dev": true, "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", - "debug": "~4.4.1", + "debug": "~4.3.2", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" @@ -2295,19 +2292,19 @@ } }, "node_modules/socket.io-adapter": { - "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==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", "dev": true, "dependencies": { - "debug": "~4.4.1", - "ws": "~8.20.1" + "debug": "~4.3.4", + "ws": "~8.17.1" } }, "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -2327,23 +2324,44 @@ "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.6", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", - "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", "dev": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1" + "debug": "~4.3.1" }, "engines": { "node": ">=10.0.0" } }, "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -2377,9 +2395,9 @@ } }, "node_modules/socket.io/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -2446,18 +2464,12 @@ "node": ">= 10.x" } }, - "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==", + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", "engines": { - "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": ">= 0.6" } }, "node_modules/statuses": { @@ -2476,30 +2488,30 @@ "dev": true }, "node_modules/tlds": { - "version": "1.261.0", - "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz", - "integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==", + "version": "1.260.0", + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.260.0.tgz", + "integrity": "sha512-78+28EWBhCEE7qlyaHA9OR3IPvbCLiDh3Ckla593TksfFc9vfTsgvH7eS+dr3o9qr31gwGbogcI16yN91PoRjQ==", "dev": true, "bin": { "tlds": "bin.js" } }, "node_modules/tldts": { - "version": "7.0.30", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz", - "integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==", + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz", + "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==", "dev": true, "dependencies": { - "tldts-core": "^7.0.30" + "tldts-core": "^7.0.17" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.30", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz", - "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz", + "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==", "dev": true }, "node_modules/toidentifier": { @@ -2512,9 +2524,9 @@ } }, "node_modules/tough-cookie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", - "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", "dev": true, "dependencies": { "tldts": "^7.0.5" @@ -2536,34 +2548,17 @@ } }, "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "dev": true, "dependencies": { - "content-type": "^2.0.0", + "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "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": ">= 0.6" } }, "node_modules/uc.micro": { @@ -2572,19 +2567,11 @@ "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.12.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", - "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==" + "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 }, "node_modules/unpipe": { "version": "1.0.0", @@ -2617,35 +2604,46 @@ } }, "node_modules/webidl-conversions": { - "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==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", "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": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", - "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "dev": true, "engines": { - "node": ">=20" + "node": ">=18" } }, "node_modules/whatwg-url": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", - "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", "dev": true, "dependencies": { - "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", - "webidl-conversions": "^8.0.1" + "webidl-conversions": "^8.0.0" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=20" } }, "node_modules/wildstring": { @@ -2661,9 +2659,9 @@ "dev": true }, "node_modules/ws": { - "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==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "engines": { "node": ">=10.0.0" diff --git a/playwright/package.json b/playwright/package.json index a7a35734..f47ec5dc 100644 --- a/playwright/package.json +++ b/playwright/package.json @@ -8,14 +8,14 @@ "author": "", "license": "ISC", "devDependencies": { - "@playwright/test": "1.60.0", - "dotenv": "17.4.2", - "dotenv-expand": "13.0.0", - "maildev": "npm:@timshel_npm/maildev@3.2.19" + "@playwright/test": "1.56.1", + "dotenv": "17.2.3", + "dotenv-expand": "12.0.3", + "maildev": "npm:@timshel_npm/maildev@3.2.5" }, "dependencies": { - "mysql2": "3.22.3", - "otpauth": "9.5.1", - "pg": "8.21.0" + "mysql2": "3.15.3", + "otpauth": "9.4.1", + "pg": "8.16.3" } } diff --git a/playwright/playwright.config.ts b/playwright/playwright.config.ts index ba5885d9..de721aa3 100644 --- a/playwright/playwright.config.ts +++ b/playwright/playwright.config.ts @@ -25,12 +25,10 @@ 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: 240 * 1000, - actionTimeout: 40 * 1000, - navigationTimeout: 40 * 1000, - expect: { timeout: 40 * 1000 }, - - "permissions": ["clipboard-read"], + timeout: 120 * 1000, + actionTimeout: 20 * 1000, + navigationTimeout: 20 * 1000, + expect: { timeout: 20 * 1000 }, /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { @@ -39,10 +37,6 @@ 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 2260f860..df182ebe 100644 --- a/playwright/test.env +++ b/playwright/test.env @@ -10,7 +10,7 @@ DOCKER_BUILDKIT=1 ##################### # Playwright Config # ##################### -PW_KEEP_SERVICE_RUNNING=${PW_KEEP_SERVICE_RUNNING:-false} +PW_KEEP_SERVICE_RUNNNING=${PW_KEEP_SERVICE_RUNNNING:-false} PW_SMTP_FROM=vaultwarden@playwright.test ##################### @@ -38,8 +38,8 @@ TEST_USER3_MAIL=${TEST_USER3}@example.com ################### # Keycloak Config # ################### -KC_BOOTSTRAP_ADMIN_USERNAME=admin -KC_BOOTSTRAP_ADMIN_PASSWORD=${KC_BOOTSTRAP_ADMIN_USERNAME} +KEYCLOAK_ADMIN=admin +KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN} KC_HTTP_HOST=127.0.0.1 KC_HTTP_PORT=8081 @@ -52,12 +52,10 @@ DUMMY_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${DUMMY_REALM} # Vaultwarden Config # ###################### ROCKET_PORT=8003 -ROCKET_TLS={certs="/data/ssl/localhost.crt",key="/data/ssl/localhost.key"} -DOMAIN=https://127.0.0.1:${ROCKET_PORT} +DOMAIN=http://localhost:${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 867386a5..786a4644 100644 --- a/playwright/tests/collection.spec.ts +++ b/playwright/tests/collection.spec.ts @@ -1,8 +1,6 @@ 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(); @@ -18,12 +16,20 @@ test.afterAll('Teardown', async ({}) => { test('Create', async ({ page }) => { await createAccount(test, page, users.user1); - await orgs.create(test, page, 'New organisation'); + 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 test.step('Create Collection', async () => { - await page.getByRole('button', { name: 'New', exact: true }).click(); + await page.getByRole('link', { name: 'Collections' }).click(); + await page.getByRole('button', { name: 'New' }).click(); await page.getByRole('menuitem', { name: 'Collection' }).click(); - await page.getByRole('textbox', { name: 'Name * (required)', exact: true }).fill('RandomCollec'); + await page.getByLabel('Name (required)').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 deleted file mode 100644 index 679874de..00000000 --- a/playwright/tests/cyphers.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -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 c5c4d9ba..87474b79 100644 --- a/playwright/tests/login.smtp.spec.ts +++ b/playwright/tests/login.smtp.spec.ts @@ -41,10 +41,13 @@ 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.getByRole('button', { name: "Send email" }).click(); + 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 utils.checkNotification(page, 'Check your email inbox for a verification link'); const verify = await mailBuffer.expect((m) => m.subject === "Verify Your Email"); @@ -75,10 +78,26 @@ test('Activate 2fa', async ({ page }) => { test('2fa', async ({ page }) => { const emails = mailserver.buffer(users.user1.email); - await logUser(test, page, users.user1, { - mailBuffer: emails, - mail2fa: true, - }); + 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); emails.close(); }); diff --git a/playwright/tests/login.spec.ts b/playwright/tests/login.spec.ts index 194976ea..aaac4708 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.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(users.user1.password); - await page.getByRole('button', { name: 'Log in', exact: true }).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(); 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 1e97ed5d..2be5fec1 100644 --- a/playwright/tests/organization.smtp.spec.ts +++ b/playwright/tests/organization.smtp.spec.ts @@ -4,7 +4,6 @@ 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(); @@ -21,7 +20,6 @@ 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); @@ -47,7 +45,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: 'Automatically enroll new' }).check(); + await page.getByRole('checkbox', { name: 'Require new members' }).check(); await page.getByRole('button', { name: 'Save' }).click(); await utils.checkNotification(page, 'Edited policy Account recovery'); }); @@ -68,16 +66,18 @@ 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.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.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.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,19 +100,21 @@ test('invited with existing account', async ({ page }) => { await page.getByRole('button', { name: 'Continue' }).click(); // Unlock 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(); + 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); // 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, { mailBuffer: mail1Buffer }); + await logUser(test, page, users.user1, mail1Buffer); await orgs.members(test, page, 'Test'); await orgs.confirm(test, page, 'Test', users.user2.email); @@ -121,30 +123,25 @@ test('Confirm invited user', async ({ page }) => { }); test('Organization is visible', async ({ page }) => { - await logUser(test, page, users.user2, { mailBuffer: mail2Buffer }); + await logUser(test, page, users.user2, 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.user2, { mailBuffer: mail2Buffer }); - await activateTOTP(test, page, users.user2); - - await logUser(test, page, users.user1, { mailBuffer: mail1Buffer }); + await logUser(test, page, users.user1, mail1Buffer); let newPassword = "TotoNewPassword"; await orgs.members(test, page, 'Test'); - await test.step(`Recover ${users.user2.email}`, async () => { + await test.step(`Rrcover ${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('checkbox', { name: 'Reset two-step login' }).check(); - await page.getByRole('button', { name: 'Save' }).click(); - await utils.checkNotification(page, 'Account recovery success'); - await mail2Buffer.expect((m) => m.subject.includes('Admin account recovery from Test organization')); + 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'); }); let user2 = { @@ -152,9 +149,5 @@ test('Recover user password', async ({ page }) => { name: users.user2.name, password: newPassword, }; - await logUser(test, page, user2, { - mailBuffer: mail2Buffer, - mail2fa: true, - notNewDevice: true, - }); + await logUser(test, page, user2, mail2Buffer); }); diff --git a/playwright/tests/secrets.spec.ts b/playwright/tests/secrets.spec.ts deleted file mode 100644 index e229d400..00000000 --- a/playwright/tests/secrets.spec.ts +++ /dev/null @@ -1,110 +0,0 @@ -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 ddb8009d..d27c3ffc 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 Send', exact: true }).click(); + await page.getByRole('button', { name: 'New', 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' }).click(); + await page.getByRole('button', { name: 'New', exact: true }).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)', exact: true }).fill('password'); + await page.getByRole('textbox', { name: 'Password (required)' }).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 d430d053..d7936420 100644 --- a/playwright/tests/setups/2fa.ts +++ b/playwright/tests/setups/2fa.ts @@ -11,11 +11,10 @@ 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.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(user.password); + await page.getByLabel('Master password (required)').fill(user.password); await page.getByRole('button', { name: 'Continue' }).click(); - const secret = await page.getByLabel('Key', { exact: true }).innerText(); - + const secret = await page.getByLabel('Key').innerText(); let totp = new OTPAuth.TOTP({ secret, period: 30 }); await page.getByLabel(/Verification code/).fill(totp.generate()); @@ -34,8 +33,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.getByRole('textbox', { name: 'Master password * (required)', exact: true }).click() - await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(user.password); + await page.getByLabel('Master password (required)').click(); + await page.getByLabel('Master password (required)').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(); @@ -50,7 +49,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.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(user.password); + await page.getByLabel('Master password (required)').fill(user.password); await page.getByRole('button', { name: 'Continue' }).click(); await page.getByRole('button', { name: 'Send email' }).click(); }); @@ -82,8 +81,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.getByRole('textbox', { name: 'Master password * (required)', exact: true }).click() - await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(user.password); + await page.getByLabel('Master password (required)').click(); + await page.getByLabel('Master password (required)').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 deleted file mode 100644 index 354c9ee7..00000000 --- a/playwright/tests/setups/admin.ts +++ /dev/null @@ -1,21 +0,0 @@ -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 86d40ac5..5f753a9d 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_RUNNING !== "true" ) { + if( process.env.PW_KEEP_SERVICE_RUNNNING !== "true" ) { utils.stopComposeService(serviceName); } }); diff --git a/playwright/tests/setups/orgs.ts b/playwright/tests/setups/orgs.ts index ce12c50e..04d81b45 100644 --- a/playwright/tests/setups/orgs.ts +++ b/playwright/tests/setups/orgs.ts @@ -3,14 +3,11 @@ 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 ${name}`, async () => { - let pm_locator = page.locator('a').filter({ hasText: 'Password Manager' }); - if( await pm_locator.count() > 0 ){ - pm_locator.first().click(); - } + await test.step('Create Org', async () => { + await page.locator('a').filter({ hasText: 'Password Manager' }).first().click(); await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible(); await page.getByRole('link', { name: 'New organisation' }).click(); - await page.getByRole('textbox', { name: 'Organisation name * (required)', exact: true }).fill(name); + await page.getByLabel('Organisation name (required)').fill(name); await page.getByRole('button', { name: 'Submit' }).click(); await utils.checkNotification(page, 'Organisation created'); @@ -21,7 +18,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 > bit-nav-group > div > bit-nav-item').filter({ hasText: `${name}` }).first().click(); + await page.locator('org-switcher').getByRole('link', { name: `${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(); @@ -33,11 +30,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 > bit-nav-group > div > bit-nav-item').filter({ hasText: `${name}` }).first().click(); + await page.locator('org-switcher').getByRole('link', { name: `${name}` }).first().click(); await expect(page.getByRole('heading', { name: `${name} collections` })).toBeVisible(); - await page.getByRole('link', { name: 'Members' }).click(); + await page.locator('div').filter({ hasText: 'Members' }).nth(2).click(); await expect(page.getByRole('heading', { name: 'Members' })).toBeVisible(); - await expect(page.getByRole('columnheader', { name: 'Select all' })).toBeVisible(); + await expect(page.getByRole('cell', { name: 'All' })).toBeVisible(); }); } @@ -45,13 +42,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.getByRole('textbox', { name: 'Email * (required)', exact: true }).fill(email); + await page.getByLabel('Email (required)').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.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.getByLabel('Select collections').click(); + await page.getByText('Default collection').click(); + await page.getByRole('cell', { 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 22934b75..2899afff 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_RUNNING === "true" ) { + if( process.env.PW_KEEP_SERVICE_RUNNNING === "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 0ad0cffb..6317f8b0 100644 --- a/playwright/tests/setups/sso.ts +++ b/playwright/tests/setups/sso.ts @@ -15,8 +15,11 @@ 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(); }); @@ -30,24 +33,26 @@ export async function logNewUser( 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(user.password); - await page.getByRole('textbox', { name: 'Confirm master password * (' }).fill(user.password); + await page.getByLabel('Master password (required)', { exact: true }).fill(user.password); + await page.getByLabel('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.includes("New Device Logged")); await mailBuffer.expect((m) => m.subject === "Welcome"); + await mailBuffer.expect((m) => m.subject.includes("New Device Logged")); }); } }); @@ -64,14 +69,16 @@ 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(); }); @@ -110,12 +117,14 @@ 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 && !options.notNewDevice ){ + if( mailBuffer ){ 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 3d3990e9..395196ae 100644 --- a/playwright/tests/setups/user.ts +++ b/playwright/tests/setups/user.ts @@ -3,7 +3,6 @@ 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 () => { @@ -18,11 +17,12 @@ export async function createAccount(test, page: Page, user: { email: string, nam await page.getByRole('button', { name: 'Continue' }).click(); // Vault finish Creation - 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.getByLabel('Master password (required)', { exact: true }).fill(user.password); + await page.getByLabel('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,16 +35,7 @@ export async function createAccount(test, page: Page, user: { email: string, nam }); } -export async function logUser( - test, - page: Page, - user: { email: string, password: string }, - options: { - mailBuffer ?: MailBuffer, - mail2fa?: boolean, - notNewDevice?: boolean, - } = {} -) { +export async function logUser(test, page: Page, user: { email: string, password: string }, mailBuffer?: MailBuffer) { await test.step(`Log user ${user.email}`, async () => { await utils.cleanLanding(page); @@ -52,23 +43,16 @@ export async function logUser( await page.getByRole('button', { name: 'Continue' }).click(); // Unlock page - await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(user.password); - await page.getByRole('button', { name: 'Log in', exact: true }).click(); + await page.getByLabel('Master password').fill(user.password); + await page.getByRole('button', { name: 'Log in with master password' }).click(); - 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(); - }); - } + await utils.ignoreExtension(page); // We are now in the default vault page await expect(page).toHaveTitle(/Vaultwarden Web/); - if( options.mailBuffer && !options.notNewDevice ){ - await options.mailBuffer.expect((m) => m.subject === "New Device Logged In From Firefox"); + if( mailBuffer ){ + await 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 1f5c9361..7a615cd6 100644 --- a/playwright/tests/sso_login.smtp.spec.ts +++ b/playwright/tests/sso_login.smtp.spec.ts @@ -1,7 +1,6 @@ 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"; @@ -20,7 +19,7 @@ test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => { await utils.startVault(browser, testInfo, { SSO_ENABLED: true, - SSO_ONLY: true, + SSO_ONLY: false, SMTP_HOST: process.env.MAILDEV_HOST, SMTP_FROM: process.env.PW_SMTP_FROM, }); @@ -33,64 +32,22 @@ test.afterAll('Teardown', async ({}) => { } }); -test('2FA email', async ({ page }) => { - +test('Create and activate 2FA', 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}); + mailBuffer.close(); +}); + +test('Log and disable', async ({ page }) => { + const mailBuffer = mailserver.buffer(users.user1.email); + + await logUser(test, page, users.user1, {mailBuffer: mailBuffer, mail2fa: true}); await disableEmail(test, page, users.user1); mailBuffer.close(); }); - - -test('Admin invite', async ({ page }) => { - const mailBuffer = mailserver.buffer(users.user2.email); - - await admin.login(test, page); - await admin.invite(test, page, users.user2.email); - - - const link = await test.step('Extract email link', async () => { - const invited = await mailBuffer.expect((m) => m.subject === "Join Vaultwarden"); - await page.setContent(invited.html); - return await page.getByTestId("invite").getAttribute("href"); - }); - - await test.step('Redirect to Keycloak', async () => { - await page.goto(link); - }); - - await test.step('Keycloak login', async () => { - await expect(page.getByRole('heading', { name: 'Sign in to your account' })).toBeVisible(); - await page.getByLabel(/Username/).fill(users.user2.name); - await page.getByLabel('Password', { exact: true }).fill(users.user2.password); - await page.getByRole('button', { name: 'Sign In' }).click(); - }); - - await test.step('Create Vault account', async () => { - await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible(); - await page.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(users.user2.password); - await page.getByRole('textbox', { name: 'Confirm master password * (' }).fill(users.user2.password); - await page.getByRole('button', { name: 'Create account' }).click(); - }); - - await test.step('Default vault page', async () => { - await expect(page).toHaveTitle('Vaults | Vaultwarden Web'); - - await utils.checkNotification(page, 'Account successfully created!'); - await utils.checkNotification(page, 'Invitation accepted'); - }); - - await test.step('Check mails', async () => { - await mailBuffer.expect((m) => m.subject.includes("New Device Logged")); - await mailBuffer.expect((m) => m.subject === "Welcome"); - }); - - mailBuffer.close(); -}); diff --git a/playwright/tests/sso_login.spec.ts b/playwright/tests/sso_login.spec.ts index e93aab14..8a1bb9ab 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.getByRole('textbox', { name: 'Master password * (required)', exact: true }).fill(users.user1.password); - await page.getByRole('button', { name: 'Log in', exact: true }).click(); + await page.getByLabel('Master password').fill(users.user1.password); + await page.getByRole('button', { name: 'Log in with master password' }).click(); // We are now in the default vault page await expect(page).toHaveTitle(/Vaultwarden Web/); @@ -58,7 +58,6 @@ 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); @@ -67,6 +66,7 @@ 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,14 +74,12 @@ 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' })).toHaveCount(1); + await expect(page.getByRole('button', { name: 'Log in with master password' })).toHaveCount(1); }); diff --git a/playwright/tests/sso_organization.smtp.spec.ts b/playwright/tests/sso_organization.smtp.spec.ts index eef4f83d..92813f72 100644 --- a/playwright/tests/sso_organization.smtp.spec.ts +++ b/playwright/tests/sso_organization.smtp.spec.ts @@ -67,16 +67,17 @@ 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.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.getByLabel('Master password (required)', { exact: true }).fill(users.user2.password); + await page.getByLabel('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 () => { @@ -94,7 +95,6 @@ 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,11 +108,13 @@ 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 ee7e28f6..c1238d45 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('Activate password policy', async ({ page }) => { +test('Enforce password policy', async ({ page }) => { await logUser(test, page, users.user1); await orgs.policies(test, page, '/Test'); @@ -61,27 +61,16 @@ test('Activate password policy', async ({ page }) => { await page.getByRole('button', { name: 'Save' }).click(); await utils.checkNotification(page, 'Edited policy Master password requirements.'); }); -}); -test('Unlock trigger policyy', async ({ page }) => { - await page.goto('/', { waitUntil: 'domcontentloaded' }); + await utils.logout(test, page, users.user1); - await page.locator("input[type=email].vw-email-sso").fill(users.user2.email); - await page.getByRole('button', { name: /Use single sign-on/ }).click(); + await test.step(`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 test.step('Keycloak login', async () => { - await expect(page.getByRole('heading', { name: 'Sign in to your account' })).toBeVisible(); - await page.getByLabel(/Username/).fill(users.user2.name); - await page.getByLabel('Password', { exact: true }).fill(users.user2.password); - await page.getByRole('button', { name: 'Sign In' }).click(); - }); - - await test.step('Unlock vault', async () => { - await expect(page).toHaveTitle('Vaultwarden Web'); - await expect(page.getByRole('heading', { name: 'Your vault is locked' })).toBeVisible(); - await page.getByLabel('Master password').fill(users.user2.password); + await page.getByRole('textbox', { name: 'Master password (required)' }).fill(users.user1.password); await page.getByRole('button', { name: 'Unlock' }).click(); - }); - await expect(page.getByRole('heading', { name: 'Update master password' })).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Update master password' })).toBeVisible(); + }); }); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 2be20926..9c5862a2 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.98.1" +channel = "1.97.1" components = [ "rustfmt", "clippy" ] profile = "minimal" diff --git a/src/api/admin.rs b/src/api/admin.rs index 4bdf8e71..7037bfb1 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -231,7 +231,7 @@ fn validate_token(token: &str) -> bool { None => false, Some(t) if t.starts_with("$argon2") => { use argon2::password_hash::PasswordVerifier; - match argon2::password_hash::phc::PasswordHash::new(t) { + match argon2::password_hash::PasswordHash::new(t) { Ok(h) => { // NOTE: hash params from `ADMIN_TOKEN` are used instead of what is configured in the `Argon2` instance. argon2::Argon2::default().verify_password(token.trim().as_ref(), &h).is_ok() @@ -425,7 +425,7 @@ async fn delete_user(user_id: UserId, token: AdminToken, conn: DbConn) -> EmptyR for membership in memberships { log_event( - EventType::OrganizationUserDeleted, + EventType::OrganizationUserDeleted as i32, &membership.uuid, &membership.org_uuid, &ACTING_ADMIN_USER.into(), @@ -446,7 +446,7 @@ async fn delete_sso_user(user_id: UserId, token: AdminToken, conn: DbConn) -> Em for membership in memberships { log_event( - EventType::OrganizationUserUnlinkedSso, + EventType::OrganizationUserUnlinkedSso as i32, &membership.uuid, &membership.org_uuid, &ACTING_ADMIN_USER.into(), @@ -571,7 +571,7 @@ async fn update_membership_type(data: Json, token: AdminToke OrgPolicy::check_user_allowed(&member_to_edit, "modify", &conn).await?; log_event( - EventType::OrganizationUserUpdated, + EventType::OrganizationUserUpdated as i32, &member_to_edit.uuid, &data.org_uuid, &ACTING_ADMIN_USER.into(), @@ -647,7 +647,7 @@ use cached::macros::cached; /// Cache this function to prevent API call rate limit. Github only allows 60 requests per hour, and we use 3 here already /// It will cache this function for 600 seconds (10 minutes) which should prevent the exhaustion of the rate limit /// Any cache will be lost if Vaultwarden is restarted -#[cached(ttl_secs = 600, sync_writes = "default")] +#[cached(ttl = 600, sync_writes = "default")] async fn get_release_info(has_http_access: bool) -> (String, String, String) { // If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway. if has_http_access { @@ -716,36 +716,6 @@ 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::*; @@ -800,7 +770,6 @@ 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 69be1334..0cb4d3c0 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -595,52 +595,29 @@ async fn post_keys(data: Json, headers: Headers, conn: DbConn) -> Json #[serde(rename_all = "camelCase")] struct ChangePassData { master_password_hash: String, + new_master_password_hash: String, master_password_hint: Option, - authentication_data: Option, - unlock_data: Option, - - // Outdated values, might still be used by older clients - new_master_password_hash: Option, - key: Option, + key: String, } #[post("/accounts/password", data = "")] async fn post_password(data: Json, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { let data: ChangePassData = data.into_inner(); - let user = headers.user; + let mut user = headers.user; if !user.check_valid_password(&data.master_password_hash) { err!("Invalid password") } - log_user_event(EventType::UserChangedPassword as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn) - .await; - - let (new_master_password_hash, new_key) = - if let (Some(unlock_data), Some(authentication_data)) = (data.unlock_data, data.authentication_data) { - if authentication_data.kdf != unlock_data.kdf { - err!("KDF settings must be equal for authentication and unlock") - } - - if user.email != authentication_data.salt || user.email != unlock_data.salt { - err!("Invalid master password salt") - } - - (authentication_data.master_password_authentication_hash, unlock_data.master_key_wrapped_user_key) - } else if let (Some(new_master_password_hash), Some(new_key)) = (data.new_master_password_hash, data.key) { - (new_master_password_hash, new_key) - } else { - err!("Invalid request!") - }; - - let mut user = user; - user.password_hint = clean_password_hint(data.master_password_hint.as_ref()); enforce_password_hint_setting(user.password_hint.as_ref())?; + log_user_event(EventType::UserChangedPassword as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn) + .await; + user.set_password( - &new_master_password_hash, - Some(new_key), + &data.new_master_password_hash, + Some(data.key), true, Some(vec![ String::from("post_rotatekey"), @@ -1340,13 +1317,11 @@ pub struct PreloginData { } #[post("/accounts/prelogin", data = "")] -async fn post_prelogin(data: Json, ip: ClientIp, conn: DbConn) -> JsonResult { - prelogin(data, ip, conn).await +async fn post_prelogin(data: Json, conn: DbConn) -> Json { + prelogin(data, conn).await } -pub async fn prelogin(data: Json, ip: ClientIp, conn: DbConn) -> JsonResult { - crate::ratelimit::check_limit_unauthenticated(&ip.ip)?; - +pub async fn prelogin(data: Json, conn: DbConn) -> Json { let data: PreloginData = data.into_inner(); let (kdf_type, kdf_iter, kdf_mem, kdf_para) = match User::find_by_mail(&data.email, &conn).await { @@ -1354,7 +1329,7 @@ pub async fn prelogin(data: Json, ip: ClientIp, conn: DbConn) -> J None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT, None, None), }; - Ok(Json(json!({ + Json(json!({ "kdf": kdf_type, "kdfIterations": kdf_iter, "kdfMemory": kdf_mem, @@ -1366,7 +1341,7 @@ pub async fn prelogin(data: Json, ip: ClientIp, conn: DbConn) -> J "parallelism": kdf_para }, "salt": null, - }))) + })) } // https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Auth/Models/Request/Accounts/SecretVerificationRequestModel.cs @@ -1597,8 +1572,6 @@ async fn post_auth_request( conn: DbConn, nt: Notify<'_>, ) -> JsonResult { - crate::ratelimit::check_limit_unauthenticated(&client_headers.ip.ip)?; - let data = data.into_inner(); let Some(user) = User::find_by_mail(&data.email, &conn).await else { @@ -1611,7 +1584,7 @@ async fn post_auth_request( _ => err!("AuthRequest doesn't exist", "Device verification failed"), }; - let auth_request = AuthRequest::new( + let mut auth_request = AuthRequest::new( user.uuid.clone(), data.device_identifier.clone(), client_headers.device_type, @@ -1760,8 +1733,6 @@ async fn get_auth_request_response( client_headers: ClientHeaders, conn: DbConn, ) -> JsonResult { - crate::ratelimit::check_limit_unauthenticated(&client_headers.ip.ip)?; - let Some(auth_request) = AuthRequest::find_by_uuid(&auth_request_id, &conn).await else { err!("AuthRequest doesn't exist", "User not found") }; diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 50be6732..0cdae612 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -167,7 +167,7 @@ async fn sync(data: SyncData, headers: Headers, client_version: Option match NaiveDateTime::parse_from_str(&dt_str, "%+") { + if let Some(dt_str) = data.archived_date { + match NaiveDateTime::parse_from_str(&dt_str, "%+") { Ok(dt) => cipher.set_archived_at(dt, &headers.user.uuid, conn).await?, Err(err) => warn!("Error parsing ArchivedDate '{dt_str}': {err}"), - }, - None => cipher.unarchive(&headers.user.uuid, conn).await?, + } } if ut != UpdateType::None { @@ -554,8 +553,16 @@ pub async fn update_cipher_from_data( (_, _) => EventType::CipherUpdated, }; - log_event(event_type, &cipher.uuid, org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn) - .await; + log_event( + event_type as i32, + &cipher.uuid, + org_id, + &headers.user.uuid, + headers.device.atype, + &headers.ip.ip, + conn, + ) + .await; } nt.send_cipher_update( ut, @@ -843,7 +850,7 @@ async fn post_collections_update( .await; log_event( - EventType::CipherUpdatedCollections, + EventType::CipherUpdatedCollections as i32, &cipher.uuid, org_uuid, &headers.user.uuid, @@ -863,7 +870,7 @@ async fn put_collections_admin( headers: Headers, conn: DbConn, nt: Notify<'_>, -) -> JsonResult { +) -> EmptyResult { post_collections_admin(cipher_id, data, headers, conn, nt).await } @@ -874,7 +881,7 @@ async fn post_collections_admin( headers: Headers, conn: DbConn, nt: Notify<'_>, -) -> JsonResult { +) -> EmptyResult { let data: CollectionsAdminData = data.into_inner(); let Some(cipher) = Cipher::find_by_uuid(&cipher_id, &conn).await else { @@ -923,7 +930,7 @@ async fn post_collections_admin( .await; log_event( - EventType::CipherUpdatedCollections, + EventType::CipherUpdatedCollections as i32, &cipher.uuid, org_uuid, &headers.user.uuid, @@ -933,7 +940,7 @@ async fn post_collections_admin( ) .await; - Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::Organization, &conn).await?)) + Ok(()) } #[derive(Deserialize)] @@ -1328,7 +1335,7 @@ async fn save_attachment( if let Some(org_id) = &cipher.organization_uuid { log_event( - EventType::CipherAttachmentCreated, + EventType::CipherAttachmentCreated as i32, &cipher.uuid, org_id, &headers.user.uuid, @@ -1689,7 +1696,7 @@ async fn purge_org_vault( nt.send_user_update(UpdateType::SyncVault, &user, headers.device.push_uuid.as_ref(), &conn).await; log_event( - EventType::OrganizationPurgedVault, + EventType::OrganizationPurgedVault as i32, &organization.org_id, &organization.org_id, &user.uuid, @@ -1817,9 +1824,9 @@ async fn delete_cipher_by_uuid( let event_type = if *delete_options == CipherDeleteOptions::SoftSingle || *delete_options == CipherDeleteOptions::SoftMulti { - EventType::CipherSoftDeleted + EventType::CipherSoftDeleted as i32 } else { - EventType::CipherDeleted + EventType::CipherDeleted as i32 }; log_event(event_type, &cipher.uuid, &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn) @@ -1888,7 +1895,7 @@ async fn restore_cipher_by_uuid( if let Some(org_id) = &cipher.organization_uuid { log_event( - EventType::CipherRestored, + EventType::CipherRestored as i32, &cipher.uuid.clone(), org_id, &headers.user.uuid, @@ -1965,7 +1972,7 @@ async fn delete_cipher_attachment_by_id( if let Some(ref org_id) = cipher.organization_uuid { log_event( - EventType::CipherAttachmentDeleted, + EventType::CipherAttachmentDeleted as i32, &cipher.uuid, org_id, &headers.user.uuid, diff --git a/src/api/core/events.rs b/src/api/core/events.rs index 2c437a36..5518fa3c 100644 --- a/src/api/core/events.rs +++ b/src/api/core/events.rs @@ -10,7 +10,7 @@ use crate::{ auth::{AdminHeaders, Headers}, db::{ DbConn, DbPool, - models::{Cipher, CipherId, Event, EventType, Membership, MembershipId, OrganizationId, UserId}, + models::{Cipher, CipherId, Event, Membership, MembershipId, OrganizationId, UserId}, }, util::parse_date, }; @@ -267,7 +267,7 @@ async fn log_user_event_impl( } pub async fn log_event( - event_type: EventType, + event_type: i32, source_uuid: &str, org_id: &OrganizationId, act_user_id: &UserId, @@ -278,7 +278,7 @@ pub async fn log_event( if !CONFIG.org_events_enabled() { return; } - log_event_impl(event_type as i32, source_uuid, org_id, act_user_id, device_type, None, ip, conn).await; + log_event_impl(event_type, source_uuid, org_id, act_user_id, device_type, None, ip, conn).await; } #[expect(clippy::too_many_arguments)] diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index e6a184dd..a5ae50a4 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -238,7 +238,7 @@ fn config() -> Json { "disableUserRegistration": CONFIG.is_signup_disabled(), // When enabled, this setting signals to clients that onboarding interstitials // (post-login welcome dialogs, extension install prompts, setup extension redirects, and premium upsell modals) should be suppressed - "suppressOnboardingInterstitials": CONFIG.client_suppress_onboarding(), + "suppressOnboardingInterstitials": false }, "environment": { "vault": domain, diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 4f490854..989ca47d 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use num_traits::FromPrimitive; -use rocket::{Route, http::Status, serde::json::Json}; +use rocket::{Route, serde::json::Json}; use serde_json::Value; use crate::{ @@ -17,8 +17,7 @@ use crate::{ models::{ Cipher, CipherId, Collection, CollectionCipher, CollectionGroup, CollectionId, CollectionUser, EventType, Group, GroupId, GroupUser, Invitation, Membership, MembershipId, MembershipStatus, MembershipType, - OrgPolicy, OrgPolicyType, Organization, OrganizationApiKey, OrganizationId, TwoFactor, TwoFactorType, User, - UserId, + OrgPolicy, OrgPolicyType, Organization, OrganizationApiKey, OrganizationId, User, UserId, }, }, mail, @@ -133,6 +132,7 @@ struct FullCollectionData { name: String, groups: Vec, users: Vec, + id: Option, external_id: Option, } @@ -269,7 +269,7 @@ async fn leave_organization(org_id: OrganizationId, headers: OrgMemberHeaders, c } log_event( - EventType::OrganizationUserLeft, + EventType::OrganizationUserLeft as i32, &membership.uuid, &org_id, &headers.user.uuid, @@ -327,7 +327,7 @@ async fn post_organization( org.save(&conn).await?; log_event( - EventType::OrganizationUpdated, + EventType::OrganizationUpdated as i32, org_id.as_ref(), &org_id, &headers.user.uuid, @@ -391,7 +391,7 @@ async fn get_org_collections(org_id: OrganizationId, headers: ManagerHeadersLoos } if !headers.membership.has_full_access() { - err_code!("Resource not found.", "User does not have full access", Status::NotFound.code); + err_code!("Resource not found.", "User does not have full access", rocket::http::Status::NotFound.code); } Ok(Json(json!({ @@ -514,7 +514,7 @@ async fn post_organization_collections( collection.save(&conn).await?; log_event( - EventType::CollectionCreated, + EventType::CollectionCreated as i32, &collection.uuid, &org_id, &headers.user.uuid, @@ -597,7 +597,7 @@ async fn post_bulk_access_collections( collection.save(&conn).await?; log_event( - EventType::CollectionUpdated, + EventType::CollectionUpdated as i32, &collection.uuid, &org_id, &headers.user.uuid, @@ -674,7 +674,7 @@ async fn post_organization_collection_update( collection.save(&conn).await?; log_event( - EventType::CollectionUpdated, + EventType::CollectionUpdated as i32, &collection.uuid, &org_id, &headers.user.uuid, @@ -723,7 +723,7 @@ async fn delete_organization_collection_impl( err!("Collection not found", "Collection does not exist or does not belong to this organization") }; log_event( - EventType::CollectionDeleted, + EventType::CollectionDeleted as i32, &collection.uuid, org_id, &headers.user.uuid, @@ -887,11 +887,11 @@ struct OrgIdData { #[get("/ciphers/organization-details?")] async fn get_org_details(data: OrgIdData, headers: ManagerHeadersLoose, conn: DbConn) -> JsonResult { if data.organization_id != headers.membership.org_uuid { - err_code!("Resource not found.", "Organization id's do not match", Status::NotFound.code); + err_code!("Resource not found.", "Organization id's do not match", rocket::http::Status::NotFound.code); } if !headers.membership.has_full_access() { - err_code!("Resource not found.", "User does not have full access", Status::NotFound.code); + err_code!("Resource not found.", "User does not have full access", rocket::http::Status::NotFound.code); } Ok(Json(json!({ @@ -955,7 +955,7 @@ async fn get_members( } if !headers.membership.has_full_access() { - err_code!("Resource not found.", "User does not have full access", Status::NotFound.code); + err_code!("Resource not found.", "User does not have full access", rocket::http::Status::NotFound.code); } let mut users_json = Vec::new(); @@ -1148,7 +1148,7 @@ async fn send_invite( } log_event( - EventType::OrganizationUserInvited, + EventType::OrganizationUserInvited as i32, &new_member.uuid, &org_id, &headers.user.uuid, @@ -1447,7 +1447,7 @@ async fn confirm_invite_impl( OrgPolicy::check_user_allowed(&member_to_confirm, "confirm", conn).await?; log_event( - EventType::OrganizationUserConfirmed, + EventType::OrganizationUserConfirmed as i32, &member_to_confirm.uuid, org_id, &headers.user.uuid, @@ -1637,7 +1637,7 @@ async fn edit_member( } log_event( - EventType::OrganizationUserUpdated, + EventType::OrganizationUserUpdated as i32, &member_to_edit.uuid, &org_id, &headers.user.uuid, @@ -1724,7 +1724,7 @@ async fn delete_member_impl( } log_event( - EventType::OrganizationUserRemoved, + EventType::OrganizationUserRemoved as i32, &member_to_delete.uuid, org_id, &headers.user.uuid, @@ -1793,22 +1793,11 @@ async fn bulk_public_keys( use super::ciphers::CipherData; use super::ciphers::update_cipher_from_data; -// The import endpoint only ever uses the name/id/external_id of a collection. -// Bitwarden's own server ignores `groups`/`users` here too, so do not make them -// mandatory: clients are free to leave them out. -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -struct ImportCollectionData { - name: String, - id: Option, - external_id: Option, -} - #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct ImportData { ciphers: Vec, - collections: Vec, + collections: Vec, collection_relationships: Vec, } @@ -2155,7 +2144,7 @@ async fn put_policy( } log_event( - EventType::OrganizationUserRemoved, + EventType::OrganizationUserRemoved as i32, &member.uuid, &org_id, &headers.user.uuid, @@ -2181,7 +2170,7 @@ async fn put_policy( policy.save(&conn).await?; log_event( - EventType::PolicyUpdated, + EventType::PolicyUpdated as i32, policy.uuid.as_ref(), &org_id, &headers.user.uuid, @@ -2350,7 +2339,7 @@ async fn revoke_member_impl( member.save(conn).await?; log_event( - EventType::OrganizationUserRevoked, + EventType::OrganizationUserRevoked as i32, &member.uuid, org_id, &headers.user.uuid, @@ -2448,7 +2437,7 @@ async fn restore_member_impl( member.save(conn).await?; log_event( - EventType::OrganizationUserRestored, + EventType::OrganizationUserRestored as i32, &member.uuid, org_id, &headers.user.uuid, @@ -2487,7 +2476,7 @@ async fn get_groups_data( || Collection::has_manageable_collection_by_user(&org_id, &headers.membership.user_uuid, &conn).await }; if !allowed { - err_code!("Resource not found.", "User does not have access", Status::NotFound.code); + err_code!("Resource not found.", "User does not have access", rocket::http::Status::NotFound.code); } let groups: Vec = if CONFIG.org_groups_enabled() { @@ -2616,7 +2605,7 @@ async fn post_groups( let group = group_request.to_group(&org_id); log_event( - EventType::GroupCreated, + EventType::GroupCreated as i32, &group.uuid, &org_id, &headers.user.uuid, @@ -2657,7 +2646,7 @@ async fn put_group( GroupUser::delete_all_by_group(&group_id, &org_id, &conn).await?; log_event( - EventType::GroupUpdated, + EventType::GroupUpdated as i32, &updated_group.uuid, &org_id, &headers.user.uuid, @@ -2690,7 +2679,7 @@ async fn add_update_group( user_entry.save(conn).await?; log_event( - EventType::OrganizationUserUpdatedGroups, + EventType::OrganizationUserUpdatedGroups as i32, &assigned_member, &org_id, &headers.user.uuid, @@ -2765,7 +2754,7 @@ async fn delete_group_impl( }; log_event( - EventType::GroupDeleted, + EventType::GroupDeleted as i32, &group.uuid, org_id, &headers.user.uuid, @@ -2876,7 +2865,7 @@ async fn put_group_members( user_entry.save(&conn).await?; log_event( - EventType::OrganizationUserUpdatedGroups, + EventType::OrganizationUserUpdatedGroups as i32, &assigned_member, &org_id, &headers.user.uuid, @@ -2914,7 +2903,7 @@ async fn post_delete_group_member( } log_event( - EventType::OrganizationUserUpdatedGroups, + EventType::OrganizationUserUpdatedGroups as i32, &member_id, &org_id, &headers.user.uuid, @@ -2938,8 +2927,8 @@ struct OrganizationUserResetPasswordEnrollmentRequest { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct OrganizationUserRecoverAccountRequest { - new_master_password_hash: Option, - key: Option, + new_master_password_hash: String, + key: String, #[serde(default)] reset_master_password: bool, @@ -2983,7 +2972,12 @@ async fn put_recover_account( conn: DbConn, nt: Notify<'_>, ) -> EmptyResult { - recover_account(org_id, member_id, headers, data.into_inner(), conn, nt).await + let req = data.into_inner(); + if req.reset_master_password && !req.reset_two_factor { + recover_account(org_id, member_id, headers, req, conn, nt).await + } else { + err!("Unsupported operation") + } } // Deprecated since `v2026.4.2` @@ -3003,7 +2997,7 @@ async fn recover_account( org_id: OrganizationId, member_id: MembershipId, headers: AdminHeaders, - req: OrganizationUserRecoverAccountRequest, + reset_request: OrganizationUserRecoverAccountRequest, conn: DbConn, nt: Notify<'_>, ) -> EmptyResult { @@ -3018,7 +3012,7 @@ async fn recover_account( err!("User to reset isn't member of required organization") }; - let Some(mut user) = User::find_by_uuid(&member.user_uuid, &conn).await else { + let Some(user) = User::find_by_uuid(&member.user_uuid, &conn).await else { err!("User not found") }; @@ -3031,56 +3025,29 @@ async fn recover_account( err!("Organization user must be confirmed for password reset functionality"); } - let fallback_2fa_email = if req.reset_two_factor && CONFIG.email_2fa_auto_fallback() { - TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email as i32, &conn).await.is_none() - } else { - false - }; - - // Sending email first ensure working email configuration and the resulting user notification. - // Also this might add some protection against security flaws and misuse - if let Err(e) = mail::send_admin_account_recovery( - &user.email, - user.display_name(), - &org.name, - req.reset_master_password, - req.reset_two_factor, - fallback_2fa_email, - ) - .await - { + // Sending email before resetting password to ensure working email configuration and the resulting + // user notification. Also this might add some protection against security flaws and misuse + if let Err(e) = mail::send_admin_reset_password(&user.email, user.display_name(), &org.name).await { err!(format!("Error sending user reset password email: {e:#?}")); } - if req.reset_master_password { - if let Some(key) = req.key - && let Some(hash) = req.new_master_password_hash - { - user.set_password(hash.as_str(), Some(key), true, None, &conn).await?; - } else { - err_code!("Unprocessable request", "Missing fields to reset password", Status::UnprocessableEntity.code); - } - } - - if req.reset_two_factor { - TwoFactor::delete_all_by_user(&user.uuid, &conn).await?; - if !fallback_2fa_email || two_factor::email::find_and_activate_email_2fa(&user.uuid, &conn).await.is_err() { - two_factor::enforce_2fa_policy(&user, &headers.user.uuid, headers.device.atype, &headers.ip.ip, &conn) - .await?; - } - } - + let mut user = user; + user.set_password(reset_request.new_master_password_hash.as_str(), Some(reset_request.key), true, None, &conn) + .await?; user.save(&conn).await?; nt.send_logout(&user, None, &conn).await; - if req.reset_master_password { - headers.log_event(EventType::OrganizationUserAdminResetPassword, &member_id, &org_id, &conn).await; - } - - if req.reset_two_factor { - headers.log_event(EventType::OrganizationUserAdminResetTwoFactor, &member_id, &org_id, &conn).await; - } + log_event( + EventType::OrganizationUserAdminResetPassword as i32, + &member_id, + &org_id, + &headers.user.uuid, + headers.device.atype, + &headers.ip.ip, + &conn, + ) + .await; Ok(()) } @@ -3199,9 +3166,9 @@ async fn put_reset_password_enrollment( membership.save(&conn).await?; let event_type = if membership.reset_password_key.is_some() { - EventType::OrganizationUserResetPasswordEnroll + EventType::OrganizationUserResetPasswordEnroll as i32 } else { - EventType::OrganizationUserResetPasswordWithdraw + EventType::OrganizationUserResetPasswordWithdraw as i32 }; log_event(event_type, &membership.uuid, &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, &conn) diff --git a/src/api/core/two_factor/email.rs b/src/api/core/two_factor/email.rs index 3667b871..44ba2e7f 100644 --- a/src/api/core/two_factor/email.rs +++ b/src/api/core/two_factor/email.rs @@ -63,19 +63,13 @@ async fn send_email_login(data: Json, client_headers: Client let user = if let Some(email) = email { let Some(user) = User::find_by_mail(email, &conn).await else { - err!( - "Username or password is incorrect. Try again", - format!("IP: {}. Username: {email}.", client_headers.ip.ip) - ) + err!("Username or password is incorrect. Try again.") }; if let Some(master_password_hash) = master_password_hash { // Check password if !user.check_valid_password(master_password_hash) { - err!( - "Username or password is incorrect. Try again", - format!("IP: {}. Username: {email}.", client_headers.ip.ip) - ) + err!("Username or password is incorrect. Try again.") } } else if let Some(auth_request_id) = auth_request_id { let Some(auth_request) = AuthRequest::find_by_uuid(auth_request_id, &conn).await else { @@ -102,10 +96,7 @@ async fn send_email_login(data: Json, client_headers: Client }; // SSO login only sends device id, so we get the user by the most recently used device let Some(user) = User::find_by_device_for_email2fa(device_identifier, &conn).await else { - err!( - "Username or password is incorrect. Try again", - format!("IP: {}. Device: {device_identifier}.", client_headers.ip.ip) - ) + err!("Username or password is incorrect. Try again.") }; user diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs index 0eb6563e..8869d23d 100644 --- a/src/api/core/two_factor/mod.rs +++ b/src/api/core/two_factor/mod.rs @@ -16,8 +16,8 @@ use crate::{ db::{ DbConn, DbPool, models::{ - Device, DeviceType, EventType, Membership, MembershipType, OrgPolicyType, Organization, OrganizationId, - TwoFactor, TwoFactorIncomplete, TwoFactorType, User, UserId, + DeviceType, EventType, Membership, MembershipType, OrgPolicyType, Organization, OrganizationId, TwoFactor, + TwoFactorIncomplete, TwoFactorType, User, UserId, }, }, mail, @@ -151,7 +151,6 @@ async fn disable_twofactor(data: Json, headers: Headers, c if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await { twofactor.delete(&conn).await?; - Device::clear_twofactor_remember_by_user(&user.uuid, &conn).await?; log_user_event(EventType::UserDisabled2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn) .await; } @@ -191,7 +190,7 @@ pub async fn enforce_2fa_policy( member.save(conn).await?; log_event( - EventType::OrganizationUserRevoked, + EventType::OrganizationUserRevoked as i32, &member.uuid, &member.org_uuid, act_user_id, @@ -225,8 +224,16 @@ pub async fn enforce_2fa_policy_for_org( member.revoke(); member.save(conn).await?; - log_event(EventType::OrganizationUserRevoked, &member.uuid, org_id, act_user_id, device_type, ip, conn) - .await; + log_event( + EventType::OrganizationUserRevoked as i32, + &member.uuid, + org_id, + act_user_id, + device_type, + ip, + conn, + ) + .await; } } diff --git a/src/api/core/two_factor/yubikey.rs b/src/api/core/two_factor/yubikey.rs index eb3d6dfd..08e8d269 100644 --- a/src/api/core/two_factor/yubikey.rs +++ b/src/api/core/two_factor/yubikey.rs @@ -1,10 +1,6 @@ use rocket::{Route, serde::json::Json}; use serde_json::Value; -use yubico_ng::{ - Verifier, YubicoError, - config::Config, - transport::{AsyncTransport, Response}, -}; +use yubico::{config::Config, verify_async}; use crate::{ CONFIG, @@ -18,39 +14,12 @@ 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 { @@ -75,7 +44,8 @@ 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().filter(|e| !e.is_empty()).cloned().collect() + + data_keys.into_iter().flatten().cloned().collect() } fn jsonify_yubikeys(yubikeys: Vec) -> Value { @@ -103,15 +73,13 @@ 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 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); + 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 client = HttpClientTransport::new()?; - let verifier = Verifier::with_client(config, client)?; - - verifier.verify(otp).await.map_res("Failed to verify OTP") + .map_res("Failed to verify OTP") } #[post("/two-factor/get-yubikey", data = "")] @@ -169,9 +137,10 @@ async fn activate_yubikey(data: Json, headers: Headers, conn: let yubikeys = parse_yubikeys(&data); if yubikeys.is_empty() { - // 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."); + return Ok(Json(json!({ + "enabled": false, + "object": "twoFactorU2f", + }))); } // Ensure they are valid OTPs diff --git a/src/api/identity.rs b/src/api/identity.rs index 6808ddde..9212ed8d 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -3,7 +3,7 @@ use num_traits::FromPrimitive; use rocket::{ Route, form::{Form, FromForm}, - http::{Accept, Cookie, CookieJar, MediaType, SameSite}, + http::{Cookie, CookieJar, SameSite}, response::Redirect, serde::json::Json, }; @@ -234,24 +234,6 @@ async fn sso_login( } ) } - Some((user, None)) - if user.private_key.is_none() - && !CONFIG.sso_signups_allowed() - && !CONFIG.is_email_domain_allowed(&user.email) - && !CONFIG.mail_enabled() - && Invitation::find_by_mail(&user.email, conn).await.is_none() => - { - error!( - "Login failure ({}), no invitation with email ({}) was found", - user_infos.identifier, user.email - ); - err_silent!( - "Missing invitation", - ErrorEvent { - event: EventType::UserFailedLogIn - } - ) - } Some((user, None)) if user.private_key.is_some() && !CONFIG.sso_signups_match_email() => { error!( "Login failure ({}), existing non SSO user ({}) with same email ({}) and association is disabled", @@ -299,15 +281,7 @@ async fn sso_login( // Will trigger 2FA flow if needed let (user, mut device, twofactor_token, sso_user) = match user_with_sso { None => { - if !CONFIG.is_sso_signup_allowed(&user_infos.email) { - if CONFIG.signups_domains_whitelist().is_empty() { - err!( - "Signups are disabled. You will need an invitation", - ErrorEvent { - event: EventType::UserFailedLogIn - } - ); - } + if !CONFIG.is_email_domain_allowed(&user_infos.email) { err!( "Email domain not allowed", ErrorEvent { @@ -344,7 +318,7 @@ async fn sso_login( Some((user, _)) if !user.enabled => { err!( "This user has been disabled", - format!("IP: {}. Username: {}.", ip.ip, user.email), + format!("IP: {}. Username: {}.", ip.ip, user.display_name()), ErrorEvent { event: EventType::UserFailedLogIn } @@ -603,7 +577,7 @@ async fn authenticated_response( result["TwoFactorToken"] = Value::String(token); } - info!("User {} logged in successfully. IP: {}", user.email, ip.ip); + info!("User {} logged in successfully. IP: {}", user.display_name(), ip.ip); Ok(Json(result)) } @@ -905,12 +879,6 @@ async fn twofactor_auth( // Remove all twofactors from the user TwoFactor::delete_all_by_user(&user.uuid, conn).await?; - - // No device may keep skipping 2FA once every second factor is gone. - // `device` is cleared in memory too, since saving it later would restore its token. - Device::clear_twofactor_remember_by_user(&user.uuid, conn).await?; - device.delete_twofactor_remember(); - enforce_2fa_policy(user, &user.uuid, device.atype, &ip.ip, conn).await?; log_user_event(EventType::UserRecovered2fa as i32, &user.uuid, device.atype, &ip.ip, conn).await; @@ -1056,13 +1024,13 @@ async fn json_err_twofactor( } #[post("/accounts/prelogin", data = "")] -async fn post_prelogin(data: Json, ip: ClientIp, conn: DbConn) -> JsonResult { - prelogin(data, ip, conn).await +async fn post_prelogin(data: Json, conn: DbConn) -> Json { + prelogin(data, conn).await } #[post("/accounts/prelogin/password", data = "")] -async fn prelogin_password(data: Json, ip: ClientIp, conn: DbConn) -> JsonResult { - prelogin(data, ip, conn).await +async fn prelogin_password(data: Json, conn: DbConn) -> Json { + prelogin(data, conn).await } #[post("/accounts/register", data = "")] @@ -1083,18 +1051,11 @@ enum RegisterVerificationResponse { #[response(status = 204)] NoContent(()), Token(Json), - PlainToken(String), -} - -// Return JSON only when the client explicitly requests it, otherwise return plain text. -fn accepts_json(accept: Option<&Accept>) -> bool { - accept.is_some_and(|accept| accept.preferred().media_type() == &MediaType::JSON) } #[post("/accounts/register/send-verification-email", data = "")] async fn register_verification_email( data: Json, - accept: Option<&Accept>, ip: ClientIp, conn: DbConn, ) -> ApiResult { @@ -1132,11 +1093,7 @@ async fn register_verification_email( } else { // If email verification is not required, return the token directly // the clients will use this token to finish the registration - Ok(if accepts_json(accept) { - RegisterVerificationResponse::Token(Json(token)) - } else { - RegisterVerificationResponse::PlainToken(token) - }) + Ok(RegisterVerificationResponse::Token(Json(token))) } } diff --git a/src/api/mod.rs b/src/api/mod.rs index 9a79ce95..05c4215d 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::{invalidate_css_cache, static_files}, + web::static_files, }; use crate::{ CONFIG, diff --git a/src/api/web.rs b/src/api/web.rs index d6d8d62c..5bd4c85d 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -1,7 +1,4 @@ -use std::{ - path::{Path, PathBuf}, - sync::{Arc, RwLock}, -}; +use std::path::{Path, PathBuf}; use rocket::{ Catcher, Route, @@ -16,13 +13,12 @@ use crate::{ CONFIG, api::{ApiResult, EmptyResult, core::now}, auth::decode_file_download, - crypto::sha256_hex, db::{ DbConn, models::{AttachmentId, CipherId}, }, error::Error, - util::{Cached, EtagCached}, + util::Cached, }; pub fn routes() -> Vec { @@ -67,27 +63,8 @@ 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() -> 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 +fn vaultwarden_css() -> Cached> { let css_options = json!({ "emergency_access_allowed": CONFIG.emergency_access_allowed(), "load_user_scss": true, @@ -135,18 +112,8 @@ fn vaultwarden_css() -> EtagCached> { } }; - 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) + // Cache for one day should be enough and not too much + Cached::ttl(Css(css), 86_400, false) } #[get("/")] @@ -301,6 +268,9 @@ pub fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Erro "jdenticon-3.3.0.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon-3.3.0.js"))), "datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))), "datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))), + "jquery-4.0.0.slim.js" => { + Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-4.0.0.slim.js"))) + } _ => err!(format!("Static file not found: {filename}")), } } diff --git a/src/auth.rs b/src/auth.rs index 07373389..762088e5 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -23,14 +23,14 @@ use rocket::{ use crate::{ CONFIG, - api::{ApiResult, core::log_event}, + api::ApiResult, config::PathType, db::{ DbConn, models::{ AttachmentId, CipherId, Collection, CollectionId, Device, DeviceId, DeviceType, EmergencyAccessId, - EventType, Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, OrganizationId, - SendFileId, SendId, User, UserId, UserStampException, + Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, OrganizationId, SendFileId, + SendId, User, UserId, UserStampException, }, }, error::Error, @@ -822,12 +822,6 @@ pub struct AdminHeaders { pub org_id: OrganizationId, } -impl AdminHeaders { - pub async fn log_event(&self, event_type: EventType, source_uuid: &str, org_id: &OrganizationId, conn: &DbConn) { - log_event(event_type, source_uuid, org_id, &self.user.uuid, self.device.atype, &self.ip.ip, conn).await; - } -} - #[rocket::async_trait] impl<'r> FromRequest<'r> for AdminHeaders { type Error = &'static str; diff --git a/src/config.rs b/src/config.rs index 37fc3e85..c4457478 100644 --- a/src/config.rs +++ b/src/config.rs @@ -659,11 +659,6 @@ 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. @@ -817,8 +812,6 @@ make_config! { sso_enabled: bool, true, def, false; /// Only SSO login |> Disable Email+Master Password login sso_only: bool, true, def, false; - /// Allow SSO flow to create account |> You probably want to disable it when using a public provider - sso_signups_allowed: bool, true, def, true; /// Allow email association |> Associate existing non-SSO user based on email sso_signups_match_email: bool, true, def, true; /// Allow unknown email verification status |> Allowing this with `SSO_SIGNUPS_MATCH_EMAIL=true` open potential account takeover. @@ -1164,8 +1157,11 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> { } #[cfg(unix)] - if nix::unistd::access(&path, nix::unistd::AccessFlags::X_OK).is_err() { - err!(format!("sendmail command at `{path:?}` isn't executable")); + { + use std::os::unix::fs::PermissionsExt; + if !metadata.permissions().mode() & 0o111 != 0 { + err!(format!("sendmail command at `{path:?}` isn't executable")); + } } } } @@ -1272,7 +1268,7 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> { if !cfg.disable_admin_token { match cfg.admin_token.as_ref() { Some(t) if t.starts_with("$argon2") => { - if let Err(e) = argon2::password_hash::phc::PasswordHash::new(t) { + if let Err(e) = argon2::password_hash::PasswordHash::new(t) { err!(format!("The configured Argon2 PHC in `ADMIN_TOKEN` is invalid: '{e}'")) } } @@ -1425,7 +1421,6 @@ pub const SUPPORTED_FEATURE_FLAGS: &[&str] = &[ "desktop-ui-migration-milestone-4", // Auth Team "pm-5594-safari-account-switching", - "pm-32413-multi-client-password-management", // Autofill Team "ssh-agent", "ssh-agent-v2", @@ -1506,9 +1501,6 @@ 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(()) } @@ -1547,17 +1539,6 @@ impl Config { } } - /// Tests whether SSO signup is allowed for an email address, taking into - /// account the sso_signups_allowed and signups_domains_whitelist settings. - pub fn is_sso_signup_allowed(&self, email: &str) -> bool { - if self.signups_domains_whitelist().is_empty() { - self.sso_signups_allowed() - } else { - // The whitelist setting overrides the signups_allowed setting. - self.is_email_domain_allowed(email) - } - } - // The registration link should be hidden if // - Signup is not allowed and email whitelist is empty unless mail is disabled and invitations are allowed // - The SSO is activated and password login is disabled. @@ -1602,9 +1583,6 @@ 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(()) } @@ -1745,7 +1723,7 @@ where reg!("email/email_footer"); reg!("email/email_footer_text"); - reg!("email/admin_account_recovery", ".html"); + reg!("email/admin_reset_password", ".html"); reg!("email/change_email_existing", ".html"); reg!("email/change_email_invited", ".html"); reg!("email/change_email", ".html"); diff --git a/src/db/models/archive.rs b/src/db/models/archive.rs index 2330fac4..83d547f2 100644 --- a/src/db/models/archive.rs +++ b/src/db/models/archive.rs @@ -41,20 +41,17 @@ impl Archive { ) -> EmptyResult { User::update_uuid_revision(user_uuid, conn).await; db_run! { conn: - mysql { - diesel::insert_into(archives::table) + sqlite, mysql { + diesel::replace_into(archives::table) .values(( archives::user_uuid.eq(user_uuid), archives::cipher_uuid.eq(cipher_uuid), archives::archived_at.eq(archived_at), )) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(archives::archived_at.eq(archived_at)) .execute(conn) .map_res("Error saving archive") } - postgresql, sqlite { + postgresql { diesel::insert_into(archives::table) .values(( archives::user_uuid.eq(user_uuid), diff --git a/src/db/models/attachment.rs b/src/db/models/attachment.rs index 0536dde5..244f8c27 100644 --- a/src/db/models/attachment.rs +++ b/src/db/models/attachment.rs @@ -82,16 +82,24 @@ impl Attachment { impl Attachment { pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - mysql { - diesel::insert_into(attachments::table) + sqlite, mysql { + match diesel::replace_into(attachments::table) .values(self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(self) .execute(conn) - .map_res("Error saving attachment") + { + Ok(_) => Ok(()), + // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. + Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { + diesel::update(attachments::table) + .filter(attachments::id.eq(&self.id)) + .set(self) + .execute(conn) + .map_res("Error saving attachment") + } + Err(e) => Err(e.into()), + }.map_res("Error saving attachment") } - postgresql, sqlite { + postgresql { diesel::insert_into(attachments::table) .values(self) .on_conflict(attachments::id) diff --git a/src/db/models/auth_request.rs b/src/db/models/auth_request.rs index cc4b60fd..a3876661 100644 --- a/src/db/models/auth_request.rs +++ b/src/db/models/auth_request.rs @@ -82,23 +82,31 @@ impl AuthRequest { } impl AuthRequest { - pub async fn save(&self, conn: &DbConn) -> EmptyResult { + pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { db_run! { conn: - mysql { - diesel::insert_into(auth_requests::table) - .values(self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(self) + sqlite, mysql { + match diesel::replace_into(auth_requests::table) + .values(&*self) .execute(conn) - .map_res("Error saving auth_request") + { + Ok(_) => Ok(()), + // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. + Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { + diesel::update(auth_requests::table) + .filter(auth_requests::uuid.eq(&self.uuid)) + .set(&*self) + .execute(conn) + .map_res("Error auth_request") + } + Err(e) => Err(e.into()), + }.map_res("Error auth_request") } - postgresql, sqlite { + postgresql { diesel::insert_into(auth_requests::table) - .values(self) + .values(&*self) .on_conflict(auth_requests::uuid) .do_update() - .set(self) + .set(&*self) .execute(conn) .map_res("Error saving auth_request") } diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index 721d9790..eed5041d 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -440,16 +440,24 @@ impl Cipher { self.updated_at = Utc::now().naive_utc(); db_run! { conn: - mysql { - diesel::insert_into(ciphers::table) + sqlite, mysql { + match diesel::replace_into(ciphers::table) .values(&*self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(&*self) .execute(conn) - .map_res("Error saving cipher") + { + Ok(_) => Ok(()), + // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. + Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { + diesel::update(ciphers::table) + .filter(ciphers::uuid.eq(&self.uuid)) + .set(&*self) + .execute(conn) + .map_res("Error saving cipher") + } + Err(e) => Err(e.into()), + }.map_res("Error saving cipher") } - postgresql, sqlite { + postgresql { diesel::insert_into(ciphers::table) .values(&*self) .on_conflict(ciphers::uuid) diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs index be108f13..8aec90ea 100644 --- a/src/db/models/collection.rs +++ b/src/db/models/collection.rs @@ -168,16 +168,24 @@ impl Collection { self.update_users_revision(conn).await; db_run! { conn: - mysql { - diesel::insert_into(collections::table) + sqlite, mysql { + match diesel::replace_into(collections::table) .values(self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(self) .execute(conn) - .map_res("Error saving collection") + { + Ok(_) => Ok(()), + // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. + Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { + diesel::update(collections::table) + .filter(collections::uuid.eq(&self.uuid)) + .set(self) + .execute(conn) + .map_res("Error saving collection") + } + Err(e) => Err(e.into()), + }.map_res("Error saving collection") } - postgresql, sqlite { + postgresql { diesel::insert_into(collections::table) .values(self) .on_conflict(collections::uuid) @@ -720,30 +728,53 @@ impl CollectionUser { ) -> EmptyResult { User::update_uuid_revision(user_uuid, conn).await; - let values = ( - users_collections::user_uuid.eq(user_uuid), - users_collections::collection_uuid.eq(collection_uuid), - users_collections::read_only.eq(read_only), - users_collections::hide_passwords.eq(hide_passwords), - users_collections::manage.eq(manage), - ); - db_run! { conn: - mysql { - diesel::insert_into(users_collections::table) - .values(values) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(values) + sqlite, mysql { + match diesel::replace_into(users_collections::table) + .values(( + users_collections::user_uuid.eq(user_uuid), + users_collections::collection_uuid.eq(collection_uuid), + users_collections::read_only.eq(read_only), + users_collections::hide_passwords.eq(hide_passwords), + users_collections::manage.eq(manage), + )) .execute(conn) - .map_res("Error adding user to collection") + { + Ok(_) => Ok(()), + // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. + Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { + diesel::update(users_collections::table) + .filter(users_collections::user_uuid.eq(user_uuid)) + .filter(users_collections::collection_uuid.eq(collection_uuid)) + .set(( + users_collections::user_uuid.eq(user_uuid), + users_collections::collection_uuid.eq(collection_uuid), + users_collections::read_only.eq(read_only), + users_collections::hide_passwords.eq(hide_passwords), + users_collections::manage.eq(manage), + )) + .execute(conn) + .map_res("Error adding user to collection") + } + Err(e) => Err(e.into()), + }.map_res("Error adding user to collection") } - postgresql, sqlite { + postgresql { diesel::insert_into(users_collections::table) - .values(values) + .values(( + users_collections::user_uuid.eq(user_uuid), + users_collections::collection_uuid.eq(collection_uuid), + users_collections::read_only.eq(read_only), + users_collections::hide_passwords.eq(hide_passwords), + users_collections::manage.eq(manage), + )) .on_conflict((users_collections::user_uuid, users_collections::collection_uuid)) .do_update() - .set(values) + .set(( + users_collections::read_only.eq(read_only), + users_collections::hide_passwords.eq(hide_passwords), + users_collections::manage.eq(manage), + )) .execute(conn) .map_res("Error adding user to collection") } @@ -878,18 +909,19 @@ impl CollectionCipher { Self::update_users_revision(collection_uuid, conn).await; db_run! { conn: - mysql { - diesel::insert_into(ciphers_collections::table) + sqlite, mysql { + // Not checking for ForeignKey Constraints here. + // Table ciphers_collections does not have ForeignKey Constraints which would cause conflicts. + // This table has no constraints pointing to itself, but only to others. + diesel::replace_into(ciphers_collections::table) .values(( ciphers_collections::cipher_uuid.eq(cipher_uuid), ciphers_collections::collection_uuid.eq(collection_uuid), )) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_nothing() .execute(conn) .map_res("Error adding cipher to collection") } - postgresql, sqlite { + postgresql { diesel::insert_into(ciphers_collections::table) .values(( ciphers_collections::cipher_uuid.eq(cipher_uuid), diff --git a/src/db/models/device.rs b/src/db/models/device.rs index 5e5f1f97..6c1b686a 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -146,18 +146,15 @@ impl Device { } db_run! { conn: - mysql { + sqlite, mysql { crate::util::retry(|| - diesel::insert_into(devices::table) + diesel::replace_into(devices::table) .values(&*self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(&*self) .execute(conn), 10, ).map_res("Error saving device") } - postgresql, sqlite { + postgresql { crate::util::retry(|| diesel::insert_into(devices::table) .values(&*self) @@ -269,22 +266,10 @@ impl Device { let devices = Self::find_by_user(user_uuid, conn).await; for mut device in devices { device.refresh_token = Device::generate_refresh_token(); - device.twofactor_remember = None; device.save(false, conn).await?; } Ok(()) } - - pub async fn clear_twofactor_remember_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult { - conn.run(move |conn| { - diesel::update(devices::table) - .filter(devices::user_uuid.eq(user_uuid)) - .set(devices::twofactor_remember.eq::>(None)) - .execute(conn) - .map_res("Error removing two factor remember tokens") - }) - .await - } } #[derive(Display)] diff --git a/src/db/models/emergency_access.rs b/src/db/models/emergency_access.rs index 09783061..45fad91f 100644 --- a/src/db/models/emergency_access.rs +++ b/src/db/models/emergency_access.rs @@ -146,16 +146,24 @@ impl EmergencyAccess { self.updated_at = Utc::now().naive_utc(); db_run! { conn: - mysql { - diesel::insert_into(emergency_access::table) + sqlite, mysql { + match diesel::replace_into(emergency_access::table) .values(&*self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(&*self) .execute(conn) - .map_res("Error saving emergency access") + { + Ok(_) => Ok(()), + // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. + Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { + diesel::update(emergency_access::table) + .filter(emergency_access::uuid.eq(&self.uuid)) + .set(&*self) + .execute(conn) + .map_res("Error updating emergency access") + } + Err(e) => Err(e.into()), + }.map_res("Error saving emergency access") } - postgresql, sqlite { + postgresql { diesel::insert_into(emergency_access::table) .values(&*self) .on_conflict(emergency_access::uuid) diff --git a/src/db/models/event.rs b/src/db/models/event.rs index 2d9ed8b2..3a6b610c 100644 --- a/src/db/models/event.rs +++ b/src/db/models/event.rs @@ -43,7 +43,7 @@ pub struct Event { pub provider_org_uuid: Option, } -// Upstream enum: https://github.com/bitwarden/server/blob/v2026.6.2/src/Core/Dirt/Enums/EventType.cs +// Upstream enum: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Enums/EventType.cs #[derive(Debug, Copy, Clone)] pub enum EventType { // User @@ -108,12 +108,6 @@ pub enum EventType { OrganizationUserRejectedAuthRequest = 1514, OrganizationUserDeleted = 1515, // Both user and organization user data were deleted OrganizationUserLeft = 1516, // User voluntarily left the organization - // OrganizationUserAutomaticallyConfirmed = 1517, - // OrganizationUserSelfRevoked = 1518, // User self-revoked due to declining organization data ownership policy - OrganizationUserAdminResetTwoFactor = 1519, - // OrganizationUserRevoked_TwoFactorNonCompliance = 1520, - // OrganizationUserRevoked_SingleOrganizationNonCompliance = 1521, - // OrganizationUserNotificationBannerActionClicked = 1522, // Organization OrganizationUpdated = 1600, @@ -208,23 +202,20 @@ impl Event { /// Basic Queries pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - mysql { - diesel::insert_into(event::table) - .values(self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(self) - .execute(conn) - .map_res("Error saving event") + sqlite, mysql { + diesel::replace_into(event::table) + .values(self) + .execute(conn) + .map_res("Error saving event") } - postgresql, sqlite { + postgresql { diesel::insert_into(event::table) - .values(self) - .on_conflict(event::uuid) - .do_update() - .set(self) - .execute(conn) - .map_res("Error saving event") + .values(self) + .on_conflict(event::uuid) + .do_update() + .set(self) + .execute(conn) + .map_res("Error saving event") } } } @@ -307,16 +298,12 @@ impl Event { ) -> Vec { conn.run(move |conn| { event::table - .inner_join( - users_organizations::table - .on(users_organizations::uuid.eq(member_uuid).and(users_organizations::org_uuid.eq(org_uuid))), - ) + .inner_join(users_organizations::table.on(users_organizations::uuid.eq(member_uuid))) .filter(event::org_uuid.eq(org_uuid)) .filter(event::event_date.between(start, end)) .filter( - event::org_user_uuid - .eq(member_uuid) - .or(event::user_uuid.eq(users_organizations::user_uuid.nullable())) + 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/folder.rs b/src/db/models/folder.rs index adbe993f..745608e3 100644 --- a/src/db/models/folder.rs +++ b/src/db/models/folder.rs @@ -77,16 +77,24 @@ impl Folder { self.updated_at = Utc::now().naive_utc(); db_run! { conn: - mysql { - diesel::insert_into(folders::table) + sqlite, mysql { + match diesel::replace_into(folders::table) .values(&*self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(&*self) .execute(conn) - .map_res("Error saving folder") + { + Ok(_) => Ok(()), + // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. + Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { + diesel::update(folders::table) + .filter(folders::uuid.eq(&self.uuid)) + .set(&*self) + .execute(conn) + .map_res("Error saving folder") + } + Err(e) => Err(e.into()), + }.map_res("Error saving folder") } - postgresql, sqlite { + postgresql { diesel::insert_into(folders::table) .values(&*self) .on_conflict(folders::uuid) @@ -139,15 +147,16 @@ impl Folder { impl FolderCipher { pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - mysql { - diesel::insert_into(folders_ciphers::table) + sqlite, mysql { + // Not checking for ForeignKey Constraints here. + // Table folders_ciphers does not have ForeignKey Constraints which would cause conflicts. + // This table has no constraints pointing to itself, but only to others. + diesel::replace_into(folders_ciphers::table) .values(self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_nothing() .execute(conn) .map_res("Error adding cipher to folder") } - postgresql, sqlite { + postgresql { diesel::insert_into(folders_ciphers::table) .values(self) .on_conflict((folders_ciphers::cipher_uuid, folders_ciphers::folder_uuid)) diff --git a/src/db/models/group.rs b/src/db/models/group.rs index 32e9333f..37037de6 100644 --- a/src/db/models/group.rs +++ b/src/db/models/group.rs @@ -166,16 +166,24 @@ impl Group { self.revision_date = Utc::now().naive_utc(); db_run! { conn: - mysql { - diesel::insert_into(groups::table) + sqlite, mysql { + match diesel::replace_into(groups::table) .values(&*self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(&*self) .execute(conn) - .map_res("Error saving group") + { + Ok(_) => Ok(()), + // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. + Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { + diesel::update(groups::table) + .filter(groups::uuid.eq(&self.uuid)) + .set(&*self) + .execute(conn) + .map_res("Error saving group") + } + Err(e) => Err(e.into()), + }.map_res("Error saving group") } - postgresql, sqlite { + postgresql { diesel::insert_into(groups::table) .values(&*self) .on_conflict(groups::uuid) @@ -318,30 +326,53 @@ impl CollectionGroup { group_user.update_user_revision(conn).await; } - let values = ( - collections_groups::collections_uuid.eq(&self.collections_uuid), - collections_groups::groups_uuid.eq(&self.groups_uuid), - collections_groups::read_only.eq(&self.read_only), - collections_groups::hide_passwords.eq(&self.hide_passwords), - collections_groups::manage.eq(&self.manage), - ); - db_run! { conn: - mysql { - diesel::insert_into(collections_groups::table) - .values(values) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(values) + sqlite, mysql { + match diesel::replace_into(collections_groups::table) + .values(( + collections_groups::collections_uuid.eq(&self.collections_uuid), + collections_groups::groups_uuid.eq(&self.groups_uuid), + collections_groups::read_only.eq(&self.read_only), + collections_groups::hide_passwords.eq(&self.hide_passwords), + collections_groups::manage.eq(&self.manage), + )) .execute(conn) - .map_res("Error adding group to collection") + { + Ok(_) => Ok(()), + // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. + Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { + diesel::update(collections_groups::table) + .filter(collections_groups::collections_uuid.eq(&self.collections_uuid)) + .filter(collections_groups::groups_uuid.eq(&self.groups_uuid)) + .set(( + collections_groups::collections_uuid.eq(&self.collections_uuid), + collections_groups::groups_uuid.eq(&self.groups_uuid), + collections_groups::read_only.eq(&self.read_only), + collections_groups::hide_passwords.eq(&self.hide_passwords), + collections_groups::manage.eq(&self.manage), + )) + .execute(conn) + .map_res("Error adding group to collection") + } + Err(e) => Err(e.into()), + }.map_res("Error adding group to collection") } - postgresql, sqlite { + postgresql { diesel::insert_into(collections_groups::table) - .values(values) + .values(( + collections_groups::collections_uuid.eq(&self.collections_uuid), + collections_groups::groups_uuid.eq(&self.groups_uuid), + collections_groups::read_only.eq(self.read_only), + collections_groups::hide_passwords.eq(self.hide_passwords), + collections_groups::manage.eq(self.manage), + )) .on_conflict((collections_groups::collections_uuid, collections_groups::groups_uuid)) .do_update() - .set(values) + .set(( + collections_groups::read_only.eq(self.read_only), + collections_groups::hide_passwords.eq(self.hide_passwords), + collections_groups::manage.eq(self.manage), + )) .execute(conn) .map_res("Error adding group to collection") } @@ -466,25 +497,43 @@ impl GroupUser { pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { self.update_user_revision(conn).await; - let values = ( - groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), - groups_users::groups_uuid.eq(&self.groups_uuid), - ); - db_run! { conn: - mysql { - diesel::insert_into(groups_users::table) - .values(values) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_nothing() + sqlite, mysql { + match diesel::replace_into(groups_users::table) + .values(( + groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), + groups_users::groups_uuid.eq(&self.groups_uuid), + )) .execute(conn) - .map_res("Error adding user to group") + { + Ok(_) => Ok(()), + // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. + Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { + diesel::update(groups_users::table) + .filter(groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid)) + .filter(groups_users::groups_uuid.eq(&self.groups_uuid)) + .set(( + groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), + groups_users::groups_uuid.eq(&self.groups_uuid), + )) + .execute(conn) + .map_res("Error adding user to group") + } + Err(e) => Err(e.into()), + }.map_res("Error adding user to group") } - postgresql, sqlite { + postgresql { diesel::insert_into(groups_users::table) - .values(values) + .values(( + groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), + groups_users::groups_uuid.eq(&self.groups_uuid), + )) .on_conflict((groups_users::users_organizations_uuid, groups_users::groups_uuid)) - .do_nothing() + .do_update() + .set(( + groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid), + groups_users::groups_uuid.eq(&self.groups_uuid), + )) .execute(conn) .map_res("Error adding user to group") } diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs index 2b45cd86..88b7872c 100644 --- a/src/db/models/org_policy.rs +++ b/src/db/models/org_policy.rs @@ -91,7 +91,6 @@ impl OrgPolicy { "type": self.atype, "data": data_json, "enabled": self.enabled, - "revisionDate": null, "object": "policy", }); @@ -318,13 +317,6 @@ impl OrgPolicy { } pub async fn org_is_reset_password_auto_enroll(org_uuid: &OrganizationId, conn: &DbConn) -> bool { - // Account recovery depends on outbound mail. When SMTP is disabled, treat the - // auto-enroll policy as inactive so invites/registration are not forced to - // supply a reset-password key (see check_reset_password_applicable). - if !CONFIG.mail_enabled() { - return false; - } - match OrgPolicy::find_by_org_and_type(org_uuid, OrgPolicyType::ResetPassword, conn).await { Some(policy) => match serde_json::from_str::(&policy.data) { Ok(opts) => { diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 29016865..bdb69864 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -353,16 +353,25 @@ impl Organization { } db_run! { conn: - mysql { - diesel::insert_into(organizations::table) + sqlite, mysql { + match diesel::replace_into(organizations::table) .values(self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(self) .execute(conn) - .map_res("Error saving organization") + { + Ok(_) => Ok(()), + // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. + Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { + diesel::update(organizations::table) + .filter(organizations::uuid.eq(&self.uuid)) + .set(self) + .execute(conn) + .map_res("Error saving organization") + } + Err(e) => Err(e.into()), + }.map_res("Error saving organization") + } - postgresql, sqlite { + postgresql { diesel::insert_into(organizations::table) .values(self) .on_conflict(organizations::uuid) @@ -744,16 +753,24 @@ impl Membership { User::update_uuid_revision(&self.user_uuid, conn).await; db_run! { conn: - mysql { - diesel::insert_into(users_organizations::table) + sqlite, mysql { + match diesel::replace_into(users_organizations::table) .values(self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(self) .execute(conn) - .map_res("Error adding user to organization") + { + Ok(_) => Ok(()), + // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. + Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { + diesel::update(users_organizations::table) + .filter(users_organizations::uuid.eq(&self.uuid)) + .set(self) + .execute(conn) + .map_res("Error adding user to organization") + }, + Err(e) => Err(e.into()), + }.map_res("Error adding user to organization") } - postgresql, sqlite { + postgresql { diesel::insert_into(users_organizations::table) .values(self) .on_conflict(users_organizations::uuid) @@ -1169,16 +1186,25 @@ impl Membership { impl OrganizationApiKey { pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - mysql { - diesel::insert_into(organization_api_key::table) + sqlite, mysql { + match diesel::replace_into(organization_api_key::table) .values(self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(self) .execute(conn) - .map_res("Error saving organization") + { + Ok(_) => Ok(()), + // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. + Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { + diesel::update(organization_api_key::table) + .filter(organization_api_key::uuid.eq(&self.uuid)) + .set(self) + .execute(conn) + .map_res("Error saving organization") + } + Err(e) => Err(e.into()), + }.map_res("Error saving organization") + } - postgresql, sqlite { + postgresql { diesel::insert_into(organization_api_key::table) .values(self) .on_conflict((organization_api_key::uuid, organization_api_key::org_uuid)) diff --git a/src/db/models/send.rs b/src/db/models/send.rs index d7de7749..c5bc98c4 100644 --- a/src/db/models/send.rs +++ b/src/db/models/send.rs @@ -202,16 +202,24 @@ impl Send { self.revision_date = Utc::now().naive_utc(); db_run! { conn: - mysql { - diesel::insert_into(sends::table) + sqlite, mysql { + match diesel::replace_into(sends::table) .values(&*self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_update() - .set(&*self) .execute(conn) - .map_res("Error saving send") + { + Ok(_) => Ok(()), + // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. + Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { + diesel::update(sends::table) + .filter(sends::uuid.eq(&self.uuid)) + .set(&*self) + .execute(conn) + .map_res("Error saving send") + } + Err(e) => Err(e.into()), + }.map_res("Error saving send") } - postgresql, sqlite { + postgresql { diesel::insert_into(sends::table) .values(&*self) .on_conflict(sends::uuid) diff --git a/src/db/models/user.rs b/src/db/models/user.rs index 81cb8d84..93d750d5 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -463,17 +463,15 @@ impl Invitation { } db_run! { conn: - // Not checking for ForeignKey Constraints here - // Table invitations does not have any ForeignKey Constraints. - mysql { - diesel::insert_into(invitations::table) + sqlite, mysql { + // Not checking for ForeignKey Constraints here + // Table invitations does not have any ForeignKey Constraints. + diesel::replace_into(invitations::table) .values(self) - .on_conflict(diesel::dsl::DuplicatedKeys) - .do_nothing() .execute(conn) .map_res("Error saving invitation") } - postgresql, sqlite { + postgresql { diesel::insert_into(invitations::table) .values(self) .on_conflict(invitations::email) @@ -530,13 +528,13 @@ pub struct UserId(String); impl SsoUser { pub async fn save(&self, conn: &DbConn) -> EmptyResult { db_run! { conn: - mysql { - diesel::insert_into(sso_users::table) + sqlite, mysql { + diesel::replace_into(sso_users::table) .values(self) .execute(conn) .map_res("Error saving SSO user") } - postgresql, sqlite { + postgresql { diesel::insert_into(sso_users::table) .values(self) .execute(conn) diff --git a/src/error.rs b/src/error.rs index d90c38e3..ecbc8199 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_ng::error::YubicoError as YubiErr; +use yubico::yubicoerror::YubicoError as YubiErr; #[derive(Serialize)] pub struct Empty {} diff --git a/src/http_client.rs b/src/http_client.rs index 5ef293fc..0831d990 100644 --- a/src/http_client.rs +++ b/src/http_client.rs @@ -14,10 +14,7 @@ use reqwest::{ }; use url::Host; -use crate::{ - CONFIG, - util::{get_env_bool, is_global}, -}; +use crate::{CONFIG, util::is_global}; pub fn make_http_request(method: reqwest::Method, url: &str) -> Result { static INSTANCE: LazyLock = @@ -39,7 +36,7 @@ pub fn get_reqwest_client_builder(enforce_block: bool) -> ClientBuilder { let mut headers = header::HeaderMap::new(); headers.insert(header::USER_AGENT, header::HeaderValue::from_static("Vaultwarden")); - let redirect_policy = reqwest::redirect::Policy::custom(move |attempt| { + let redirect_policy = reqwest::redirect::Policy::custom(|attempt| { if attempt.previous().len() >= 5 { return attempt.error("Too many redirects"); } @@ -48,7 +45,7 @@ pub fn get_reqwest_client_builder(enforce_block: bool) -> ClientBuilder { return attempt.error("Invalid host"); }; - if enforce_block && let Err(e) = should_block_host(&host) { + if let Err(e) = should_block_host(&host) { return attempt.error(e); } @@ -62,14 +59,6 @@ pub fn get_reqwest_client_builder(enforce_block: bool) -> ClientBuilder { .timeout(Duration::from_secs(10)) } -fn dns_prefer_ipv6() -> bool { - // CONFIG may require DNS to initialize, so avoid forcing it during bootstrap. - match LazyLock::get(&CONFIG) { - Some(config) => config.dns_prefer_ipv6(), - None => get_env_bool("DNS_PREFER_IPV6").unwrap_or(false), - } -} - fn should_block_ip(ip: IpAddr) -> bool { if !CONFIG.http_request_block_non_global_ips() { return false; @@ -269,8 +258,12 @@ impl CustomDnsResolver { fn new() -> Arc { TokioResolver::builder(TokioRuntimeProvider::default()) .and_then(|mut builder| { - // Query both families; the preferred order is applied per lookup below. - builder.options_mut().ip_strategy = hickory_resolver::config::LookupIpStrategy::Ipv4AndIpv6; + // Hickory's default since v0.26 is `Ipv6AndIpv4`, which sorts IPv6 first + // This might cause issues on IPv4 only systems or containers + // Unless someone enabled DNS_PREFER_IPV6, use Ipv4AndIpv6, which returns IPv4 first which was our previous default + if !CONFIG.dns_prefer_ipv6() { + builder.options_mut().ip_strategy = hickory_resolver::config::LookupIpStrategy::Ipv4AndIpv6; + } builder.build() }) .inspect_err(|e| warn!("Error creating Hickory resolver, falling back to default: {e:?}")) @@ -296,17 +289,6 @@ impl CustomDnsResolver { } } -fn sort_addresses(addresses: &mut [SocketAddr], prefer_ipv6: bool) { - // `sort_by_key` orders `false` before `true`. - // When IPv6 is preferred, IPv6 addresses return `false` for `is_ipv4()` and sort first. - // When IPv4 is preferred, IPv4 addresses return `false` for `is_ipv6()` and sort first. - if prefer_ipv6 { - addresses.sort_by_key(SocketAddr::is_ipv4); - } else { - addresses.sort_by_key(SocketAddr::is_ipv6); - } -} - fn pre_resolve(name: &str, enforce_block: bool) -> Result<(), CustomHttpClientError> { let Ok(host) = get_valid_host(name) else { return Err(CustomHttpClientError::Invalid { @@ -338,9 +320,7 @@ impl Resolve for CustomDns { let this = Arc::clone(&self.resolver); Box::pin(async move { let name = name.as_str(); - let mut results = this.resolve_domain(name, enforce_block).await?; - // Recheck after bootstrap so long-lived clients adopt the loaded config. - sort_addresses(&mut results, dns_prefer_ipv6()); + let results = this.resolve_domain(name, enforce_block).await?; if results.is_empty() { warn!("Unable to resolve {name} to any valid IP address"); } @@ -359,29 +339,10 @@ pub(crate) mod aws { }; use reqwest::Client; - use super::get_reqwest_client_builder; - // Adapter that wraps reqwest to be compatible with the AWS SDK #[derive(Debug)] pub(crate) struct AwsReqwestConnector { - client: Client, - } - - impl AwsReqwestConnector { - pub(crate) fn new() -> Self { - let client = get_reqwest_client_builder(false).build().expect("Failed to build AWS HTTP client"); - Self { - client, - } - } - } - - fn connector_error(error: reqwest::Error) -> ConnectorError { - if error.is_timeout() { - ConnectorError::timeout(Box::new(error)) - } else { - ConnectorError::io(Box::new(error)) - } + pub(crate) client: Client, } impl HttpConnector for AwsReqwestConnector { @@ -401,10 +362,10 @@ pub(crate) mod aws { req_builder = req_builder.body(body_bytes.to_vec()); } - let response = req_builder.send().await.map_err(connector_error)?; + let response = req_builder.send().await.map_err(|e| ConnectorError::io(Box::new(e)))?; let status = response.status().into(); - let bytes = response.bytes().await.map_err(connector_error)?; + let bytes = response.bytes().await.map_err(|e| ConnectorError::io(Box::new(e)))?; Ok(HttpResponse::new(status, bytes.into())) }; @@ -430,7 +391,7 @@ pub(crate) mod aws { mod tests { use super::*; use crate::util::is_global_hardcoded; - use std::net::{Ipv4Addr, Ipv6Addr}; + use std::net::Ipv4Addr; use url::Host; // === @@ -443,26 +404,6 @@ mod tests { } } - #[test] - fn dns_setup_does_not_initialize_config() { - assert!(LazyLock::get(&CONFIG).is_none()); - drop(CustomDns::instance(false)); - assert!(LazyLock::get(&CONFIG).is_none()); - } - - #[test] - fn dns_preference_orders_addresses() { - let ipv4 = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0); - let ipv6 = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 0); - let mut addresses = [ipv6, ipv4]; - - sort_addresses(&mut addresses, false); - assert_eq!(addresses, [ipv4, ipv6]); - - sort_addresses(&mut addresses, true); - assert_eq!(addresses, [ipv6, ipv4]); - } - #[test] fn dotted_decimal_loopback_normalizes() { let ip = parse_to_ip("127.0.0.1").unwrap(); diff --git a/src/mail.rs b/src/mail.rs index b20f2853..a7e5e5ae 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -633,24 +633,14 @@ pub async fn send_test(address: &str) -> EmptyResult { send_email(address, &subject, body_html, body_text).await } -pub async fn send_admin_account_recovery( - address: &str, - user_name: &str, - org_name: &str, - reset_password: bool, - reset_2fa: bool, - fallback_2fa_email: bool, -) -> EmptyResult { +pub async fn send_admin_reset_password(address: &str, user_name: &str, org_name: &str) -> EmptyResult { let (subject, body_html, body_text) = get_text( - "email/admin_account_recovery", + "email/admin_reset_password", json!({ "url": CONFIG.domain(), "img_src": CONFIG._smtp_img_src(), "user_name": user_name, "org_name": org_name, - "reset_password": reset_password, - "reset_2fa": reset_2fa, - "fallback_2fa_email": fallback_2fa_email, }), )?; send_email(address, &subject, body_html, body_text).await diff --git a/src/main.rs b/src/main.rs index 437354af..28645694 100644 --- a/src/main.rs +++ b/src/main.rs @@ -137,7 +137,9 @@ fn parse_args() { if let Some(command) = pargs.subcommand().unwrap_or_default() { if command == "hash" { - use argon2::{Algorithm::Argon2id, Argon2, ParamsBuilder, PasswordHasher, Version::V0x13}; + use argon2::{ + Algorithm::Argon2id, Argon2, ParamsBuilder, PasswordHasher, Version::V0x13, password_hash::SaltString, + }; let mut argon2_params = ParamsBuilder::new(); let preset: Option = pargs.opt_value_from_str(["-p", "--preset"]).unwrap_or_default(); @@ -170,10 +172,10 @@ fn parse_args() { } let argon2 = Argon2::new(Argon2id, V0x13, argon2_params.build().unwrap()); - let salt = crypto::get_random_bytes::<32>(); + let salt = SaltString::encode_b64(&crypto::get_random_bytes::<32>()).unwrap(); let argon2_timer = tokio::time::Instant::now(); - if let Ok(password_hash) = argon2.hash_password_with_salt(password.as_bytes(), &salt) { + if let Ok(password_hash) = argon2.hash_password(password.as_bytes(), &salt) { println!( "\n\ ADMIN_TOKEN='{password_hash}'\n\n\ diff --git a/src/static/scripts/admin.js b/src/static/scripts/admin.js index fa949a40..3f6bb1df 100644 --- a/src/static/scripts/admin.js +++ b/src/static/scripts/admin.js @@ -1,5 +1,6 @@ "use strict"; -/* exported BASE_URL, _post, _delete */ +/* eslint-env es2017, browser */ +/* 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 ae4d4235..2cff4410 100644 --- a/src/static/scripts/admin_diagnostics.js +++ b/src/static/scripts/admin_diagnostics.js @@ -1,4 +1,5 @@ "use strict"; +/* eslint-env es2017, browser */ /* global BASE_URL:readable, bootstrap:readable */ var dnsCheck = false; @@ -79,44 +80,37 @@ 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: ${yn(dj.running_within_container)} (Base: ${dj.container_base_image})\n`; + supportString += `* Running within a container: ${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: ${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`; + supportString += `* Uses config.json: ${dj.overrides !== ""}\n`; + supportString += `* Uses a reverse proxy: ${dj.ip_header_exists}\n`; if (dj.ip_header_exists) { - supportString += `* IP Header check: ${chk(dj.ip_header_match)} (${dj.ip_header_name})\n`; + supportString += `* IP Header check: ${dj.ip_header_match} (${dj.ip_header_name})\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`; + supportString += `* Internet access: ${dj.has_http_access}\n`; + supportString += `* Internet access via a proxy: ${dj.uses_proxy}\n`; + supportString += `* DNS Check: ${dnsCheck}\n`; if (dj.tz_env !== "") { supportString += `* TZ environment: ${dj.tz_env}\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`; + 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`; if (dj.enable_websocket) { - supportString += `* Websocket Check: ${chk(websocketCheck)}\n`; + supportString += `* Websocket Check: ${websocketCheck}\n`; } else { supportString += "* Websocket Check: disabled\n"; } - supportString += `* HTTP Response Checks: ${chk(httpResponseCheck)}\n`; + supportString += `* HTTP Response Checks: ${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 0aa57dcd..c885344e 100644 --- a/src/static/scripts/admin_organizations.js +++ b/src/static/scripts/admin_organizations.js @@ -1,5 +1,6 @@ "use strict"; -/* global DataTable, _post:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ +/* eslint-env es2017, browser, jquery */ +/* global _post:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ function deleteOrganization(event) { event.preventDefault(); @@ -41,9 +42,8 @@ function initActions() { // onLoad events document.addEventListener("DOMContentLoaded", (/*event*/) => { - const columnCount = document.getElementById("orgs-table").querySelectorAll("thead th").length; - new DataTable("#orgs-table", { - "drawCallback": function () { + jQuery("#orgs-table").DataTable({ + "drawCallback": function() { initActions(); }, "stateSave": true, @@ -54,7 +54,7 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => { ], "pageLength": -1, // Default show all "columnDefs": [{ - "targets": [columnCount - 2, columnCount - 1], // Do not include the last two columns into the search/order features + "targets": [4,5], "searchable": false, "orderable": false }] @@ -67,4 +67,4 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => { if (btnReload) { btnReload.addEventListener("click", reload); } -}); +}); \ No newline at end of file diff --git a/src/static/scripts/admin_settings.js b/src/static/scripts/admin_settings.js index 9061719e..3d61a508 100644 --- a/src/static/scripts/admin_settings.js +++ b/src/static/scripts/admin_settings.js @@ -1,4 +1,5 @@ "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 63ee2d7b..99e39aab 100644 --- a/src/static/scripts/admin_users.js +++ b/src/static/scripts/admin_users.js @@ -1,5 +1,6 @@ "use strict"; -/* global DataTable, _post:readable, _delete:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ +/* eslint-env es2017, browser, jquery */ +/* global _post:readable, _delete:readable BASE_URL:readable, reload:readable, jdenticon:readable */ function deleteUser(event) { event.preventDefault(); @@ -141,7 +142,7 @@ function inviteUser(event) { ); } -function resendUserInvite(event) { +function resendUserInvite (event) { event.preventDefault(); event.stopPropagation(); const id = event.target.parentNode.dataset.vwUserUuid; @@ -179,9 +180,37 @@ const ORG_TYPES = { }, }; +// Special sort function to sort dates in ISO format +jQuery.extend(jQuery.fn.dataTableExt.oSort, { + "date-iso-pre": function(a) { + let x; + const sortDate = a.replace(/(<([^>]+)>)/gi, "").trim(); + if (sortDate !== "") { + const dtParts = sortDate.split(" "); + const timeParts = (undefined != dtParts[1]) ? dtParts[1].split(":") : ["00", "00", "00"]; + const dateParts = dtParts[0].split("-"); + x = (dateParts[0] + dateParts[1] + dateParts[2] + timeParts[0] + timeParts[1] + ((undefined != timeParts[2]) ? timeParts[2] : 0)) * 1; + if (isNaN(x)) { + x = 0; + } + } else { + x = Infinity; + } + return x; + }, + + "date-iso-asc": function(a, b) { + return a - b; + }, + + "date-iso-desc": function(a, b) { + return b - a; + } +}); + const userOrgTypeDialog = document.getElementById("userOrgTypeDialog"); // Fill the form and title -userOrgTypeDialog.addEventListener("show.bs.modal", function (event) { +userOrgTypeDialog.addEventListener("show.bs.modal", function(event) { // Get shared values const userEmail = event.relatedTarget.parentNode.dataset.vwUserEmail; const userUuid = event.relatedTarget.parentNode.dataset.vwUserUuid; @@ -199,7 +228,7 @@ userOrgTypeDialog.addEventListener("show.bs.modal", function (event) { }, false); // Prevent accidental submission of the form with valid elements after the modal has been hidden. -userOrgTypeDialog.addEventListener("hide.bs.modal", function () { +userOrgTypeDialog.addEventListener("hide.bs.modal", function() { document.getElementById("userOrgTypeDialogOrgName").textContent = ""; document.getElementById("userOrgTypeDialogUserEmail").textContent = ""; document.getElementById("userOrgTypeUserUuid").value = ""; @@ -221,7 +250,7 @@ function updateUserOrgType(event) { function initUserTable() { // Color all the org buttons per type - document.querySelectorAll("button[data-vw-org-type]").forEach(function (e) { + document.querySelectorAll("button[data-vw-org-type]").forEach(function(e) { const orgType = ORG_TYPES[e.dataset.vwOrgType]; e.style.backgroundColor = orgType.bg; if (orgType.font !== undefined) { @@ -257,37 +286,12 @@ function initUserTable() { } } -// Special sort function to sort dates in ISO format and have anything else as 0 -DataTable.ext.type.order["date-iso-pre"] = function (a) { - let x; - const sortDate = a.replace(/(<([^>]+)>)/gi, "").trim(); - if (sortDate !== "") { - const dtParts = sortDate.split(" "); - const timeParts = (undefined != dtParts[1]) ? dtParts[1].split(":") : ["00", "00", "00"]; - const dateParts = dtParts[0].split("-"); - x = (dateParts[0] + dateParts[1] + dateParts[2] + timeParts[0] + timeParts[1] + ((undefined != timeParts[2]) ? timeParts[2] : 0)) * 1; - if (isNaN(x)) { - x = 0; - } - } else { - x = Infinity; - } - return x; -}; - // onLoad events document.addEventListener("DOMContentLoaded", (/*event*/) => { - DataTable.ext.type.detect.unshift(function (data) { - if (typeof data !== "string") { return null; } - return data.indexOf("data-sort-type=\"date-iso\"") !== -1 - ? "date-iso" - : null; - }); - - const columnCount = document.getElementById("users-table").querySelectorAll("thead th").length; - new DataTable("#users-table", { - "typeDetect": true, - "drawCallback": function () { + const size = jQuery("#users-table > thead th").length; + const ssoOffset = size-7; + jQuery("#users-table").DataTable({ + "drawCallback": function() { initUserTable(); }, "stateSave": true, @@ -298,7 +302,10 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => { ], "pageLength": -1, // Default show all "columnDefs": [{ - "targets": columnCount - 1, // Do not include the last column into the search/order features + "targets": [1 + ssoOffset, 2 + ssoOffset], + "type": "date-iso" + }, { + "targets": size-1, "searchable": false, "orderable": false }] diff --git a/src/static/scripts/datatables.css b/src/static/scripts/datatables.css index 48b7400c..e518c143 100644 --- a/src/static/scripts/datatables.css +++ b/src/static/scripts/datatables.css @@ -4,32 +4,25 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#bs5/dt-3.0.3 + * https://datatables.net/download/#bs5/dt-2.3.8 * * Included libraries: - * DataTables 3.0.3 + * DataTables 2.3.8 */ -/*! DataTables Bootstrap 5 integration - * © SpryMedia Ltd - datatables.net/license - */ :root { - --dt_background-selected: 13, 110, 253; - --dt_color-selected: 255, 255, 255; - --dt_link_color-selected: 228, 228, 228; - --dt-row_background: transparent; - --dt-row_background-selected: var(--dt_background-selected); - --dt-row-text_color-selected: var(--dt_color-selected); - --dt-row-link_color-selected: var(--dt_link_color-selected); - --dt-row_background-stripe: 0, 0, 0; - --dt-row_background-hover: 0, 0, 0; - --dt-column-ordering_background: 0, 0, 0; - --dt-header-cell_align-items: center; - --dt-header-cell_vertical-align: middle; - --dt-html_background: white; + --dt-row-selected: 13, 110, 253; + --dt-row-selected-text: 255, 255, 255; + --dt-row-selected-link: 228, 228, 228; + --dt-row-stripe: 0, 0, 0; + --dt-row-hover: 0, 0, 0; + --dt-column-ordering: 0, 0, 0; + --dt-header-align-items: center; + --dt-header-vertical-align: middle; + --dt-html-background: white; } :root.dark { - --dt-html_background: rgb(33, 37, 41); + --dt-html-background: rgb(33, 37, 41); } table.dataTable tbody td.dt-control { @@ -51,13 +44,16 @@ table.dataTable tbody tr.dt-hasChild td.dt-control:before { border-bottom: 0px solid transparent; border-right: 5px solid transparent; } +table.dataTable tfoot:empty { + display: none; +} -:root.dark table.dataTable td.dt-control:before, +html.dark table.dataTable td.dt-control:before, :root[data-bs-theme=dark] table.dataTable td.dt-control:before, :root[data-theme=dark] table.dataTable td.dt-control:before { border-left-color: rgba(255, 255, 255, 0.5); } -:root.dark table.dataTable tr.dt-hasChild td.dt-control:before, +html.dark table.dataTable tr.dt-hasChild td.dt-control:before, :root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before, :root[data-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before { border-top-color: rgba(255, 255, 255, 0.5); @@ -88,25 +84,6 @@ div.dt-scroll-body tfoot tr td div.dt-scroll-sizing { overflow: hidden !important; } -/*! DataTables Bootstrap 5 integration - * © SpryMedia Ltd - datatables.net/license - */ -:root { - --dt-order-arrow_color: rgb(51, 51, 51); - --dt-order-arrow_color-current: rgb(51, 51, 51); - --dt-order-arrow-height: 7px; - --dt-order-arrow_opacity: 0.125; - --dt-order-arrow_opacity-current: 0.65; - --dt-order-arrow-width: 8px; - --dt-order-arrow-gap: 1px; - --dt-order-header_outline-hover: 2px solid rgba(0, 0, 0, 0.05); -} -:root.dark, :root[data-bs-theme=dark], :root[data-theme=dark] { - --dt-order-arrow_color: rgb(229, 233, 238); - --dt-order-arrow_color-current: rgb(229, 233, 238); - --dt-order-header_outline-hover: 2px solid rgba(255, 255, 255, 0.05); -} - table.dataTable thead > tr > th:active, table.dataTable thead > tr > td:active { outline: none; @@ -114,18 +91,20 @@ table.dataTable thead > tr > td:active { table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > td.dt-orderable-asc .dt-column-order:before, table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before { - bottom: calc(50% + var(--dt-order-arrow-gap)); - border-bottom: var(--dt-order-arrow-height) solid var(--dt-order-arrow_color); - border-left: calc(var(--dt-order-arrow-width) / 2) solid transparent; - border-right: calc(var(--dt-order-arrow-width) / 2) solid transparent; + position: absolute; + display: block; + bottom: 50%; + content: "\25B2"; + content: "\25B2"/""; } table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, table.dataTable thead > tr > td.dt-orderable-desc .dt-column-order:after, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:after { - top: calc(50% + 1px); - border-top: var(--dt-order-arrow-height) solid var(--dt-order-arrow_color); - border-left: calc(var(--dt-order-arrow-width) / 2) solid transparent; - border-right: calc(var(--dt-order-arrow-width) / 2) solid transparent; + position: absolute; + display: block; + top: 50%; + content: "\25BC"; + content: "\25BC"/""; } table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order, table.dataTable thead > tr > td.dt-orderable-asc .dt-column-order, @@ -133,8 +112,8 @@ table.dataTable thead > tr > td.dt-orderable-desc .dt-column-order, table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order { position: relative; - width: var(--dt-order-arrow-width); - align-self: stretch; + width: 12px; + height: 20px; } table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, table.dataTable thead > tr > td.dt-orderable-asc .dt-column-order:before, @@ -145,14 +124,10 @@ table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:after, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:before, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:after { - position: absolute; - display: block; - content: " "; - height: 0; - width: 0; left: 0; - color: var(--dt-order-arrow_color); - opacity: var(--dt-order-arrow_opacity); + opacity: 0.125; + line-height: 9px; + font-size: 0.8em; } table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > td.dt-orderable-asc, @@ -162,18 +137,13 @@ table.dataTable thead > tr > td.dt-orderable-desc { table.dataTable thead > tr > th.dt-orderable-asc:hover, table.dataTable thead > tr > th.dt-orderable-desc:hover, table.dataTable thead > tr > td.dt-orderable-asc:hover, table.dataTable thead > tr > td.dt-orderable-desc:hover { - outline: var(--dt-order-header_outline-hover); + outline: 2px solid rgba(0, 0, 0, 0.05); outline-offset: -2px; } -table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, -table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before { - border-bottom-color: var(--dt-order-arrow_color-current); - opacity: var(--dt-order-arrow_opacity-current); -} -table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, +table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:after { - border-top-color: var(--dt-order-arrow_color-current); - opacity: var(--dt-order-arrow_opacity-current); + opacity: 0.6; } table.dataTable thead > tr > th.dt-orderable-none:not(.dt-ordering-asc, .dt-ordering-desc) .dt-column-order:empty, table.dataTable thead > tr > th.sorting_desc_disabled .dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled .dt-column-order:before, table.dataTable thead > tr > td.dt-orderable-none:not(.dt-ordering-asc, .dt-ordering-desc) .dt-column-order:empty, @@ -196,7 +166,7 @@ table.dataTable tfoot > tr > td div.dt-column-header, table.dataTable tfoot > tr > td div.dt-column-footer { display: flex; justify-content: space-between; - align-items: var(--dt-header-cell_align-items); + align-items: var(--dt-header-align-items); gap: 4px; } table.dataTable thead > tr > th div.dt-column-header .dt-column-title, @@ -232,14 +202,7 @@ div.dt-scroll-body > table.dataTable > thead > tr > td { :root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-desc:hover, :root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-asc:hover, :root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-desc:hover { - outline: var(--dt-order-header_outline-hover); -} - -/*! DataTables Bootstrap 5 integration - * © SpryMedia Ltd - datatables.net/license - */ -:root { - --dt-processing-circle_background: var(--dt_background-selected); + outline: 2px solid rgba(255, 255, 255, 0.05); } div.dt-processing { @@ -265,7 +228,8 @@ div.dt-processing > div:last-child > div { width: 13px; height: 13px; border-radius: 50%; - background: rgb(var(--dt-processing-circle_background)); + background: rgb(13, 110, 253); + background: rgb(var(--dt-row-selected)); animation-timing-function: cubic-bezier(0, 1, 1, 0); } div.dt-processing > div:last-child > div:nth-child(1) { @@ -378,7 +342,7 @@ table.dataTable thead td, table.dataTable tfoot th, table.dataTable tfoot td { text-align: left; - vertical-align: var(--dt-header-cell_vertical-align); + vertical-align: var(--dt-header-vertical-align); } table.dataTable thead th.dt-head-left, table.dataTable thead td.dt-head-left, @@ -461,16 +425,11 @@ table.dataTable tbody td.dt-body-nowrap { white-space: nowrap; } -:root { - --dt_background-selected: 13, 110, 253; -} - -:root[data-bs-theme=dark] { - --dt-row_background-hover: 255, 255, 255; - --dt-row_background-stripe: 255, 255, 255; - --dt-column-ordering_background: 255, 255, 255; -} - +/*! Bootstrap 5 integration for DataTables + * + * ©2020 SpryMedia Ltd, all rights reserved. + * License: MIT datatables.net/license/mit + */ table.table.dataTable { clear: both; margin-bottom: 0; @@ -484,26 +443,31 @@ table.table.dataTable > :not(caption) > * > * { background-color: var(--bs-table-bg); } table.table.dataTable > tbody > tr { - background-color: var(--dt-row_background); + background-color: transparent; } table.table.dataTable > tbody > tr.selected > * { - box-shadow: inset 0 0 0 9999px rgb(var(--dt-row_background-selected)); - color: rgb(var(--dt-row-text_color-selected)); + box-shadow: inset 0 0 0 9999px rgb(13, 110, 253); + box-shadow: inset 0 0 0 9999px rgb(var(--dt-row-selected)); + color: rgb(255, 255, 255); + color: rgb(var(--dt-row-selected-text)); } table.table.dataTable > tbody > tr.selected a { - color: rgb(var(--dt-row-link_color-selected)); + color: rgb(228, 228, 228); + color: rgb(var(--dt-row-selected-link)); } table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * { - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row_background-stripe), 0.05); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05); } table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1).selected > * { - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row_background-selected), 0.95); + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95); } table.table.dataTable.table-hover > tbody > tr:hover > * { - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row_background-hover), 0.075); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075); } table.table.dataTable.table-hover > tbody > tr.selected:hover > * { - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row_background-selected), 0.975); + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975); } div.dt-container div.dt-layout-start > *:not(:last-child) { @@ -652,4 +616,10 @@ div.table-responsive > div.dt-container > div.row > div[class^=col-]:last-child padding-right: 0; } +:root[data-bs-theme=dark] { + --dt-row-hover: 255, 255, 255; + --dt-row-stripe: 255, 255, 255; + --dt-column-ordering: 255, 255, 255; +} + diff --git a/src/static/scripts/datatables.js b/src/static/scripts/datatables.js index 1ae94cd2..c9f9ea56 100644 --- a/src/static/scripts/datatables.js +++ b/src/static/scripts/datatables.js @@ -4,12792 +4,14196 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#bs5/dt-3.0.3 + * https://datatables.net/download/#bs5/dt-2.3.8 * * Included libraries: - * DataTables 3.0.3 + * DataTables 2.3.8 */ -/*! DataTables 3.0.3 - * Copyright (c) SpryMedia Ltd - datatables.net/license +/*! DataTables 2.3.8 + * © SpryMedia Ltd - datatables.net/license */ -(function(factory){ - if (typeof define === 'function' && define.amd) { +(function( factory ) { + "use strict"; + + if ( typeof define === 'function' && define.amd ) { // AMD - define([], function () { - return factory(window, document); - }); + define( ['jquery'], function ( $ ) { + return factory( $, window, document ); + } ); } - else if (typeof exports === 'object') { + else if ( typeof exports === 'object' ) { // CommonJS - var cjsRequires = function (root) { }; + // jQuery's factory checks for a global window - if it isn't present then it + // returns a factory function that expects the window object + var jq = require('jquery'); if (typeof window === 'undefined') { - module.exports = function (root) { - if (! root) { + module.exports = function (root, $) { + if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } - cjsRequires(root); - return factory(root, root.document); + if ( ! $ ) { + $ = jq( root ); + } + + return factory( $, root, root.document ); }; } else { - cjsRequires(window); - module.exports = factory(window, window.document); + module.exports = factory( jq, window, window.document ); } } else { // Browser - window.DataTable = factory(window, document); + window.DataTable = factory( jQuery, window, document ); } -}(function(window, document) { -'use strict'; +}(function( $, window, document ) { + "use strict"; + + var DataTable = function ( selector, options ) + { + // Check if called with a window or jQuery object for DOM less applications + // This is for backwards compatibility + if (DataTable.factory(selector, options)) { + return DataTable; + } + + // When creating with `new`, create a new DataTable, returning the API instance + if (this instanceof DataTable) { + return $(selector).DataTable(options); + } + else { + // Argument switching + options = selector; + } + + var _that = this; + var emptyInit = options === undefined; + var len = this.length; + + if ( emptyInit ) { + options = {}; + } + + // Method to get DT API instance from jQuery object + this.api = function () + { + return new _Api( this ); + }; + + this.each(function() { + // For each initialisation we want to give it a clean initialisation + // object that can be bashed around + var o = {}; + var oInit = len > 1 ? // optimisation for single table case + _fnExtend( o, options, true ) : + options; + + + var i=0, iLen; + var sId = this.getAttribute( 'id' ); + var defaults = DataTable.defaults; + var $this = $(this); + + // Sanity check + if ( this.nodeName.toLowerCase() != 'table' ) + { + _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); + return; + } + + // Special case for options + if (oInit.on && oInit.on.options) { + _fnListener($this, 'options', oInit.on.options); + } + + $this.trigger( 'options.dt', oInit ); + + /* Backwards compatibility for the defaults */ + _fnCompatOpts( defaults ); + _fnCompatCols( defaults.column ); + + /* Convert the camel-case defaults to Hungarian */ + _fnCamelToHungarian( defaults, defaults, true ); + _fnCamelToHungarian( defaults.column, defaults.column, true ); + + /* Setting up the initialisation object */ + _fnCamelToHungarian( defaults, $.extend( oInit, _fnEscapeObject($this.data()) ), true ); + + + + /* Check to see if we are re-initialising a table */ + var allSettings = DataTable.settings; + for ( i=0, iLen=allSettings.length ; i'), + fastData: function (row, column, type) { + return _fnGetCellData(oSettings, row, column, type); + } + } ); + oSettings.nTable = this; + oSettings.oInit = oInit; + + allSettings.push( oSettings ); + + // Make a single API instance available for internal handling + oSettings.api = new _Api( oSettings ); + + // Need to add the instance after the instance after the settings object has been added + // to the settings array, so we can self reference the table instance if more than one + oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable(); + + // Backwards compatibility, before we apply all the defaults + _fnCompatOpts( oInit ); + + // If the length menu is given, but the init display length is not, use the length menu + if ( oInit.aLengthMenu && ! oInit.iDisplayLength ) + { + oInit.iDisplayLength = Array.isArray(oInit.aLengthMenu[0]) + ? oInit.aLengthMenu[0][0] + : $.isPlainObject( oInit.aLengthMenu[0] ) + ? oInit.aLengthMenu[0].value + : oInit.aLengthMenu[0]; + } + + // Apply the defaults and init options to make a single init object will all + // options defined from defaults and instance options. + oInit = _fnExtend( $.extend( true, {}, defaults ), oInit ); + + + // Map the initialisation options onto the settings object + _fnMap( oSettings.oFeatures, oInit, [ + "bPaginate", + "bLengthChange", + "bFilter", + "bSort", + "bSortMulti", + "bInfo", + "bProcessing", + "bAutoWidth", + "bSortClasses", + "bServerSide", + "bDeferRender" + ] ); + _fnMap( oSettings, oInit, [ + "ajax", + "fnFormatNumber", + "sServerMethod", + "aaSorting", + "aaSortingFixed", + "aLengthMenu", + "sPaginationType", + "iStateDuration", + "bSortCellsTop", + "iTabIndex", + "sDom", + "fnStateLoadCallback", + "fnStateSaveCallback", + "renderer", + "searchDelay", + "rowId", + "caption", + "layout", + "orderDescReverse", + "orderIndicators", + "orderHandler", + "titleRow", + "typeDetect", + "columnTitleTag", + [ "iCookieDuration", "iStateDuration" ], // backwards compat + [ "oSearch", "oPreviousSearch" ], + [ "aoSearchCols", "aoPreSearchCols" ], + [ "iDisplayLength", "_iDisplayLength" ] + ] ); + _fnMap( oSettings.oScroll, oInit, [ + [ "sScrollX", "sX" ], + [ "sScrollXInner", "sXInner" ], + [ "sScrollY", "sY" ], + [ "bScrollCollapse", "bCollapse" ] + ] ); + _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" ); + + /* Callback functions which are array driven */ + _fnCallbackReg( oSettings, 'aoDrawCallback', oInit.fnDrawCallback ); + _fnCallbackReg( oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams ); + _fnCallbackReg( oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams ); + _fnCallbackReg( oSettings, 'aoStateLoaded', oInit.fnStateLoaded ); + _fnCallbackReg( oSettings, 'aoRowCallback', oInit.fnRowCallback ); + _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow ); + _fnCallbackReg( oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback ); + _fnCallbackReg( oSettings, 'aoFooterCallback', oInit.fnFooterCallback ); + _fnCallbackReg( oSettings, 'aoInitComplete', oInit.fnInitComplete ); + _fnCallbackReg( oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback ); + + oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId ); + + // Add event listeners + if (oInit.on) { + Object.keys(oInit.on).forEach(function (key) { + _fnListener($this, key, oInit.on[key]); + }); + } + + /* Browser support detection */ + _fnBrowserDetect( oSettings ); + + var oClasses = oSettings.oClasses; + + $.extend( oClasses, DataTable.ext.classes, oInit.oClasses ); + $this.addClass( oClasses.table ); + + if (! oSettings.oFeatures.bPaginate) { + oInit.iDisplayStart = 0; + } + + if ( oSettings.iInitDisplayStart === undefined ) + { + /* Display start point, taking into account the save saving */ + oSettings.iInitDisplayStart = oInit.iDisplayStart; + oSettings._iDisplayStart = oInit.iDisplayStart; + } + + var defer = oInit.iDeferLoading; + if ( defer !== null ) + { + oSettings.deferLoading = true; + + var tmp = Array.isArray(defer); + oSettings._iRecordsDisplay = tmp ? defer[0] : defer; + oSettings._iRecordsTotal = tmp ? defer[1] : defer; + } + + /* + * Columns + * See if we should load columns automatically or use defined ones + */ + var columnsInit = []; + var thead = this.getElementsByTagName('thead'); + var initHeaderLayout = _fnDetectHeader( oSettings, thead[0] ); + + // If we don't have a columns array, then generate one with nulls + if ( oInit.aoColumns ) { + columnsInit = oInit.aoColumns; + } + else if ( initHeaderLayout.length ) { + for ( i=0, iLen=initHeaderLayout[0].length ; i').prependTo( $this ); + } + + caption.html( oSettings.caption ); + } + + // Store the caption side, so we can remove the element from the document + // when creating the element + if (caption.length) { + caption[0]._captionSide = caption.css('caption-side'); + oSettings.captionNode = caption[0]; + } + + // Place the colgroup element in the correct location for the HTML structure + if (caption.length) { + oSettings.colgroup.insertAfter(caption); + } + else { + oSettings.colgroup.prependTo(oSettings.nTable); + } + + if ( thead.length === 0 ) { + thead = $('').appendTo($this); + } + oSettings.nTHead = thead[0]; + + var tbody = $this.children('tbody'); + if ( tbody.length === 0 ) { + tbody = $('').insertAfter(thead); + } + oSettings.nTBody = tbody[0]; + + var tfoot = $this.children('tfoot'); + if ( tfoot.length === 0 ) { + // If we are a scrolling table, and no footer has been given, then we need to create + // a tfoot element for the caption element to be appended to + tfoot = $('').appendTo($this); + } + oSettings.nTFoot = tfoot[0]; + + // Copy the data index array + oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); + + // Initialisation complete - table can be drawn + oSettings.bInitialised = true; + + // Language definitions + var oLanguage = oSettings.oLanguage; + $.extend( true, oLanguage, oInit.oLanguage ); + + if ( oLanguage.sUrl ) { + // Get the language definitions from a file + $.ajax( { + dataType: 'json', + url: oLanguage.sUrl, + success: function ( json ) { + _fnCamelToHungarian( defaults.oLanguage, json ); + $.extend( true, oLanguage, json, oSettings.oInit.oLanguage ); + + _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true); + _fnInitialise( oSettings ); + }, + error: function () { + // Error occurred loading language file + _fnLog( oSettings, 0, 'i18n file loading error', 21 ); + + // Continue on as best we can + _fnInitialise( oSettings ); + } + } ); + } + else { + _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true); + _fnInitialise( oSettings ); + } + } ); + _that = null; + return this; + }; + + + + /** + * DataTables extensions + * + * This namespace acts as a collection area for plug-ins that can be used to + * extend DataTables capabilities. Indeed many of the build in methods + * use this method to provide their own capabilities (sorting methods for + * example). + * + * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy + * reasons + * + * @namespace + */ + DataTable.ext = _ext = { + /** + * DataTables build type (expanded by the download builder) + * + * @type string + */ + builder: "bs5/dt-2.3.8", + + /** + * Buttons. For use with the Buttons extension for DataTables. This is + * defined here so other extensions can define buttons regardless of load + * order. It is _not_ used by DataTables core. + * + * @type object + * @default {} + */ + buttons: {}, + + + /** + * ColumnControl buttons and content + * + * @type object + */ + ccContent: {}, + + + /** + * Element class names + * + * @type object + * @default {} + */ + classes: {}, + + + /** + * Error reporting. + * + * How should DataTables report an error. Can take the value 'alert', + * 'throw', 'none' or a function. + * + * @type string|function + * @default alert + */ + errMode: "alert", + + /** HTML entity escaping */ + escape: { + /** When reading data-* attributes for initialisation options */ + attributes: false + }, + + /** + * Legacy so v1 plug-ins don't throw js errors on load + */ + feature: [], + + /** + * Feature plug-ins. + * + * This is an object of callbacks which provide the features for DataTables + * to be initialised via the `layout` option. + */ + features: {}, + + + /** + * Row searching. + * + * This method of searching is complimentary to the default type based + * searching, and a lot more comprehensive as it allows you complete control + * over the searching logic. Each element in this array is a function + * (parameters described below) that is called for every row in the table, + * and your logic decides if it should be included in the searching data set + * or not. + * + * Searching functions have the following input parameters: + * + * 1. `{object}` DataTables settings object: see + * {@link DataTable.models.oSettings} + * 2. `{array|object}` Data for the row to be processed (same as the + * original format that was passed in as the data source, or an array + * from a DOM data source + * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which + * can be useful to retrieve the `TR` element if you need DOM interaction. + * + * And the following return is expected: + * + * * {boolean} Include the row in the searched result set (true) or not + * (false) + * + * Note that as with the main search ability in DataTables, technically this + * is "filtering", since it is subtractive. However, for consistency in + * naming we call it searching here. + * + * @type array + * @default [] + * + * @example + * // The following example shows custom search being applied to the + * // fourth column (i.e. the data[3] index) based on two input values + * // from the end-user, matching the data in a certain range. + * $.fn.dataTable.ext.search.push( + * function( settings, data, dataIndex ) { + * var min = document.getElementById('min').value * 1; + * var max = document.getElementById('max').value * 1; + * var version = data[3] == "-" ? 0 : data[3]*1; + * + * if ( min == "" && max == "" ) { + * return true; + * } + * else if ( min == "" && version < max ) { + * return true; + * } + * else if ( min < version && "" == max ) { + * return true; + * } + * else if ( min < version && version < max ) { + * return true; + * } + * return false; + * } + * ); + */ + search: [], + + + /** + * Selector extensions + * + * The `selector` option can be used to extend the options available for the + * selector modifier options (`selector-modifier` object data type) that + * each of the three built in selector types offer (row, column and cell + + * their plural counterparts). For example the Select extension uses this + * mechanism to provide an option to select only rows, columns and cells + * that have been marked as selected by the end user (`{selected: true}`), + * which can be used in conjunction with the existing built in selector + * options. + * + * Each property is an array to which functions can be pushed. The functions + * take three attributes: + * + * * Settings object for the host table + * * Options object (`selector-modifier` object type) + * * Array of selected item indexes + * + * The return is an array of the resulting item indexes after the custom + * selector has been applied. + * + * @type object + */ + selector: { + cell: [], + column: [], + row: [] + }, + + + /** + * Legacy configuration options. Enable and disable legacy options that + * are available in DataTables. + * + * @type object + */ + legacy: { + /** + * Enable / disable DataTables 1.9 compatible server-side processing + * requests + * + * @type boolean + * @default null + */ + ajax: null + }, + + + /** + * Pagination plug-in methods. + * + * Each entry in this object is a function and defines which buttons should + * be shown by the pagination rendering method that is used for the table: + * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the + * buttons are displayed in the document, while the functions here tell it + * what buttons to display. This is done by returning an array of button + * descriptions (what each button will do). + * + * Pagination types (the four built in options and any additional plug-in + * options defined here) can be used through the `paginationType` + * initialisation parameter. + * + * The functions defined take two parameters: + * + * 1. `{int} page` The current page index + * 2. `{int} pages` The number of pages in the table + * + * Each function is expected to return an array where each element of the + * array can be one of: + * + * * `first` - Jump to first page when activated + * * `last` - Jump to last page when activated + * * `previous` - Show previous page when activated + * * `next` - Show next page when activated + * * `{int}` - Show page of the index given + * * `{array}` - A nested array containing the above elements to add a + * containing 'DIV' element (might be useful for styling). + * + * Note that DataTables v1.9- used this object slightly differently whereby + * an object with two functions would be defined for each plug-in. That + * ability is still supported by DataTables 1.10+ to provide backwards + * compatibility, but this option of use is now decremented and no longer + * documented in DataTables 1.10+. + * + * @type object + * @default {} + * + * @example + * // Show previous, next and current page buttons only + * $.fn.dataTableExt.oPagination.current = function ( page, pages ) { + * return [ 'previous', page, 'next' ]; + * }; + */ + pager: {}, + + + renderer: { + pageButton: {}, + header: {} + }, + + + /** + * Ordering plug-ins - custom data source + * + * The extension options for ordering of data available here is complimentary + * to the default type based ordering that DataTables typically uses. It + * allows much greater control over the data that is being used to + * order a column, but is necessarily therefore more complex. + * + * This type of ordering is useful if you want to do ordering based on data + * live from the DOM (for example the contents of an 'input' element) rather + * than just the static string that DataTables knows of. + * + * The way these plug-ins work is that you create an array of the values you + * wish to be ordering for the column in question and then return that + * array. The data in the array much be in the index order of the rows in + * the table (not the currently ordering order!). Which order data gathering + * function is run here depends on the `dt-init columns.orderDataType` + * parameter that is used for the column (if any). + * + * The functions defined take two parameters: + * + * 1. `{object}` DataTables settings object: see + * {@link DataTable.models.oSettings} + * 2. `{int}` Target column index + * + * Each function is expected to return an array: + * + * * `{array}` Data for the column to be ordering upon + * + * @type array + * + * @example + * // Ordering using `input` node values + * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col ) + * { + * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { + * return $('input', td).val(); + * } ); + * } + */ + order: {}, + + + /** + * Type based plug-ins. + * + * Each column in DataTables has a type assigned to it, either by automatic + * detection or by direct assignment using the `type` option for the column. + * The type of a column will effect how it is ordering and search (plug-ins + * can also make use of the column type if required). + * + * @namespace + */ + type: { + /** + * Automatic column class assignment + */ + className: {}, + + /** + * Type detection functions. + * + * The functions defined in this object are used to automatically detect + * a column's type, making initialisation of DataTables super easy, even + * when complex data is in the table. + * + * The functions defined take two parameters: + * + * 1. `{*}` Data from the column cell to be analysed + * 2. `{settings}` DataTables settings object. This can be used to + * perform context specific type detection - for example detection + * based on language settings such as using a comma for a decimal + * place. Generally speaking the options from the settings will not + * be required + * + * Each function is expected to return: + * + * * `{string|null}` Data type detected, or null if unknown (and thus + * pass it on to the other type detection functions. + * + * @type array + * + * @example + * // Currency type detection plug-in: + * $.fn.dataTable.ext.type.detect.push( + * function ( data, settings ) { + * // Check the numeric part + * if ( ! data.substring(1).match(/[0-9]/) ) { + * return null; + * } + * + * // Check prefixed by currency + * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) { + * return 'currency'; + * } + * return null; + * } + * ); + */ + detect: [], + + /** + * Automatic renderer assignment + */ + render: {}, + + + /** + * Type based search formatting. + * + * The type based searching functions can be used to pre-format the + * data to be search on. For example, it can be used to strip HTML + * tags or to de-format telephone numbers for numeric only searching. + * + * Note that is a search is not defined for a column of a given type, + * no search formatting will be performed. + * + * Pre-processing of searching data plug-ins - When you assign the sType + * for a column (or have it automatically detected for you by DataTables + * or a type detection plug-in), you will typically be using this for + * custom sorting, but it can also be used to provide custom searching + * by allowing you to pre-processing the data and returning the data in + * the format that should be searched upon. This is done by adding + * functions this object with a parameter name which matches the sType + * for that target column. This is the corollary of afnSortData + * for searching data. + * + * The functions defined take a single parameter: + * + * 1. `{*}` Data from the column cell to be prepared for searching + * + * Each function is expected to return: + * + * * `{string|null}` Formatted string that will be used for the searching. + * + * @type object + * @default {} + * + * @example + * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { + * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); + * } + */ + search: {}, + + + /** + * Type based ordering. + * + * The column type tells DataTables what ordering to apply to the table + * when a column is sorted upon. The order for each type that is defined, + * is defined by the functions available in this object. + * + * Each ordering option can be described by three properties added to + * this object: + * + * * `{type}-pre` - Pre-formatting function + * * `{type}-asc` - Ascending order function + * * `{type}-desc` - Descending order function + * + * All three can be used together, only `{type}-pre` or only + * `{type}-asc` and `{type}-desc` together. It is generally recommended + * that only `{type}-pre` is used, as this provides the optimal + * implementation in terms of speed, although the others are provided + * for compatibility with existing JavaScript sort functions. + * + * `{type}-pre`: Functions defined take a single parameter: + * + * 1. `{*}` Data from the column cell to be prepared for ordering + * + * And return: + * + * * `{*}` Data to be sorted upon + * + * `{type}-asc` and `{type}-desc`: Functions are typical JavaScript sort + * functions, taking two parameters: + * + * 1. `{*}` Data to compare to the second parameter + * 2. `{*}` Data to compare to the first parameter + * + * And returning: + * + * * `{*}` Ordering match: <0 if first parameter should be sorted lower + * than the second parameter, ===0 if the two parameters are equal and + * >0 if the first parameter should be sorted height than the second + * parameter. + * + * @type object + * @default {} + * + * @example + * // Numeric ordering of formatted numbers with a pre-formatter + * $.extend( $.fn.dataTable.ext.type.order, { + * "string-pre": function(x) { + * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); + * return parseFloat( a ); + * } + * } ); + * + * @example + * // Case-sensitive string ordering, with no pre-formatting method + * $.extend( $.fn.dataTable.ext.order, { + * "string-case-asc": function(x,y) { + * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + * }, + * "string-case-desc": function(x,y) { + * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + * } + * } ); + */ + order: {} + }, + + /** + * Unique DataTables instance counter + * + * @type int + * @private + */ + _unique: 0, + + + // + // Depreciated + // The following properties are retained for backwards compatibility only. + // The should not be used in new projects and will be removed in a future + // version + // + + /** + * Version check function. + * @type function + * @depreciated Since 1.10 + */ + fnVersionCheck: DataTable.fnVersionCheck, + + + /** + * Index for what 'this' index API functions should use + * @type int + * @deprecated Since v1.10 + */ + iApiIndex: 0, + + + /** + * Software version + * @type string + * @deprecated Since v1.10 + */ + sVersion: DataTable.version + }; + + + // + // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts + // + $.extend( _ext, { + afnFiltering: _ext.search, + aTypes: _ext.type.detect, + ofnSearch: _ext.type.search, + oSort: _ext.type.order, + afnSortData: _ext.order, + aoFeatures: _ext.feature, + oStdClasses: _ext.classes, + oPagination: _ext.pager + } ); + + + $.extend( DataTable.ext.classes, { + container: 'dt-container', + empty: { + row: 'dt-empty' + }, + info: { + container: 'dt-info' + }, + layout: { + row: 'dt-layout-row', + cell: 'dt-layout-cell', + tableRow: 'dt-layout-table', + tableCell: '', + start: 'dt-layout-start', + end: 'dt-layout-end', + full: 'dt-layout-full' + }, + length: { + container: 'dt-length', + select: 'dt-input' + }, + order: { + canAsc: 'dt-orderable-asc', + canDesc: 'dt-orderable-desc', + isAsc: 'dt-ordering-asc', + isDesc: 'dt-ordering-desc', + none: 'dt-orderable-none', + position: 'sorting_' + }, + processing: { + container: 'dt-processing' + }, + scrolling: { + body: 'dt-scroll-body', + container: 'dt-scroll', + footer: { + self: 'dt-scroll-foot', + inner: 'dt-scroll-footInner' + }, + header: { + self: 'dt-scroll-head', + inner: 'dt-scroll-headInner' + } + }, + search: { + container: 'dt-search', + input: 'dt-input' + }, + table: 'dataTable', + tbody: { + cell: '', + row: '' + }, + thead: { + cell: '', + row: '' + }, + tfoot: { + cell: '', + row: '' + }, + paging: { + active: 'current', + button: 'dt-paging-button', + container: 'dt-paging', + disabled: 'disabled', + nav: '' + } + } ); + + + /* + * It is useful to have variables which are scoped locally so only the + * DataTables functions can access them and they don't leak into global space. + * At the same time these functions are often useful over multiple files in the + * core and API, so we list, or at least document, all variables which are used + * by DataTables as private variables here. This also ensures that there is no + * clashing of variable names and that they can easily referenced for reuse. + */ + + + // Defined else where + // _selector_run + // _selector_opts + // _selector_row_indexes + + var _ext; // DataTable.ext + var _Api; // DataTable.Api + var _api_register; // DataTable.Api.register + var _api_registerPlural; // DataTable.Api.registerPlural + + var _re_dic = {}; + var _re_new_lines = /[\r\n\u2028]/g; + var _re_html = /<([^>]*>)/g; + var _max_str_len = Math.pow(2, 28); + + // This is not strict ISO8601 - Date.parse() is quite lax, although + // implementations differ between browsers. + var _re_date = /^\d{2,4}[./-]\d{1,2}[./-]\d{1,2}([T ]{1}\d{1,2}[:.]\d{2}([.:]\d{2})?)?$/; + + // Escape regular expression special characters + var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); + + // https://en.wikipedia.org/wiki/Foreign_exchange_market + // - \u20BD - Russian ruble. + // - \u20a9 - South Korean Won + // - \u20BA - Turkish Lira + // - \u20B9 - Indian Rupee + // - R - Brazil (R$) and South Africa + // - fr - Swiss Franc + // - kr - Swedish krona, Norwegian krone and Danish krone + // - \u2009 is thin space and \u202F is narrow no-break space, both used in many + // - Ƀ - Bitcoin + // - Ξ - Ethereum + // standards as thousands separators. + var _re_formatted_numeric = /['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi; + + + var _empty = function ( d ) { + return !d || d === true || d === '-' ? true : false; + }; + + + var _intVal = function ( s ) { + var integer = parseInt( s, 10 ); + return !isNaN(integer) && isFinite(s) ? integer : null; + }; + + // Convert from a formatted number with characters other than `.` as the + // decimal place, to a JavaScript number + var _numToDecimal = function ( num, decimalPoint ) { + // Cache created regular expressions for speed as this function is called often + if ( ! _re_dic[ decimalPoint ] ) { + _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' ); + } + return typeof num === 'string' && decimalPoint !== '.' ? + num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) : + num; + }; + + + var _isNumber = function ( d, decimalPoint, formatted, allowEmpty ) { + var type = typeof d; + var strType = type === 'string'; + + if ( type === 'number' || type === 'bigint') { + return true; + } + + // If empty return immediately so there must be a number if it is a + // formatted string (this stops the string "k", or "kr", etc being detected + // as a formatted number for currency + if ( allowEmpty && _empty( d ) ) { + return true; + } + + if ( decimalPoint && strType ) { + d = _numToDecimal( d, decimalPoint ); + } + + if ( formatted && strType ) { + d = d.replace( _re_formatted_numeric, '' ); + } + + return !isNaN( parseFloat(d) ) && isFinite( d ); + }; + + + // A string without HTML in it can be considered to be HTML still + var _isHtml = function ( d ) { + return _empty( d ) || typeof d === 'string'; + }; + + // Is a string a number surrounded by HTML? + var _htmlNumeric = function ( d, decimalPoint, formatted, allowEmpty ) { + if ( allowEmpty && _empty( d ) ) { + return true; + } + + // input and select strings mean that this isn't just a number + if (typeof d === 'string' && d.match(/<(input|select)/i)) { + return null; + } + + var html = _isHtml( d ); + return ! html ? + null : + _isNumber( _stripHtml( d ), decimalPoint, formatted, allowEmpty ) ? + true : + null; + }; + + + var _pluck = function ( a, prop, prop2 ) { + var out = []; + var i=0, iLen=a.length; + + // Could have the test in the loop for slightly smaller code, but speed + // is essential here + if ( prop2 !== undefined ) { + for ( ; i _max_str_len) { + throw new Error('Exceeded max str len'); + } + + var previous; + + input = input.replace(_re_html, replacement || ''); // Complete tags + + // Safety for incomplete script tag - use do / while to ensure that + // we get all instances + do { + previous = input; + input = input.replace(/ diff --git a/src/static/templates/admin/users.hbs b/src/static/templates/admin/users.hbs index b1dfb17d..4c91bc0e 100644 --- a/src/static/templates/admin/users.hbs +++ b/src/static/templates/admin/users.hbs @@ -14,7 +14,7 @@ Entries Attachments Organizations - Actions + Actions @@ -47,10 +47,10 @@ {{/if}} - {{created_at}} + {{created_at}} - {{last_active}} + {{last_active}} {{cipher_count}} @@ -153,6 +153,7 @@ + diff --git a/src/static/templates/email/admin_account_recovery.hbs b/src/static/templates/email/admin_account_recovery.hbs deleted file mode 100644 index a35a1d05..00000000 --- a/src/static/templates/email/admin_account_recovery.hbs +++ /dev/null @@ -1,12 +0,0 @@ -Admin account recovery from {{org_name}} organization - -{{#if reset_password}} -The master password for {{user_name}} has been changed. -{{/if}} -{{#if reset_2fa}} -Your two-step verification providers have been reset.{{#if fallback_2fa_email}} Email two factor has been activated as a fallback.{{/if}} -{{/if}} - -If you did not initiate this request, please reach out to your administrator immediately. - -{{> email/email_footer_text }} diff --git a/src/static/templates/email/admin_reset_password.hbs b/src/static/templates/email/admin_reset_password.hbs new file mode 100644 index 00000000..f70423f1 --- /dev/null +++ b/src/static/templates/email/admin_reset_password.hbs @@ -0,0 +1,4 @@ +Master Password Has Been Changed + +The master password for {{user_name}} has been changed by an administrator in your {{org_name}} organization. If you did not initiate this request, please reach out to your administrator immediately. +{{> email/email_footer_text }} diff --git a/src/static/templates/email/admin_account_recovery.html.hbs b/src/static/templates/email/admin_reset_password.html.hbs similarity index 55% rename from src/static/templates/email/admin_account_recovery.html.hbs rename to src/static/templates/email/admin_reset_password.html.hbs index cf8eebed..d9749d22 100644 --- a/src/static/templates/email/admin_account_recovery.html.hbs +++ b/src/static/templates/email/admin_reset_password.html.hbs @@ -1,17 +1,10 @@ -Admin account recovery from {{org_name}} organization +Master Password Has Been Changed {{> email/email_header }}
- {{#if reset_password}} - The master password for {{user_name}} has been changed. - {{/if}} - {{#if reset_2fa}} - Your two-step verification providers have been reset.{{#if fallback_2fa_email}} Email two factor has been activated as a fallback.{{/if}} - {{/if}} -
- If you did not initiate this request, please reach out to your administrator immediately. + The master password for {{user_name}} has been changed by an administrator in your {{org_name}} organization. If you did not initiate this request, please reach out to your administrator immediately.
diff --git a/src/storage.rs b/src/storage.rs index 32562a0d..ac88d026 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -67,7 +67,7 @@ pub(crate) fn operator_for_path(path: &str) -> Result Result = LazyLock::new(|| { - // Storage endpoints are administrator-configured and may be private. - crate::http_client::get_reqwest_client_builder(false).build().expect("Failed to build OpenDAL HTTP client") - }); - pub(super) fn is_uri(path: &str) -> bool { path.starts_with("s3://") } @@ -185,7 +177,12 @@ mod s3 { let chain = DEFAULT_CREDENTIAL_CHAIN .get_or_init(|| { - let conf = ProviderConfig::default().with_http_client(AwsReqwestConnector::new()); + let reqwest_client = reqwest::Client::builder().build().unwrap(); + let connector = AwsReqwestConnector { + client: reqwest_client, + }; + + let conf = ProviderConfig::default().with_http_client(connector); DefaultCredentialsChain::builder().configure(conf).build() }) @@ -239,9 +236,7 @@ mod s3 { builder.credential_provider_chain(ProvideCredentialChain::new().push(OpenDALS3CredentialProvider)); } - let http_transport = opendal::HttpTransporter::new(ReqwestTransport::new(HTTP_CLIENT.clone())); - let context = opendal::OperationContext::new().with_http_transport(http_transport); - Ok(opendal::Operator::new(builder)?.with_context(context)) + Ok(opendal::Operator::new(builder)?.finish()) } fn uri_has_option(uri: &opendal::OperatorUri, names: &[&str]) -> bool { diff --git a/src/util.rs b/src/util.rs index 6de2d803..91f075d1 100644 --- a/src/util.rs +++ b/src/util.rs @@ -257,44 +257,6 @@ 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"]; @@ -537,7 +499,10 @@ pub fn is_valid_email(email: &str) -> bool { let Ok(email_url) = url::Url::parse(&format!("https://{}", email.domain())) else { return false; }; - email_url.domain().is_some() && email_url.path() == "/" && email_url.query().is_none() + if email_url.path().ne("/") || email_url.domain().is_none() || email_url.query().is_some() { + return false; + } + true } //