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..9fc29989 100644 --- a/.env.template +++ b/.env.template @@ -390,7 +390,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 +518,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..3111e20b 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@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0 with: dockerfile: docker/Dockerfile.debian - name: Run hadolint on Dockerfile.alpine - uses: hadolint/hadolint-action@06be81baf89a55ffd0e24b8f04a4185738dd3387 # v3.5.0 + uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0 with: dockerfile: docker/Dockerfile.alpine # End Test Dockerfiles with hadolint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 311891b7..9d15dd88 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: diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 7f41885d..942a99e9 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -50,6 +50,6 @@ jobs: severity: CRITICAL,HIGH - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 + uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 00fcbab4..779cd6e3 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -23,4 +23,4 @@ jobs: # When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too - name: Spell Check Repo - uses: crate-ci/typos@d43b6c087ac471e2ea7b8af622ff15f05c0c365b # v1.50.1 + uses: crate-ci/typos@8a48f81b6c64dcfea44b3633223084c4be58ac5f # v1.49.0 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 31153b07..e1de58c3 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4 + uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 with: # intentionally not scanning the entire repository, # since it contains integration tests. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e8319414..f9920696 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too - repo: https://github.com/crate-ci/typos - rev: d43b6c087ac471e2ea7b8af622ff15f05c0c365b # v1.50.1 + rev: 8a48f81b6c64dcfea44b3633223084c4be58ac5f # v1.49.0 hooks: - id: typos always_run: true diff --git a/Cargo.lock b/Cargo.lock index 55a7233b..b0a36edf 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", @@ -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.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a8ec73eb862508b7041723c89386365894b4e7d9f6998bf1b8529e5b0ee254" +checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8" dependencies = [ "compression-codecs", "compression-core", @@ -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.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d7b388a9fc3a6db15a5ec778c38b354eff1364882c94d08e0252f7a47dcaa4" +checksum = "1b180a3c8b55960db3426d8964b8745e652466a1a49fe1a2eda828046d30b5e4" dependencies = [ "aws-credential-types", "aws-runtime", @@ -403,9 +392,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.9.2" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef47857a1d4488b528f4a5d5715fa7c3300820897824152234d3fa22b1426657" +checksum = "c9007227e10b5fed2f3e0a2beff489211e2b5604c400b7a9d5d81ca9d64c24bb" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -428,9 +417,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.109.0" +version = "1.105.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3cfe74df5d9ad2fedd691973ad3521ebf4f27a3c68c792556686aedb5519bab" +checksum = "6ffd0fbe7873cb548a7aa60f9573c268fff94155397fd4f14dc9f1ecaaab8516" dependencies = [ "arc-swap", "aws-credential-types", @@ -454,9 +443,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.111.0" +version = "1.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b0ec31ed6191bd11350aae4b2004198f2db21350cb0a20c57e0a92e55dd161" +checksum = "175763eb222a46377df7aa257a3bca980ab3e96703fefc8f4d0b8da6ad2e254c" dependencies = [ "arc-swap", "aws-credential-types", @@ -480,9 +469,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.114.0" +version = "1.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef45745026107ec30c4ef86bd8ae4b002e7e5f6a86e4225240bdf6b06a0b944a" +checksum = "dd8b14781dfbff48984017d57167b6ea0b6471c6920ec52b44a2677c7feb3c13" dependencies = [ "arc-swap", "aws-credential-types", @@ -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", @@ -619,9 +608,9 @@ 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", @@ -659,9 +648,9 @@ dependencies = [ [[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", @@ -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", @@ -791,11 +780,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 +807,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 +884,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 +912,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", @@ -954,12 +942,12 @@ 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 +989,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 +999,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 +1013,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 +1069,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 +1122,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,9 +1133,9 @@ 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", ] @@ -1170,9 +1152,9 @@ dependencies = [ [[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", ] @@ -1197,27 +1179,27 @@ dependencies = [ [[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 +1296,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 +1334,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 +1369,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]] @@ -1450,7 +1432,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 +1558,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 +1595,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", @@ -1676,7 +1658,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.3", ] [[package]] @@ -1769,9 +1751,9 @@ dependencies = [ [[package]] name = "either" -version = "1.18.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "elliptic-curve" @@ -1815,17 +1797,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]] @@ -1920,19 +1896,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 +1948,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 +1963,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 +1973,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 +1990,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 +2009,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 +2038,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 +2164,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,9 +2183,9 @@ 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", @@ -2218,7 +2193,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.5.0", - "indexmap 2.14.2", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -2238,9 +2213,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 +2224,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.20", + "thiserror 2.0.19", "walkdir", ] @@ -2285,11 +2260,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 +2269,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 +2281,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 +2296,7 @@ dependencies = [ "ipnet", "jni", "rand 0.10.2", - "thiserror 2.0.20", + "thiserror 2.0.19", "tinyvec", "tokio", "tracing", @@ -2335,9 +2305,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 +2317,7 @@ dependencies = [ "prefix-trie", "rand 0.10.2", "ring", - "thiserror 2.0.20", + "thiserror 2.0.19", "tinyvec", "tracing", "url", @@ -2355,9 +2325,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 +2344,7 @@ dependencies = [ "resolv-conf", "smallvec", "system-configuration", - "thiserror 2.0.20", + "thiserror 2.0.19", "tokio", "tracing", ] @@ -2470,9 +2440,9 @@ dependencies = [ [[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", @@ -2495,9 +2465,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.15" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" dependencies = [ "typenum", ] @@ -2527,9 +2497,9 @@ 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", @@ -2553,11 +2523,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.5.0", - "hyper 1.11.1", + "hyper 1.11.0", "hyper-util", - "rustls 0.23.44", + "rustls 0.23.43", "tokio", - "tokio-rustls 0.26.5", + "tokio-rustls 0.26.4", "tower-service", ] @@ -2573,7 +2543,7 @@ dependencies = [ "futures-util", "http 1.5.0", "http-body 1.1.0", - "hyper 1.11.1", + "hyper 1.11.0", "ipnet", "libc", "percent-encoding", @@ -2612,9 +2582,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 +2596,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 +2609,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 +2623,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 +2643,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 +2702,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 +2733,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.2" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" dependencies = [ "serde", ] @@ -2870,7 +2839,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.20", + "thiserror 2.0.19", "walkdir", "windows-link", ] @@ -2930,9 +2899,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 +2922,7 @@ dependencies = [ "p256", "p384", "pem", - "rand 0.8.8", + "rand 0.8.7", "rsa", "serde", "serde_json", @@ -3011,12 +2980,12 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "rustls 0.23.44", + "rustls 0.23.43", "rustls-native-certs", "serde", "socket2 0.6.5", "tokio", - "tokio-rustls 0.26.5", + "tokio-rustls 0.26.4", "tracing", "url", ] @@ -3044,9 +3013,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 +3030,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 +3051,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 +3078,7 @@ name = "macros" version = "0.1.0" dependencies = [ "quote", - "syn 3.0.5", + "syn 3.0.3", ] [[package]] @@ -3133,11 +3102,10 @@ dependencies = [ [[package]] name = "mea" -version = "0.6.7" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c709842c4ce65cb91e2666ad5319dfc1efc3af0d34f02075eddca9000d9f8afb" +checksum = "31fc7d159de0085ab6dd7ff145a9819442cfd3d098f783263120503c3f3e58b0" dependencies = [ - "hashbrown 0.17.1", "slab", ] @@ -3191,9 +3159,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 +3169,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,9 +3180,9 @@ 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", @@ -3249,33 +3217,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "multiversion" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c" -dependencies = [ - "multiversion-macros", -] - -[[package]] -name = "multiversion-macros" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83" -dependencies = [ - "proc-macro2", - "quote", - "rustversion", - "syn 3.0.5", -] - -[[package]] -name = "multiversion_no_op" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" - [[package]] name = "mysqlclient-sys" version = "0.5.2" @@ -3360,7 +3301,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.8", + "rand 0.8.7", "smallvec", "zeroize", ] @@ -3379,14 +3320,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 +3344,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" @@ -3455,7 +3396,7 @@ dependencies = [ "chrono", "getrandom 0.2.17", "http 1.5.0", - "rand 0.8.8", + "rand 0.8.7", "serde", "serde_json", "serde_path_to_error", @@ -3485,9 +3426,9 @@ dependencies = [ [[package]] name = "opendal" -version = "0.59.1" +version = "0.58.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950151f9587a51a7bed70a15fa0cff464eae96e41ae7499f97067bdafdf43eb" +checksum = "4f20562cc7447fcc915fc5c23df305a412ea80a733c9f2fd9e2d267e2815be6d" dependencies = [ "opendal-core", "opendal-service-fs", @@ -3496,12 +3437,11 @@ dependencies = [ [[package]] name = "opendal-core" -version = "0.59.1" +version = "0.58.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a43405d217dfdfb543f58847336d3af672897dd1939bb7dcf314b63cf364f1c9" +checksum = "ec75551ff4cf3e57da98979f6a937aaa9ddb3915bf68cc17d03df733be6646ed" dependencies = [ "anyhow", - "asyncband", "base64 0.23.1", "bytes", "futures", @@ -3509,6 +3449,7 @@ dependencies = [ "jiff", "log", "md-5", + "mea", "percent-encoding", "quick-xml", "reqsign-core", @@ -3520,25 +3461,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.58.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb9caf04d6d38713299dd4abac984b95ab16ee20e1ff09160f495c5a64644083" +checksum = "826c4e17a30643b888fe983897f9a4b23b07066e1d069727a923cc8fb419a702" dependencies = [ "bytes", "log", @@ -3550,9 +3477,9 @@ dependencies = [ [[package]] name = "opendal-service-s3" -version = "0.59.1" +version = "0.58.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388b1d39b62535c62803754ebef89808859558697366dbedd0299345887ba461" +checksum = "58e80cdf192d7eff05feed747894d64f81905ac4eaf132edf7ea270abdd2d663" dependencies = [ "base64 0.23.1", "bytes", @@ -3586,7 +3513,7 @@ dependencies = [ "oauth2", "p256", "p384", - "rand 0.8.8", + "rand 0.8.7", "rsa", "serde", "serde-value", @@ -3733,12 +3660,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 +3731,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 +3741,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 +3751,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 +3764,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 +3797,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 +3883,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 +3903,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 +3971,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" @@ -4172,9 +4080,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 +4174,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]] @@ -4332,9 +4240,9 @@ dependencies = [ [[package]] name = "reqsign-aws-core" -version = "3.1.1" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac4749b7dfa7bfaccd01eb03e9dc795ed37e3f20d6f0f38e2c67ee85ad6bc86" +checksum = "e4af084e1f3cbf3e67e0c972765399bce54ecec804cceba46b39a8331f3c1bff" dependencies = [ "bytes", "form_urlencoded", @@ -4353,9 +4261,9 @@ dependencies = [ [[package]] name = "reqsign-aws-v4" -version = "3.3.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff250f0fd0b913fbd565e405acc553da0f13bde30bfb5403178c9d0313cdc15f" +checksum = "4ac5b3b7cefa28933792b439186459f77f19f9b6edbeab41b8b187150361a206" dependencies = [ "bytes", "http 1.5.0", @@ -4368,9 +4276,9 @@ dependencies = [ [[package]] name = "reqsign-core" -version = "3.3.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff052daffb0599681c50f85c59e7236438976efe991ab864edd9f3b235501a0f" +checksum = "c07dd510b1e1b9b241883e483358147fb2ed2d497a7b39b065ba61eb93deceb0" dependencies = [ "anyhow", "base64 0.23.1", @@ -4381,7 +4289,6 @@ dependencies = [ "http 1.5.0", "jiff", "log", - "mea", "percent-encoding", "sha1 0.11.0", "sha2 0.11.0", @@ -4390,9 +4297,9 @@ dependencies = [ [[package]] name = "reqsign-file-read-tokio" -version = "3.0.6" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3235df90a6bca681aa47dd86f2393d122a6d77042aa8a7c81e218cd45c5bfc0" +checksum = "663d9d55abd0df0830ef0ae43708297cc1371cf4e8ca91f3ac813c309cca8c98" dependencies = [ "anyhow", "reqsign-core", @@ -4401,11 +4308,11 @@ 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", @@ -4416,7 +4323,7 @@ dependencies = [ "http 1.5.0", "http-body 1.1.0", "http-body-util", - "hyper 1.11.1", + "hyper 1.11.0", "hyper-rustls", "hyper-util", "js-sys", @@ -4424,7 +4331,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustls 0.23.44", + "rustls 0.23.43", "rustls-pki-types", "rustls-platform-verifier", "serde", @@ -4432,7 +4339,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-rustls 0.26.5", + "tokio-rustls 0.26.4", "tokio-util", "tower", "tower-http", @@ -4506,14 +4413,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 +4445,7 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" dependencies = [ "devise", "glob", - "indexmap 2.14.2", + "indexmap 2.14.0", "proc-macro2", "quote", "rocket_http", @@ -4558,7 +4465,7 @@ dependencies = [ "futures", "http 0.2.12", "hyper 0.14.32", - "indexmap 2.14.2", + "indexmap 2.14.0", "log", "memchr", "pear", @@ -4625,17 +4532,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 +4600,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.44" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.15", + "rustls-webpki 0.103.13", "subtle", "zeroize", ] @@ -4747,10 +4654,10 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.44", + "rustls 0.23.43", "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 +4682,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", @@ -4959,7 +4866,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.3", ] [[package]] @@ -4968,6 +4875,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,17 +4935,16 @@ 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", "serde_core", @@ -5048,14 +4955,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 +4983,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 +5005,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 +5084,7 @@ checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.20", + "thiserror 2.0.19", "time", ] @@ -5195,9 +5102,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" @@ -5300,11 +5207,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 +5231,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 +5323,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 +5343,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]] @@ -5497,9 +5404,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 +5414,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", ] @@ -5545,7 +5452,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.3", ] [[package]] @@ -5560,11 +5467,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.43", "tokio", ] @@ -5615,7 +5522,7 @@ dependencies = [ "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", - "toml_edit 0.22.27", + "toml_edit", ] [[package]] @@ -5649,22 +5556,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,18 +5570,6 @@ 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" @@ -5841,7 +5727,7 @@ dependencies = [ "http 1.5.0", "httparse", "log", - "rand 0.8.8", + "rand 0.8.7", "sha1 0.10.7", "thiserror 1.0.69", "url", @@ -5936,9 +5822,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 +5840,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "value-bag" -version = "1.14.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2799ffb329a792ecfd902b71306c8a815a6ef1c0470fa9953a6aa4d4cecbe511" +checksum = "068e763e8279de7ab94b6afebded2cb701678af094feb1c12ccb061b4783c1be" [[package]] name = "vaultwarden" @@ -6003,7 +5889,6 @@ dependencies = [ "num-derive", "num-traits", "opendal", - "opendal-http-transport-reqwest", "openidconnect", "openssl", "pastey 0.2.3", @@ -6019,7 +5904,7 @@ dependencies = [ "rocket", "rocket_ws", "rpassword", - "rustls 0.23.44", + "rustls 0.23.43", "semver", "serde", "serde_json", @@ -6094,9 +5979,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 +5992,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 +6002,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 +6012,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 +6047,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 +6144,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 +6276,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" @@ -6535,9 +6429,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 +6438,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 +6471,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" @@ -6637,18 +6528,18 @@ dependencies = [ [[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 +6589,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 +6600,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 +6611,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 +6628,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..3e187ff3 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", @@ -66,7 +66,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 +90,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 +107,7 @@ serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.151" # A safe, extensible ORM and Query builder -diesel = { version = "2.3.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,16 +120,16 @@ 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.43", 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"] } @@ -180,10 +180,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 +201,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 +212,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 +232,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.1" # 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 +245,10 @@ semver = "1.0.28" # Mainly used for the musl builds, since the default musl malloc is very slow mimalloc = { version = "0.1.52", optional = true, default-features = false, features = ["secure"] } -which = "8.0.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 +257,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.58.1", 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.1", 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 } +aws-smithy-runtime-api = { version = "1.14.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 } +reqsign-aws-v4 = { version = "3.1.0", optional = true } +reqsign-core = { version = "3.2.1", optional = true } # Strip debuginfo from the release builds # The debug symbols are to provide better panic traces diff --git a/docker/DockerSettings.yaml b/docker/DockerSettings.yaml index 6ae6e9a8..4c5e851b 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -5,8 +5,7 @@ vault_image_digest: "sha256:ba8bab66d4330ab9dbafa8f245bcbe99cf6ee3f2c8ce9b5fbb10 # We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts # https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707" -# 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..7045138d 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -32,10 +32,10 @@ FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:ba8bab66d4330 ########################## ALPINE BUILD IMAGES ########################## ## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64 ## And for Alpine we define all build images here, they will only be loaded when actually used -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.98.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 diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index b8490609..9ab02568 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -36,7 +36,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 -FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.98.1-slim-trixie AS build +FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.97.1-slim-trixie AS build # hadolint ignore=DL3067 COPY --from=xx / / ARG TARGETARCH 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/docker-compose.yml b/playwright/docker-compose.yml index 5bfc47a5..5dd04ff4 100644 --- a/playwright/docker-compose.yml +++ b/playwright/docker-compose.yml @@ -61,7 +61,7 @@ services: Mariadb: profiles: ["playwright"] container_name: playwright_mariadb - image: mariadb:12.2.2 + image: mariadb:11.2.4 env_file: test.env healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] diff --git a/playwright/tests/organization.smtp.spec.ts b/playwright/tests/organization.smtp.spec.ts index 1e97ed5d..6d0eb859 100644 --- a/playwright/tests/organization.smtp.spec.ts +++ b/playwright/tests/organization.smtp.spec.ts @@ -127,9 +127,6 @@ test('Organization is visible', async ({ page }) => { }); test('Recover user password', async ({ page }) => { - await logUser(test, page, users.user2, { mailBuffer: mail2Buffer }); - await activateTOTP(test, page, users.user2); - await logUser(test, page, users.user1, { mailBuffer: mail1Buffer }); let newPassword = "TotoNewPassword"; @@ -141,10 +138,9 @@ test('Recover user password', async ({ page }) => { await page.getByRole('menuitem', { name: 'Recover account' }).click(); await page.getByRole('textbox', { name: 'New master password * (required)', exact: true }).fill(newPassword); await page.getByRole('textbox', { name: 'Confirm new master password * (' }).fill(newPassword); - await page.getByRole('checkbox', { name: 'Reset two-step login' }).check(); await page.getByRole('button', { name: 'Save' }).click(); await utils.checkNotification(page, 'Account recovery success'); - await mail2Buffer.expect((m) => m.subject.includes('Admin account recovery from Test organization')); + await mail2Buffer.expect((m) => m.subject.includes('Master Password Has Been Changed')); }); let user2 = { @@ -154,7 +150,6 @@ test('Recover user password', async ({ page }) => { }; await logUser(test, page, user2, { mailBuffer: mail2Buffer, - mail2fa: true, notNewDevice: true, }); }); 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..48f36afd 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 { 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..2b51fd0c 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, @@ -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, @@ -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/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/identity.rs b/src/api/identity.rs index 6808ddde..23411dc7 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 { @@ -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/web.rs b/src/api/web.rs index d6d8d62c..a7eca9fc 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -301,6 +301,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..72b58252 100644 --- a/src/config.rs +++ b/src/config.rs @@ -817,8 +817,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. @@ -1272,7 +1270,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 +1423,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", @@ -1547,17 +1544,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. @@ -1745,7 +1731,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..86cbf5d0 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") } } } 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..d501f8b9 100644 --- a/src/db/models/org_policy.rs +++ b/src/db/models/org_policy.rs @@ -318,13 +318,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/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_organizations.js b/src/static/scripts/admin_organizations.js index 0aa57dcd..33314ad7 100644 --- a/src/static/scripts/admin_organizations.js +++ b/src/static/scripts/admin_organizations.js @@ -1,5 +1,5 @@ "use strict"; -/* global DataTable, _post:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ +/* global jQuery, _post:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ function deleteOrganization(event) { event.preventDefault(); @@ -41,9 +41,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 +53,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 +66,4 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => { if (btnReload) { btnReload.addEventListener("click", reload); } -}); +}); \ No newline at end of file diff --git a/src/static/scripts/admin_users.js b/src/static/scripts/admin_users.js index 63ee2d7b..a2a643c3 100644 --- a/src/static/scripts/admin_users.js +++ b/src/static/scripts/admin_users.js @@ -1,5 +1,5 @@ "use strict"; -/* global DataTable, _post:readable, _delete:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ +/* global jQuery, _post:readable, _delete:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ function deleteUser(event) { event.preventDefault(); @@ -141,7 +141,7 @@ function inviteUser(event) { ); } -function resendUserInvite(event) { +function resendUserInvite (event) { event.preventDefault(); event.stopPropagation(); const id = event.target.parentNode.dataset.vwUserUuid; @@ -179,9 +179,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 +227,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 +249,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 +285,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 +301,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..689be302 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -77,18 +77,10 @@ pub(crate) fn operator_for_path(path: &str) -> Result = LazyLock::new(|| { - // Storage endpoints are administrator-configured and may be private. - crate::http_client::get_reqwest_client_builder(false).build().expect("Failed to build OpenDAL HTTP client") - }); - pub(super) fn is_uri(path: &str) -> bool { path.starts_with("s3://") } @@ -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)?) } fn uri_has_option(uri: &opendal::OperatorUri, names: &[&str]) -> bool { diff --git a/src/util.rs b/src/util.rs index 6de2d803..0e8a93e4 100644 --- a/src/util.rs +++ b/src/util.rs @@ -537,7 +537,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 } //