diff --git a/.github/actions/build-policies/action.yml b/.github/actions/build-policies/action.yml index e1dc28547..6264047ef 100644 --- a/.github/actions/build-policies/action.yml +++ b/.github/actions/build-policies/action.yml @@ -13,7 +13,7 @@ runs: uses: open-policy-agent/setup-opa@v2.2.0 with: # Keep in sync with the Dockerfile and policies/Makefile - version: 1.8.0 + version: 1.13.1 - name: Build the policies run: make diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index fbbf4fb2d..9a8845327 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -84,7 +84,7 @@ jobs: chmod -R u=rwX,go=rX assets-dist/ - name: Upload assets - uses: actions/upload-artifact@v6.0.0 + uses: actions/upload-artifact@v7.0.0 with: name: assets path: assets-dist @@ -143,7 +143,7 @@ jobs: -p mas-cli - name: Upload binary artifact - uses: actions/upload-artifact@v6.0.0 + uses: actions/upload-artifact@v7.0.0 with: name: binary-${{ matrix.target }} path: target/${{ matrix.target }}/release/mas-cli @@ -162,19 +162,19 @@ jobs: steps: - name: Download assets - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: assets path: assets-dist - name: Download binary x86_64 - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: binary-x86_64-unknown-linux-gnu path: binary-x86_64 - name: Download binary aarch64 - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: binary-aarch64-unknown-linux-gnu path: binary-aarch64 @@ -192,13 +192,13 @@ jobs: done - name: Upload aarch64 archive - uses: actions/upload-artifact@v6.0.0 + uses: actions/upload-artifact@v7.0.0 with: name: mas-cli-aarch64-linux path: mas-cli-aarch64-linux.tar.gz - name: Upload x86_64 archive - uses: actions/upload-artifact@v6.0.0 + uses: actions/upload-artifact@v7.0.0 with: name: mas-cli-x86_64-linux path: mas-cli-x86_64-linux.tar.gz @@ -226,7 +226,7 @@ jobs: steps: - name: Docker meta id: meta - uses: docker/metadata-action@v5.10.0 + uses: docker/metadata-action@v6.0.0 with: images: "${{ env.IMAGE }}" bake-target: docker-metadata-action @@ -242,7 +242,7 @@ jobs: - name: Docker meta (debug variant) id: meta-debug - uses: docker/metadata-action@v5.10.0 + uses: docker/metadata-action@v6.0.0 with: images: "${{ env.IMAGE }}" bake-target: docker-metadata-action-debug @@ -258,17 +258,17 @@ jobs: type=sha - name: Setup Cosign - uses: sigstore/cosign-installer@v4.0.0 + uses: sigstore/cosign-installer@v4.1.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.12.0 + uses: docker/setup-buildx-action@v4.0.0 with: buildkitd-config-inline: | [registry."docker.io"] mirrors = ["mirror.gcr.io"] - name: Login to GitHub Container Registry - uses: docker/login-action@v3.6.0 + uses: docker/login-action@v4.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -276,7 +276,7 @@ jobs: - name: Build and push id: bake - uses: docker/bake-action@v6.10.0 + uses: docker/bake-action@v7.0.0 with: files: | ./docker-bake.hcl @@ -320,14 +320,14 @@ jobs: - build-image steps: - name: Download the artifacts from the previous job - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: pattern: mas-cli-* path: artifacts merge-multiple: true - name: Prepare a release - uses: softprops/action-gh-release@v2.5.0 + uses: softprops/action-gh-release@v2.6.1 with: generate_release_notes: true body: | @@ -382,7 +382,7 @@ jobs: .github/scripts - name: Download the artifacts from the previous job - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: pattern: mas-cli-* path: artifacts @@ -396,7 +396,7 @@ jobs: await script({ core, github, context }); - name: Update unstable release - uses: softprops/action-gh-release@v2.5.0 + uses: softprops/action-gh-release@v2.6.1 with: name: "Unstable build" tag_name: unstable diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8dc71d0d6..1fe4f1a8d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,7 +42,7 @@ jobs: uses: StyraInc/setup-regal@v1 with: # Keep in sync with policies/Makefile - version: 0.36.1 + version: 0.38.1 - name: Lint policies working-directory: ./policies @@ -64,7 +64,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.3.0 with: node-version: 24 @@ -88,7 +88,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.3.0 with: node-version: 24 @@ -112,7 +112,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.3.0 with: node-version: 24 @@ -216,7 +216,7 @@ jobs: uses: actions/checkout@v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@1.89.0 + uses: dtolnay/rust-toolchain@1.93.0 with: components: clippy @@ -257,7 +257,7 @@ jobs: SQLX_OFFLINE: "1" - name: Upload archive to workflow - uses: actions/upload-artifact@v6.0.0 + uses: actions/upload-artifact@v7.0.0 with: name: nextest-archive path: nextest-archive.tar.zst @@ -305,7 +305,7 @@ jobs: - uses: ./.github/actions/build-policies - name: Download archive - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: nextest-archive diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 3053b56fb..1b950fe4e 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -39,7 +39,7 @@ jobs: tool: mdbook - name: Install Node - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.3.0 with: node-version: 24 diff --git a/.github/workflows/release-branch.yaml b/.github/workflows/release-branch.yaml index e058a62be..f0843f717 100644 --- a/.github/workflows/release-branch.yaml +++ b/.github/workflows/release-branch.yaml @@ -64,7 +64,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.3.0 with: node-version: 24 diff --git a/.github/workflows/translations-download.yaml b/.github/workflows/translations-download.yaml index 70419fce0..586d833da 100644 --- a/.github/workflows/translations-download.yaml +++ b/.github/workflows/translations-download.yaml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.3.0 with: node-version: 24 @@ -42,7 +42,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@v8.0.0 + uses: peter-evans/create-pull-request@v8.1.0 with: sign-commits: true token: ${{ secrets.BOT_GITHUB_TOKEN }} diff --git a/.github/workflows/translations-upload.yaml b/.github/workflows/translations-upload.yaml index ba723278e..392cec1ef 100644 --- a/.github/workflows/translations-upload.yaml +++ b/.github/workflows/translations-upload.yaml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.3.0 with: node-version: 24 diff --git a/Cargo.lock b/Cargo.lock index ba314ee8c..a500974fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,9 +185,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arbitrary" @@ -197,9 +197,12 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" +dependencies = [ + "rustversion", +] [[package]] name = "argon2" @@ -531,9 +534,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.14.0" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b8ff6c09cd57b16da53641caa860168b88c172a5ee163b0288d3d6eea12786" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" dependencies = [ "aws-lc-sys", "zeroize", @@ -541,11 +544,10 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.31.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e44d16778acaf6a9ec9899b92cebd65580b83f685446bf2e1f5d3d732f99dcd" +checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" dependencies = [ - "bindgen", "cc", "cmake", "dunce", @@ -677,9 +679,9 @@ checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bcrypt" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abaf6da45c74385272ddf00e1ac074c7d8a6c1a1dda376902bd6a427522a8b2c" +checksum = "9a0f5948f30df5f43ac29d310b7476793be97c50787e6ef4a63d960a0d0be827" dependencies = [ "base64", "blowfish", @@ -688,26 +690,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "bindgen" -version = "0.72.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools 0.13.0", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn", -] - [[package]] name = "bit-set" version = "0.5.3" @@ -805,9 +787,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] @@ -867,21 +849,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom 7.1.3", -] - [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chacha20" version = "0.9.1" @@ -960,17 +939,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "4.5.50" @@ -1130,6 +1098,16 @@ dependencies = [ "url", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -1166,36 +1144,36 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.127.3" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08cdfa673abeaf2aa0634988468a751fbf5b3de612bd48c1bb36a3dc7e42fe44" +checksum = "d6abf69c884fde2d9d4cc232a585fb18f16af3ae04c634315c84ebe158ded92d" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.127.3" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744d5b84c226fe5dd5cc522552d2c69a55e1ea9f98e650b9075493d263698fca" +checksum = "263d31fcdf83a10267e8c38b53bc8f7688dfbc331267fd8fdf5b22e0dc47a55b" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.127.3" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb9850ce67c4bdc5708204a24f3f571e1e933be2852ec785c778ad76e1f91a5e" +checksum = "d459d5377c01c4472b71029caa2df41afaf47711676aa9b12d7414f15104637b" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-bitset" -version = "0.127.3" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa84e3a1dba026781d0a24761b072e03bbb404b8015f621d332457f627b3a19" +checksum = "8283088d5823ba7856ab8d531b7c3654b24984748f9fd99dcf3210701fd1d065" dependencies = [ "serde", "serde_derive", @@ -1203,9 +1181,9 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.127.3" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cabfe32111207a68ddd237d184300789c6d650d47db0ff7c9c53ef48e347902" +checksum = "7d3138316d8dd341d725d6ab1598750545c76ad32892837fde558edd68a01b43" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -1230,9 +1208,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.127.3" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdc5479395bb325f96e2e5e714e8c276b061c0eaa020525332bf16c6046a825" +checksum = "505cead19304a8dc8689e31b29038775c3f73f9d5ea7a5e33864437a1f46c6b6" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -1243,24 +1221,24 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.127.3" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678aea3a48ca54a38e1b057c253daf2ff4c2869b1e70af6545bee1475434b20d" +checksum = "ce62ba94f570644ce7de6ed05bd39ca28936665dec10a2a1f6f2c531d6add45c" [[package]] name = "cranelift-control" -version = "0.127.3" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5210a53058d2b2504269d168fb075f80f3921126dd27e593e726b6387413be" +checksum = "db6cfe339689c3926412a7060ab00ef3b2b43d936b537e7a3f696121be9d0eaa" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.127.3" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb138631be4325459938ea0507fb6001a9bbfe6022ee130423acbd8583c47244" +checksum = "625518090e912bdfe3c41464bf97ae421f6044d4ca0f5c3267dcacdb352b033d" dependencies = [ "cranelift-bitset", "serde", @@ -1269,9 +1247,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.127.3" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c087396f79a0cdcdd38c7adc1e9955ba3022d026afb9f08769f0c13795d1b6b" +checksum = "17f652d40ddf3afb55be64d8979d312b52b384a8cebbcde1dd1c2e32ebcd4466" dependencies = [ "cranelift-codegen", "log", @@ -1281,15 +1259,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.127.3" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52eaa7b30b2a2d85f177790227f8f7a9b76d35da96302ef28fb394e588e3530b" +checksum = "9f512767e83015f4baf6e732cabca93cea82907e3ab237f826ef64f7ece75eb6" [[package]] name = "cranelift-native" -version = "0.127.3" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c68cefa46cc4e37728d0789a11744dc619a5bd96cabbe44cb9d8dcacc20134" +checksum = "cb1ca6e4dca568ff988d367e4707be2362cee9782265b0a501eaf467ffd550a8" dependencies = [ "cranelift-codegen", "libc", @@ -1298,9 +1276,9 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.127.3" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9092860471c4562c18ea1e47f446072795ad344a4a01f7d0f8cee445390d545" +checksum = "97400ad8fbd3a434092fc0b486fa7784150b53187941d818b1087f3ac0a547f0" [[package]] name = "crc" @@ -2064,12 +2042,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - [[package]] name = "gloo-timers" version = "0.3.0" @@ -2366,9 +2338,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2779,14 +2753,15 @@ dependencies = [ [[package]] name = "insta" -version = "1.43.2" +version = "1.46.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0" +checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4" dependencies = [ "console", "once_cell", "serde", "similar", + "tempfile", ] [[package]] @@ -2983,7 +2958,7 @@ dependencies = [ "httpdate", "idna", "mime", - "nom 8.0.0", + "nom", "percent-encoding", "quoted_printable", "rustls", @@ -3001,16 +2976,6 @@ version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" -[[package]] -name = "libloading" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" -dependencies = [ - "cfg-if", - "windows-targets 0.53.3", -] - [[package]] name = "libm" version = "0.2.15" @@ -3097,7 +3062,7 @@ dependencies = [ [[package]] name = "mas-axum-utils" -version = "1.10.0" +version = "1.14.0" dependencies = [ "anyhow", "axum", @@ -3131,7 +3096,7 @@ dependencies = [ [[package]] name = "mas-cli" -version = "1.10.0" +version = "1.14.0" dependencies = [ "anyhow", "axum", @@ -3206,7 +3171,7 @@ dependencies = [ [[package]] name = "mas-config" -version = "1.10.0" +version = "1.14.0" dependencies = [ "anyhow", "camino", @@ -3237,7 +3202,7 @@ dependencies = [ [[package]] name = "mas-context" -version = "1.10.0" +version = "1.14.0" dependencies = [ "console", "opentelemetry", @@ -3253,19 +3218,17 @@ dependencies = [ [[package]] name = "mas-data-model" -version = "1.10.0" +version = "1.14.0" dependencies = [ "base64ct", "chrono", "crc", - "lettre", "mas-iana", "mas-jose", "oauth2-types", "rand 0.8.5", "rand_chacha 0.3.1", "regex", - "ruma-common", "serde", "serde_json", "thiserror 2.0.17", @@ -3276,7 +3239,7 @@ dependencies = [ [[package]] name = "mas-email" -version = "1.10.0" +version = "1.14.0" dependencies = [ "async-trait", "lettre", @@ -3287,7 +3250,7 @@ dependencies = [ [[package]] name = "mas-handlers" -version = "1.10.0" +version = "1.14.0" dependencies = [ "aide", "anyhow", @@ -3342,6 +3305,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "reqwest", + "ruma-common", "rustls", "schemars 0.9.0", "sentry", @@ -3367,7 +3331,7 @@ dependencies = [ [[package]] name = "mas-http" -version = "1.10.0" +version = "1.14.0" dependencies = [ "futures-util", "headers", @@ -3388,7 +3352,7 @@ dependencies = [ [[package]] name = "mas-i18n" -version = "1.10.0" +version = "1.14.0" dependencies = [ "camino", "icu_calendar", @@ -3410,7 +3374,7 @@ dependencies = [ [[package]] name = "mas-i18n-scan" -version = "1.10.0" +version = "1.14.0" dependencies = [ "camino", "clap", @@ -3424,7 +3388,7 @@ dependencies = [ [[package]] name = "mas-iana" -version = "1.10.0" +version = "1.14.0" dependencies = [ "schemars 0.9.0", "serde", @@ -3432,7 +3396,7 @@ dependencies = [ [[package]] name = "mas-iana-codegen" -version = "1.10.0" +version = "1.14.0" dependencies = [ "anyhow", "async-trait", @@ -3449,7 +3413,7 @@ dependencies = [ [[package]] name = "mas-jose" -version = "1.10.0" +version = "1.14.0" dependencies = [ "base64ct", "chrono", @@ -3479,7 +3443,7 @@ dependencies = [ [[package]] name = "mas-keystore" -version = "1.10.0" +version = "1.14.0" dependencies = [ "aead", "base64ct", @@ -3507,7 +3471,7 @@ dependencies = [ [[package]] name = "mas-listener" -version = "1.10.0" +version = "1.14.0" dependencies = [ "anyhow", "bytes", @@ -3531,7 +3495,7 @@ dependencies = [ [[package]] name = "mas-matrix" -version = "1.10.0" +version = "1.14.0" dependencies = [ "anyhow", "async-trait", @@ -3541,7 +3505,7 @@ dependencies = [ [[package]] name = "mas-matrix-synapse" -version = "1.10.0" +version = "1.14.0" dependencies = [ "anyhow", "async-trait", @@ -3558,7 +3522,7 @@ dependencies = [ [[package]] name = "mas-oidc-client" -version = "1.10.0" +version = "1.14.0" dependencies = [ "assert_matches", "async-trait", @@ -3594,7 +3558,7 @@ dependencies = [ [[package]] name = "mas-policy" -version = "1.10.0" +version = "1.14.0" dependencies = [ "anyhow", "arc-swap", @@ -3611,7 +3575,7 @@ dependencies = [ [[package]] name = "mas-router" -version = "1.10.0" +version = "1.14.0" dependencies = [ "axum", "serde", @@ -3622,7 +3586,7 @@ dependencies = [ [[package]] name = "mas-spa" -version = "1.10.0" +version = "1.14.0" dependencies = [ "camino", "serde", @@ -3631,7 +3595,7 @@ dependencies = [ [[package]] name = "mas-storage" -version = "1.10.0" +version = "1.14.0" dependencies = [ "async-trait", "chrono", @@ -3653,7 +3617,7 @@ dependencies = [ [[package]] name = "mas-storage-pg" -version = "1.10.0" +version = "1.14.0" dependencies = [ "async-trait", "chrono", @@ -3683,7 +3647,7 @@ dependencies = [ [[package]] name = "mas-tasks" -version = "1.10.0" +version = "1.14.0" dependencies = [ "anyhow", "async-trait", @@ -3715,7 +3679,7 @@ dependencies = [ [[package]] name = "mas-templates" -version = "1.10.0" +version = "1.14.0" dependencies = [ "anyhow", "arc-swap", @@ -3747,7 +3711,7 @@ dependencies = [ [[package]] name = "mas-tower" -version = "1.10.0" +version = "1.14.0" dependencies = [ "http", "opentelemetry", @@ -3823,9 +3787,9 @@ dependencies = [ [[package]] name = "minijinja" -version = "2.12.0" +version = "2.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9f264d75233323f4b7d2f03aefe8a990690cdebfbfe26ea86bcbaec5e9ac990" +checksum = "b479616bb6f0779fb0f3964246beda02d4b01144e1b0d5519616e012ccc2a245" dependencies = [ "memo-map", "percent-encoding", @@ -3845,12 +3809,6 @@ dependencies = [ "serde", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -3888,16 +3846,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nom" version = "8.0.0" @@ -3950,9 +3898,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -4016,7 +3964,7 @@ dependencies = [ [[package]] name = "oauth2-types" -version = "1.10.0" +version = "1.14.0" dependencies = [ "assert_matches", "base64ct", @@ -4620,16 +4568,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "primeorder" version = "0.13.6" @@ -4708,7 +4646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn", @@ -4740,9 +4678,9 @@ dependencies = [ [[package]] name = "pulley-interpreter" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63350fc565d2b7ab7f610d0655b28f5f80348658c2cf33d05d7ec43356c4be3c" +checksum = "5de307c194cf6310d486dd5595ffc329c53b4acafd54e214752c1eb2e68be3a9" dependencies = [ "cranelift-bitset", "log", @@ -4752,9 +4690,9 @@ dependencies = [ [[package]] name = "pulley-macros" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d46b2339b894fed7983d91e5723c40e4bc593cb78cd86ffac0798d29f21372e0" +checksum = "99dca2747e910d10bafe911e172a1b35860268421c3ee5ddb7e16c35e0288b4a" dependencies = [ "proc-macro2", "quote", @@ -4968,9 +4906,9 @@ checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", @@ -5191,7 +5129,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be59af91596cac372a6942530653ad0c3a246cdd491aaa9dcaee47f88d67d5a0" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "jni", "log", @@ -5214,9 +5152,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.6" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "aws-lc-rs", "ring", @@ -5388,7 +5326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -5418,10 +5356,11 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "sentry" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9794f69ad475e76c057e326175d3088509649e3aed98473106b9fe94ba59424" +checksum = "d92d893ba7469d361a6958522fa440e4e2bc8bf4c5803cd1bf40b9af63f8f9a8" dependencies = [ + "cfg_aliases", "httpdate", "reqwest", "sentry-backtrace", @@ -5435,9 +5374,9 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e81137ad53b8592bd0935459ad74c0376053c40084aa170451e74eeea8dbc6c3" +checksum = "5f8784d0a27b5cd4b5f75769ffc84f0b7580e3c35e1af9cd83cb90b612d769cc" dependencies = [ "backtrace", "regex", @@ -5446,9 +5385,9 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfb403c66cc2651a01b9bacda2e7c22cd51f7e8f56f206aa4310147eb3259282" +checksum = "0e5eb42f4cd4f9fdfec9e3b07b25a4c9769df83d218a7e846658984d5948ad3e" dependencies = [ "hostname", "libc", @@ -5460,9 +5399,9 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc409727ae90765ca8ea76fe6c949d6f159a11d02e130b357fa652ee9efcada" +checksum = "b0b1e7ca40f965db239da279bf278d87b7407469b98835f27f0c8e59ed189b06" dependencies = [ "rand 0.9.2", "sentry-types", @@ -5473,9 +5412,9 @@ dependencies = [ [[package]] name = "sentry-panic" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df79f4e1e72b2a8b75a0ebf49e78709ceb9b3f0b451f13adc92a0361b0aaabe" +checksum = "8906f8be87aea5ac7ef937323fb655d66607427f61007b99b7cb3504dc5a156c" dependencies = [ "sentry-backtrace", "sentry-core", @@ -5483,9 +5422,9 @@ dependencies = [ [[package]] name = "sentry-tower" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eec9885bceb8ba374858d015bb6fa39dbb341d94ca088bc8f13bee2e64e2c68" +checksum = "56aebe376310840b49dad4cca55c7b32d9abdc14946cd071d4158ecb149b63a4" dependencies = [ "axum", "http", @@ -5498,9 +5437,9 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2046f527fd4b75e0b6ab3bd656c67dce42072f828dc4d03c206d15dca74a93" +checksum = "5b07eefe04486316c57aba08ab53dd44753c25102d1d3fe05775cc93a13262d9" dependencies = [ "bitflags", "sentry-backtrace", @@ -5511,9 +5450,9 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7b9b4e4c03a4d3643c18c78b8aa91d2cbee5da047d2fa0ca4bb29bc67e6c55c" +checksum = "567711f01f86a842057e1fc17779eba33a336004227e1a1e7e6cc2599e22e259" dependencies = [ "debugid", "hex", @@ -5786,9 +5725,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -6120,7 +6059,7 @@ dependencies = [ [[package]] name = "syn2mas" -version = "1.10.0" +version = "1.14.0" dependencies = [ "anyhow", "arc-swap", @@ -6175,6 +6114,27 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "target-lexicon" version = "0.13.3" @@ -6276,31 +6236,32 @@ dependencies = [ [[package]] name = "time" -version = "0.3.43" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", + "itoa", "libc", "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -6491,9 +6452,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags", "bytes", @@ -6531,9 +6492,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -6555,9 +6516,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -6566,9 +6527,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -6616,9 +6577,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", @@ -7031,9 +6992,9 @@ dependencies = [ [[package]] name = "wasmtime" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f033059744520d5943887511a83731856a78a00f0dac943dc9e9d2292289cad" +checksum = "0702b64d4c3fe43ae4ce229e06af06a27783e48c519e68586d180717cdd24314" dependencies = [ "addr2line", "anyhow", @@ -7074,9 +7035,9 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c6de1f26b145fbe9e6980b0495e1c855920091d31c0d1e32e7e49318211103" +checksum = "3ffeb777a21965a85e4b1ce7b308c63ba130df91912096b49b95523bf3bdd2c7" dependencies = [ "anyhow", "cranelift-bitset", @@ -7097,9 +7058,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-macro" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc0996221d4e178c1b9286aa40e448af1cdf40a37f1a3f71755f0502a11eb23" +checksum = "935bb8db1e2829bf26b80ed3daeed6cf9fc804f7002c90a8d17dbd5e93c69e0b" dependencies = [ "anyhow", "proc-macro2", @@ -7112,15 +7073,15 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-util" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146d953836b26c44dc39173b00c5a783e9adcb4369460b2052169cd81e90e729" +checksum = "52625d0c8fe2df1d7dd96d45d37dae8818c183c183a82b2368e3741ee5253859" [[package]] name = "wasmtime-internal-cranelift" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1fbd0cae8d129883a7bad7f2272e6662dcebf4e0f6b38539603359235959a" +checksum = "85da1ba5fee01a3ee21c4d0c8052cc9035388639fa091a969b534d4c6f8449d4" dependencies = [ "anyhow", "cfg-if", @@ -7146,9 +7107,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-fiber" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a2a724a50b8ace66a6089002cbe99eec0f611a15c78262739b6aeb590ab252" +checksum = "a4c7de5a0872764c1ca640886af10a70cf7f8526386906245b43cdb345ece0e6" dependencies = [ "anyhow", "cc", @@ -7161,9 +7122,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-debug" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f3ea5e264b8f6a3a91444ffee58449288e239c9d60c2483bf78c631f3269fa7" +checksum = "160acd973d770d62bef1b2697d7fac83a8fe63ef966215e624382b2a9532bd58" dependencies = [ "cc", "wasmtime-internal-versioned-export-macros", @@ -7171,9 +7132,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c161f4e0636998a68f2c2159260a0d8bbb2d2d2b762938f7be62b2ac0535ed4" +checksum = "cc57f590ba7ea967ea9e8c8560175c6926e5b15d11c29bbde3ad0013a29470eb" dependencies = [ "anyhow", "cfg-if", @@ -7183,24 +7144,24 @@ dependencies = [ [[package]] name = "wasmtime-internal-math" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55d246d16cad85ab49f6e76026d934df2f45974d97eb2ab837a6312dda4c76a" +checksum = "07612904518d47b677e8db67ca47c16d8c8cefb0099020729f886776950cb58b" dependencies = [ "libm", ] [[package]] name = "wasmtime-internal-slab" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6e7127a10a3d38939c54fa3e1701512bd78340ec112ffc628c36516e38bd3a" +checksum = "bc83ff16531e1e1537e0de2b630af56a0a9e1fab864130c5b7e213da71783a0f" [[package]] name = "wasmtime-internal-unwinder" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a899f4006b6332a9312060c9216beaf58447da5939af8f19144138f59d6e366" +checksum = "47371d697244785e4bbb371229f9a2daa8628d1e03368ec895cf658370e1bf38" dependencies = [ "anyhow", "cfg-if", @@ -7211,9 +7172,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426e1088960ab200c49b8e5812667a442c705df018b5c57bd9b3cf80c12b0bdb" +checksum = "ad3cd4aff8f2e7ec658c7a0424b74ad88a6940505303fb0616323592a1c400a6" dependencies = [ "proc-macro2", "quote", @@ -7222,9 +7183,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-wit-bindgen" -version = "40.0.3" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bf7e687a48c5b82d81c59c8963a4f90ca495955170fab9c1bf75176fd1ba014" +checksum = "73a5774737acccdc70ff27bbe9e09c45b156bd7792bb83906735a572ce122247" dependencies = [ "anyhow", "bitflags", @@ -7318,7 +7279,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -7374,6 +7335,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f91f87ce112ffb7275000ea98eb1940912c21c1567c9312fde20261f3eadd29" +dependencies = [ + "windows-link 0.2.1", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 274b9510b..4586bfea7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = ["crates/*"] resolver = "2" # Updated in the CI with a `sed` command -package.version = "1.10.0" +package.version = "1.14.0" package.license = "AGPL-3.0-only OR LicenseRef-Element-Commercial" package.authors = ["Element Backend Team"] package.edition = "2024" @@ -34,35 +34,35 @@ broken_intra_doc_links = "deny" [workspace.dependencies] # Workspace crates -mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.10.0" } -mas-cli = { path = "./crates/cli/", version = "=1.10.0" } -mas-config = { path = "./crates/config/", version = "=1.10.0" } -mas-context = { path = "./crates/context/", version = "=1.10.0" } -mas-data-model = { path = "./crates/data-model/", version = "=1.10.0" } -mas-email = { path = "./crates/email/", version = "=1.10.0" } -mas-graphql = { path = "./crates/graphql/", version = "=1.10.0" } -mas-handlers = { path = "./crates/handlers/", version = "=1.10.0" } -mas-http = { path = "./crates/http/", version = "=1.10.0" } -mas-i18n = { path = "./crates/i18n/", version = "=1.10.0" } -mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.10.0" } -mas-iana = { path = "./crates/iana/", version = "=1.10.0" } -mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.10.0" } -mas-jose = { path = "./crates/jose/", version = "=1.10.0" } -mas-keystore = { path = "./crates/keystore/", version = "=1.10.0" } -mas-listener = { path = "./crates/listener/", version = "=1.10.0" } -mas-matrix = { path = "./crates/matrix/", version = "=1.10.0" } -mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.10.0" } -mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.10.0" } -mas-policy = { path = "./crates/policy/", version = "=1.10.0" } -mas-router = { path = "./crates/router/", version = "=1.10.0" } -mas-spa = { path = "./crates/spa/", version = "=1.10.0" } -mas-storage = { path = "./crates/storage/", version = "=1.10.0" } -mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.10.0" } -mas-tasks = { path = "./crates/tasks/", version = "=1.10.0" } -mas-templates = { path = "./crates/templates/", version = "=1.10.0" } -mas-tower = { path = "./crates/tower/", version = "=1.10.0" } -oauth2-types = { path = "./crates/oauth2-types/", version = "=1.10.0" } -syn2mas = { path = "./crates/syn2mas", version = "=1.10.0" } +mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.14.0" } +mas-cli = { path = "./crates/cli/", version = "=1.14.0" } +mas-config = { path = "./crates/config/", version = "=1.14.0" } +mas-context = { path = "./crates/context/", version = "=1.14.0" } +mas-data-model = { path = "./crates/data-model/", version = "=1.14.0" } +mas-email = { path = "./crates/email/", version = "=1.14.0" } +mas-graphql = { path = "./crates/graphql/", version = "=1.14.0" } +mas-handlers = { path = "./crates/handlers/", version = "=1.14.0" } +mas-http = { path = "./crates/http/", version = "=1.14.0" } +mas-i18n = { path = "./crates/i18n/", version = "=1.14.0" } +mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.14.0" } +mas-iana = { path = "./crates/iana/", version = "=1.14.0" } +mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.14.0" } +mas-jose = { path = "./crates/jose/", version = "=1.14.0" } +mas-keystore = { path = "./crates/keystore/", version = "=1.14.0" } +mas-listener = { path = "./crates/listener/", version = "=1.14.0" } +mas-matrix = { path = "./crates/matrix/", version = "=1.14.0" } +mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.14.0" } +mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.14.0" } +mas-policy = { path = "./crates/policy/", version = "=1.14.0" } +mas-router = { path = "./crates/router/", version = "=1.14.0" } +mas-spa = { path = "./crates/spa/", version = "=1.14.0" } +mas-storage = { path = "./crates/storage/", version = "=1.14.0" } +mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.14.0" } +mas-tasks = { path = "./crates/tasks/", version = "=1.14.0" } +mas-templates = { path = "./crates/templates/", version = "=1.14.0" } +mas-tower = { path = "./crates/tower/", version = "=1.14.0" } +oauth2-types = { path = "./crates/oauth2-types/", version = "=1.14.0" } +syn2mas = { path = "./crates/syn2mas", version = "=1.14.0" } # OpenAPI schema generation and validation [workspace.dependencies.aide] @@ -71,7 +71,7 @@ features = ["axum", "axum-extra", "axum-extra-query", "axum-json", "macros"] # An `Arc` that can be atomically updated [workspace.dependencies.arc-swap] -version = "1.7.1" +version = "1.8.1" # GraphQL server [workspace.dependencies.async-graphql] @@ -88,7 +88,7 @@ version = "0.1.89" # High-level error handling [workspace.dependencies.anyhow] -version = "1.0.100" +version = "1.0.102" # Assert that a value matches a pattern [workspace.dependencies.assert_matches] @@ -124,7 +124,7 @@ features = ["std"] # Bcrypt password hashing [workspace.dependencies.bcrypt] -version = "0.17.1" +version = "0.18.0" default-features = true # Packed bitfields @@ -330,7 +330,7 @@ version = "2.0.6" # Snapshot testing [workspace.dependencies.insta] -version = "1.43.2" +version = "1.46.3" features = ["yaml", "json"] # IP network address types @@ -378,7 +378,7 @@ version = "0.3.17" # Templates [workspace.dependencies.minijinja] -version = "2.12.0" +version = "2.15.1" features = ["urlencode", "loader", "json", "speedups", "unstable_machinery"] # Additional filters for minijinja @@ -508,6 +508,7 @@ features = [ "charset", "json", "socks", + "system-proxy", ] # RSA cryptography @@ -567,7 +568,7 @@ features = [ # Sentry error tracking [workspace.dependencies.sentry] -version = "0.46.0" +version = "0.46.2" default-features = false features = ["backtrace", "contexts", "panic", "tower", "reqwest"] @@ -614,7 +615,7 @@ version = "2.2.0" # Low-level socket manipulation [workspace.dependencies.socket2] -version = "0.6.1" +version = "0.6.2" # Subject Public Key Info [workspace.dependencies.spki] @@ -685,7 +686,7 @@ features = ["cors", "fs", "add-extension", "set-header"] [workspace.dependencies.tracing] version = "0.1.41" [workspace.dependencies.tracing-subscriber] -version = "0.3.20" +version = "0.3.22" features = ["env-filter"] [workspace.dependencies.tracing-appender] version = "0.2.4" diff --git a/Dockerfile b/Dockerfile index 479dee308..ba2ed16c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ -# syntax = docker/dockerfile:1.7.1 +# syntax = docker/dockerfile:1.21.0 +# Copyright 2025, 2026 Element Creations Ltd. # Copyright 2025 New Vector Ltd. # # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial @@ -10,13 +11,14 @@ # being the platform being built. # The Debian version and version name must be in sync -ARG DEBIAN_VERSION=12 -ARG DEBIAN_VERSION_NAME=bookworm -ARG RUSTC_VERSION=1.89.0 -ARG NODEJS_VERSION=24.11.0 +ARG DEBIAN_VERSION=13 +ARG DEBIAN_VERSION_NAME=trixie +# Keep in sync with .github/workflows/ci.yaml +ARG RUSTC_VERSION=1.93.0 +ARG NODEJS_VERSION=24.13.0 # Keep in sync with .github/actions/build-policies/action.yml and policies/Makefile -ARG OPA_VERSION=1.8.0 -ARG CARGO_AUDITABLE_VERSION=0.7.0 +ARG OPA_VERSION=1.13.1 +ARG CARGO_AUDITABLE_VERSION=0.7.2 ########################################## ## Build stage that builds the frontend ## diff --git a/crates/cli/src/commands/manage.rs b/crates/cli/src/commands/manage.rs index 5bbd870ea..65aceda4d 100644 --- a/crates/cli/src/commands/manage.rs +++ b/crates/cli/src/commands/manage.rs @@ -638,6 +638,12 @@ impl Options { // synchronously yet. let user = repo.user().lock(&clock, user).await?; + // Schedule a job to provision the user so that the lock flag is propagated + // to Synapse + repo.queue_job() + .schedule_job(&mut rng, &clock, ProvisionUserJob::new(&user)) + .await?; + if deactivate { warn!(%user.id, "Scheduling user deactivation"); repo.queue_job() @@ -668,6 +674,12 @@ impl Options { .await? .context("User not found")?; + // Schedule a job to provision the user so that the lock flag is propagated + // to Synapse + repo.queue_job() + .schedule_job(&mut rng, &clock, ProvisionUserJob::new(&user)) + .await?; + if reactivate { warn!(%user.id, "Scheduling user reactivation"); repo.queue_job() diff --git a/crates/config/src/sections/clients.rs b/crates/config/src/sections/clients.rs index 8b9120045..2387d642d 100644 --- a/crates/config/src/sections/clients.rs +++ b/crates/config/src/sections/clients.rs @@ -16,19 +16,6 @@ use url::Url; use super::{ClientSecret, ClientSecretRaw, ConfigurationSection}; -#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "snake_case")] -pub enum JwksOrJwksUri { - Jwks(PublicJsonWebKeySet), - JwksUri(Url), -} - -impl From for JwksOrJwksUri { - fn from(jwks: PublicJsonWebKeySet) -> Self { - Self::Jwks(jwks) - } -} - /// Authentication method used by clients #[derive(JsonSchema, Serialize, Deserialize, Copy, Clone, Debug)] #[serde(rename_all = "snake_case")] diff --git a/crates/config/src/sections/email.rs b/crates/config/src/sections/email.rs index 3df0c99db..8c365e3e5 100644 --- a/crates/config/src/sections/email.rs +++ b/crates/config/src/sections/email.rs @@ -14,15 +14,6 @@ use serde::{Deserialize, Serialize, de::Error}; use super::ConfigurationSection; -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -pub struct Credentials { - /// Username for use to authenticate when connecting to the SMTP server - pub username: String, - - /// Password for use to authenticate when connecting to the SMTP server - pub password: String, -} - /// Encryption mode to use #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "lowercase")] diff --git a/crates/data-model/Cargo.toml b/crates/data-model/Cargo.toml index da7021b11..7e9be8282 100644 --- a/crates/data-model/Cargo.toml +++ b/crates/data-model/Cargo.toml @@ -29,8 +29,6 @@ rand.workspace = true rand_chacha.workspace = true regex.workspace = true woothee.workspace = true -ruma-common.workspace = true -lettre.workspace = true mas-iana.workspace = true mas-jose.workspace = true diff --git a/crates/data-model/src/oauth2/authorization_grant.rs b/crates/data-model/src/oauth2/authorization_grant.rs index 738277b84..2ad0488ce 100644 --- a/crates/data-model/src/oauth2/authorization_grant.rs +++ b/crates/data-model/src/oauth2/authorization_grant.rs @@ -4,8 +4,6 @@ // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial // Please see LICENSE files in the repository root for full details. -use std::str::FromStr as _; - use chrono::{DateTime, Utc}; use mas_iana::oauth::PkceCodeChallengeMethod; use oauth2_types::{ @@ -17,7 +15,6 @@ use rand::{ RngCore, distributions::{Alphanumeric, DistString}, }; -use ruma_common::UserId; use serde::Serialize; use ulid::Ulid; use url::Url; @@ -142,12 +139,6 @@ impl AuthorizationGrantStage { } } -pub enum LoginHint<'a> { - MXID(&'a UserId), - Email(lettre::Address), - None, -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct AuthorizationGrant { pub id: Ulid, @@ -175,31 +166,6 @@ impl std::ops::Deref for AuthorizationGrant { } impl AuthorizationGrant { - /// Parse a `login_hint` - /// - /// Returns `LoginHint::MXID` for valid mxid 'mxid:@john.doe:example.com' - /// - /// Returns `LoginHint::Email` for valid email 'john.doe@example.com' - /// - /// Otherwise returns `LoginHint::None` - #[must_use] - pub fn parse_login_hint(&self, homeserver: &str) -> LoginHint<'_> { - let Some(login_hint) = &self.login_hint else { - return LoginHint::None; - }; - - if let Some(value) = login_hint.strip_prefix("mxid:") - && let Ok(mxid) = <&UserId>::try_from(value) - && mxid.server_name() == homeserver - { - LoginHint::MXID(mxid) - } else if let Ok(email) = lettre::Address::from_str(login_hint) { - LoginHint::Email(email) - } else { - LoginHint::None - } - } - /// Mark the authorization grant as exchanged. /// /// # Errors @@ -266,101 +232,3 @@ impl AuthorizationGrant { } } } - -#[cfg(test)] -mod tests { - use rand::SeedableRng; - - use super::*; - use crate::clock::{Clock, MockClock}; - - #[test] - fn no_login_hint() { - let now = MockClock::default().now(); - let mut rng = rand_chacha::ChaChaRng::seed_from_u64(42); - - let grant = AuthorizationGrant { - login_hint: None, - ..AuthorizationGrant::sample(now, &mut rng) - }; - - let hint = grant.parse_login_hint("example.com"); - - assert!(matches!(hint, LoginHint::None)); - } - - #[test] - fn valid_login_hint() { - let now = MockClock::default().now(); - let mut rng = rand_chacha::ChaChaRng::seed_from_u64(42); - - let grant = AuthorizationGrant { - login_hint: Some(String::from("mxid:@example-user:example.com")), - ..AuthorizationGrant::sample(now, &mut rng) - }; - - let hint = grant.parse_login_hint("example.com"); - - assert!(matches!(hint, LoginHint::MXID(mxid) if mxid.localpart() == "example-user")); - } - - #[test] - fn valid_login_hint_with_email() { - let now = MockClock::default().now(); - let mut rng = rand_chacha::ChaChaRng::seed_from_u64(42); - - let grant = AuthorizationGrant { - login_hint: Some(String::from("example@user")), - ..AuthorizationGrant::sample(now, &mut rng) - }; - - let hint = grant.parse_login_hint("example.com"); - - assert!(matches!(hint, LoginHint::Email(email) if email.to_string() == "example@user")); - } - - #[test] - fn invalid_login_hint() { - let now = MockClock::default().now(); - let mut rng = rand_chacha::ChaChaRng::seed_from_u64(42); - - let grant = AuthorizationGrant { - login_hint: Some(String::from("example-user")), - ..AuthorizationGrant::sample(now, &mut rng) - }; - - let hint = grant.parse_login_hint("example.com"); - - assert!(matches!(hint, LoginHint::None)); - } - - #[test] - fn valid_login_hint_for_wrong_homeserver() { - let now = MockClock::default().now(); - let mut rng = rand_chacha::ChaChaRng::seed_from_u64(42); - - let grant = AuthorizationGrant { - login_hint: Some(String::from("mxid:@example-user:matrix.org")), - ..AuthorizationGrant::sample(now, &mut rng) - }; - - let hint = grant.parse_login_hint("example.com"); - - assert!(matches!(hint, LoginHint::None)); - } - - #[test] - fn unknown_login_hint_type() { - let now = MockClock::default().now(); - let mut rng = rand_chacha::ChaChaRng::seed_from_u64(42); - - let grant = AuthorizationGrant { - login_hint: Some(String::from("something:anything")), - ..AuthorizationGrant::sample(now, &mut rng) - }; - - let hint = grant.parse_login_hint("example.com"); - - assert!(matches!(hint, LoginHint::None)); - } -} diff --git a/crates/data-model/src/oauth2/mod.rs b/crates/data-model/src/oauth2/mod.rs index 6221a32fc..798e07e54 100644 --- a/crates/data-model/src/oauth2/mod.rs +++ b/crates/data-model/src/oauth2/mod.rs @@ -10,9 +10,7 @@ mod device_code_grant; mod session; pub use self::{ - authorization_grant::{ - AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, LoginHint, Pkce, - }, + authorization_grant::{AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, Pkce}, client::{Client, InvalidRedirectUriError, JwksOrJwksUri}, device_code_grant::{DeviceCodeGrant, DeviceCodeGrantState}, session::{Session, SessionState}, diff --git a/crates/email/src/mailer.rs b/crates/email/src/mailer.rs index d31fe96ab..0b5f96d97 100644 --- a/crates/email/src/mailer.rs +++ b/crates/email/src/mailer.rs @@ -53,6 +53,9 @@ impl Mailer { Message::builder() .from(self.from.clone()) .reply_to(self.reply_to.clone()) + // By passing `None`, lettre generates a random message ID + // with a random UUID and the hostname for us + .message_id(None) } fn prepare_verification_email( diff --git a/crates/handlers/Cargo.toml b/crates/handlers/Cargo.toml index 57f391854..97710930b 100644 --- a/crates/handlers/Cargo.toml +++ b/crates/handlers/Cargo.toml @@ -49,6 +49,7 @@ psl.workspace = true rand_chacha.workspace = true rand.workspace = true reqwest.workspace = true +ruma-common.workspace = true rustls.workspace = true schemars.workspace = true sentry.workspace = true diff --git a/crates/handlers/src/admin/v1/users/add.rs b/crates/handlers/src/admin/v1/users/add.rs index 07e87fb40..9b3307e8d 100644 --- a/crates/handlers/src/admin/v1/users/add.rs +++ b/crates/handlers/src/admin/v1/users/add.rs @@ -166,7 +166,7 @@ pub async fn handler( let user = repo.user().add(&mut rng, &clock, params.username).await?; homeserver - .provision_user(&ProvisionRequest::new(&user.username, &user.sub)) + .provision_user(&ProvisionRequest::new(&user.username, &user.sub, false)) .await .map_err(RouteError::Homeserver)?; diff --git a/crates/handlers/src/admin/v1/users/lock.rs b/crates/handlers/src/admin/v1/users/lock.rs index 6d6ccfcf9..d80cc6451 100644 --- a/crates/handlers/src/admin/v1/users/lock.rs +++ b/crates/handlers/src/admin/v1/users/lock.rs @@ -4,10 +4,12 @@ // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial // Please see LICENSE files in the repository root for full details. -use aide::{OperationIo, transform::TransformOperation}; +use aide::{NoApi, OperationIo, transform::TransformOperation}; use axum::{Json, response::IntoResponse}; use hyper::StatusCode; use mas_axum_utils::record_error; +use mas_data_model::BoxRng; +use mas_storage::queue::{ProvisionUserJob, QueueJobRepositoryExt}; use ulid::Ulid; use crate::{ @@ -69,6 +71,7 @@ pub async fn handler( CallContext { mut repo, clock, .. }: CallContext, + NoApi(mut rng): NoApi, id: UlidPathParam, ) -> Result>, RouteError> { let id = *id; @@ -80,6 +83,12 @@ pub async fn handler( let user = repo.user().lock(&clock, user).await?; + // Schedule a job to provision the user so that the lock flag is propagated + // to Synapse + repo.queue_job() + .schedule_job(&mut rng, &clock, ProvisionUserJob::new(&user)) + .await?; + repo.save().await?; Ok(Json(SingleResponse::new( @@ -93,7 +102,11 @@ mod tests { use chrono::Duration; use hyper::{Request, StatusCode}; use mas_data_model::Clock; - use mas_storage::{RepositoryAccess, user::UserRepository}; + use mas_storage::{ + RepositoryAccess, + queue::{ProvisionUserJob, QueueJobRepositoryExt}, + user::UserRepository, + }; use sqlx::PgPool; use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup}; @@ -110,8 +123,25 @@ mod tests { .add(&mut state.rng(), &state.clock, "alice".to_owned()) .await .unwrap(); + + repo.queue_job() + .schedule_job(&mut state.rng(), &state.clock, ProvisionUserJob::new(&user)) + .await + .unwrap(); + repo.save().await.unwrap(); + state.run_jobs_in_queue().await; + assert!( + !state + .homeserver_connection + .query_user_raw("alice") + .await + .unwrap() + .locked, + "User should not be locked at start of test" + ); + let request = Request::post(format!("/api/admin/v1/users/{}/lock", user.id)) .bearer(&token) .empty(); @@ -124,6 +154,17 @@ mod tests { body["data"]["attributes"]["locked_at"], serde_json::json!(state.clock.now()) ); + + state.run_jobs_in_queue().await; + assert!( + state + .homeserver_connection + .query_user_raw("alice") + .await + .unwrap() + .locked, + "User should be locked" + ); } #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] diff --git a/crates/handlers/src/admin/v1/users/reactivate.rs b/crates/handlers/src/admin/v1/users/reactivate.rs index 835ef0b40..01f83888a 100644 --- a/crates/handlers/src/admin/v1/users/reactivate.rs +++ b/crates/handlers/src/admin/v1/users/reactivate.rs @@ -129,7 +129,7 @@ mod tests { // because this endpoint will try to reactivate it state .homeserver_connection - .provision_user(&ProvisionRequest::new(&user.username, &user.sub)) + .provision_user(&ProvisionRequest::new(&user.username, &user.sub, false)) .await .unwrap(); state @@ -181,7 +181,7 @@ mod tests { // Provision the user on the homeserver state .homeserver_connection - .provision_user(&ProvisionRequest::new(&user.username, &user.sub)) + .provision_user(&ProvisionRequest::new(&user.username, &user.sub, false)) .await .unwrap(); diff --git a/crates/handlers/src/admin/v1/users/unlock.rs b/crates/handlers/src/admin/v1/users/unlock.rs index 72987a9ff..d580ab27c 100644 --- a/crates/handlers/src/admin/v1/users/unlock.rs +++ b/crates/handlers/src/admin/v1/users/unlock.rs @@ -4,10 +4,12 @@ // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial // Please see LICENSE files in the repository root for full details. -use aide::{OperationIo, transform::TransformOperation}; +use aide::{NoApi, OperationIo, transform::TransformOperation}; use axum::{Json, response::IntoResponse}; use hyper::StatusCode; use mas_axum_utils::record_error; +use mas_data_model::BoxRng; +use mas_storage::queue::{ProvisionUserJob, QueueJobRepositoryExt}; use ulid::Ulid; use crate::{ @@ -66,7 +68,10 @@ This DOES NOT reactivate a deactivated user, which will remain unavailable until #[tracing::instrument(name = "handler.admin.v1.users.unlock", skip_all)] pub async fn handler( - CallContext { mut repo, .. }: CallContext, + CallContext { + mut repo, clock, .. + }: CallContext, + NoApi(mut rng): NoApi, id: UlidPathParam, ) -> Result>, RouteError> { let id = *id; @@ -78,6 +83,12 @@ pub async fn handler( let user = repo.user().unlock(user).await?; + // Schedule a job to provision the user so that the lock flag is propagated + // to Synapse + repo.queue_job() + .schedule_job(&mut rng, &clock, ProvisionUserJob::new(&user)) + .await?; + repo.save().await?; Ok(Json(SingleResponse::new( @@ -91,7 +102,11 @@ mod tests { use hyper::{Request, StatusCode}; use mas_data_model::Clock; use mas_matrix::{HomeserverConnection, ProvisionRequest}; - use mas_storage::{RepositoryAccess, user::UserRepository}; + use mas_storage::{ + RepositoryAccess, + queue::{ProvisionUserJob, QueueJobRepositoryExt}, + user::UserRepository, + }; use sqlx::PgPool; use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup}; @@ -109,16 +124,27 @@ mod tests { .await .unwrap(); let user = repo.user().lock(&state.clock, user).await.unwrap(); - repo.save().await.unwrap(); // Also provision the user on the homeserver, because this endpoint will try to // reactivate it - state - .homeserver_connection - .provision_user(&ProvisionRequest::new(&user.username, &user.sub)) + repo.queue_job() + .schedule_job(&mut state.rng(), &state.clock, ProvisionUserJob::new(&user)) .await .unwrap(); + repo.save().await.unwrap(); + + state.run_jobs_in_queue().await; + assert!( + state + .homeserver_connection + .query_user_raw("alice") + .await + .unwrap() + .locked, + "User should be locked at start of test" + ); + let request = Request::post(format!("/api/admin/v1/users/{}/unlock", user.id)) .bearer(&token) .empty(); @@ -130,6 +156,17 @@ mod tests { body["data"]["attributes"]["locked_at"], serde_json::Value::Null ); + + state.run_jobs_in_queue().await; + assert!( + !state + .homeserver_connection + .query_user_raw("alice") + .await + .unwrap() + .locked, + "User should not be locked" + ); } #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] @@ -151,7 +188,7 @@ mod tests { // Provision the user on the homeserver state .homeserver_connection - .provision_user(&ProvisionRequest::new(&user.username, &user.sub)) + .provision_user(&ProvisionRequest::new(&user.username, &user.sub, false)) .await .unwrap(); // but then deactivate it diff --git a/crates/handlers/src/captcha.rs b/crates/handlers/src/captcha.rs index c206df5cf..1624edd69 100644 --- a/crates/handlers/src/captcha.rs +++ b/crates/handlers/src/captcha.rs @@ -14,7 +14,7 @@ use thiserror::Error; use crate::BoundActivityTracker; // https://developers.google.com/recaptcha/docs/verify#api_request -const RECAPTCHA_VERIFY_URL: &str = "https://www.google.com/recaptcha/api/siteverify"; +const RECAPTCHA_VERIFY_URL: &str = "https://www.recaptcha.net/recaptcha/api/siteverify"; // https://docs.hcaptcha.com/#verify-the-user-response-server-side const HCAPTCHA_VERIFY_URL: &str = "https://api.hcaptcha.com/siteverify"; diff --git a/crates/handlers/src/cleanup_tests.rs b/crates/handlers/src/cleanup_tests.rs new file mode 100644 index 000000000..7b1a74f53 --- /dev/null +++ b/crates/handlers/src/cleanup_tests.rs @@ -0,0 +1,774 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +//! Integration tests for session cleanup jobs. +//! +//! These tests verify that cleanup jobs correctly respect the session hierarchy +//! required for OIDC Backchannel Logout to function properly. +//! +//! Session hierarchy: +//! ```text +//! upstream_oauth_authorization_sessions (matched by sub/sid claims) +//! │ user_session_id +//! ▼ +//! user_sessions (browser sessions) +//! │ user_session_id FK +//! ┌────┴──────────────┐ +//! │ │ +//! ▼ ▼ +//! compat_sessions oauth2_sessions +//! ``` + +use chrono::Duration; +use hyper::{Request, StatusCode}; +use mas_data_model::{ + BrowserSession, Clock as _, CompatSession, Device, UpstreamOAuthAuthorizationSession, + UpstreamOAuthLink, UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports, + UpstreamOAuthProviderDiscoveryMode, UpstreamOAuthProviderOnBackchannelLogout, + UpstreamOAuthProviderPkceMode, UpstreamOAuthProviderTokenAuthMethod, User, +}; +use mas_iana::jose::JsonWebSignatureAlg; +use mas_jose::jwt::{JsonWebSignatureHeader, Jwt}; +use mas_storage::{ + RepositoryAccess, + queue::{DeactivateUserJob, QueueJobRepositoryExt}, + upstream_oauth2::UpstreamOAuthProviderParams, +}; +use oauth2_types::scope::{OPENID, Scope}; +use sqlx::PgPool; +use wiremock::{ + Mock, MockServer, ResponseTemplate, + matchers::{method, path}, +}; + +use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup}; + +/// Helper struct to hold all the entities created for testing the session +/// hierarchy. +struct TestSessionHierarchy { + user: User, + browser_session: BrowserSession, + compat_session: Option, + oauth2_session: Option, + upstream_session: Option, + #[expect(dead_code)] + upstream_link: Option, + provider: Option, + #[expect(dead_code)] + mock_server: MockServer, +} + +const UPSTREAM_OAUTH_ISSUER: &str = "https://idp.example.com"; +const UPSTREAM_OAUTH_CLIENT_ID: &str = "test-client"; +const UPSTREAM_OAUTH_SESSION_ID: &str = "upstream-oauth-session-id"; +const UPSTREAM_OAUTH_SUBJECT: &str = "upstream-oauth-sub"; + +/// Create the complete session hierarchy for testing. +/// +/// This creates: +/// - A user +/// - A browser session +/// - Optionally a compat session linked to the browser session +/// - Optionally an OAuth 2.0 session linked to the browser session +/// - Optionally an upstream OAuth session linked to the browser session +async fn create_session_hierarchy( + state: &TestState, + with_compat: bool, + with_oauth2: bool, + with_upstream: bool, +) -> TestSessionHierarchy { + let mut rng = state.rng(); + let mut repo = state.repository().await.unwrap(); + + // Start a mock server to answer to JWKS requests for the backchannel logout + // tests + let mock_server = MockServer::start().await; + + let jwks = state.key_store.public_jwks(); + + let mock_jwks = Mock::given(method("GET")) + .and(path("jwks.json")) + .respond_with(ResponseTemplate::new(200).set_body_json(jwks)); + mock_server.register(mock_jwks).await; + + // Create user + let user = repo + .user() + .add(&mut rng, &state.clock, "testuser".to_owned()) + .await + .unwrap(); + + // Create browser session + let browser_session = repo + .browser_session() + .add(&mut rng, &state.clock, &user, None) + .await + .unwrap(); + + // Create compat session if requested + let compat_session = if with_compat { + let device = Device::generate(&mut rng); + let session = repo + .compat_session() + .add( + &mut rng, + &state.clock, + &user, + device, + Some(&browser_session), + false, + None, + ) + .await + .unwrap(); + Some(session) + } else { + None + }; + + // Create OAuth2 session if requested + let oauth2_session = if with_oauth2 { + // First create an OAuth2 client + let client = repo + .oauth2_client() + .add( + &mut rng, + &state.clock, + vec!["https://example.com/callback".parse().unwrap()], + None, + None, + None, + vec![], + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ) + .await + .unwrap(); + + let session = repo + .oauth2_session() + .add_from_browser_session( + &mut rng, + &state.clock, + &client, + &browser_session, + Scope::from_iter([OPENID]), + ) + .await + .unwrap(); + Some(session) + } else { + None + }; + + // Create upstream OAuth session if requested + let (provider, upstream_link, upstream_session) = if with_upstream { + let params = UpstreamOAuthProviderParams { + issuer: Some(UPSTREAM_OAUTH_ISSUER.to_owned()), + human_name: Some("Test IdP".to_owned()), + brand_name: None, + scope: Scope::from_iter([OPENID]), + token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::ClientSecretBasic, + token_endpoint_signing_alg: None, + id_token_signed_response_alg: JsonWebSignatureAlg::Rs256, + fetch_userinfo: false, + userinfo_signed_response_alg: None, + client_id: UPSTREAM_OAUTH_CLIENT_ID.to_owned(), + encrypted_client_secret: None, + claims_imports: UpstreamOAuthProviderClaimsImports::default(), + authorization_endpoint_override: None, + token_endpoint_override: None, + userinfo_endpoint_override: None, + // Point to the mock server to have it use a JWKS we can use for signing + jwks_uri_override: Some(format!("{}/jwks.json", mock_server.uri()).parse().unwrap()), + discovery_mode: UpstreamOAuthProviderDiscoveryMode::Disabled, + pkce_mode: UpstreamOAuthProviderPkceMode::Auto, + response_mode: None, + additional_authorization_parameters: vec![], + forward_login_hint: false, + ui_order: 0, + on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::LogoutAll, + }; + + let provider = repo + .upstream_oauth_provider() + .add(&mut rng, &state.clock, params) + .await + .unwrap(); + + // Create a link + let link = repo + .upstream_oauth_link() + .add( + &mut rng, + &state.clock, + &provider, + UPSTREAM_OAUTH_SUBJECT.to_owned(), + Some("test@idp.example.com".to_owned()), + ) + .await + .unwrap(); + + // Associate link to user + repo.upstream_oauth_link() + .associate_to_user(&link, &user) + .await + .unwrap(); + + // Create an upstream session + let session = repo + .upstream_oauth_session() + .add( + &mut rng, + &state.clock, + &provider, + "state123".to_owned(), + Some("verifier123".to_owned()), + Some("nonce123".to_owned()), + ) + .await + .unwrap(); + + // Complete the session with the link and ID token claims (including sub and + // sid) + let id_token_claims = serde_json::json!({ + "sub": UPSTREAM_OAUTH_SUBJECT, + "sid": UPSTREAM_OAUTH_SESSION_ID, + }); + let session = repo + .upstream_oauth_session() + .complete_with_link( + &state.clock, + session, + &link, + Some("fake-id-token".to_owned()), + Some(id_token_claims), + None, + None, + ) + .await + .unwrap(); + + // Consume the session and link it to the browser session + let session = repo + .upstream_oauth_session() + .consume(&state.clock, session, &browser_session) + .await + .unwrap(); + + (Some(provider), Some(link), Some(session)) + } else { + (None, None, None) + }; + + repo.save().await.unwrap(); + + TestSessionHierarchy { + user, + browser_session, + compat_session, + oauth2_session, + upstream_session, + upstream_link, + provider, + mock_server, + } +} + +/// Test that sessions finished less than 30 days ago are NOT deleted. +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_cleanup_sessions_within_retention_preserved(pool: PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + // Create a full hierarchy with all session types + let hierarchy = create_session_hierarchy(&state, true, true, false).await; + + // Finish all sessions + let mut repo = state.repository().await.unwrap(); + let browser_session = repo + .browser_session() + .finish(&state.clock, hierarchy.browser_session) + .await + .unwrap(); + let compat_session = repo + .compat_session() + .finish(&state.clock, hierarchy.compat_session.unwrap()) + .await + .unwrap(); + let oauth2_session = repo + .oauth2_session() + .finish(&state.clock, hierarchy.oauth2_session.unwrap()) + .await + .unwrap(); + repo.save().await.unwrap(); + + // Wait one day and run the cleanup jobs a few times + state.clock.advance(Duration::try_days(1).unwrap()); + state.run_jobs_in_queue().await; + for _ in 0..5 { + state.clock.advance(Duration::try_hours(1).unwrap()); + state.run_jobs_in_queue().await; + } + + // Verify all sessions still exist + let mut repo = state.repository().await.unwrap(); + assert!( + repo.browser_session() + .lookup(browser_session.id) + .await + .unwrap() + .is_some(), + "Browser session should still exist" + ); + assert!( + repo.compat_session() + .lookup(compat_session.id) + .await + .unwrap() + .is_some(), + "Compat session should still exist" + ); + assert!( + repo.oauth2_session() + .lookup(oauth2_session.id) + .await + .unwrap() + .is_some(), + "OAuth2 session should still exist" + ); +} + +/// Test that deactivated users eventually get all their sessions cleaned up +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_cleanup_deactivated_users(pool: PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + let mut rng = state.rng(); + + // Create a hierarchy with all session types + let hierarchy = create_session_hierarchy(&state, true, true, true).await; + + // Deactivate the user + let mut repo = state.repository().await.unwrap(); + let user = repo + .user() + .lookup(hierarchy.user.id) + .await + .unwrap() + .unwrap(); + let user = repo.user().deactivate(&state.clock, user).await.unwrap(); + repo.queue_job() + .schedule_job(&mut rng, &state.clock, DeactivateUserJob::new(&user, false)) + .await + .unwrap(); + repo.save().await.unwrap(); + + state.run_jobs_in_queue().await; + + // Verify all sessions are finished + let mut repo = state.repository().await.unwrap(); + assert!( + repo.compat_session() + .lookup(hierarchy.compat_session.as_ref().unwrap().id) + .await + .unwrap() + .unwrap() + .is_finished(), + "Compat session should be finished" + ); + assert!( + repo.oauth2_session() + .lookup(hierarchy.oauth2_session.as_ref().unwrap().id) + .await + .unwrap() + .unwrap() + .is_finished(), + "OAuth2 session should be finished" + ); + assert!( + repo.browser_session() + .lookup(hierarchy.browser_session.id) + .await + .unwrap() + .unwrap() + .finished_at + .is_some(), + "Browser session should be there" + ); + assert!( + repo.upstream_oauth_session() + .lookup(hierarchy.upstream_session.as_ref().unwrap().id) + .await + .unwrap() + .is_some(), + "Upstream OAuth session should be there" + ); + + // Wait 31 days and run the cleanup jobs a few times + state.clock.advance(Duration::try_days(31).unwrap()); + state.run_jobs_in_queue().await; + for _ in 0..5 { + state.clock.advance(Duration::try_hours(1).unwrap()); + state.run_jobs_in_queue().await; + } + + // Verify all sessions are deleted + let mut repo = state.repository().await.unwrap(); + assert!( + repo.compat_session() + .lookup(hierarchy.compat_session.as_ref().unwrap().id) + .await + .unwrap() + .is_none(), + "Compat session should be deleted" + ); + assert!( + repo.oauth2_session() + .lookup(hierarchy.oauth2_session.as_ref().unwrap().id) + .await + .unwrap() + .is_none(), + "OAuth2 session should be deleted" + ); + assert!( + repo.browser_session() + .lookup(hierarchy.browser_session.id) + .await + .unwrap() + .is_none(), + "Browser session should be deleted" + ); + assert!( + repo.upstream_oauth_session() + .lookup(hierarchy.upstream_session.as_ref().unwrap().id) + .await + .unwrap() + .is_none(), + "Upstream OAuth session should be deleted" + ); +} + +/// Test that sessions finished more than 30 days ago ARE deleted. +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_cleanup_sessions_after_retention_deleted(pool: PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + // Create hierarchy with compat and oauth2 sessions + let hierarchy = create_session_hierarchy(&state, true, true, false).await; + + // Finish all sessions + let mut repo = state.repository().await.unwrap(); + let browser_session = repo + .browser_session() + .finish(&state.clock, hierarchy.browser_session) + .await + .unwrap(); + let compat_session = repo + .compat_session() + .finish(&state.clock, hierarchy.compat_session.unwrap()) + .await + .unwrap(); + let oauth2_session = repo + .oauth2_session() + .finish(&state.clock, hierarchy.oauth2_session.unwrap()) + .await + .unwrap(); + repo.save().await.unwrap(); + + // Wait 31 days and run the cleanup jobs a few times + state.clock.advance(Duration::try_days(31).unwrap()); + state.run_jobs_in_queue().await; + for _ in 0..5 { + state.clock.advance(Duration::try_hours(1).unwrap()); + state.run_jobs_in_queue().await; + } + + // Verify all sessions are deleted + let mut repo = state.repository().await.unwrap(); + assert!( + repo.compat_session() + .lookup(compat_session.id) + .await + .unwrap() + .is_none(), + "Compat session should be deleted" + ); + assert!( + repo.oauth2_session() + .lookup(oauth2_session.id) + .await + .unwrap() + .is_none(), + "OAuth2 session should be deleted" + ); + // Browser session should also be deleted since children are gone + assert!( + repo.browser_session() + .lookup(browser_session.id) + .await + .unwrap() + .is_none(), + "Browser session should be deleted after children are gone" + ); +} + +/// Test that user sessions with remaining child sessions are NOT deleted. +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_cleanup_user_session_blocked_by_child_sessions(pool: PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + // Create hierarchy with compat session only + let hierarchy = create_session_hierarchy(&state, true, false, false).await; + + // Finish only the browser session (not the compat session) + let mut repo = state.repository().await.unwrap(); + let browser_session = repo + .browser_session() + .finish(&state.clock, hierarchy.browser_session) + .await + .unwrap(); + repo.save().await.unwrap(); + + let compat_session_id = hierarchy.compat_session.as_ref().unwrap().id; + + // Wait 31 days and run the cleanup jobs a few times + state.clock.advance(Duration::try_days(31).unwrap()); + state.run_jobs_in_queue().await; + for _ in 0..5 { + state.clock.advance(Duration::try_hours(1).unwrap()); + state.run_jobs_in_queue().await; + } + + // Verify browser session still exists because compat session is still active + let mut repo = state.repository().await.unwrap(); + assert!( + repo.browser_session() + .lookup(browser_session.id) + .await + .unwrap() + .is_some(), + "Browser session should NOT be deleted because it has an active child session" + ); + assert!( + repo.compat_session() + .lookup(compat_session_id) + .await + .unwrap() + .is_some(), + "Compat session should still exist (not finished)" + ); +} + +/// Test that backchannel logout can find sessions before cleanup. +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_backchannel_logout_works_before_cleanup(pool: PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + // Create hierarchy with upstream session + let hierarchy = create_session_hierarchy(&state, true, true, true).await; + + let provider = hierarchy.provider.as_ref().unwrap(); + + // The edge case we're trying to make works, is that if the browser session + // is finished for 30 days but *not* the child sessions, that browser + // session and the upstream sessions stay there so that backchannel logout + // still works + let mut repo = state.repository().await.unwrap(); + let browser_session = repo + .browser_session() + .lookup(hierarchy.browser_session.id) + .await + .unwrap() + .unwrap(); + repo.browser_session() + .finish(&state.clock, browser_session) + .await + .unwrap(); + repo.save().await.unwrap(); + + // Now wait 31 days and run the cleanup jobs a few times + state.clock.advance(Duration::try_days(31).unwrap()); + state.run_jobs_in_queue().await; + for _ in 0..5 { + state.clock.advance(Duration::try_hours(1).unwrap()); + state.run_jobs_in_queue().await; + } + + // Now let's craft a backchannel logout request + let ts = state.clock.now().timestamp(); + let payload = serde_json::json!({ + "iss": UPSTREAM_OAUTH_ISSUER, + "aud": UPSTREAM_OAUTH_CLIENT_ID, + "sub": UPSTREAM_OAUTH_SUBJECT, + "sid": UPSTREAM_OAUTH_SESSION_ID, + "jti": "iswearthisisrandom", + "iat": ts, + "exp": ts + 300, + "events": { + "http://schemas.openid.net/event/backchannel-logout": {} + } + }); + + let key = state + .key_store + .signing_key_for_algorithm(&JsonWebSignatureAlg::Rs256) + .unwrap(); + let signer = key + .params() + .signing_key_for_alg(&JsonWebSignatureAlg::Rs256) + .unwrap(); + let signed = Jwt::sign( + JsonWebSignatureHeader::new(JsonWebSignatureAlg::Rs256), + payload, + &signer, + ) + .unwrap(); + + let request = Request::post(format!("/upstream/backchannel-logout/{}", provider.id)).form( + serde_json::json!({ + "logout_token": signed.as_str(), + }), + ); + + let response = state.request(request).await; + response.assert_status(StatusCode::OK); + + // The session should still exist, but are finished + let mut repo = state.repository().await.unwrap(); + assert!( + !repo + .browser_session() + .lookup(hierarchy.browser_session.id) + .await + .unwrap() + .unwrap() + .active(), + "Inactive browser session should not be cleaned up" + ); + assert!( + repo.compat_session() + .lookup(hierarchy.compat_session.as_ref().unwrap().id) + .await + .unwrap() + .unwrap() + .is_finished(), + "Active compat session should not be cleaned up" + ); + assert!( + repo.oauth2_session() + .lookup(hierarchy.oauth2_session.as_ref().unwrap().id) + .await + .unwrap() + .unwrap() + .is_finished(), + "Active OAuth2 session should not be cleaned up" + ); + + // Wait again, then the sessions should be completely deleted + state.clock.advance(Duration::try_days(31).unwrap()); + state.run_jobs_in_queue().await; + for _ in 0..5 { + state.clock.advance(Duration::try_hours(1).unwrap()); + state.run_jobs_in_queue().await; + } + + let mut repo = state.repository().await.unwrap(); + assert!( + repo.browser_session() + .lookup(hierarchy.browser_session.id) + .await + .unwrap() + .is_none(), + "Browser session should be deleted" + ); + assert!( + repo.compat_session() + .lookup(hierarchy.compat_session.as_ref().unwrap().id) + .await + .unwrap() + .is_none(), + "Compat session should be deleted" + ); + assert!( + repo.oauth2_session() + .lookup(hierarchy.oauth2_session.as_ref().unwrap().id) + .await + .unwrap() + .is_none(), + "OAuth2 session should be deleted" + ); + assert!( + repo.upstream_oauth_session() + .lookup(hierarchy.upstream_session.as_ref().unwrap().id) + .await + .unwrap() + .is_none(), + "Upstream OAuth session should be deleted" + ); +} + +/// Test that active sessions are not cleaned up even after retention period. +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_active_sessions_not_cleaned_up(pool: PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + // Create hierarchy without finishing any sessions + let hierarchy = create_session_hierarchy(&state, true, true, false).await; + + let browser_session_id = hierarchy.browser_session.id; + let compat_session_id = hierarchy.compat_session.as_ref().unwrap().id; + let oauth2_session_id = hierarchy.oauth2_session.as_ref().unwrap().id; + + // Wait 31 days and run the cleanup jobs a few times + state.clock.advance(Duration::try_days(31).unwrap()); + state.run_jobs_in_queue().await; + for _ in 0..5 { + state.clock.advance(Duration::try_hours(1).unwrap()); + state.run_jobs_in_queue().await; + } + + // All sessions should still exist because they're active + let mut repo = state.repository().await.unwrap(); + assert!( + repo.browser_session() + .lookup(browser_session_id) + .await + .unwrap() + .is_some(), + "Active browser session should not be cleaned up" + ); + assert!( + repo.compat_session() + .lookup(compat_session_id) + .await + .unwrap() + .is_some(), + "Active compat session should not be cleaned up" + ); + assert!( + repo.oauth2_session() + .lookup(oauth2_session_id) + .await + .unwrap() + .is_some(), + "Active OAuth2 session should not be cleaned up" + ); +} diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index 1c3710644..5e57ce5c0 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -935,7 +935,7 @@ mod tests { .unwrap(); state .homeserver_connection - .provision_user(&ProvisionRequest::new(&user.username, &user.sub)) + .provision_user(&ProvisionRequest::new(&user.username, &user.sub, locked)) .await .unwrap(); @@ -1238,7 +1238,7 @@ mod tests { state .homeserver_connection - .provision_user(&ProvisionRequest::new(&user.username, &user.sub)) + .provision_user(&ProvisionRequest::new(&user.username, &user.sub, false)) .await .unwrap(); @@ -1343,7 +1343,7 @@ mod tests { state .homeserver_connection - .provision_user(&ProvisionRequest::new(&user.username, &user.sub)) + .provision_user(&ProvisionRequest::new(&user.username, &user.sub, false)) .await .unwrap(); diff --git a/crates/handlers/src/compat/login_sso_complete.rs b/crates/handlers/src/compat/login_sso_complete.rs index 84c8f8525..df059cd36 100644 --- a/crates/handlers/src/compat/login_sso_complete.rs +++ b/crates/handlers/src/compat/login_sso_complete.rs @@ -83,7 +83,7 @@ pub async fn get( Some(CompatLoginSsoAction::Register) => { url_builder.redirect(&mas_router::Register::and_continue_compat_sso_login(id)) } - Some(CompatLoginSsoAction::Login) | None => { + Some(CompatLoginSsoAction::Login | CompatLoginSsoAction::Unknown) | None => { url_builder.redirect(&mas_router::Login::and_continue_compat_sso_login(id)) } }; @@ -224,7 +224,7 @@ pub async fn post( Some(CompatLoginSsoAction::Register) => { url_builder.redirect(&mas_router::Register::and_continue_compat_sso_login(id)) } - Some(CompatLoginSsoAction::Login) | None => { + Some(CompatLoginSsoAction::Login | CompatLoginSsoAction::Unknown) | None => { url_builder.redirect(&mas_router::Login::and_continue_compat_sso_login(id)) } }; diff --git a/crates/handlers/src/compat/login_sso_redirect.rs b/crates/handlers/src/compat/login_sso_redirect.rs index 1ad47c55e..8edb868fd 100644 --- a/crates/handlers/src/compat/login_sso_redirect.rs +++ b/crates/handlers/src/compat/login_sso_redirect.rs @@ -13,7 +13,6 @@ use mas_router::{CompatLoginSsoAction, CompatLoginSsoComplete, UrlBuilder}; use mas_storage::{BoxRepository, compat::CompatSsoLoginRepository}; use rand::distributions::{Alphanumeric, DistString}; use serde::Deserialize; -use serde_with::serde; use thiserror::Error; use url::Url; @@ -23,12 +22,21 @@ use crate::impl_from_error_for_route; pub struct Params { #[serde(rename = "redirectUrl")] redirect_url: Option, + action: Option, #[serde(rename = "org.matrix.msc3824.action")] unstable_action: Option, } +impl Params { + fn action(&self) -> Option { + self.action + .filter(CompatLoginSsoAction::is_known) + .or(self.unstable_action.filter(CompatLoginSsoAction::is_known)) + } +} + #[derive(Debug, Error)] pub enum RouteError { #[error(transparent)] @@ -62,6 +70,8 @@ pub async fn get( State(url_builder): State, Query(params): Query, ) -> Result { + let action = params.action(); + // Check the redirectUrl parameter let redirect_url = params.redirect_url.ok_or(RouteError::MissingRedirectUrl)?; let redirect_url = Url::parse(&redirect_url).map_err(|_| RouteError::InvalidRedirectUrl)?; @@ -84,10 +94,7 @@ pub async fn get( repo.save().await?; - Ok(url_builder.absolute_redirect(&CompatLoginSsoComplete::new( - login.id, - params.action.or(params.unstable_action), - ))) + Ok(url_builder.absolute_redirect(&CompatLoginSsoComplete::new(login.id, action))) } #[cfg(test)] @@ -121,4 +128,29 @@ mod tests { assert!(location.contains("org.matrix.msc3824.action=register")); assert!(location.contains("action=register")); } + + #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] + async fn test_unknown_action(pool: PgPool) { + let state: TestState = TestState::from_pool(pool).await.unwrap(); + + let request = Request::get( + "/_matrix/client/v3/login/sso/redirect?\ + redirectUrl=http://example.com/\ + &org.matrix.msc3824.action=undefinedaction", + ) + .empty(); + + let response = state.request(request).await; + + response.assert_status(StatusCode::SEE_OTHER); + + let location = response + .headers() + .get("Location") + .unwrap() + .to_str() + .unwrap(); + assert!(!location.contains("org.matrix.msc3824.action")); + assert!(!location.contains("action")); + } } diff --git a/crates/handlers/src/compat/mod.rs b/crates/handlers/src/compat/mod.rs index 1c30d5e04..dd5600afe 100644 --- a/crates/handlers/src/compat/mod.rs +++ b/crates/handlers/src/compat/mod.rs @@ -25,6 +25,9 @@ pub(crate) mod logout; pub(crate) mod logout_all; pub(crate) mod refresh; +#[cfg(test)] +mod tests; + #[derive(Debug, Serialize)] struct MatrixError { errcode: &'static str, diff --git a/crates/handlers/src/compat/refresh.rs b/crates/handlers/src/compat/refresh.rs index 95679af1d..dabdaefe7 100644 --- a/crates/handlers/src/compat/refresh.rs +++ b/crates/handlers/src/compat/refresh.rs @@ -156,7 +156,7 @@ pub(crate) async fn post( .await?; repo.compat_refresh_token() - .consume(&clock, refresh_token) + .consume_and_replace(&clock, refresh_token, &new_refresh_token) .await?; if let Some(access_token) = access_token { diff --git a/crates/handlers/src/compat/tests.rs b/crates/handlers/src/compat/tests.rs new file mode 100644 index 000000000..da1e8e501 --- /dev/null +++ b/crates/handlers/src/compat/tests.rs @@ -0,0 +1,233 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +use axum::http::{Request, StatusCode}; +use mas_matrix::{HomeserverConnection, ProvisionRequest}; +use serde::{Deserialize, Serialize}; +use zeroize::Zeroizing; + +use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup}; + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +enum LoginCredentials { + #[serde(rename = "m.login.password")] + Password { + identifier: LoginIdentifier, + password: String, + }, +} + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +enum LoginIdentifier { + #[serde(rename = "m.id.user")] + User { user: String }, +} + +#[derive(Debug, Serialize)] +struct LoginRequest { + #[serde(flatten)] + credentials: LoginCredentials, + #[serde(default)] + refresh_token: bool, +} + +#[derive(Debug, Deserialize)] +struct LoginResponse { + #[allow(dead_code)] + access_token: String, + #[allow(dead_code)] + user_id: String, + #[allow(dead_code)] + device_id: Option, + refresh_token: Option, +} + +#[derive(Debug, Serialize)] +struct RefreshRequest { + refresh_token: String, +} + +#[derive(Debug, Deserialize, PartialEq, Eq)] +struct RefreshResponse { + access_token: String, + refresh_token: String, + expires_in_ms: i64, +} + +/// Test using a compatibility refresh token. +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_compat_refresh(pool: sqlx::PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + // Create a user + create_test_user(&state, "testuser").await; + + // Login to get initial tokens + let login_request = Request::post("/_matrix/client/v3/login").json(&LoginRequest { + credentials: LoginCredentials::Password { + identifier: LoginIdentifier::User { + user: "testuser".to_owned(), + }, + password: "password".to_owned(), + }, + refresh_token: true, + }); + + let login_response = state.request(login_request).await; + login_response.assert_status(StatusCode::OK); + + let login_response: LoginResponse = login_response.json(); + let initial_refresh_token = login_response + .refresh_token + .expect("Login should return a refresh token"); + + // First refresh + let refresh_request = Request::post("/_matrix/client/v3/refresh").json(&RefreshRequest { + refresh_token: initial_refresh_token.clone(), + }); + + let first_refresh_response = state.request(refresh_request).await; + first_refresh_response.assert_status(StatusCode::OK); + + let first_refresh_response: RefreshResponse = first_refresh_response.json(); + let first_new_refresh_token = first_refresh_response.refresh_token.clone(); + + assert_eq!( + first_refresh_response, + RefreshResponse { + access_token: "mct_fNbm5KAQovodfVQz7IvDc44woP66fR_fsaiD1".to_owned(), + refresh_token: "mcr_42oTpLoieH5IecxG6gZXyvelQWW9Xq_a8g5N3".to_owned(), + expires_in_ms: 300_000 + } + ); + + // Use the token from the /refresh response to /refresh again, + // proving that it works. + // This is a regression test: we were previously consuming the refresh token + // before it was returned from /refresh. + let second_refresh_request = + Request::post("/_matrix/client/v3/refresh").json(&RefreshRequest { + refresh_token: first_new_refresh_token.clone(), + }); + + let second_refresh_response = state.request(second_refresh_request).await; + second_refresh_response.assert_status(StatusCode::OK); + + let second_refresh_response: RefreshResponse = second_refresh_response.json(); + + assert_eq!( + second_refresh_response, + RefreshResponse { + access_token: "mct_Wc6Hx4l9DGzqGtgLoYqtrtBUBcWlE4_ZFyTp2".to_owned(), + refresh_token: "mcr_Yp7FM44zJN5qePGMLvvMXC4Ds1A3lC_0YcYCM".to_owned(), + expires_in_ms: 300_000 + } + ); +} + +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_refresh_with_invalid_token(pool: sqlx::PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + let refresh_request = RefreshRequest { + refresh_token: "invalid_token".to_owned(), + }; + + let refresh_request = Request::post("/_matrix/client/v3/refresh").json(&refresh_request); + + let response = state.request(refresh_request).await; + response.assert_status(StatusCode::UNAUTHORIZED); +} + +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_refresh_with_consumed_token(pool: sqlx::PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + // Create a user and login + create_test_user(&state, "testuser").await; + + let login_request = LoginRequest { + credentials: LoginCredentials::Password { + identifier: LoginIdentifier::User { + user: "testuser".to_owned(), + }, + password: "password".to_owned(), + }, + refresh_token: true, + }; + + let login_request = Request::post("/_matrix/client/v3/login").json(&login_request); + + let login_response = state.request(login_request).await; + login_response.assert_status(StatusCode::OK); + + let login_response: LoginResponse = login_response.json(); + let refresh_token = login_response + .refresh_token + .expect("Login should return a refresh token"); + + let refresh_request = RefreshRequest { + refresh_token: refresh_token.clone(), + }; + + // Use the refresh token once + let first_refresh_request = Request::post("/_matrix/client/v3/refresh").json(&refresh_request); + let first_refresh_response = state.request(first_refresh_request).await; + first_refresh_response.assert_status(StatusCode::OK); + + let _first_refresh_response: RefreshResponse = first_refresh_response.json(); + + // Try to use the same refresh token again - should fail because it's consumed + let second_refresh_request = Request::post("/_matrix/client/v3/refresh").json(&refresh_request); + + let second_refresh_response = state.request(second_refresh_request).await; + second_refresh_response.assert_status(StatusCode::UNAUTHORIZED); +} + +async fn create_test_user(state: &TestState, username: &str) -> mas_data_model::User { + let mut repo = state.repository().await.unwrap(); + let mut rng = state.rng(); + + let user = repo + .user() + .add(&mut rng, &state.clock, username.to_owned()) + .await + .unwrap(); + + let password = Zeroizing::new("password".to_owned()); + let (version, hashed_password) = state + .password_manager + .hash(&mut rng, password) + .await + .unwrap(); + + repo.user_password() + .add( + &mut rng, + &state.clock, + &user, + version, + hashed_password, + None, + ) + .await + .unwrap(); + + // Provision the user on the homeserver + state + .homeserver_connection + .provision_user(&ProvisionRequest::new(&user.username, &user.sub, false)) + .await + .unwrap(); + + repo.save().await.unwrap(); + + user +} diff --git a/crates/handlers/src/graphql/mutations/user.rs b/crates/handlers/src/graphql/mutations/user.rs index 355c7d0ac..376652a46 100644 --- a/crates/handlers/src/graphql/mutations/user.rs +++ b/crates/handlers/src/graphql/mutations/user.rs @@ -551,6 +551,12 @@ impl UserMutations { let user = repo.user().lock(&state.clock(), user).await?; + // Schedule a job to provision the user so that the lock flag is propagated + // to Synapse + repo.queue_job() + .schedule_job(&mut rng, &clock, ProvisionUserJob::new(&user)) + .await?; + if deactivate { info!(%user.id, "Scheduling deactivation of user"); repo.queue_job() @@ -570,6 +576,8 @@ impl UserMutations { input: UnlockUserInput, ) -> Result { let state = ctx.state(); + let clock = state.clock(); + let mut rng = state.rng(); let requester = ctx.requester(); let matrix = state.homeserver_connection(); @@ -592,6 +600,12 @@ impl UserMutations { let user = repo.user().reactivate(user).await?; let user = repo.user().unlock(user).await?; + // Schedule a job to provision the user so that the lock flag is propagated + // to Synapse + repo.queue_job() + .schedule_job(&mut rng, &clock, ProvisionUserJob::new(&user)) + .await?; + repo.save().await?; Ok(UnlockUserPayload::Unlocked(user)) diff --git a/crates/handlers/src/graphql/tests.rs b/crates/handlers/src/graphql/tests.rs index 888d477d0..7fed79c09 100644 --- a/crates/handlers/src/graphql/tests.rs +++ b/crates/handlers/src/graphql/tests.rs @@ -530,7 +530,7 @@ async fn test_oauth2_client_credentials(pool: PgPool) { // so we need to do it manually state .homeserver_connection - .provision_user(&ProvisionRequest::new("alice", user_id)) + .provision_user(&ProvisionRequest::new("alice", user_id, false)) .await .unwrap(); diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index ebd223e4a..0cb450f53 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -62,6 +62,8 @@ mod views; mod activity_tracker; mod captcha; +#[cfg(test)] +mod cleanup_tests; mod preferred_language; mod rate_limit; mod session; diff --git a/crates/handlers/src/oauth2/authorization/mod.rs b/crates/handlers/src/oauth2/authorization/mod.rs index 6037bd9ad..a3988b0ce 100644 --- a/crates/handlers/src/oauth2/authorization/mod.rs +++ b/crates/handlers/src/oauth2/authorization/mod.rs @@ -278,9 +278,15 @@ pub(crate) async fn get( // Other cases where we don't have a session, ask for a login repo.save().await?; - url_builder - .redirect(&mas_router::Login::and_then(continue_grant)) - .into_response() + let mut url = mas_router::Login::and_then(continue_grant); + + url = if let Some(login_hint) = grant.login_hint { + url.with_login_hint(login_hint) + } else { + url + }; + + url_builder.redirect(&url).into_response() } Some(user_session) => { diff --git a/crates/handlers/src/oauth2/introspection.rs b/crates/handlers/src/oauth2/introspection.rs index 17f508921..c093ed7bc 100644 --- a/crates/handlers/src/oauth2/introspection.rs +++ b/crates/handlers/src/oauth2/introspection.rs @@ -805,7 +805,7 @@ mod tests { state .homeserver_connection - .provision_user(&ProvisionRequest::new(&user.username, &user.sub)) + .provision_user(&ProvisionRequest::new(&user.username, &user.sub, false)) .await .unwrap(); @@ -1005,7 +1005,7 @@ mod tests { state .homeserver_connection - .provision_user(&ProvisionRequest::new(&user.username, &user.sub)) + .provision_user(&ProvisionRequest::new(&user.username, &user.sub, false)) .await .unwrap(); diff --git a/crates/handlers/src/upstream_oauth2/link.rs b/crates/handlers/src/upstream_oauth2/link.rs index 4ed536ad0..e081b0f79 100644 --- a/crates/handlers/src/upstream_oauth2/link.rs +++ b/crates/handlers/src/upstream_oauth2/link.rs @@ -476,53 +476,6 @@ pub(crate) async fn get( let forced_or_required = provider.claims_imports.localpart.is_forced_or_required(); - // We've got a localpart from the template. Let's run the policy - // engine on this registration and react early to a problem on - // the username - let res = policy - .evaluate_register(mas_policy::RegisterInput { - registration_method: mas_policy::RegistrationMethod::UpstreamOAuth2, - username: &localpart, - email: email.as_deref(), - requester: mas_policy::Requester { - ip_address: activity_tracker.ip(), - user_agent: user_agent.clone(), - }, - }) - .await?; - - // We don't do a full policy check at this point, only look for violations on - // the username - if res - .violations - .iter() - .any(|violation| violation.field.as_deref() == Some("username")) - { - if !forced_or_required { - tracing::warn!( - upstream_oauth_provider.id = %provider.id, - upstream_oauth_link.id = %link.id, - "Upstream provider returned a localpart {localpart:?} which was denied by the policy ({res}). As the username is just a suggestion, it was ignored." - ); - break 'localpart None; - } - - // If the username policy check fails, we display an error message. - // TODO: translate - let ctx = ErrorContext::new() - .with_code("Policy error") - .with_description(format!( - r"Upstream account provider returned {localpart:?} as username, - which does not pass the policy check: {res}" - )) - .with_language(&locale); - - return Ok(( - cookie_jar, - Html(templates.render_error(&ctx)?).into_response(), - )); - } - // We got a localpart from the template. We need to check if it's // available, and if it's not apply the conflict resolution setup in // the config @@ -730,6 +683,53 @@ pub(crate) async fn get( )); } + // We've got a localpart from the template. Let's run the policy + // engine on this registration and react early to a problem on + // the username + let res = policy + .evaluate_register(mas_policy::RegisterInput { + registration_method: mas_policy::RegistrationMethod::UpstreamOAuth2, + username: &localpart, + email: email.as_deref(), + requester: mas_policy::Requester { + ip_address: activity_tracker.ip(), + user_agent: user_agent.clone(), + }, + }) + .await?; + + // We don't do a full policy check at this point, only look for violations on + // the username + if res + .violations + .iter() + .any(|violation| violation.field.as_deref() == Some("username")) + { + if !forced_or_required { + tracing::warn!( + upstream_oauth_provider.id = %provider.id, + upstream_oauth_link.id = %link.id, + "Upstream provider returned a localpart {localpart:?} which was denied by the policy ({res}). As the username is just a suggestion, it was ignored." + ); + break 'localpart None; + } + + // If the username policy check fails, we display an error message. + // TODO: translate + let ctx = ErrorContext::new() + .with_code("Policy error") + .with_description(format!( + r"Upstream account provider returned {localpart:?} as username, + which does not pass the policy check: {res}" + )) + .with_language(&locale); + + return Ok(( + cookie_jar, + Html(templates.render_error(&ctx)?).into_response(), + )); + } + // Now let's check if the localpart is allowed by the homeserver. It's possible // that it's plain invalid (although that should have been caught by the // policy), or just reserved by an application service diff --git a/crates/handlers/src/views/app.rs b/crates/handlers/src/views/app.rs index 4ae5f5222..36c9cd4f9 100644 --- a/crates/handlers/src/views/app.rs +++ b/crates/handlers/src/views/app.rs @@ -25,6 +25,9 @@ use crate::{ pub struct Params { #[serde(default, flatten)] action: Option, + + #[serde(rename = "org.matrix.msc4198.login_hint")] + unstable_login_hint: Option, } #[tracing::instrument(name = "handlers.views.app.get", skip_all)] @@ -33,7 +36,10 @@ pub async fn get( State(templates): State, activity_tracker: BoundActivityTracker, State(url_builder): State, - Query(Params { action }): Query, + Query(Params { + action, + unstable_login_hint, + }): Query, mut repo: BoxRepository, clock: BoxClock, mut rng: BoxRng, @@ -54,13 +60,13 @@ pub async fn get( // TODO: keep the full path, not just the action let Some(session) = maybe_session else { - return Ok(( - cookie_jar, - url_builder.redirect(&mas_router::Login::and_then( - PostAuthAction::manage_account(action), - )), - ) - .into_response()); + let mut url = mas_router::Login::and_then(PostAuthAction::manage_account(action)); + + if let Some(login_hint) = unstable_login_hint { + url = url.with_login_hint(login_hint); + } + + return Ok((cookie_jar, url_builder.redirect(&url)).into_response()); }; activity_tracker diff --git a/crates/handlers/src/views/login.rs b/crates/handlers/src/views/login.rs index 72e1566fe..a4fef8eba 100644 --- a/crates/handlers/src/views/login.rs +++ b/crates/handlers/src/views/login.rs @@ -17,7 +17,7 @@ use mas_axum_utils::{ cookies::CookieJar, csrf::{CsrfExt, ProtectedForm}, }; -use mas_data_model::{BoxClock, BoxRng, Clock, oauth2::LoginHint}; +use mas_data_model::{BoxClock, BoxRng, Clock}; use mas_i18n::DataLocale; use mas_matrix::HomeserverConnection; use mas_router::{UpstreamOAuth2Authorize, UrlBuilder}; @@ -28,14 +28,14 @@ use mas_storage::{ }; use mas_templates::{ AccountInactiveContext, FieldError, FormError, FormState, LoginContext, LoginFormField, - PostAuthContext, PostAuthContextInner, TemplateContext, Templates, ToFormState, + TemplateContext, Templates, ToFormState, }; use opentelemetry::{Key, KeyValue, metrics::Counter}; use rand::Rng; use serde::{Deserialize, Serialize}; use zeroize::Zeroizing; -use super::shared::OptionalPostAuthAction; +use super::shared::{LoginHint, OptionalPostAuthAction, QueryLoginHint}; use crate::{ BoundActivityTracker, Limiter, METER, PreferredLanguage, RequesterFingerprint, SiteConfig, passwords::{PasswordManager, PasswordVerificationResult}, @@ -73,6 +73,7 @@ pub(crate) async fn get( mut repo: BoxRepository, activity_tracker: BoundActivityTracker, Query(query): Query, + Query(query_login_hint): Query, cookie_jar: CookieJar, ) -> Result { let (cookie_jar, maybe_session) = match load_session_or_fallback( @@ -124,6 +125,7 @@ pub(crate) async fn get( &templates, &homeserver, &site_config, + query_login_hint, ) .await } @@ -142,7 +144,7 @@ pub(crate) async fn post( mut repo: BoxRepository, activity_tracker: BoundActivityTracker, requester: RequesterFingerprint, - Query(query): Query, + (Query(query), Query(query_login_hint)): (Query, Query), cookie_jar: CookieJar, user_agent: Option>, Form(form): Form>, @@ -180,6 +182,7 @@ pub(crate) async fn post( &templates, &homeserver, &site_config, + query_login_hint, ) .await; } @@ -206,6 +209,7 @@ pub(crate) async fn post( &templates, &homeserver, &site_config, + query_login_hint, ) .await; }; @@ -226,6 +230,7 @@ pub(crate) async fn post( &templates, &homeserver, &site_config, + query_login_hint, ) .await; } @@ -248,6 +253,7 @@ pub(crate) async fn post( &templates, &homeserver, &site_config, + query_login_hint, ) .await; }; @@ -293,6 +299,7 @@ pub(crate) async fn post( &templates, &homeserver, &site_config, + query_login_hint, ) .await; } @@ -375,7 +382,7 @@ async fn get_user_by_email_or_by_username( fn handle_login_hint( mut ctx: LoginContext, - next: &PostAuthContext, + query_login_hint: &QueryLoginHint, homeserver: &dyn HomeserverConnection, site_config: &SiteConfig, ) -> LoginContext { @@ -386,16 +393,12 @@ fn handle_login_hint( return ctx; } - if let PostAuthContextInner::ContinueAuthorizationGrant { ref grant } = next.ctx { - let value = match grant.parse_login_hint(homeserver.homeserver()) { - LoginHint::MXID(mxid) => Some(mxid.localpart().to_owned()), - LoginHint::Email(email) if site_config.login_with_email_allowed => { - Some(email.to_string()) - } - _ => None, - }; - form_state.set_value(LoginFormField::Username, value); - } + let value = match query_login_hint.parse_login_hint(homeserver.homeserver()) { + LoginHint::Mxid(mxid) => Some(mxid.localpart().to_owned()), + LoginHint::Email(email) if site_config.login_with_email_allowed => Some(email.to_string()), + _ => None, + }; + form_state.set_value(LoginFormField::Username, value); ctx } @@ -411,6 +414,7 @@ async fn render( templates: &Templates, homeserver: &dyn HomeserverConnection, site_config: &SiteConfig, + query_login_hint: QueryLoginHint, ) -> Result { let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock, rng); let providers = repo.upstream_oauth_provider().all_enabled().await?; @@ -419,12 +423,13 @@ async fn render( .with_form_state(form_state) .with_upstream_providers(providers); + let ctx = handle_login_hint(ctx, &query_login_hint, homeserver, site_config); + let next = action .load_context(repo) .await .map_err(InternalError::from_anyhow)?; let ctx = if let Some(next) = next { - let ctx = handle_login_hint(ctx, &next, homeserver, site_config); ctx.with_post_action(next) } else { ctx diff --git a/crates/handlers/src/views/shared.rs b/crates/handlers/src/views/shared.rs index 85edf299f..b0c7b8ac1 100644 --- a/crates/handlers/src/views/shared.rs +++ b/crates/handlers/src/views/shared.rs @@ -4,6 +4,8 @@ // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial // Please see LICENSE files in the repository root for full details. +use std::str::FromStr as _; + use anyhow::Context; use mas_router::{PostAuthAction, Route, UrlBuilder}; use mas_storage::{ @@ -13,6 +15,7 @@ use mas_storage::{ upstream_oauth2::{UpstreamOAuthLinkRepository, UpstreamOAuthProviderRepository}, }; use mas_templates::{PostAuthContext, PostAuthContextInner}; +use ruma_common::UserId; use serde::{Deserialize, Serialize}; use tracing::warn; @@ -107,3 +110,109 @@ impl OptionalPostAuthAction { })) } } + +pub enum LoginHint<'a> { + Mxid(&'a UserId), + Email(lettre::Address), + None, +} + +#[derive(Debug, Deserialize)] +pub(crate) struct QueryLoginHint { + login_hint: Option, +} + +impl QueryLoginHint { + /// Parse a `login_hint` + /// + /// Returns `LoginHint::MXID` for valid mxid 'mxid:@john.doe:example.com' + /// + /// Returns `LoginHint::Email` for valid email 'john.doe@example.com' + /// + /// Otherwise returns `LoginHint::None` + pub fn parse_login_hint(&self, homeserver: &str) -> LoginHint<'_> { + let Some(login_hint) = &self.login_hint else { + return LoginHint::None; + }; + + if let Some(value) = login_hint.strip_prefix("mxid:") + && let Ok(mxid) = <&UserId>::try_from(value) + && mxid.server_name() == homeserver + { + LoginHint::Mxid(mxid) + } else if let Ok(email) = lettre::Address::from_str(login_hint) { + LoginHint::Email(email) + } else { + LoginHint::None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn no_login_hint() { + let query_login_hint = QueryLoginHint { login_hint: None }; + + let hint = query_login_hint.parse_login_hint("example.com"); + + assert!(matches!(hint, LoginHint::None)); + } + + #[test] + fn valid_login_hint() { + let query_login_hint = QueryLoginHint { + login_hint: Some(String::from("mxid:@example-user:example.com")), + }; + + let hint = query_login_hint.parse_login_hint("example.com"); + + assert!(matches!(hint, LoginHint::Mxid(mxid) if mxid.localpart() == "example-user")); + } + + #[test] + fn valid_login_hint_with_email() { + let query_login_hint = QueryLoginHint { + login_hint: Some(String::from("example@user")), + }; + + let hint = query_login_hint.parse_login_hint("example.com"); + + assert!(matches!(hint, LoginHint::Email(email) if email.to_string() == "example@user")); + } + + #[test] + fn invalid_login_hint() { + let query_login_hint = QueryLoginHint { + login_hint: Some(String::from("example-user")), + }; + + let hint = query_login_hint.parse_login_hint("example.com"); + + assert!(matches!(hint, LoginHint::None)); + } + + #[test] + fn valid_login_hint_for_wrong_homeserver() { + let query_login_hint = QueryLoginHint { + login_hint: Some(String::from("mxid:@example-user:matrix.org")), + }; + + let hint = query_login_hint.parse_login_hint("example.com"); + + assert!(matches!(hint, LoginHint::None)); + } + + #[test] + fn unknown_login_hint_type() { + let query_login_hint = QueryLoginHint { + login_hint: Some(String::from("something:anything")), + }; + + let hint = query_login_hint.parse_login_hint("example.com"); + + assert!(matches!(hint, LoginHint::None)); + } +} diff --git a/crates/matrix-synapse/src/modern.rs b/crates/matrix-synapse/src/modern.rs index 3d70f52de..198072b31 100644 --- a/crates/matrix-synapse/src/modern.rs +++ b/crates/matrix-synapse/src/modern.rs @@ -141,6 +141,8 @@ impl HomeserverConnection for SynapseConnection { set_emails: Option>, #[serde(skip_serializing_if = "std::ops::Not::not")] unset_emails: bool, + #[serde(skip_serializing_if = "Option::is_none")] + locked: Option, } let mut body = Request { @@ -151,6 +153,7 @@ impl HomeserverConnection for SynapseConnection { unset_avatar_url: false, set_emails: None, unset_emails: false, + locked: Some(request.locked()), }; request.on_displayname(|displayname| match displayname { diff --git a/crates/matrix/src/lib.rs b/crates/matrix/src/lib.rs index f1fbe9c83..cb8534eb7 100644 --- a/crates/matrix/src/lib.rs +++ b/crates/matrix/src/lib.rs @@ -33,6 +33,7 @@ enum FieldAction { pub struct ProvisionRequest { localpart: String, sub: String, + locked: bool, displayname: FieldAction, avatar_url: FieldAction, emails: FieldAction>, @@ -45,11 +46,13 @@ impl ProvisionRequest { /// /// * `localpart` - The localpart of the user to provision. /// * `sub` - The `sub` of the user, aka the internal ID. + /// * `locked` - Whether the user is locked. #[must_use] - pub fn new(localpart: impl Into, sub: impl Into) -> Self { + pub fn new(localpart: impl Into, sub: impl Into, locked: bool) -> Self { Self { localpart: localpart.into(), sub: sub.into(), + locked, displayname: FieldAction::DoNothing, avatar_url: FieldAction::DoNothing, emails: FieldAction::DoNothing, @@ -68,6 +71,12 @@ impl ProvisionRequest { &self.localpart } + /// Get the locked flag of the user to provision + #[must_use] + pub fn locked(&self) -> bool { + self.locked + } + /// Ask to set the displayname of the user. /// /// # Parameters diff --git a/crates/matrix/src/mock.rs b/crates/matrix/src/mock.rs index 4180597e2..2dbbf4b9a 100644 --- a/crates/matrix/src/mock.rs +++ b/crates/matrix/src/mock.rs @@ -10,9 +10,10 @@ use anyhow::Context; use async_trait::async_trait; use tokio::sync::RwLock; -use crate::{MatrixUser, ProvisionRequest}; +use crate::{HomeserverConnection as _, MatrixUser, ProvisionRequest}; -struct MockUser { +#[derive(Clone)] +pub struct MockUser { sub: String, avatar_url: Option, displayname: Option, @@ -20,6 +21,7 @@ struct MockUser { emails: Option>, cross_signing_reset_allowed: bool, deactivated: bool, + pub locked: bool, } /// A mock implementation of a [`HomeserverConnection`], which never fails and @@ -50,6 +52,18 @@ impl HomeserverConnection { pub async fn reserve_localpart(&self, localpart: &'static str) { self.reserved_localparts.write().await.insert(localpart); } + + /// Like `query_user` but get the raw test state of the user. + /// + /// # Errors + /// + /// Will fail if the user doesn't exist. + pub async fn query_user_raw(&self, localpart: &str) -> Result { + let mxid = self.mxid(localpart); + let users = self.users.read().await; + let user = users.get(&mxid).context("User not found")?; + Ok(user.clone()) + } } #[async_trait] @@ -85,6 +99,7 @@ impl crate::HomeserverConnection for HomeserverConnection { emails: None, cross_signing_reset_allowed: false, deactivated: false, + locked: false, }); anyhow::ensure!( @@ -104,6 +119,8 @@ impl crate::HomeserverConnection for HomeserverConnection { user.avatar_url = avatar_url.map(ToOwned::to_owned); }); + user.locked = request.locked(); + Ok(inserted) } @@ -219,7 +236,6 @@ impl crate::HomeserverConnection for HomeserverConnection { #[cfg(test)] mod tests { use super::*; - use crate::HomeserverConnection as _; #[tokio::test] async fn test_mock_connection() { @@ -234,7 +250,7 @@ mod tests { assert!(conn.upsert_device("test", device, None).await.is_err()); assert!(conn.delete_device("test", device).await.is_err()); - let request = ProvisionRequest::new("test", "test") + let request = ProvisionRequest::new("test", "test", false) .set_displayname("Test User".into()) .set_avatar_url("mxc://example.org/1234567890".into()) .set_emails(vec!["test@example.org".to_owned()]); diff --git a/crates/router/src/endpoints.rs b/crates/router/src/endpoints.rs index 37c200aac..239f24b25 100644 --- a/crates/router/src/endpoints.rs +++ b/crates/router/src/endpoints.rs @@ -172,20 +172,23 @@ impl SimpleRoute for Healthcheck { } /// `GET|POST /login` -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct Login { + #[serde(flatten)] post_auth_action: Option, + + login_hint: Option, } impl Route for Login { - type Query = PostAuthAction; + type Query = Self; fn route() -> &'static str { "/login" } fn query(&self) -> Option<&Self::Query> { - self.post_auth_action.as_ref() + Some(self) } } @@ -194,6 +197,7 @@ impl Login { pub const fn and_then(action: PostAuthAction) -> Self { Self { post_auth_action: Some(action), + login_hint: None, } } @@ -201,6 +205,7 @@ impl Login { pub const fn and_continue_grant(id: Ulid) -> Self { Self { post_auth_action: Some(PostAuthAction::continue_grant(id)), + login_hint: None, } } @@ -208,6 +213,7 @@ impl Login { pub const fn and_continue_device_code_grant(id: Ulid) -> Self { Self { post_auth_action: Some(PostAuthAction::continue_device_code_grant(id)), + login_hint: None, } } @@ -215,6 +221,7 @@ impl Login { pub const fn and_continue_compat_sso_login(id: Ulid) -> Self { Self { post_auth_action: Some(PostAuthAction::continue_compat_sso_login(id)), + login_hint: None, } } @@ -222,9 +229,16 @@ impl Login { pub const fn and_link_upstream(id: Ulid) -> Self { Self { post_auth_action: Some(PostAuthAction::link_upstream(id)), + login_hint: None, } } + #[must_use] + pub fn with_login_hint(mut self, login_hint: String) -> Self { + self.login_hint = Some(login_hint); + self + } + /// Get a reference to the login's post auth action. #[must_use] pub fn post_auth_action(&self) -> Option<&PostAuthAction> { @@ -241,7 +255,10 @@ impl Login { impl From> for Login { fn from(post_auth_action: Option) -> Self { - Self { post_auth_action } + Self { + post_auth_action, + login_hint: None, + } } } @@ -628,6 +645,16 @@ impl SimpleRoute for CompatLoginSsoRedirectIdp { pub enum CompatLoginSsoAction { Login, Register, + #[serde(other)] + Unknown, +} + +impl CompatLoginSsoAction { + /// Returns true if the action is a known action. + #[must_use] + pub fn is_known(&self) -> bool { + !matches!(self, Self::Unknown) + } } #[derive(Debug, Serialize, Deserialize, Clone, Copy)] diff --git a/crates/storage-pg/.sqlx/query-f75e44b528234dac708640ad9a111f3f6b468a91bf0d5b574795bf8c80605f19.json b/crates/storage-pg/.sqlx/query-4e64540bbffe5f4b9c4a6589012cf69eb67adaa4d40fc1910dfcd2640e32ab37.json similarity index 66% rename from crates/storage-pg/.sqlx/query-f75e44b528234dac708640ad9a111f3f6b468a91bf0d5b574795bf8c80605f19.json rename to crates/storage-pg/.sqlx/query-4e64540bbffe5f4b9c4a6589012cf69eb67adaa4d40fc1910dfcd2640e32ab37.json index 0d597ca3b..54b801174 100644 --- a/crates/storage-pg/.sqlx/query-f75e44b528234dac708640ad9a111f3f6b468a91bf0d5b574795bf8c80605f19.json +++ b/crates/storage-pg/.sqlx/query-4e64540bbffe5f4b9c4a6589012cf69eb67adaa4d40fc1910dfcd2640e32ab37.json @@ -1,15 +1,16 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE compat_refresh_tokens\n SET consumed_at = $2\n WHERE compat_session_id = $1\n AND consumed_at IS NULL\n ", + "query": "\n UPDATE compat_refresh_tokens\n SET consumed_at = $2\n WHERE compat_session_id = $1\n AND consumed_at IS NULL\n AND compat_refresh_token_id <> $3\n ", "describe": { "columns": [], "parameters": { "Left": [ "Uuid", - "Timestamptz" + "Timestamptz", + "Uuid" ] }, "nullable": [] }, - "hash": "f75e44b528234dac708640ad9a111f3f6b468a91bf0d5b574795bf8c80605f19" + "hash": "4e64540bbffe5f4b9c4a6589012cf69eb67adaa4d40fc1910dfcd2640e32ab37" } diff --git a/crates/storage-pg/src/compat/mod.rs b/crates/storage-pg/src/compat/mod.rs index d42c9b1af..b9ce82da4 100644 --- a/crates/storage-pg/src/compat/mod.rs +++ b/crates/storage-pg/src/compat/mod.rs @@ -437,6 +437,7 @@ mod tests { async fn test_refresh_token_repository(pool: PgPool) { const ACCESS_TOKEN: &str = "access_token"; const REFRESH_TOKEN: &str = "refresh_token"; + const REFRESH_TOKEN2: &str = "refresh_token2"; let mut rng = ChaChaRng::seed_from_u64(42); let clock = MockClock::default(); let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed(); @@ -508,16 +509,28 @@ mod tests { assert!(refresh_token_lookup.is_valid()); assert!(!refresh_token_lookup.is_consumed()); - // Consume it + // Consume the first token, but to do so we need a 2nd to replace it with + let refresh_token2 = repo + .compat_refresh_token() + .add( + &mut rng, + &clock, + &session, + &access_token, + REFRESH_TOKEN2.to_owned(), + ) + .await + .unwrap(); + let refresh_token = repo .compat_refresh_token() - .consume(&clock, refresh_token) + .consume_and_replace(&clock, refresh_token, &refresh_token2) .await .unwrap(); assert!(!refresh_token.is_valid()); assert!(refresh_token.is_consumed()); - // Reload it and check again + // Reload the first token and check again let refresh_token_lookup = repo .compat_refresh_token() .find_by_token(REFRESH_TOKEN) @@ -530,7 +543,7 @@ mod tests { // Consuming it again should not work assert!( repo.compat_refresh_token() - .consume(&clock, refresh_token) + .consume_and_replace(&clock, refresh_token, &refresh_token2) .await .is_err() ); diff --git a/crates/storage-pg/src/compat/refresh_token.rs b/crates/storage-pg/src/compat/refresh_token.rs index 5c6add046..3cb9d7944 100644 --- a/crates/storage-pg/src/compat/refresh_token.rs +++ b/crates/storage-pg/src/compat/refresh_token.rs @@ -185,20 +185,26 @@ impl CompatRefreshTokenRepository for PgCompatRefreshTokenRepository<'_> { } #[tracing::instrument( - name = "db.compat_refresh_token.consume", + name = "db.compat_refresh_token.consume_and_replace", skip_all, fields( db.query.text, %compat_refresh_token.id, + %successor_compat_refresh_token.id, compat_session.id = %compat_refresh_token.session_id, ), err, )] - async fn consume( + async fn consume_and_replace( &mut self, clock: &dyn Clock, compat_refresh_token: CompatRefreshToken, + successor_compat_refresh_token: &CompatRefreshToken, ) -> Result { + if compat_refresh_token.session_id != successor_compat_refresh_token.session_id { + return Err(DatabaseError::invalid_operation()); + } + let consumed_at = clock.now(); let res = sqlx::query!( r#" @@ -206,9 +212,11 @@ impl CompatRefreshTokenRepository for PgCompatRefreshTokenRepository<'_> { SET consumed_at = $2 WHERE compat_session_id = $1 AND consumed_at IS NULL + AND compat_refresh_token_id <> $3 "#, Uuid::from(compat_refresh_token.session_id), consumed_at, + Uuid::from(successor_compat_refresh_token.id), ) .traced() .execute(&mut *self.conn) diff --git a/crates/storage/src/compat/refresh_token.rs b/crates/storage/src/compat/refresh_token.rs index 6ffefbc69..d4434569c 100644 --- a/crates/storage/src/compat/refresh_token.rs +++ b/crates/storage/src/compat/refresh_token.rs @@ -69,16 +69,22 @@ pub trait CompatRefreshTokenRepository: Send + Sync { token: String, ) -> Result; - /// Consume a compat refresh token. + /// Consume the given compat refresh token, as well as all other refresh + /// tokens from the same session, except for the given successor compat + /// refresh token. /// - /// This also marks other refresh tokens in the same session as consumed. + /// The given successor refresh token will thereafter be the only valid + /// refresh token for the session. + /// + /// # Historical context + /// + /// When using a refresh token, we must be able to mark multiple other + /// refresh tokens in the same session as consumed. /// This is desirable because the syn2mas migration process can import /// multiple refresh tokens for one device (compat session). /// But once the user uses one of those, the others should no longer /// be valid. /// - /// Returns the consumed compat refresh token - /// /// # Parameters /// /// * `clock`: The clock used to generate timestamps @@ -86,11 +92,15 @@ pub trait CompatRefreshTokenRepository: Send + Sync { /// /// # Errors /// - /// Returns [`Self::Error`] if the underlying repository fails - async fn consume( + /// - Returns [`Self::Error`] if the underlying repository fails + /// - Returns an error if `compat_refresh_token` is not valid to be + /// consumed. + /// - Returns an error if no refresh tokens would be consumed. + async fn consume_and_replace( &mut self, clock: &dyn Clock, compat_refresh_token: CompatRefreshToken, + successor_compat_refresh_token: &CompatRefreshToken, ) -> Result; } @@ -111,9 +121,10 @@ repository_impl!(CompatRefreshTokenRepository: token: String, ) -> Result; - async fn consume( + async fn consume_and_replace( &mut self, clock: &dyn Clock, compat_refresh_token: CompatRefreshToken, + successor_compat_refresh_token: &CompatRefreshToken, ) -> Result; ); diff --git a/crates/tasks/src/cleanup/misc.rs b/crates/tasks/src/cleanup/misc.rs new file mode 100644 index 000000000..52fd62e5c --- /dev/null +++ b/crates/tasks/src/cleanup/misc.rs @@ -0,0 +1,88 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +//! Miscellaneous cleanup tasks + +use std::time::Duration; + +use async_trait::async_trait; +use mas_storage::queue::{CleanupQueueJobsJob, PruneStalePolicyDataJob}; +use tracing::{debug, info}; +use ulid::Ulid; + +use super::BATCH_SIZE; +use crate::{ + State, + new_queue::{JobContext, JobError, RunnableJob}, +}; + +#[async_trait] +impl RunnableJob for CleanupQueueJobsJob { + #[tracing::instrument(name = "job.cleanup_queue_jobs", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove completed and failed queue jobs after 30 days. + // Keep them for debugging purposes. + let until = state.clock.now() - chrono::Duration::days(30); + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + let (count, cursor) = repo + .queue_job() + .cleanup(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no queue jobs to clean up"); + } else { + info!(count = total, "cleaned up queue jobs"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for PruneStalePolicyDataJob { + #[tracing::instrument(name = "job.prune_stale_policy_data", skip_all)] + async fn run(&self, state: &State, _context: JobContext) -> Result<(), JobError> { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // Keep the last 10 policy data + let count = repo + .policy_data() + .prune(10) + .await + .map_err(JobError::retry)?; + + repo.save().await.map_err(JobError::retry)?; + + if count == 0 { + debug!("no stale policy data to prune"); + } else { + info!(count, "pruned stale policy data"); + } + + Ok(()) + } +} diff --git a/crates/tasks/src/cleanup/mod.rs b/crates/tasks/src/cleanup/mod.rs new file mode 100644 index 000000000..02ace1ded --- /dev/null +++ b/crates/tasks/src/cleanup/mod.rs @@ -0,0 +1,24 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +//! Database cleanup tasks +//! +//! This module contains tasks for cleaning up old data from the database. +//! Tasks are grouped by domain: +//! +//! - [`tokens`]: OAuth token cleanup (access and refresh tokens) +//! - [`sessions`]: Session cleanup (compat, `OAuth2`, user sessions and their +//! IPs) +//! - [`oauth`]: OAuth grants and upstream OAuth cleanup +//! - [`user`]: User-related cleanup (registrations, recovery, email auth) +//! - [`misc`]: Miscellaneous cleanup (queue jobs, policy data) + +mod misc; +mod oauth; +mod sessions; +mod tokens; +mod user; + +pub(crate) const BATCH_SIZE: usize = 1000; diff --git a/crates/tasks/src/cleanup/oauth.rs b/crates/tasks/src/cleanup/oauth.rs new file mode 100644 index 000000000..2a201d4df --- /dev/null +++ b/crates/tasks/src/cleanup/oauth.rs @@ -0,0 +1,216 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +//! OAuth grants and upstream OAuth cleanup tasks + +use std::time::Duration; + +use async_trait::async_trait; +use mas_storage::queue::{ + CleanupOAuthAuthorizationGrantsJob, CleanupOAuthDeviceCodeGrantsJob, + CleanupUpstreamOAuthLinksJob, CleanupUpstreamOAuthSessionsJob, +}; +use tracing::{debug, info}; +use ulid::Ulid; + +use super::BATCH_SIZE; +use crate::{ + State, + new_queue::{JobContext, JobError, RunnableJob}, +}; + +#[async_trait] +impl RunnableJob for CleanupOAuthAuthorizationGrantsJob { + #[tracing::instrument(name = "job.cleanup_oauth_authorization_grants", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove authorization grants after 7 days. They are in practice only + // valid for a short time, but keeping them around helps investigate abuse + // patterns. + let until = state.clock.now() - chrono::Duration::days(7); + // We use the fact that ULIDs include the creation time in their first 48 bits + // as a cursor + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + // This returns the number of deleted grants, and the greatest ULID processed + let (count, cursor) = repo + .oauth2_authorization_grant() + .cleanup(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no authorization grants to clean up"); + } else { + info!(count = total, "cleaned up authorization grants"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupOAuthDeviceCodeGrantsJob { + #[tracing::instrument(name = "job.cleanup_oauth_device_code_grants", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove device code grants after 7 days. They are in practice only + // valid for a short time, but keeping them around helps investigate abuse + // patterns. + let until = state.clock.now() - chrono::Duration::days(7); + // We use the fact that ULIDs include the creation time in their first 48 bits + // as a cursor + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + // This returns the number of deleted grants, and the greatest ULID processed + let (count, cursor) = repo + .oauth2_device_code_grant() + .cleanup(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no device code grants to clean up"); + } else { + info!(count = total, "cleaned up device code grants"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupUpstreamOAuthSessionsJob { + #[tracing::instrument(name = "job.cleanup_upstream_oauth_sessions", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove pending upstream OAuth authorization sessions after 7 days. + let until = state.clock.now() - chrono::Duration::days(7); + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + let (count, cursor) = repo + .upstream_oauth_session() + .cleanup_orphaned(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no pending upstream OAuth sessions to clean up"); + } else { + info!(count = total, "cleaned up pending upstream OAuth sessions"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupUpstreamOAuthLinksJob { + #[tracing::instrument(name = "job.cleanup_upstream_oauth_links", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove orphaned upstream OAuth links after 7 days. + let until = state.clock.now() - chrono::Duration::days(7); + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + let (count, cursor) = repo + .upstream_oauth_link() + .cleanup_orphaned(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no orphaned upstream OAuth links to clean up"); + } else { + info!(count = total, "cleaned up orphaned upstream OAuth links"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} diff --git a/crates/tasks/src/cleanup/sessions.rs b/crates/tasks/src/cleanup/sessions.rs new file mode 100644 index 000000000..0a11a6b99 --- /dev/null +++ b/crates/tasks/src/cleanup/sessions.rs @@ -0,0 +1,290 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +//! Session cleanup tasks + +use std::time::Duration; + +use async_trait::async_trait; +use mas_storage::queue::{ + CleanupFinishedCompatSessionsJob, CleanupFinishedOAuth2SessionsJob, + CleanupFinishedUserSessionsJob, CleanupInactiveCompatSessionIpsJob, + CleanupInactiveOAuth2SessionIpsJob, CleanupInactiveUserSessionIpsJob, +}; +use tracing::{debug, info}; + +use super::BATCH_SIZE; +use crate::{ + State, + new_queue::{JobContext, JobError, RunnableJob}, +}; + +#[async_trait] +impl RunnableJob for CleanupFinishedCompatSessionsJob { + #[tracing::instrument(name = "job.cleanup_finished_compat_sessions", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Cleanup compat sessions that were finished more than 30 days ago + let until = state.clock.now() - chrono::Duration::days(30); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // This returns the number of deleted sessions, and the last finished_at + // timestamp + let (count, last_finished_at) = repo + .compat_session() + .cleanup_finished(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_finished_at; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no finished compat sessions to clean up"); + } else { + info!(count = total, "cleaned up finished compat sessions"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupFinishedOAuth2SessionsJob { + #[tracing::instrument(name = "job.cleanup_finished_oauth2_sessions", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Cleanup OAuth2 sessions that were finished more than 30 days ago + let until = state.clock.now() - chrono::Duration::days(30); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // This returns the number of deleted sessions, and the last finished_at + // timestamp + let (count, last_finished_at) = repo + .oauth2_session() + .cleanup_finished(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_finished_at; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no finished OAuth2 sessions to clean up"); + } else { + info!(count = total, "cleaned up finished OAuth2 sessions"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupFinishedUserSessionsJob { + #[tracing::instrument(name = "job.cleanup_finished_user_sessions", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Cleanup user/browser sessions that were finished more than 30 days ago + let until = state.clock.now() - chrono::Duration::days(30); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // This returns the number of deleted sessions, and the last finished_at + // timestamp. Only deletes sessions that have no child sessions + // (compat_sessions or oauth2_sessions). + let (count, last_finished_at) = repo + .browser_session() + .cleanup_finished(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_finished_at; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no finished user sessions to clean up"); + } else { + info!(count = total, "cleaned up finished user sessions"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupInactiveOAuth2SessionIpsJob { + #[tracing::instrument(name = "job.cleanup_inactive_oauth2_session_ips", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Clear IPs from sessions inactive for 30+ days + let threshold = state.clock.now() - chrono::Duration::days(30); + let mut total = 0; + + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + let (count, last_active_at) = repo + .oauth2_session() + .cleanup_inactive_ips(since, threshold, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_active_at; + total += count; + + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no OAuth2 session IPs to clean up"); + } else { + info!(count = total, "cleaned up inactive OAuth2 session IPs"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupInactiveCompatSessionIpsJob { + #[tracing::instrument(name = "job.cleanup_inactive_compat_session_ips", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Clear IPs from sessions inactive for 30+ days + let threshold = state.clock.now() - chrono::Duration::days(30); + let mut total = 0; + + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + let (count, last_active_at) = repo + .compat_session() + .cleanup_inactive_ips(since, threshold, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_active_at; + total += count; + + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no compat session IPs to clean up"); + } else { + info!(count = total, "cleaned up inactive compat session IPs"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupInactiveUserSessionIpsJob { + #[tracing::instrument(name = "job.cleanup_inactive_user_session_ips", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Clear IPs from sessions inactive for 30+ days + let threshold = state.clock.now() - chrono::Duration::days(30); + let mut total = 0; + + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + let (count, last_active_at) = repo + .browser_session() + .cleanup_inactive_ips(since, threshold, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_active_at; + total += count; + + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no user session IPs to clean up"); + } else { + info!(count = total, "cleaned up inactive user session IPs"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + Some(Duration::from_secs(10 * 60)) + } +} diff --git a/crates/tasks/src/cleanup/tokens.rs b/crates/tasks/src/cleanup/tokens.rs new file mode 100644 index 000000000..dd91de2b9 --- /dev/null +++ b/crates/tasks/src/cleanup/tokens.rs @@ -0,0 +1,214 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +//! OAuth token cleanup tasks + +use std::time::Duration; + +use async_trait::async_trait; +use mas_storage::queue::{ + CleanupConsumedOAuthRefreshTokensJob, CleanupExpiredOAuthAccessTokensJob, + CleanupRevokedOAuthAccessTokensJob, CleanupRevokedOAuthRefreshTokensJob, +}; +use tracing::{debug, info}; + +use super::BATCH_SIZE; +use crate::{ + State, + new_queue::{JobContext, JobError, RunnableJob}, +}; + +#[async_trait] +impl RunnableJob for CleanupRevokedOAuthAccessTokensJob { + #[tracing::instrument(name = "job.cleanup_revoked_oauth_access_tokens", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Cleanup tokens that were revoked more than an hour ago + let until = state.clock.now() - chrono::Duration::hours(1); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // This returns the number of deleted tokens, and the last revoked_at timestamp + let (count, last_revoked_at) = repo + .oauth2_access_token() + .cleanup_revoked(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_revoked_at; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no token to clean up"); + } else { + info!(count = total, "cleaned up revoked tokens"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupExpiredOAuthAccessTokensJob { + #[tracing::instrument(name = "job.cleanup_expired_oauth_access_tokens", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Cleanup tokens that expired more than a month ago + // It is important to keep them around for a bit because of refresh + // token idempotency. When we see a refresh token twice, we allow + // reusing it *only* if both the next refresh token and the next access + // tokens were not used. By keeping expired access tokens around for a + // month, we cannot make the *correct* decision, we will assume that the + // token wasn't used. Refer to the token refresh logic for details. + let until = state.clock.now() - chrono::Duration::days(30); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // This returns the number of deleted tokens, and the last expires_at timestamp + let (count, last_expires_at) = repo + .oauth2_access_token() + .cleanup_expired(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_expires_at; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no token to clean up"); + } else { + info!(count = total, "cleaned up expired tokens"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + Some(Duration::from_secs(60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupRevokedOAuthRefreshTokensJob { + #[tracing::instrument(name = "job.cleanup_revoked_oauth_refresh_tokens", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Cleanup tokens that were revoked more than an hour ago + let until = state.clock.now() - chrono::Duration::hours(1); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // This returns the number of deleted tokens, and the last revoked_at timestamp + let (count, last_revoked_at) = repo + .oauth2_refresh_token() + .cleanup_revoked(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_revoked_at; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no token to clean up"); + } else { + info!(count = total, "cleaned up revoked tokens"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupConsumedOAuthRefreshTokensJob { + #[tracing::instrument(name = "job.cleanup_consumed_oauth_refresh_tokens", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Cleanup tokens that were consumed more than an hour ago + let until = state.clock.now() - chrono::Duration::hours(1); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // This returns the number of deleted tokens, and the last consumed_at timestamp + let (count, last_consumed_at) = repo + .oauth2_refresh_token() + .cleanup_consumed(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_consumed_at; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no token to clean up"); + } else { + info!(count = total, "cleaned up consumed tokens"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} diff --git a/crates/tasks/src/cleanup/user.rs b/crates/tasks/src/cleanup/user.rs new file mode 100644 index 000000000..d682c1b51 --- /dev/null +++ b/crates/tasks/src/cleanup/user.rs @@ -0,0 +1,181 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +//! User-related cleanup tasks + +use std::time::Duration; + +use async_trait::async_trait; +use mas_storage::queue::{ + CleanupUserEmailAuthenticationsJob, CleanupUserRecoverySessionsJob, CleanupUserRegistrationsJob, +}; +use tracing::{debug, info}; +use ulid::Ulid; + +use super::BATCH_SIZE; +use crate::{ + State, + new_queue::{JobContext, JobError, RunnableJob}, +}; + +#[async_trait] +impl RunnableJob for CleanupUserRegistrationsJob { + #[tracing::instrument(name = "job.cleanup_user_registrations", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove user registrations after 30 days. They are in practice only + // valid for 1h, but keeping them around helps investigate abuse patterns. + let until = state.clock.now() - chrono::Duration::days(30); + // We use the fact that ULIDs include the creation time in their first 48 bits + // as a cursor + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + // This returns the number of deleted registrations, and the greatest ULID + // processed + let (count, cursor) = repo + .user_registration() + .cleanup(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no user registrations to clean up"); + } else { + info!(count = total, "cleaned up user registrations"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupUserRecoverySessionsJob { + #[tracing::instrument(name = "job.cleanup_user_recovery_sessions", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove recovery sessions after 7 days. They are in practice only + // valid for a short time (tickets expire after 10 minutes), but keeping + // them around helps investigate abuse patterns. + let until = state.clock.now() - chrono::Duration::days(7); + // We use the fact that ULIDs include the creation time in their first 48 bits + // as a cursor + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + // This returns the number of deleted sessions, and the greatest ULID processed + let (count, cursor) = repo + .user_recovery() + .cleanup(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no user recovery sessions to clean up"); + } else { + info!(count = total, "cleaned up user recovery sessions"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupUserEmailAuthenticationsJob { + #[tracing::instrument(name = "job.cleanup_user_email_authentications", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove email authentications after 7 days. They are in practice only + // valid for a short time (codes expire after 10 minutes), but keeping + // them around helps investigate abuse patterns. + let until = state.clock.now() - chrono::Duration::days(7); + // We use the fact that ULIDs include the creation time in their first 48 bits + // as a cursor + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + // This returns the number of deleted authentications, and the greatest ULID + // processed + let (count, cursor) = repo + .user_email() + .cleanup_authentications(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no user email authentications to clean up"); + } else { + info!(count = total, "cleaned up user email authentications"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} diff --git a/crates/tasks/src/database.rs b/crates/tasks/src/database.rs deleted file mode 100644 index c3fb46f78..000000000 --- a/crates/tasks/src/database.rs +++ /dev/null @@ -1,916 +0,0 @@ -// Copyright 2025, 2026 Element Creations Ltd. -// Copyright 2024, 2025 New Vector Ltd. -// Copyright 2023, 2024 The Matrix.org Foundation C.I.C. -// -// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial -// Please see LICENSE files in the repository root for full details. - -//! Database-related tasks - -use std::time::Duration; - -use async_trait::async_trait; -use mas_storage::queue::{ - CleanupConsumedOAuthRefreshTokensJob, CleanupExpiredOAuthAccessTokensJob, - CleanupFinishedCompatSessionsJob, CleanupFinishedOAuth2SessionsJob, - CleanupFinishedUserSessionsJob, CleanupInactiveCompatSessionIpsJob, - CleanupInactiveOAuth2SessionIpsJob, CleanupInactiveUserSessionIpsJob, - CleanupOAuthAuthorizationGrantsJob, CleanupOAuthDeviceCodeGrantsJob, CleanupQueueJobsJob, - CleanupRevokedOAuthAccessTokensJob, CleanupRevokedOAuthRefreshTokensJob, - CleanupUpstreamOAuthLinksJob, CleanupUpstreamOAuthSessionsJob, - CleanupUserEmailAuthenticationsJob, CleanupUserRecoverySessionsJob, - CleanupUserRegistrationsJob, PruneStalePolicyDataJob, -}; -use tracing::{debug, info}; -use ulid::Ulid; - -use crate::{ - State, - new_queue::{JobContext, JobError, RunnableJob}, -}; - -const BATCH_SIZE: usize = 1000; - -#[async_trait] -impl RunnableJob for CleanupRevokedOAuthAccessTokensJob { - #[tracing::instrument(name = "job.cleanup_revoked_oauth_access_tokens", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Cleanup tokens that were revoked more than an hour ago - let until = state.clock.now() - chrono::Duration::hours(1); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // This returns the number of deleted tokens, and the last revoked_at timestamp - let (count, last_revoked_at) = repo - .oauth2_access_token() - .cleanup_revoked(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_revoked_at; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no token to clean up"); - } else { - info!(count = total, "cleaned up revoked tokens"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupExpiredOAuthAccessTokensJob { - #[tracing::instrument(name = "job.cleanup_expired_oauth_access_tokens", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Cleanup tokens that expired more than a month ago - // It is important to keep them around for a bit because of refresh - // token idempotency. When we see a refresh token twice, we allow - // reusing it *only* if both the next refresh token and the next access - // tokens were not used. By keeping expired access tokens around for a - // month, we cannot make the *correct* decision, we will assume that the - // token wasn't used. Refer to the token refresh logic for details. - let until = state.clock.now() - chrono::Duration::days(30); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // This returns the number of deleted tokens, and the last expires_at timestamp - let (count, last_expires_at) = repo - .oauth2_access_token() - .cleanup_expired(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_expires_at; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no token to clean up"); - } else { - info!(count = total, "cleaned up expired tokens"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - Some(Duration::from_secs(60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupRevokedOAuthRefreshTokensJob { - #[tracing::instrument(name = "job.cleanup_revoked_oauth_refresh_tokens", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Cleanup tokens that were revoked more than an hour ago - let until = state.clock.now() - chrono::Duration::hours(1); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // This returns the number of deleted tokens, and the last revoked_at timestamp - let (count, last_revoked_at) = repo - .oauth2_refresh_token() - .cleanup_revoked(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_revoked_at; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no token to clean up"); - } else { - info!(count = total, "cleaned up revoked tokens"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupConsumedOAuthRefreshTokensJob { - #[tracing::instrument(name = "job.cleanup_consumed_oauth_refresh_tokens", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Cleanup tokens that were consumed more than an hour ago - let until = state.clock.now() - chrono::Duration::hours(1); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // This returns the number of deleted tokens, and the last consumed_at timestamp - let (count, last_consumed_at) = repo - .oauth2_refresh_token() - .cleanup_consumed(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_consumed_at; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no token to clean up"); - } else { - info!(count = total, "cleaned up consumed tokens"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupUserRecoverySessionsJob { - #[tracing::instrument(name = "job.cleanup_user_recovery_sessions", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove recovery sessions after 7 days. They are in practice only - // valid for a short time (tickets expire after 10 minutes), but keeping - // them around helps investigate abuse patterns. - let until = state.clock.now() - chrono::Duration::days(7); - // We use the fact that ULIDs include the creation time in their first 48 bits - // as a cursor - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - // This returns the number of deleted sessions, and the greatest ULID processed - let (count, cursor) = repo - .user_recovery() - .cleanup(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no user recovery sessions to clean up"); - } else { - info!(count = total, "cleaned up user recovery sessions"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupUserEmailAuthenticationsJob { - #[tracing::instrument(name = "job.cleanup_user_email_authentications", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove email authentications after 7 days. They are in practice only - // valid for a short time (codes expire after 10 minutes), but keeping - // them around helps investigate abuse patterns. - let until = state.clock.now() - chrono::Duration::days(7); - // We use the fact that ULIDs include the creation time in their first 48 bits - // as a cursor - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - // This returns the number of deleted authentications, and the greatest ULID - // processed - let (count, cursor) = repo - .user_email() - .cleanup_authentications(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no user email authentications to clean up"); - } else { - info!(count = total, "cleaned up user email authentications"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupUpstreamOAuthSessionsJob { - #[tracing::instrument(name = "job.cleanup_upstream_oauth_sessions", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove pending upstream OAuth authorization sessions after 7 days. - let until = state.clock.now() - chrono::Duration::days(7); - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - let (count, cursor) = repo - .upstream_oauth_session() - .cleanup_orphaned(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no pending upstream OAuth sessions to clean up"); - } else { - info!(count = total, "cleaned up pending upstream OAuth sessions"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupUpstreamOAuthLinksJob { - #[tracing::instrument(name = "job.cleanup_upstream_oauth_links", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove orphaned upstream OAuth links after 7 days. - let until = state.clock.now() - chrono::Duration::days(7); - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - let (count, cursor) = repo - .upstream_oauth_link() - .cleanup_orphaned(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no orphaned upstream OAuth links to clean up"); - } else { - info!(count = total, "cleaned up orphaned upstream OAuth links"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupQueueJobsJob { - #[tracing::instrument(name = "job.cleanup_queue_jobs", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove completed and failed queue jobs after 30 days. - // Keep them for debugging purposes. - let until = state.clock.now() - chrono::Duration::days(30); - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - let (count, cursor) = repo - .queue_job() - .cleanup(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no queue jobs to clean up"); - } else { - info!(count = total, "cleaned up queue jobs"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupUserRegistrationsJob { - #[tracing::instrument(name = "job.cleanup_user_registrations", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove user registrations after 30 days. They are in practice only - // valid for 1h, but keeping them around helps investigate abuse patterns. - let until = state.clock.now() - chrono::Duration::days(30); - // We use the fact that ULIDs include the creation time in their first 48 bits - // as a cursor - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - // This returns the number of deleted registrations, and the greatest ULID - // processed - let (count, cursor) = repo - .user_registration() - .cleanup(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no user registrations to clean up"); - } else { - info!(count = total, "cleaned up user registrations"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupFinishedCompatSessionsJob { - #[tracing::instrument(name = "job.cleanup_finished_compat_sessions", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Cleanup compat sessions that were finished more than 30 days ago - let until = state.clock.now() - chrono::Duration::days(30); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // This returns the number of deleted sessions, and the last finished_at - // timestamp - let (count, last_finished_at) = repo - .compat_session() - .cleanup_finished(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_finished_at; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no finished compat sessions to clean up"); - } else { - info!(count = total, "cleaned up finished compat sessions"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupFinishedOAuth2SessionsJob { - #[tracing::instrument(name = "job.cleanup_finished_oauth2_sessions", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Cleanup OAuth2 sessions that were finished more than 30 days ago - let until = state.clock.now() - chrono::Duration::days(30); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // This returns the number of deleted sessions, and the last finished_at - // timestamp - let (count, last_finished_at) = repo - .oauth2_session() - .cleanup_finished(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_finished_at; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no finished OAuth2 sessions to clean up"); - } else { - info!(count = total, "cleaned up finished OAuth2 sessions"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupFinishedUserSessionsJob { - #[tracing::instrument(name = "job.cleanup_finished_user_sessions", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Cleanup user/browser sessions that were finished more than 30 days ago - let until = state.clock.now() - chrono::Duration::days(30); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // This returns the number of deleted sessions, and the last finished_at - // timestamp. Only deletes sessions that have no child sessions - // (compat_sessions or oauth2_sessions). - let (count, last_finished_at) = repo - .browser_session() - .cleanup_finished(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_finished_at; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no finished user sessions to clean up"); - } else { - info!(count = total, "cleaned up finished user sessions"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupOAuthAuthorizationGrantsJob { - #[tracing::instrument(name = "job.cleanup_oauth_authorization_grants", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove authorization grants after 7 days. They are in practice only - // valid for a short time, but keeping them around helps investigate abuse - // patterns. - let until = state.clock.now() - chrono::Duration::days(7); - // We use the fact that ULIDs include the creation time in their first 48 bits - // as a cursor - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - // This returns the number of deleted grants, and the greatest ULID processed - let (count, cursor) = repo - .oauth2_authorization_grant() - .cleanup(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no authorization grants to clean up"); - } else { - info!(count = total, "cleaned up authorization grants"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupOAuthDeviceCodeGrantsJob { - #[tracing::instrument(name = "job.cleanup_oauth_device_code_grants", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove device code grants after 7 days. They are in practice only - // valid for a short time, but keeping them around helps investigate abuse - // patterns. - let until = state.clock.now() - chrono::Duration::days(7); - // We use the fact that ULIDs include the creation time in their first 48 bits - // as a cursor - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - // This returns the number of deleted grants, and the greatest ULID processed - let (count, cursor) = repo - .oauth2_device_code_grant() - .cleanup(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no device code grants to clean up"); - } else { - info!(count = total, "cleaned up device code grants"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for PruneStalePolicyDataJob { - #[tracing::instrument(name = "job.prune_stale_policy_data", skip_all)] - async fn run(&self, state: &State, _context: JobContext) -> Result<(), JobError> { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // Keep the last 10 policy data - let count = repo - .policy_data() - .prune(10) - .await - .map_err(JobError::retry)?; - - repo.save().await.map_err(JobError::retry)?; - - if count == 0 { - debug!("no stale policy data to prune"); - } else { - info!(count, "pruned stale policy data"); - } - - Ok(()) - } -} - -#[async_trait] -impl RunnableJob for CleanupInactiveOAuth2SessionIpsJob { - #[tracing::instrument(name = "job.cleanup_inactive_oauth2_session_ips", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Clear IPs from sessions inactive for 30+ days - let threshold = state.clock.now() - chrono::Duration::days(30); - let mut total = 0; - - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - let (count, last_active_at) = repo - .oauth2_session() - .cleanup_inactive_ips(since, threshold, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_active_at; - total += count; - - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no OAuth2 session IPs to clean up"); - } else { - info!(count = total, "cleaned up inactive OAuth2 session IPs"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupInactiveCompatSessionIpsJob { - #[tracing::instrument(name = "job.cleanup_inactive_compat_session_ips", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Clear IPs from sessions inactive for 30+ days - let threshold = state.clock.now() - chrono::Duration::days(30); - let mut total = 0; - - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - let (count, last_active_at) = repo - .compat_session() - .cleanup_inactive_ips(since, threshold, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_active_at; - total += count; - - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no compat session IPs to clean up"); - } else { - info!(count = total, "cleaned up inactive compat session IPs"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupInactiveUserSessionIpsJob { - #[tracing::instrument(name = "job.cleanup_inactive_user_session_ips", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Clear IPs from sessions inactive for 30+ days - let threshold = state.clock.now() - chrono::Duration::days(30); - let mut total = 0; - - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - let (count, last_active_at) = repo - .browser_session() - .cleanup_inactive_ips(since, threshold, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_active_at; - total += count; - - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no user session IPs to clean up"); - } else { - info!(count = total, "cleaned up inactive user session IPs"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - Some(Duration::from_secs(10 * 60)) - } -} diff --git a/crates/tasks/src/lib.rs b/crates/tasks/src/lib.rs index ca339f96f..33748ae8e 100644 --- a/crates/tasks/src/lib.rs +++ b/crates/tasks/src/lib.rs @@ -21,7 +21,7 @@ use tokio_util::{sync::CancellationToken, task::TaskTracker}; pub use crate::new_queue::QueueWorker; -mod database; +mod cleanup; mod email; mod matrix; mod new_queue; @@ -162,125 +162,128 @@ pub async fn init( .register_handler::() .register_handler::() .register_deprecated_queue("cleanup-expired-tokens") + // Recurring jobs are spread across the hour at ~5 minute intervals + // to avoid clustering and distribute database load evenly. .add_schedule( "cleanup-revoked-oauth-access-tokens", - // Run this job every hour + // Run this job every hour at minute 0 "0 0 * * * *".parse()?, mas_storage::queue::CleanupRevokedOAuthAccessTokensJob, ) .add_schedule( "cleanup-revoked-oauth-refresh-tokens", - // Run this job every hour - "0 10 * * * *".parse()?, + // Run this job every hour at minute 5 + "0 5 * * * *".parse()?, mas_storage::queue::CleanupRevokedOAuthRefreshTokensJob, ) .add_schedule( "cleanup-consumed-oauth-refresh-tokens", - // Run this job every hour - "0 20 * * * *".parse()?, + // Run this job every hour at minute 5 (safe to parallelize with revoked) + "0 5 * * * *".parse()?, mas_storage::queue::CleanupConsumedOAuthRefreshTokensJob, ) - .add_schedule( - "cleanup-user-registrations", - // Run this job every hour - "0 30 * * * *".parse()?, - mas_storage::queue::CleanupUserRegistrationsJob, - ) .add_schedule( "cleanup-finished-compat-sessions", - // Run this job every hour - "0 40 * * * *".parse()?, + // Run this job every hour at minute 10 + "0 10 * * * *".parse()?, mas_storage::queue::CleanupFinishedCompatSessionsJob, ) .add_schedule( "cleanup-finished-oauth2-sessions", - // Run this job every hour - "0 42 * * * *".parse()?, + // Run this job every hour at minute 15 + "0 15 * * * *".parse()?, mas_storage::queue::CleanupFinishedOAuth2SessionsJob, ) .add_schedule( "cleanup-finished-user-sessions", - // Run this job every hour - "0 44 * * * *".parse()?, + // Run this job every hour at minute 20 + "0 20 * * * *".parse()?, mas_storage::queue::CleanupFinishedUserSessionsJob, ) + .add_schedule( + "cleanup-inactive-oauth2-session-ips", + // Run this job every hour at minute 25 + "0 25 * * * *".parse()?, + mas_storage::queue::CleanupInactiveOAuth2SessionIpsJob, + ) + .add_schedule( + "cleanup-inactive-compat-session-ips", + // Run this job every hour at minute 25 + "0 25 * * * *".parse()?, + mas_storage::queue::CleanupInactiveCompatSessionIpsJob, + ) + .add_schedule( + "cleanup-inactive-user-session-ips", + // Run this job every hour at minute 25 + "0 25 * * * *".parse()?, + mas_storage::queue::CleanupInactiveUserSessionIpsJob, + ) .add_schedule( "cleanup-oauth-authorization-grants", - // Run this job every hour - "0 50 * * * *".parse()?, + // Run this job every hour at minute 30 + "0 30 * * * *".parse()?, mas_storage::queue::CleanupOAuthAuthorizationGrantsJob, ) .add_schedule( "cleanup-oauth-device-code-grants", - // Run this job every hour - "0 55 * * * *".parse()?, + // Run this job every hour at minute 35 + "0 35 * * * *".parse()?, mas_storage::queue::CleanupOAuthDeviceCodeGrantsJob, ) - .add_schedule( - "cleanup-user-recovery-sessions", - // Run this job every hour - "0 56 * * * *".parse()?, - mas_storage::queue::CleanupUserRecoverySessionsJob, - ) - .add_schedule( - "cleanup-user-email-authentications", - // Run this job every hour - "0 57 * * * *".parse()?, - mas_storage::queue::CleanupUserEmailAuthenticationsJob, - ) .add_schedule( "cleanup-upstream-oauth-sessions", - // Run this job every hour - "0 58 * * * *".parse()?, + // Run this job every hour at minute 40 (independent, safe to parallelize) + "0 40 * * * *".parse()?, mas_storage::queue::CleanupUpstreamOAuthSessionsJob, ) .add_schedule( "cleanup-upstream-oauth-links", - // Run this job every hour - "0 59 * * * *".parse()?, + // Run this job every hour at minute 40 + "0 40 * * * *".parse()?, mas_storage::queue::CleanupUpstreamOAuthLinksJob, ) + // User cleanup jobs (minutes 45, 50) + .add_schedule( + "cleanup-user-registrations", + // Run this job every hour at minute 45 + "0 45 * * * *".parse()?, + mas_storage::queue::CleanupUserRegistrationsJob, + ) + .add_schedule( + "cleanup-user-recovery-sessions", + // Run this job every hour at minute 50 + "0 50 * * * *".parse()?, + mas_storage::queue::CleanupUserRecoverySessionsJob, + ) + .add_schedule( + "cleanup-user-email-authentications", + // Run this job every hour at minute 50 + "0 50 * * * *".parse()?, + mas_storage::queue::CleanupUserEmailAuthenticationsJob, + ) .add_schedule( "cleanup-queue-jobs", - // Run this job every hour - "0 45 * * * *".parse()?, + // Run this job every hour at minute 55 + "0 55 * * * *".parse()?, mas_storage::queue::CleanupQueueJobsJob, ) .add_schedule( "cleanup-expired-oauth-access-tokens", - // Run this job every 4 hours + // Run this job every 4 hours at minute 5 "0 5 */4 * * *".parse()?, mas_storage::queue::CleanupExpiredOAuthAccessTokensJob, ) .add_schedule( "expire-inactive-sessions", - // Run this job every 15 minutes + // Run this job every 15 minutes at second 30 "30 */15 * * * *".parse()?, mas_storage::queue::ExpireInactiveSessionsJob, ) .add_schedule( "prune-stale-policy-data", - // Run once a day + // Run once a day at 2:00 AM "0 0 2 * * *".parse()?, mas_storage::queue::PruneStalePolicyDataJob, - ) - .add_schedule( - "cleanup-inactive-oauth2-session-ips", - // Run this job every hour - "0 46 * * * *".parse()?, - mas_storage::queue::CleanupInactiveOAuth2SessionIpsJob, - ) - .add_schedule( - "cleanup-inactive-compat-session-ips", - // Run this job every hour - "0 47 * * * *".parse()?, - mas_storage::queue::CleanupInactiveCompatSessionIpsJob, - ) - .add_schedule( - "cleanup-inactive-user-session-ips", - // Run this job every hour - "0 48 * * * *".parse()?, - mas_storage::queue::CleanupInactiveUserSessionIpsJob, ); Ok(worker) diff --git a/crates/tasks/src/matrix.rs b/crates/tasks/src/matrix.rs index 68905fe53..2f12ce6da 100644 --- a/crates/tasks/src/matrix.rs +++ b/crates/tasks/src/matrix.rs @@ -60,8 +60,12 @@ impl RunnableJob for ProvisionUserJob { .into_iter() .map(|email| email.email) .collect(); - let mut request = - ProvisionRequest::new(user.username.clone(), user.sub.clone()).set_emails(emails); + let mut request = ProvisionRequest::new( + user.username.clone(), + user.sub.clone(), + user.locked_at.is_some(), + ) + .set_emails(emails); if let Some(display_name) = self.display_name_to_set() { request = request.set_displayname(display_name.to_owned()); diff --git a/deny.toml b/deny.toml index 1671119ca..47b0bb9ca 100644 --- a/deny.toml +++ b/deny.toml @@ -1,3 +1,4 @@ +# Copyright 2025, 2026 Element Creations Ltd. # Copyright 2025 New Vector Ltd. # # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial @@ -33,19 +34,11 @@ allow = [ "ISC", "MIT", "MPL-2.0", - "OpenSSL", "Unicode-3.0", "Zlib", "CDLA-Permissive-2.0", ] -# Ring's license is a bit complicated, so we need to specify it manually -[[licenses.clarify]] -name = "ring" -version = "*" -expression = "MIT AND ISC AND OpenSSL" -license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] - [bans] # List of crates to deny deny = [ @@ -71,6 +64,8 @@ skip = [ # cron depends on this old version # https://github.com/zslayton/cron/pull/137 { name = "winnow", version = "0.6.20" }, + # hyper-util -> system-configuration depends on this old version + { name = "core-foundation", version = "0.9.4" }, # We are still mainly using rand 0.8 { name = "rand", version = "0.8.5" }, diff --git a/docs/development/cleanup-jobs.md b/docs/development/cleanup-jobs.md index 639d0e7a3..f64cb7f21 100644 --- a/docs/development/cleanup-jobs.md +++ b/docs/development/cleanup-jobs.md @@ -11,9 +11,23 @@ Cleanup jobs are scheduled tasks that hard-delete old data from the database. Th 1. **Job struct** in `crates/storage/src/queue/tasks.rs` - Defines the job and queue name 2. **Storage trait** in `crates/storage/src/{domain}/` - Declares the cleanup method interface 3. **PostgreSQL implementation** in `crates/storage-pg/src/{domain}/` - Implements the actual cleanup logic -4. **Job runner** in `crates/tasks/src/database.rs` - Implements the `RunnableJob` trait with batching logic +4. **Job runner** in `crates/tasks/src/cleanup/` - Implements the `RunnableJob` trait with batching logic 5. **Registration** in `crates/tasks/src/lib.rs` - Registers the handler and schedules execution +### Module Structure + +The cleanup job implementations are organized into submodules by domain: + +``` +crates/tasks/src/cleanup/ +├── mod.rs # Re-exports, shared BATCH_SIZE constant +├── tokens.rs # OAuth token cleanup (access and refresh tokens) +├── sessions.rs # Session cleanup (compat, OAuth2, user sessions and their IPs) +├── oauth.rs # OAuth grants and upstream OAuth cleanup +├── user.rs # User-related cleanup (registrations, recovery, email auth) +└── misc.rs # Queue jobs, policy data cleanup +``` + ## All Cleanup Jobs | Job | Entity | Retention | Notes | @@ -183,7 +197,7 @@ The partial index (`WHERE timestamp_col IS NOT NULL`) makes queries more efficie ### 5. Implement RunnableJob -In `crates/tasks/src/database.rs`: +In the appropriate submodule under `crates/tasks/src/cleanup/` (e.g., `tokens.rs`, `sessions.rs`, `oauth.rs`, `user.rs`, or `misc.rs`): ```rust #[async_trait] diff --git a/docs/setup/sso.md b/docs/setup/sso.md index 15d0212d7..b461315fc 100644 --- a/docs/setup/sso.md +++ b/docs/setup/sso.md @@ -408,6 +408,38 @@ upstream_oauth2: ``` +### Kanidm + + +Install and configure a Kanidm instance using the [Official Docs](https://kanidm.github.io/kanidm/stable/introduction_to_kanidm.html) + +Create a OAuth2 Client following the [OAuth2 Setup](https://kanidm.github.io/kanidm/stable/integrations/oauth2.html) guide + +> ⚠️ **Important** +> Ensure you configure the client to prefer short usernames. This means instead of `user@domain.tld` it will return just `user` as the preferred username +> You can configure this by running `kanidm system oauth2 prefer-short-username `. + +```yaml +upstream_oauth2: + providers: + - id: "[ulid]" # randomly generated ulid (https://www.ulidtools.com/) + id_token_signed_response_alg: ES256 # This is important since Kanidm doesn't support RS256 by default + issuer: "https:///oauth2/openid/" # TO BE FILLED + token_endpoint_auth_method: client_secret_basic + client_id: "" # TO BE FILLED + client_secret: "" # TO BE FILLED + scope: "openid profile email" # Add any additional scopes + claims_imports: + localpart: + action: require + template: "{{ user.preferred_username }}" + displayname: + action: suggest + template: "{{ user.name }}" + email: + action: suggest + template: "{{ user.email }}" +``` ### Keycloak diff --git a/frontend/.storybook/locales.ts b/frontend/.storybook/locales.ts index 9b0e3684d..ffcc627ed 100644 --- a/frontend/.storybook/locales.ts +++ b/frontend/.storybook/locales.ts @@ -27,7 +27,7 @@ export type LocalazyMetadata = { }; const localazyMetadata: LocalazyMetadata = { - projectUrl: "https://localazy.com/p/matrix-authentication-service!v1.10", + projectUrl: "https://localazy.com/p/matrix-authentication-service", baseLocale: "en", languages: [ { @@ -138,6 +138,15 @@ const localazyMetadata: LocalazyMetadata = { localizedName: "Português", pluralType: (n) => { return (n>=0 && n<=1) ? "one" : "other"; } }, + { + language: "pt", + region: "BR", + script: "", + isRtl: false, + name: "Brazilian Portuguese", + localizedName: "Português (Brasil)", + pluralType: (n) => { return (n>=0 && n<=1) ? "one" : "other"; } + }, { language: "ru", region: "", @@ -147,6 +156,15 @@ const localazyMetadata: LocalazyMetadata = { localizedName: "Русский", pluralType: (n) => { return ((n%10===1) && (n%100!==11)) ? "one" : ((n%10>=2 && n%10<=4) && ((n%100<12 || n%100>14))) ? "few" : "many"; } }, + { + language: "sk", + region: "", + script: "", + isRtl: false, + name: "Slovak", + localizedName: "Slovenčina", + pluralType: (n) => { return (n===1) ? "one" : (n>=2 && n<=4) ? "few" : "other"; } + }, { language: "sv", region: "", @@ -165,6 +183,15 @@ const localazyMetadata: LocalazyMetadata = { localizedName: "Українська", pluralType: (n) => { return ((n%10===1) && (n%100!==11)) ? "one" : ((n%10>=2 && n%10<=4) && ((n%100<12 || n%100>14))) ? "few" : "many"; } }, + { + language: "uz", + region: "", + script: "", + isRtl: false, + name: "Uzbek", + localizedName: "O‘zbek", + pluralType: (n) => { return (n===1) ? "one" : "other"; } + }, { language: "zh", region: "", @@ -181,22 +208,25 @@ const localazyMetadata: LocalazyMetadata = { file: "frontend.json", path: "", cdnFiles: { - "cs": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/cs/frontend.json", - "da": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/da/frontend.json", - "de": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/de/frontend.json", - "en": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/en/frontend.json", - "et": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/et/frontend.json", - "fi": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fi/frontend.json", - "fr": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fr/frontend.json", - "hu": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/hu/frontend.json", - "nb_NO": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nb-NO/frontend.json", - "nl": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nl/frontend.json", - "pl": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pl/frontend.json", - "pt": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt/frontend.json", - "ru": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json", - "sv": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json", - "uk": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json", - "zh#Hans": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json" + "cs": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/cs/frontend.json", + "da": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/da/frontend.json", + "de": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/de/frontend.json", + "en": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/en/frontend.json", + "et": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/et/frontend.json", + "fi": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fi/frontend.json", + "fr": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fr/frontend.json", + "hu": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/hu/frontend.json", + "nb_NO": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nb-NO/frontend.json", + "nl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nl/frontend.json", + "pl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pl/frontend.json", + "pt": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt/frontend.json", + "pt_BR": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt-BR/frontend.json", + "ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json", + "sk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sk/frontend.json", + "sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json", + "uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json", + "uz": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uz/frontend.json", + "zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json" } }, { @@ -204,22 +234,25 @@ const localazyMetadata: LocalazyMetadata = { file: "file.json", path: "", cdnFiles: { - "cs": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/cs/file.json", - "da": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/da/file.json", - "de": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/de/file.json", - "en": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/en/file.json", - "et": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/et/file.json", - "fi": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fi/file.json", - "fr": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fr/file.json", - "hu": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/hu/file.json", - "nb_NO": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nb-NO/file.json", - "nl": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nl/file.json", - "pl": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pl/file.json", - "pt": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt/file.json", - "ru": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json", - "sv": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json", - "uk": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json", - "zh#Hans": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json" + "cs": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/cs/file.json", + "da": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/da/file.json", + "de": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/de/file.json", + "en": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/en/file.json", + "et": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/et/file.json", + "fi": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fi/file.json", + "fr": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fr/file.json", + "hu": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/hu/file.json", + "nb_NO": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nb-NO/file.json", + "nl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nl/file.json", + "pl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pl/file.json", + "pt": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt/file.json", + "pt_BR": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt-BR/file.json", + "ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json", + "sk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sk/file.json", + "sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json", + "uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json", + "uz": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uz/file.json", + "zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json" } } ] diff --git a/frontend/.storybook/public/mockServiceWorker.js b/frontend/.storybook/public/mockServiceWorker.js index 558540fa5..258b1b1e1 100644 --- a/frontend/.storybook/public/mockServiceWorker.js +++ b/frontend/.storybook/public/mockServiceWorker.js @@ -7,7 +7,7 @@ * - Please do NOT modify this file. */ -const PACKAGE_VERSION = '2.12.4' +const PACKAGE_VERSION = '2.12.8' const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() diff --git a/frontend/locales/nl.json b/frontend/locales/nl.json index 15b643f05..33645eb71 100644 --- a/frontend/locales/nl.json +++ b/frontend/locales/nl.json @@ -257,8 +257,8 @@ "button": "Identiteit resetten", "cancelled": { "description_1": "You can close this window and go back to the app to continue.", - "description_2": "If you're signed out everywhere and don't remember your recovery code, you'll still need to reset your identity.", - "heading": "Identity reset cancelled." + "description_2": "If don’t have access to any other verified devices and you don’t have your recovery key, then you’ll need to reset your digital identity to continue using the app.", + "heading": "Digital identity reset cancelled." }, "description": "Als u niet bent aangemeld bij andere apparaten en u bent uw herstelsleutel kwijt, moet u uw identiteit opnieuw instellen om de app te kunnen blijven gebruiken.", "effect_list": { @@ -270,7 +270,7 @@ }, "failure": { "description": "This might be a temporary problem, so please try again later. If the problem persists, please contact your server administrator.", - "heading": "Failed to allow crypto identity reset", + "heading": "Failed to allow digital identity reset", "title": "Kan crypto identiteit niet toestaan" }, "finish_reset": "Finish reset", @@ -278,10 +278,10 @@ "start_reset": "Start reset", "success": { "description": "De identiteits reset is goedgekeurd voor de volgende {{minutes}} minuten. U kunt dit venster sluiten en teruggaan naar de app om door te gaan.", - "heading": "Identity reset successfully. Go back to the app to finish the process.", + "heading": "Digital identity reset successfully. Go back to the app to finish the process.", "title": "Het opnieuw instellen van de crypto identiteit is tijdelijk toegestaan" }, - "warning": "Only reset your identity if you don't have access to another signed-in device and you've lost your recovery key." + "warning": "Only reset your digital identity if you don't have access to another verified device and you don't have your recovery key." }, "selectable_session": { "label": "Sessie selecteren" diff --git a/frontend/locales/pt-BR.json b/frontend/locales/pt-BR.json new file mode 100644 index 000000000..bee110cae --- /dev/null +++ b/frontend/locales/pt-BR.json @@ -0,0 +1,401 @@ +{ + "action": { + "back": "Voltar", + "cancel": "Cancelar", + "clear": "Clear", + "close": "Close", + "collapse": "Collapse", + "confirm": "Confirm", + "continue": "Continuar", + "edit": "Edit", + "expand": "Expand", + "save": "Salvar", + "save_and_continue": "Save and continue", + "sign_out": "Sair", + "start_over": "Start over" + }, + "branding": { + "privacy_policy": { + "alt": "Link to the service privacy policy", + "link": "Privacy Policy" + }, + "terms_and_conditions": { + "alt": "Link to the service terms and conditions", + "link": "Terms & Conditions" + } + }, + "common": { + "add": "Adicionar", + "e2ee": "End-to-end encryption", + "error": "Erro", + "loading": "Carregando…", + "next": "Próximo", + "password": "Senha", + "previous": "Anterior", + "saved": "Saved", + "saving": "Saving…" + }, + "frontend": { + "account": { + "account_password": "Account password", + "contact_info": "Contact info", + "delete_account": { + "alert_description": "This account will be permanently erased and you’ll no longer have access to any of your messages.", + "alert_title": "You’re about to lose all of your data", + "button": "Delete account", + "dialog_description": "Confirm that you would like to delete your account:\n\n\nYou will not be able to reactivate your account\nYou will no longer be able to sign in\nNo one will be able to reuse your username (MXID), including you\nYou will leave all rooms and direct messages you are in\nYou will be removed from the identity server, and no one will be able to find you with your email or phone number\n\nYour old messages will still be visible to people who received them. Would you like to hide your sent messages from people who join rooms in the future?", + "dialog_title": "Delete this account?", + "erase_checkbox_label": "Yes, hide all my messages from new joiners", + "incorrect_password": "Incorrect password, please try again", + "mxid_label": "Confirm your Matrix ID ({{ mxid }})", + "mxid_mismatch": "This value does not match your Matrix ID", + "password_label": "Enter your password to continue" + }, + "edit_profile": { + "display_name_help": "This is what others will see wherever you’re signed in.", + "display_name_label": "Display name", + "title": "Edit profile", + "username_label": "Nome de usuário" + }, + "password": { + "change": "Alterar a senha", + "change_disabled": "Password changes are disabled by the administrator.", + "label": "Senha" + }, + "sign_out": { + "button": "Sign out of account", + "dialog": "Sign out of this account?" + }, + "title": "Your account" + }, + "add_email_form": { + "email_denied_alert": { + "text": "O endereço de e-mail inserido não é permitido pela política do servidor.", + "title": "Email denied by policy" + }, + "email_denied_error": "The entered email is not allowed by the server policy", + "email_exists_alert": { + "text": "O endereço de e-mail inserido já foi adicionado a esta conta.", + "title": "Email already exists" + }, + "email_exists_error": "O endereço de e-mail inserido já foi adicionado a esta conta.", + "email_field_help": "Add an alternative email you can use to access this account.", + "email_field_label": "Adicionar e-mail", + "email_in_use_error": "The entered email is already in use", + "email_invalid_alert": { + "text": "O endereço de e-mail inserido é inválido.", + "title": "E-mail inválido" + }, + "email_invalid_error": "O endereço de e-mail inserido é inválido.", + "incorrect_password_error": "Incorrect password, please try again", + "password_confirmation": "Confirm your account password to add this email address" + }, + "app_sessions_list": { + "error": "Failed to load app sessions", + "heading": "Apps" + }, + "browser_session_details": { + "current_badge": "Current", + "session_details_title": "Session" + }, + "browser_sessions_overview": { + "body:one": "{{count}} active session", + "body:other": "{{count}} active sessions", + "heading": "Browsers", + "no_active_sessions": { + "default": "You are not signed in to any web browsers.", + "inactive_90_days": "All your sessions have been active in the last 90 days." + }, + "view_all_button": "View all" + }, + "compat_session_detail": { + "client_details_title": "Client info", + "name": "Nome", + "session_details_title": "Session" + }, + "device_type_icon_label": { + "desktop": "Desktop", + "mobile": "Mobile", + "pc": "Computer", + "tablet": "Tablet", + "unknown": "Unknown device type", + "web": "Web" + }, + "email_in_use": { + "heading": "The email address {{email}} is already in use." + }, + "end_session_button": { + "confirmation_modal_title": "Are you sure you want to end this session?", + "text": "Remove device" + }, + "error": { + "hideDetails": "Hide details", + "showDetails": "Show details", + "subtitle": "An unexpected error occurred. Please try again.", + "title": "Something went wrong" + }, + "error_boundary_title": "Something went wrong", + "errors": { + "field_required": "Este campo é obrigatório", + "rate_limit_exceeded": "You've made too many requests in a short period. Please wait a few minutes and try again." + }, + "last_active": { + "active_date": "Active {{relativeDate}}", + "active_now": "Active now", + "inactive_90_days": "Inactive for 90+ days" + }, + "nav": { + "devices": "Devices", + "plan": "Plan", + "profile": "Perfil", + "sessions": "Sessões", + "settings": "Settings" + }, + "not_found_alert_title": "Not found.", + "not_logged_in_alert": "You're not logged in.", + "oauth2_client_detail": { + "details_title": "Client info", + "id": "Client ID", + "name": "Nome", + "policy": "Policy", + "terms": "Terms of service" + }, + "oauth2_session_detail": { + "client_details_name": "Nome", + "client_title": "Client info", + "session_details_title": "Session" + }, + "pagination_controls": { + "total": "Total: {{totalCount}}" + }, + "password_change": { + "current_password_label": "Senha atual", + "failure": { + "description": { + "account_locked": "Your account is locked and can not be recovered at this time. If this is not expected, please contact your server administrator.", + "expired_recovery_ticket": "The recovery link has expired. Please start the account recovery process again from the start.", + "invalid_new_password": "The new password you chose is invalid; it may not meet the configured security policy.", + "no_current_password": "You don't have a current password.", + "no_such_recovery_ticket": "The recovery link is invalid. If you copied the link from the recovery e-mail, please check the full link was copied.", + "password_changes_disabled": "Password changes are disabled.", + "recovery_ticket_already_used": "The recovery link has already been used. It cannot be used again.", + "unspecified": "This might be a temporary problem, so please try again later. If the problem persists, please contact your server administrator.", + "wrong_password": "The password you supplied as your current password is incorrect. Please try again." + }, + "title": "Failed to update password" + }, + "new_password_again_label": "Enter new password again", + "new_password_label": "Nova senha", + "passwords_match": "Passwords match!", + "passwords_no_match": "Passwords don't match", + "subtitle": "Choose a new password for your account.", + "success": { + "description": "Your password has been updated successfully.", + "title": "Password updated" + }, + "title": "Change your password" + }, + "password_reset": { + "consumed": { + "subtitle": "To create a new password, start over and select ”Forgot password“.", + "title": "The link to reset your password has already been used" + }, + "expired": { + "resend_email": "Resend email", + "subtitle": "Request a new email that will be sent to: {{email}}", + "title": "The link to reset your password has expired" + }, + "subtitle": "Choose a new password for your account.", + "title": "Reset your password" + }, + "password_strength": { + "placeholder": "Password strength", + "score": { + "0": "Extremely weak password", + "1": "Very weak password", + "2": "Weak password", + "3": "Strong password", + "4": "Very strong password" + }, + "suggestion": { + "all_uppercase": "Capitalise some, but not all letters.", + "another_word": "Add more words that are less common.", + "associated_years": "Avoid years that are associated with you.", + "capitalization": "Capitalise more than the first letter.", + "dates": "Avoid dates and years that are associated with you.", + "l33t": "Avoid predictable letter substitutions like '@' for 'a'.", + "longer_keyboard_pattern": "Use longer keyboard patterns and change typing direction multiple times.", + "no_need": "You can create strong passwords without using symbols, numbers, or uppercase letters.", + "pwned": "If you use this password elsewhere, you should change it.", + "recent_years": "Avoid recent years.", + "repeated": "Avoid repeated words and characters.", + "reverse_words": "Avoid reversed spellings of common words.", + "sequences": "Avoid common character sequences.", + "use_words": "Use multiple words, but avoid common phrases." + }, + "too_weak": "This password is too weak", + "warning": { + "common": "This is a commonly used password.", + "common_names": "Common names and surnames are easy to guess.", + "dates": "Dates are easy to guess.", + "extended_repeat": "Repeated character patterns like \"abcabcabc\" are easy to guess.", + "key_pattern": "Short keyboard patterns are easy to guess.", + "names_by_themselves": "Single names or surnames are easy to guess.", + "pwned": "Your password was exposed by a data breach on the Internet.", + "recent_years": "Recent years are easy to guess.", + "sequences": "Common character sequences like \"abc\" are easy to guess.", + "similar_to_common": "This is similar to a commonly used password.", + "simple_repeat": "Repeated characters like \"aaa\" are easy to guess.", + "straight_row": "Straight rows of keys on your keyboard are easy to guess.", + "top_hundred": "This is a frequently used password.", + "top_ten": "This is a heavily used password.", + "user_inputs": "There should not be any personal or page related data.", + "word_by_itself": "Single words are easy to guess." + } + }, + "reset_cross_signing": { + "button": "Reset identity", + "cancelled": { + "description_1": "You can close this window and go back to the app to continue.", + "description_2": "If don’t have access to any other verified devices and you don’t have your recovery key, then you’ll need to reset your digital identity to continue using the app.", + "heading": "Digital identity reset cancelled." + }, + "description": "If don’t have access to any other verified devices and you don’t have your recovery key, then you’ll need to reset your digital identity to continue using the app.", + "effect_list": { + "negative_1": "You will lose your existing message history", + "negative_2": "You will need to verify all your existing devices and contacts again", + "neutral_1": "You will lose any message history that's stored only on the server", + "neutral_2": "You will need to verify all your existing devices and contacts again", + "positive_1": "Your account details, contacts, preferences, and chat list will be kept" + }, + "failure": { + "description": "This might be a temporary problem, so please try again later. If the problem persists, please contact your server administrator.", + "heading": "Failed to allow digital identity reset", + "title": "Failed to allow crypto identity" + }, + "finish_reset": "Finish reset", + "heading": "Reset your digital identity in case you can't confirm another way", + "start_reset": "Start reset", + "success": { + "description": "The digital identity reset has been approved for the next {{minutes}} minutes. You can close this window and go back to the app to continue.", + "heading": "Digital identity reset successfully. Go back to the app to finish the process.", + "title": "Crypto identity reset temporarily allowed" + }, + "warning": "Only reset your digital identity if you don't have access to another verified device and you don't have your recovery key." + }, + "selectable_session": { + "label": "Select session" + }, + "session": { + "client_id_label": "Client ID", + "current": "Current", + "current_badge": "Current", + "device_id_label": "Device ID", + "finished_date": "Finished ", + "finished_label": "Finished", + "generic_browser_session": "Browser session", + "id_label": "ID", + "ip_label": "IP Address", + "last_active_label": "Last Active", + "last_auth_label": "Last Authentication", + "name_for_platform": "{{name}} for {{platform}}", + "scopes_label": "Scopes", + "set_device_name": { + "help": "Set a name that will help you identify this device.", + "label": "Device name", + "title": "Edit device name" + }, + "signed_in_date": "Signed in ", + "signed_in_label": "Signed in", + "title": "Device details", + "unknown_browser": "Unknown browser", + "unknown_device": "Unknown device", + "uri_label": "Uri", + "user_id_label": "User ID", + "username_label": "User name" + }, + "session_detail": { + "alert": { + "button": "Voltar", + "text": "This session does not exist, or is no longer active.", + "title": "Cannot find session: {{deviceId}}" + } + }, + "unknown_route": "Unknown route {{route}}", + "unverified_email_alert": { + "button": "Review and verify", + "text:one": "You have {{count}} unverified email address.", + "text:other": "You have {{count}} unverified email addresses.", + "title": "Unverified email" + }, + "user_email": { + "cant_delete_primary": "Choose a different primary email to delete this one.", + "delete_button_confirmation_modal": { + "action": "Delete email", + "body": "Delete this email?", + "incorrect_password": "Incorrect password, please try again", + "password_confirmation": "Confirm your account password to delete this email address" + }, + "delete_button_title": "Remove email address", + "email": "E-mail", + "make_primary_button": "Make primary", + "not_verified": "Not verified", + "primary_email": "Primary email", + "retry_button": "Resend code", + "unverified": "Não verificado" + }, + "user_email_list": { + "heading": "Emails", + "no_primary_email_alert": "No primary email address" + }, + "user_greeting": { + "error": "Failed to load user" + }, + "user_name": { + "display_name_field_label": "Nome e sobrenome" + }, + "user_sessions_overview": { + "active_sessions:one": "{{count}} active session", + "active_sessions:other": "{{count}} active sessions", + "heading": "Where you're signed in", + "no_active_sessions": { + "default": "You are not signed in to any application.", + "inactive_90_days": "All your sessions have been active in the last 90 days." + } + }, + "verify_email": { + "code_expired_alert": { + "description": "The code has expired. Please request a new code.", + "title": "Code expired" + }, + "code_field_error": "Code not recognised", + "code_field_label": "6-digit code", + "code_field_wrong_shape": "Code must be 6 digits", + "email_sent_alert": { + "description": "Enter the new code below.", + "title": "New code sent" + }, + "enter_code_prompt": "Enter the 6-digit code sent to: {{email}}", + "heading": "Verifique seu e-mail", + "invalid_code_alert": { + "description": "Check the code sent to your email and update the fields below to continue.", + "title": "You entered the wrong code" + }, + "resend_code": "Resend code", + "resend_email": "Resend email", + "sent": "Sent!", + "unknown_email": "Unknown email" + } + }, + "mas": { + "scope": { + "edit_profile": "Edite seu perfil e seus dados de contato.", + "manage_sessions": "Gerencie seus dispositivos e sessões", + "mas_admin": "Manage users (urn:mas:admin)", + "send_messages": "Send new messages on your behalf", + "synapse_admin": "Administer the server (urn:synapse:admin:*)", + "view_messages": "View your existing messages and data", + "view_profile": "Veja as informações do seu perfil e os seus dados de contato." + } + } +} \ No newline at end of file diff --git a/frontend/locales/sk.json b/frontend/locales/sk.json new file mode 100644 index 000000000..a43a7cf78 --- /dev/null +++ b/frontend/locales/sk.json @@ -0,0 +1,401 @@ +{ + "action": { + "back": "Naspäť", + "cancel": "Zrušiť", + "clear": "Clear", + "close": "Close", + "collapse": "Collapse", + "confirm": "Confirm", + "continue": "Pokračovať", + "edit": "Edit", + "expand": "Expand", + "save": "Uložiť", + "save_and_continue": "Save and continue", + "sign_out": "Odhlásiť sa", + "start_over": "Start over" + }, + "branding": { + "privacy_policy": { + "alt": "Link to the service privacy policy", + "link": "Privacy Policy" + }, + "terms_and_conditions": { + "alt": "Link to the service terms and conditions", + "link": "Terms & Conditions" + } + }, + "common": { + "add": "Pridať", + "e2ee": "End-to-end encryption", + "error": "Chyba", + "loading": "Načítavanie…", + "next": "Ďalej", + "password": "Heslo", + "previous": "Previous", + "saved": "Saved", + "saving": "Saving…" + }, + "frontend": { + "account": { + "account_password": "Account password", + "contact_info": "Contact info", + "delete_account": { + "alert_description": "This account will be permanently erased and you’ll no longer have access to any of your messages.", + "alert_title": "You’re about to lose all of your data", + "button": "Delete account", + "dialog_description": "Confirm that you would like to delete your account:\n\n\nYou will not be able to reactivate your account\nYou will no longer be able to sign in\nNo one will be able to reuse your username (MXID), including you\nYou will leave all rooms and direct messages you are in\nYou will be removed from the identity server, and no one will be able to find you with your email or phone number\n\nYour old messages will still be visible to people who received them. Would you like to hide your sent messages from people who join rooms in the future?", + "dialog_title": "Delete this account?", + "erase_checkbox_label": "Yes, hide all my messages from new joiners", + "incorrect_password": "Incorrect password, please try again", + "mxid_label": "Confirm your Matrix ID ({{ mxid }})", + "mxid_mismatch": "This value does not match your Matrix ID", + "password_label": "Enter your password to continue" + }, + "edit_profile": { + "display_name_help": "This is what others will see wherever you’re signed in.", + "display_name_label": "Display name", + "title": "Edit profile", + "username_label": "Meno používateľa" + }, + "password": { + "change": "Zmeniť heslo", + "change_disabled": "Password changes are disabled by the administrator.", + "label": "Heslo" + }, + "sign_out": { + "button": "Sign out of account", + "dialog": "Sign out of this account?" + }, + "title": "Your account" + }, + "add_email_form": { + "email_denied_alert": { + "text": "The entered email is not allowed by the server policy.", + "title": "Email denied by policy" + }, + "email_denied_error": "The entered email is not allowed by the server policy", + "email_exists_alert": { + "text": "The entered email is already added to this account", + "title": "Email already exists" + }, + "email_exists_error": "The entered email is already added to this account", + "email_field_help": "Add an alternative email you can use to access this account.", + "email_field_label": "Add email", + "email_in_use_error": "The entered email is already in use", + "email_invalid_alert": { + "text": "The entered email is invalid", + "title": "Invalid email" + }, + "email_invalid_error": "The entered email is invalid", + "incorrect_password_error": "Incorrect password, please try again", + "password_confirmation": "Confirm your account password to add this email address" + }, + "app_sessions_list": { + "error": "Failed to load app sessions", + "heading": "Apps" + }, + "browser_session_details": { + "current_badge": "Current", + "session_details_title": "Session" + }, + "browser_sessions_overview": { + "body:one": "{{count}} active session", + "body:other": "{{count}} active sessions", + "heading": "Browsers", + "no_active_sessions": { + "default": "You are not signed in to any web browsers.", + "inactive_90_days": "All your sessions have been active in the last 90 days." + }, + "view_all_button": "Zobraziť všetky" + }, + "compat_session_detail": { + "client_details_title": "Client info", + "name": "Názov", + "session_details_title": "Session" + }, + "device_type_icon_label": { + "desktop": "Desktop", + "mobile": "Mobile", + "pc": "Computer", + "tablet": "Tablet", + "unknown": "Unknown device type", + "web": "Web" + }, + "email_in_use": { + "heading": "The email address {{email}} is already in use." + }, + "end_session_button": { + "confirmation_modal_title": "Are you sure you want to end this session?", + "text": "Remove device" + }, + "error": { + "hideDetails": "Skryť podrobnosti", + "showDetails": "Zobraziť podrobnosti", + "subtitle": "An unexpected error occurred. Please try again.", + "title": "Something went wrong" + }, + "error_boundary_title": "Something went wrong", + "errors": { + "field_required": "Toto pole je povinné", + "rate_limit_exceeded": "You've made too many requests in a short period. Please wait a few minutes and try again." + }, + "last_active": { + "active_date": "Active {{relativeDate}}", + "active_now": "Active now", + "inactive_90_days": "Inactive for 90+ days" + }, + "nav": { + "devices": "Devices", + "plan": "Plan", + "profile": "Profil", + "sessions": "Relácie", + "settings": "Settings" + }, + "not_found_alert_title": "Not found.", + "not_logged_in_alert": "You're not logged in.", + "oauth2_client_detail": { + "details_title": "Client info", + "id": "Client ID", + "name": "Názov", + "policy": "Policy", + "terms": "Terms of service" + }, + "oauth2_session_detail": { + "client_details_name": "Názov", + "client_title": "Client info", + "session_details_title": "Session" + }, + "pagination_controls": { + "total": "Total: {{totalCount}}" + }, + "password_change": { + "current_password_label": "Súčasné heslo", + "failure": { + "description": { + "account_locked": "Your account is locked and can not be recovered at this time. If this is not expected, please contact your server administrator.", + "expired_recovery_ticket": "The recovery link has expired. Please start the account recovery process again from the start.", + "invalid_new_password": "The new password you chose is invalid; it may not meet the configured security policy.", + "no_current_password": "You don't have a current password.", + "no_such_recovery_ticket": "The recovery link is invalid. If you copied the link from the recovery e-mail, please check the full link was copied.", + "password_changes_disabled": "Password changes are disabled.", + "recovery_ticket_already_used": "The recovery link has already been used. It cannot be used again.", + "unspecified": "This might be a temporary problem, so please try again later. If the problem persists, please contact your server administrator.", + "wrong_password": "The password you supplied as your current password is incorrect. Please try again." + }, + "title": "Failed to update password" + }, + "new_password_again_label": "Enter new password again", + "new_password_label": "Nové heslo", + "passwords_match": "Passwords match!", + "passwords_no_match": "Passwords don't match", + "subtitle": "Choose a new password for your account.", + "success": { + "description": "Your password has been updated successfully.", + "title": "Password updated" + }, + "title": "Change your password" + }, + "password_reset": { + "consumed": { + "subtitle": "To create a new password, start over and select ”Forgot password“.", + "title": "The link to reset your password has already been used" + }, + "expired": { + "resend_email": "Resend email", + "subtitle": "Request a new email that will be sent to: {{email}}", + "title": "The link to reset your password has expired" + }, + "subtitle": "Choose a new password for your account.", + "title": "Reset your password" + }, + "password_strength": { + "placeholder": "Password strength", + "score": { + "0": "Extremely weak password", + "1": "Very weak password", + "2": "Weak password", + "3": "Strong password", + "4": "Very strong password" + }, + "suggestion": { + "all_uppercase": "Capitalise some, but not all letters.", + "another_word": "Add more words that are less common.", + "associated_years": "Avoid years that are associated with you.", + "capitalization": "Capitalise more than the first letter.", + "dates": "Avoid dates and years that are associated with you.", + "l33t": "Avoid predictable letter substitutions like '@' for 'a'.", + "longer_keyboard_pattern": "Use longer keyboard patterns and change typing direction multiple times.", + "no_need": "You can create strong passwords without using symbols, numbers, or uppercase letters.", + "pwned": "If you use this password elsewhere, you should change it.", + "recent_years": "Avoid recent years.", + "repeated": "Avoid repeated words and characters.", + "reverse_words": "Avoid reversed spellings of common words.", + "sequences": "Avoid common character sequences.", + "use_words": "Use multiple words, but avoid common phrases." + }, + "too_weak": "This password is too weak", + "warning": { + "common": "This is a commonly used password.", + "common_names": "Common names and surnames are easy to guess.", + "dates": "Dates are easy to guess.", + "extended_repeat": "Repeated character patterns like \"abcabcabc\" are easy to guess.", + "key_pattern": "Short keyboard patterns are easy to guess.", + "names_by_themselves": "Single names or surnames are easy to guess.", + "pwned": "Your password was exposed by a data breach on the Internet.", + "recent_years": "Recent years are easy to guess.", + "sequences": "Common character sequences like \"abc\" are easy to guess.", + "similar_to_common": "This is similar to a commonly used password.", + "simple_repeat": "Repeated characters like \"aaa\" are easy to guess.", + "straight_row": "Straight rows of keys on your keyboard are easy to guess.", + "top_hundred": "This is a frequently used password.", + "top_ten": "This is a heavily used password.", + "user_inputs": "There should not be any personal or page related data.", + "word_by_itself": "Single words are easy to guess." + } + }, + "reset_cross_signing": { + "button": "Reset identity", + "cancelled": { + "description_1": "You can close this window and go back to the app to continue.", + "description_2": "If don’t have access to any other verified devices and you don’t have your recovery key, then you’ll need to reset your digital identity to continue using the app.", + "heading": "Digital identity reset cancelled." + }, + "description": "If don’t have access to any other verified devices and you don’t have your recovery key, then you’ll need to reset your digital identity to continue using the app.", + "effect_list": { + "negative_1": "You will lose your existing message history", + "negative_2": "You will need to verify all your existing devices and contacts again", + "neutral_1": "You will lose any message history that's stored only on the server", + "neutral_2": "You will need to verify all your existing devices and contacts again", + "positive_1": "Your account details, contacts, preferences, and chat list will be kept" + }, + "failure": { + "description": "This might be a temporary problem, so please try again later. If the problem persists, please contact your server administrator.", + "heading": "Failed to allow digital identity reset", + "title": "Failed to allow crypto identity" + }, + "finish_reset": "Finish reset", + "heading": "Reset your digital identity in case you can't confirm another way", + "start_reset": "Start reset", + "success": { + "description": "The digital identity reset has been approved for the next {{minutes}} minutes. You can close this window and go back to the app to continue.", + "heading": "Digital identity reset successfully. Go back to the app to finish the process.", + "title": "Crypto identity reset temporarily allowed" + }, + "warning": "Only reset your digital identity if you don't have access to another verified device and you don't have your recovery key." + }, + "selectable_session": { + "label": "Select session" + }, + "session": { + "client_id_label": "Client ID", + "current": "Current", + "current_badge": "Current", + "device_id_label": "Device ID", + "finished_date": "Finished ", + "finished_label": "Finished", + "generic_browser_session": "Browser session", + "id_label": "ID", + "ip_label": "IP Address", + "last_active_label": "Last Active", + "last_auth_label": "Last Authentication", + "name_for_platform": "{{name}} for {{platform}}", + "scopes_label": "Scopes", + "set_device_name": { + "help": "Set a name that will help you identify this device.", + "label": "Device name", + "title": "Edit device name" + }, + "signed_in_date": "Signed in ", + "signed_in_label": "Signed in", + "title": "Device details", + "unknown_browser": "Unknown browser", + "unknown_device": "Unknown device", + "uri_label": "Uri", + "user_id_label": "User ID", + "username_label": "User name" + }, + "session_detail": { + "alert": { + "button": "Naspäť", + "text": "This session does not exist, or is no longer active.", + "title": "Cannot find session: {{deviceId}}" + } + }, + "unknown_route": "Unknown route {{route}}", + "unverified_email_alert": { + "button": "Review and verify", + "text:one": "You have {{count}} unverified email address.", + "text:other": "You have {{count}} unverified email addresses.", + "title": "Unverified email" + }, + "user_email": { + "cant_delete_primary": "Choose a different primary email to delete this one.", + "delete_button_confirmation_modal": { + "action": "Delete email", + "body": "Delete this email?", + "incorrect_password": "Incorrect password, please try again", + "password_confirmation": "Confirm your account password to delete this email address" + }, + "delete_button_title": "Remove email address", + "email": "Email", + "make_primary_button": "Make primary", + "not_verified": "Not verified", + "primary_email": "Primary email", + "retry_button": "Resend code", + "unverified": "Neoverené" + }, + "user_email_list": { + "heading": "Emails", + "no_primary_email_alert": "No primary email address" + }, + "user_greeting": { + "error": "Failed to load user" + }, + "user_name": { + "display_name_field_label": "Zobrazované meno" + }, + "user_sessions_overview": { + "active_sessions:one": "{{count}} active session", + "active_sessions:other": "{{count}} active sessions", + "heading": "Where you're signed in", + "no_active_sessions": { + "default": "You are not signed in to any application.", + "inactive_90_days": "All your sessions have been active in the last 90 days." + } + }, + "verify_email": { + "code_expired_alert": { + "description": "The code has expired. Please request a new code.", + "title": "Code expired" + }, + "code_field_error": "Code not recognised", + "code_field_label": "6-digit code", + "code_field_wrong_shape": "Code must be 6 digits", + "email_sent_alert": { + "description": "Enter the new code below.", + "title": "New code sent" + }, + "enter_code_prompt": "Enter the 6-digit code sent to: {{email}}", + "heading": "Overte svoj e-mail", + "invalid_code_alert": { + "description": "Check the code sent to your email and update the fields below to continue.", + "title": "You entered the wrong code" + }, + "resend_code": "Resend code", + "resend_email": "Resend email", + "sent": "Sent!", + "unknown_email": "Unknown email" + } + }, + "mas": { + "scope": { + "edit_profile": "Upravte si profil a kontaktné údaje", + "manage_sessions": "Spravujte svoje zariadenia a prihlásenia.", + "mas_admin": "Spravovať ľubovoľného používateľa v matrix-authentication-service", + "send_messages": "Odosielať nové správy vo vašom mene", + "synapse_admin": "Spravovať domovský server", + "view_messages": "Zobraziť existujúce správy a údaje", + "view_profile": "Zobraziť informácie o vašom profile a kontaktné údaje" + } + } +} \ No newline at end of file diff --git a/frontend/locales/uz.json b/frontend/locales/uz.json new file mode 100644 index 000000000..773b949d2 --- /dev/null +++ b/frontend/locales/uz.json @@ -0,0 +1,401 @@ +{ + "action": { + "back": "Orqaga", + "cancel": "Bekor qilish", + "clear": "Tozalash", + "close": "Yopish", + "collapse": "Collapse", + "confirm": "Confirm", + "continue": "Davom etish", + "edit": "Tahrirlash", + "expand": "Expand", + "save": "Saqlash", + "save_and_continue": "Save and continue", + "sign_out": "Chiqish", + "start_over": "Start over" + }, + "branding": { + "privacy_policy": { + "alt": "Xizmat maxfiylik siyosatiga havola", + "link": "Maxfiylik siyosati" + }, + "terms_and_conditions": { + "alt": "Xizmat ko‘rsatish shartlari va qoidalariga havola", + "link": "Foydalanish shartlari" + } + }, + "common": { + "add": "Qo‘shish", + "e2ee": "End-to-end encryption", + "error": "Xatolik", + "loading": "Yuklanmoqda…", + "next": "Keyingisi", + "password": "Parol", + "previous": "Oldingi", + "saved": "Saved", + "saving": "Saving…" + }, + "frontend": { + "account": { + "account_password": "Account password", + "contact_info": "Contact info", + "delete_account": { + "alert_description": "This account will be permanently erased and you’ll no longer have access to any of your messages.", + "alert_title": "You’re about to lose all of your data", + "button": "Delete account", + "dialog_description": "Confirm that you would like to delete your account:\n\n\nYou will not be able to reactivate your account\nYou will no longer be able to sign in\nNo one will be able to reuse your username (MXID), including you\nYou will leave all rooms and direct messages you are in\nYou will be removed from the identity server, and no one will be able to find you with your email or phone number\n\nYour old messages will still be visible to people who received them. Would you like to hide your sent messages from people who join rooms in the future?", + "dialog_title": "Delete this account?", + "erase_checkbox_label": "Yes, hide all my messages from new joiners", + "incorrect_password": "Incorrect password, please try again", + "mxid_label": "Confirm your Matrix ID ({{ mxid }})", + "mxid_mismatch": "This value does not match your Matrix ID", + "password_label": "Enter your password to continue" + }, + "edit_profile": { + "display_name_help": "Siz tizimga kirgan joyingizda boshqalar buni koʻrishadi.", + "display_name_label": "Ko'rsatiladigan ism", + "title": "Profilni tahrirlash", + "username_label": "Foydalanuvchi nomi" + }, + "password": { + "change": "Parolni o‘zgartirish", + "change_disabled": "Password changes are disabled by the administrator.", + "label": "Parol" + }, + "sign_out": { + "button": "Sign out of account", + "dialog": "Sign out of this account?" + }, + "title": "Sizning hisobingiz" + }, + "add_email_form": { + "email_denied_alert": { + "text": "Kiritilgan elektron pochta manzili server siyosati tomonidan ruxsat etilmagan.", + "title": "Elektron pochta siyosat tomonidan rad etildi" + }, + "email_denied_error": "The entered email is not allowed by the server policy", + "email_exists_alert": { + "text": "Kiritilgan elektron pochta manzili ushbu hisobga allaqachon qo‘shilgan.", + "title": "Elektron pochta allaqachon mavjud" + }, + "email_exists_error": "Kiritilgan elektron pochta manzili ushbu hisobga allaqachon qo‘shilgan.", + "email_field_help": "Bu hisobga kirish uchun ishlatishingiz mumkin bo‘lgan muqobil email manzilini kiriting.", + "email_field_label": "Email manzilini kiritish", + "email_in_use_error": "The entered email is already in use", + "email_invalid_alert": { + "text": "Kiritilgan elektron pochta manzili notogʻri", + "title": "Xato elektron pochta manzili" + }, + "email_invalid_error": "Kiritilgan elektron pochta manzili notogʻri", + "incorrect_password_error": "Incorrect password, please try again", + "password_confirmation": "Confirm your account password to add this email address" + }, + "app_sessions_list": { + "error": "Ilova seanslari yuklanmadi", + "heading": "Ilovalar" + }, + "browser_session_details": { + "current_badge": "Hozirgi", + "session_details_title": "Seans" + }, + "browser_sessions_overview": { + "body:one": "{{count}} ta faol seans", + "body:other": "{{count}} ta faol seans", + "heading": "Brauzerlar", + "no_active_sessions": { + "default": "You are not signed in to any web browsers.", + "inactive_90_days": "All your sessions have been active in the last 90 days." + }, + "view_all_button": "Hammasini ko‘rish" + }, + "compat_session_detail": { + "client_details_title": "Mijoz haqidagi axborot", + "name": "Ism", + "session_details_title": "Seans" + }, + "device_type_icon_label": { + "desktop": "Ish stoli", + "mobile": "Mobil", + "pc": "Kompyuter", + "tablet": "Planshet", + "unknown": "Noma’lum qurilma turi", + "web": "Veb" + }, + "email_in_use": { + "heading": "The email address {{email}} is already in use." + }, + "end_session_button": { + "confirmation_modal_title": "Haqiqatan ham bu seansni tugatmoqchimisiz?", + "text": "Qurilmani olib tashlash" + }, + "error": { + "hideDetails": "Tafsilotlarni yashirish", + "showDetails": "Tafsilotlarni koʻrsatish", + "subtitle": "Kutilmagan xatolik yuz berdi. Qaytadan urinib koʻring.", + "title": "Nimadir xato ketdi" + }, + "error_boundary_title": "Nimadir xato ketdi", + "errors": { + "field_required": "Ushbu qator toʻldirilishi shart", + "rate_limit_exceeded": "You've made too many requests in a short period. Please wait a few minutes and try again." + }, + "last_active": { + "active_date": "Faol {{relativeDate}}", + "active_now": "Hozir faol", + "inactive_90_days": "90+ kun davomida faol emas" + }, + "nav": { + "devices": "Qurilmalar", + "plan": "Plan", + "profile": "Profil", + "sessions": "Seanslar", + "settings": "Sozlamalar" + }, + "not_found_alert_title": "Topilmadi.", + "not_logged_in_alert": "Hisobingizga kirmagansiz.", + "oauth2_client_detail": { + "details_title": "Mijoz haqidagi axborot", + "id": "Mijoz ID raqami", + "name": "Ism", + "policy": "Siyosat", + "terms": "Xizmat shartlari" + }, + "oauth2_session_detail": { + "client_details_name": "Ism", + "client_title": "Mijoz haqidagi axborot", + "session_details_title": "Seans" + }, + "pagination_controls": { + "total": "Jami: {{totalCount}}" + }, + "password_change": { + "current_password_label": "Joriy parol", + "failure": { + "description": { + "account_locked": "Your account is locked and can not be recovered at this time. If this is not expected, please contact your server administrator.", + "expired_recovery_ticket": "The recovery link has expired. Please start the account recovery process again from the start.", + "invalid_new_password": "The new password you chose is invalid; it may not meet the configured security policy.", + "no_current_password": "You don't have a current password.", + "no_such_recovery_ticket": "The recovery link is invalid. If you copied the link from the recovery e-mail, please check the full link was copied.", + "password_changes_disabled": "Password changes are disabled.", + "recovery_ticket_already_used": "The recovery link has already been used. It cannot be used again.", + "unspecified": "This might be a temporary problem, so please try again later. If the problem persists, please contact your server administrator.", + "wrong_password": "The password you supplied as your current password is incorrect. Please try again." + }, + "title": "Failed to update password" + }, + "new_password_again_label": "Enter new password again", + "new_password_label": "Yangi parol", + "passwords_match": "Passwords match!", + "passwords_no_match": "Passwords don't match", + "subtitle": "Choose a new password for your account.", + "success": { + "description": "Your password has been updated successfully.", + "title": "Password updated" + }, + "title": "Change your password" + }, + "password_reset": { + "consumed": { + "subtitle": "To create a new password, start over and select ”Forgot password“.", + "title": "The link to reset your password has already been used" + }, + "expired": { + "resend_email": "Elektron pochtani qayta yuborish", + "subtitle": "Request a new email that will be sent to: {{email}}", + "title": "The link to reset your password has expired" + }, + "subtitle": "Choose a new password for your account.", + "title": "Reset your password" + }, + "password_strength": { + "placeholder": "Password strength", + "score": { + "0": "Extremely weak password", + "1": "Very weak password", + "2": "Weak password", + "3": "Strong password", + "4": "Very strong password" + }, + "suggestion": { + "all_uppercase": "Capitalise some, but not all letters.", + "another_word": "Add more words that are less common.", + "associated_years": "Avoid years that are associated with you.", + "capitalization": "Capitalise more than the first letter.", + "dates": "Avoid dates and years that are associated with you.", + "l33t": "Avoid predictable letter substitutions like '@' for 'a'.", + "longer_keyboard_pattern": "Use longer keyboard patterns and change typing direction multiple times.", + "no_need": "You can create strong passwords without using symbols, numbers, or uppercase letters.", + "pwned": "If you use this password elsewhere, you should change it.", + "recent_years": "Avoid recent years.", + "repeated": "Avoid repeated words and characters.", + "reverse_words": "Avoid reversed spellings of common words.", + "sequences": "Avoid common character sequences.", + "use_words": "Use multiple words, but avoid common phrases." + }, + "too_weak": "This password is too weak", + "warning": { + "common": "This is a commonly used password.", + "common_names": "Common names and surnames are easy to guess.", + "dates": "Dates are easy to guess.", + "extended_repeat": "Repeated character patterns like \"abcabcabc\" are easy to guess.", + "key_pattern": "Short keyboard patterns are easy to guess.", + "names_by_themselves": "Single names or surnames are easy to guess.", + "pwned": "Your password was exposed by a data breach on the Internet.", + "recent_years": "Recent years are easy to guess.", + "sequences": "Common character sequences like \"abc\" are easy to guess.", + "similar_to_common": "This is similar to a commonly used password.", + "simple_repeat": "Repeated characters like \"aaa\" are easy to guess.", + "straight_row": "Straight rows of keys on your keyboard are easy to guess.", + "top_hundred": "This is a frequently used password.", + "top_ten": "This is a heavily used password.", + "user_inputs": "There should not be any personal or page related data.", + "word_by_itself": "Single words are easy to guess." + } + }, + "reset_cross_signing": { + "button": "Shaxsni tiklash", + "cancelled": { + "description_1": "You can close this window and go back to the app to continue.", + "description_2": "If don’t have access to any other verified devices and you don’t have your recovery key, then you’ll need to reset your digital identity to continue using the app.", + "heading": "Digital identity reset cancelled." + }, + "description": "Agar boshqa tasdiqlangan qurilmalarga kirish imkoningiz boʻlmasa va sizda tiklash kaliti boʻlmasa, ilovadan foydalanishda davom etish uchun raqamli identifikatsiyangizni tiklashingiz kerak boʻladi.", + "effect_list": { + "negative_1": "You will lose your existing message history", + "negative_2": "You will need to verify all your existing devices and contacts again", + "neutral_1": "You will lose any message history that's stored only on the server", + "neutral_2": "You will need to verify all your existing devices and contacts again", + "positive_1": "Your account details, contacts, preferences, and chat list will be kept" + }, + "failure": { + "description": "This might be a temporary problem, so please try again later. If the problem persists, please contact your server administrator.", + "heading": "Failed to allow digital identity reset", + "title": "Kripto identifikatsiyasiga ruxsat berilmadi" + }, + "finish_reset": "Finish reset", + "heading": "Boshqa usulda tasdiqlay olmasangiz, raqamli identifikatoringizni asliga qaytaring", + "start_reset": "Start reset", + "success": { + "description": "Raqamli identifikatorni tiklash keyingi {{minutes}} daqiqa uchun tasdiqlandi. Bu oynani yopib, davom etish uchun ilovaga qaytishingiz mumkin.", + "heading": "Digital identity reset successfully. Go back to the app to finish the process.", + "title": "Kripto identifikatorini asliga qaytarishga vaqtincha ruxsat berildi" + }, + "warning": "Only reset your digital identity if you don't have access to another verified device and you don't have your recovery key." + }, + "selectable_session": { + "label": "Sessiyani tanlang" + }, + "session": { + "client_id_label": "Mijoz ID raqami", + "current": "Hozirgi", + "current_badge": "Hozirgi", + "device_id_label": "Qurilma ID", + "finished_date": "Tugadi ", + "finished_label": "Tugadi", + "generic_browser_session": "Browser session", + "id_label": "ID", + "ip_label": "IP-manzil", + "last_active_label": "Oxirgi faollik", + "last_auth_label": "Oxirgi autentifikatsiya", + "name_for_platform": "{{platform}} uchun {{name}}", + "scopes_label": "Ko‘lamlar", + "set_device_name": { + "help": "Set a name that will help you identify this device.", + "label": "Device name", + "title": "Edit device name" + }, + "signed_in_date": " kirildi", + "signed_in_label": "Kirish", + "title": "Qurilma tafsilotlari", + "unknown_browser": "Noma’lum brauzer", + "unknown_device": "Noma’lum qurilma.", + "uri_label": "Uri", + "user_id_label": "Foydalanuvchi ID'si", + "username_label": "Foydalanuvchi nomi" + }, + "session_detail": { + "alert": { + "button": "Ortga qaytish", + "text": "Bu sessiya mavjud emas yoki endi faol emas.", + "title": "Sessiya topilmadi:{{deviceId}}" + } + }, + "unknown_route": "Nomaʼlum yoʻnalish {{route}}", + "unverified_email_alert": { + "button": "Koʻrib chiqing va tasdiqlang", + "text:one": "Sizda {{count}} ta tasdiqlanmagan email manzili bor.", + "text:other": "Sizda {{count}} ta tasdiqlanmagan email manzili bor.", + "title": "Tasdiqlanmagan elektron pochta" + }, + "user_email": { + "cant_delete_primary": "Buni oʻchirish uchun boshqa asosiy elektron pochta manzilini tanlang.", + "delete_button_confirmation_modal": { + "action": "Elektron pochtani oʻchirish", + "body": "Bu email o‘chirib tashlansinmi?", + "incorrect_password": "Incorrect password, please try again", + "password_confirmation": "Confirm your account password to delete this email address" + }, + "delete_button_title": "Elektron pochta manzilini olib tashlash", + "email": "Elektron pochta", + "make_primary_button": "Asosiy qilish", + "not_verified": "Tasdiqlanmagan", + "primary_email": "Asosiy elektron pochta", + "retry_button": "Kodni qayta yuborish", + "unverified": "Tasdiqlanmagan" + }, + "user_email_list": { + "heading": "Elektron pochta xabarlari", + "no_primary_email_alert": "Asosiy elektron pochta manzili yo‘q" + }, + "user_greeting": { + "error": "Foydalanuvchi yuklanmadi" + }, + "user_name": { + "display_name_field_label": "Ko'rsatiladigan ism" + }, + "user_sessions_overview": { + "active_sessions:one": "{{count}} ta faol seans", + "active_sessions:other": "{{count}} ta faol seans", + "heading": "Qayerda tizimga kirgansiz", + "no_active_sessions": { + "default": "You are not signed in to any application.", + "inactive_90_days": "All your sessions have been active in the last 90 days." + } + }, + "verify_email": { + "code_expired_alert": { + "description": "The code has expired. Please request a new code.", + "title": "Code expired" + }, + "code_field_error": "Kod tan olinmadi", + "code_field_label": "6 xonali kod", + "code_field_wrong_shape": "Kod 6 xonali bo‘lishi kerak", + "email_sent_alert": { + "description": "Quyidagi yangi kodni kiriting.", + "title": "Yangi kod yuborildi" + }, + "enter_code_prompt": "{{email}} manziliga yuborilgan 6 xonali kodni kiriting", + "heading": "Elektron pochtangizni tasdiqlang", + "invalid_code_alert": { + "description": "Davom etish uchun elektron pochtangizga yuborilgan kodni tekshiring va quyidagi maydonlarni yangilang.", + "title": "Kodni xato kiritdingiz" + }, + "resend_code": "Kodni qayta yuborish", + "resend_email": "Elektron pochtani qayta yuborish", + "sent": "Yuborildi!", + "unknown_email": "Noma’lum email" + } + }, + "mas": { + "scope": { + "edit_profile": "Profilingiz va aloqa maʼlumotlaringizni tahrirlang", + "manage_sessions": "Qurilmalaringiz va sessiyalaringizni boshqaring", + "mas_admin": "Foydalanuvchilarni boshqarish (urn:mas:admin)", + "send_messages": "Sizning nomingizdan yangi xabarlar yuborish", + "synapse_admin": "Serverni boshqarish (urn:synapse:admin:*)", + "view_messages": "Mavjud xabar va ma’lumotlaringizni ko‘rish", + "view_profile": "Profilingiz ma’lumotlari va kontakt tafsilotlarini ko‘rish" + } + } +} \ No newline at end of file diff --git a/frontend/locales/zh-Hans.json b/frontend/locales/zh-Hans.json index ec5b8534f..9335c994e 100644 --- a/frontend/locales/zh-Hans.json +++ b/frontend/locales/zh-Hans.json @@ -99,7 +99,7 @@ "session_details_title": "会话" }, "browser_sessions_overview": { - "body:other": "{{count}}活跃会话", + "body:other": "{{count}} 个活跃会话", "heading": "浏览器", "no_active_sessions": { "default": "你尚未登录任何 Web 浏览器。", @@ -145,7 +145,7 @@ }, "nav": { "devices": "设备", - "plan": "Plan", + "plan": "配额", "profile": "个人资料", "sessions": "会话", "settings": "设置" @@ -353,7 +353,7 @@ "display_name_field_label": "显示名称" }, "user_sessions_overview": { - "active_sessions:other": "{{count}}活跃会话", + "active_sessions:other": "{{count}} 个活跃会话", "heading": "你已登录的位置", "no_active_sessions": { "default": "你尚未登录任何 app。", @@ -373,7 +373,7 @@ "title": "新代码已发送" }, "enter_code_prompt": "输入发送至以下地址的6位数代码:{{email}}", - "heading": "验证邮箱", + "heading": "验证邮件地址", "invalid_code_alert": { "description": "检查发送到你的邮件地址中的代码,并更新以下字段以继续。", "title": "你输入的代码错误" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 837f12940..74c5d5304 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,61 +12,61 @@ "@fontsource/inter": "^5.2.8", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", - "@tanstack/react-query": "^5.90.16", - "@tanstack/react-router": "^1.150.0", - "@vector-im/compound-design-tokens": "6.4.3", + "@tanstack/react-query": "^5.90.21", + "@tanstack/react-router": "^1.167.4", + "@vector-im/compound-design-tokens": "6.10.1", "@vector-im/compound-web": "^8.3.5", "@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4", "classnames": "^2.5.1", "date-fns": "^4.1.0", - "i18next": "^25.7.4", - "react": "^19.2.3", - "react-dom": "^19.2.3", - "react-i18next": "^16.5.3", + "i18next": "^25.8.18", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-i18next": "^16.5.8", "swagger-ui-dist": "^5.31.0", "valibot": "^1.2.0", "vaul": "^1.1.2" }, "devDependencies": { - "@biomejs/biome": "^2.3.9", + "@biomejs/biome": "^2.4.2", "@browser-logos/chrome": "^2.0.0", "@browser-logos/firefox": "^3.0.10", "@browser-logos/safari": "^2.1.0", - "@graphql-codegen/cli": "^6.1.0", - "@graphql-codegen/client-preset": "^5.2.2", + "@graphql-codegen/cli": "^6.1.2", + "@graphql-codegen/client-preset": "^5.2.3", "@graphql-codegen/typescript-msw": "^3.0.1", - "@storybook/addon-docs": "^10.1.11", - "@storybook/react-vite": "^10.1.11", - "@tanstack/react-query-devtools": "^5.91.2", - "@tanstack/react-router-devtools": "^1.150.0", - "@tanstack/router-plugin": "^1.150.0", + "@storybook/addon-docs": "^10.2.19", + "@storybook/react-vite": "^10.2.19", + "@tanstack/react-query-devtools": "^5.91.3", + "@tanstack/react-router-devtools": "^1.166.9", + "@tanstack/router-plugin": "^1.166.13", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", "@testing-library/user-event": "^14.6.1", - "@types/node": "^25.0.3", - "@types/react": "19.2.7", + "@types/node": "^25.3.0", + "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", - "@vitejs/plugin-react": "^5.1.2", - "@vitest/coverage-v8": "^4.0.16", + "@vitejs/plugin-react": "^6.0.1", + "@vitest/coverage-v8": "^4.1.0", "autoprefixer": "^10.4.23", "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.12.0", - "happy-dom": "^20.0.11", - "i18next-cli": "^1.36.1", + "happy-dom": "^20.5.0", + "i18next-cli": "^1.50.3", "knip": "^5.81.0", - "msw": "^2.12.4", + "msw": "^2.12.8", "msw-storybook-addon": "^2.0.6", "postcss": "^8.5.6", "postcss-import": "^16.1.1", - "postcss-nesting": "^13.0.2", + "postcss-nesting": "^14.0.0", "rimraf": "^6.1.2", "storybook": "^10.1.11", "tailwindcss": "^3.4.19", "tinyglobby": "^0.2.15", "typescript": "^5.9.3", - "vite": "7.3.1", + "vite": "8.0.0", "vite-plugin-graphql-codegen": "^3.8.0", "vitest": "^4.0.15" } @@ -117,13 +117,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -132,9 +132,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -142,21 +142,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -173,14 +173,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -203,13 +203,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -266,29 +266,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -383,27 +383,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -830,38 +830,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-shorthand-properties": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", @@ -912,42 +880,42 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -955,9 +923,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -979,9 +947,9 @@ } }, "node_modules/@biomejs/biome": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.9.tgz", - "integrity": "sha512-js+34KpnY65I00k8P71RH0Uh2rJk4BrpxMGM5m2nBfM9XTlKE5N1URn5ydILPRyXXq4ebhKCjsvR+txS+D4z2A==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.2.tgz", + "integrity": "sha512-vVE/FqLxNLbvYnFDYg3Xfrh1UdFhmPT5i+yPT9GE2nTUgI4rkqo5krw5wK19YHBd7aE7J6r91RRmb8RWwkjy6w==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -995,20 +963,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.3.9", - "@biomejs/cli-darwin-x64": "2.3.9", - "@biomejs/cli-linux-arm64": "2.3.9", - "@biomejs/cli-linux-arm64-musl": "2.3.9", - "@biomejs/cli-linux-x64": "2.3.9", - "@biomejs/cli-linux-x64-musl": "2.3.9", - "@biomejs/cli-win32-arm64": "2.3.9", - "@biomejs/cli-win32-x64": "2.3.9" + "@biomejs/cli-darwin-arm64": "2.4.2", + "@biomejs/cli-darwin-x64": "2.4.2", + "@biomejs/cli-linux-arm64": "2.4.2", + "@biomejs/cli-linux-arm64-musl": "2.4.2", + "@biomejs/cli-linux-x64": "2.4.2", + "@biomejs/cli-linux-x64-musl": "2.4.2", + "@biomejs/cli-win32-arm64": "2.4.2", + "@biomejs/cli-win32-x64": "2.4.2" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.9.tgz", - "integrity": "sha512-hHbYYnna/WBwem5iCpssQQLtm5ey8ADuDT8N2zqosk6LVWimlEuUnPy6Mbzgu4GWVriyL5ijWd+1zphX6ll4/A==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.2.tgz", + "integrity": "sha512-3pEcKCP/1POKyaZZhXcxFl3+d9njmeAihZ17k8lL/1vk+6e0Cbf0yPzKItFiT+5Yh6TQA4uKvnlqe0oVZwRxCA==", "cpu": [ "arm64" ], @@ -1023,9 +991,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.9.tgz", - "integrity": "sha512-sKMW5fpvGDmPdqCchtVH5MVlbVeSU3ad4CuKS45x8VHt3tNSC8CZ2QbxffAOKYK9v/mAeUiPC6Cx6+wtyU1q7g==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.2.tgz", + "integrity": "sha512-P7hK1jLVny+0R9UwyGcECxO6sjETxfPyBm/1dmFjnDOHgdDPjPqozByunrwh4xPKld8sxOr5eAsSqal5uKgeBg==", "cpu": [ "x64" ], @@ -1040,9 +1008,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.9.tgz", - "integrity": "sha512-BXBB6HbAgZI6T6QB8q6NSwIapVngqArP6K78BqkMerht7YjL6yWctqfeTnJm0qGF2bKBYFexslrbV+VTlM2E6g==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.2.tgz", + "integrity": "sha512-DI3Mi7GT2zYNgUTDEbSjl3e1KhoP76OjQdm8JpvZYZWtVDRyLd3w8llSr2TWk1z+U3P44kUBWY3X7H9MD1/DGQ==", "cpu": [ "arm64" ], @@ -1057,9 +1025,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.9.tgz", - "integrity": "sha512-JOHyG2nl8XDpncbMazm1uBSi1dPX9VbQDOjKcfSVXTqajD0PsgodMOKyuZ/PkBu5Lw877sWMTGKfEfpM7jE7Cw==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.2.tgz", + "integrity": "sha512-/x04YK9+7erw6tYEcJv9WXoBHcULI/wMOvNdAyE9S3JStZZ9yJyV67sWAI+90UHuDo/BDhq0d96LDqGlSVv7WA==", "cpu": [ "arm64" ], @@ -1074,9 +1042,9 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.9.tgz", - "integrity": "sha512-PjYuv2WLmvf0WtidxAkFjlElsn0P6qcvfPijrqu1j+3GoW3XSQh3ywGu7gZ25J25zrYj3KEovUjvUZB55ATrGw==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.2.tgz", + "integrity": "sha512-GK2ErnrKpWFigYP68cXiCHK4RTL4IUWhK92AFS3U28X/nuAL5+hTuy6hyobc8JZRSt+upXt1nXChK+tuHHx4mA==", "cpu": [ "x64" ], @@ -1091,9 +1059,9 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.9.tgz", - "integrity": "sha512-FUkb/5beCIC2trpqAbW9e095X4vamdlju80c1ExSmhfdrojLZnWkah/XfTSixKb/dQzbAjpD7vvs6rWkJ+P07Q==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.2.tgz", + "integrity": "sha512-wbBmTkeAoAYbOQ33f6sfKG7pcRSydQiF+dTYOBjJsnXO2mWEOQHllKlC2YVnedqZFERp2WZhFUoO7TNRwnwEHQ==", "cpu": [ "x64" ], @@ -1108,9 +1076,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.9.tgz", - "integrity": "sha512-w48Yh/XbYHO2cBw8B5laK3vCAEKuocX5ItGXVDAqFE7Ze2wnR00/1vkY6GXglfRDOjWHu2XtxI0WKQ52x1qxEA==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.2.tgz", + "integrity": "sha512-k2uqwLYrNNxnaoiW3RJxoMGnbKda8FuCmtYG3cOtVljs3CzWxaTR+AoXwKGHscC9thax9R4kOrtWqWN0+KdPTw==", "cpu": [ "arm64" ], @@ -1125,9 +1093,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.9.tgz", - "integrity": "sha512-90+J63VT7qImy9s3pkWL0ZX27VzVwMNCRzpLpe5yMzMYPbO1vcjL/w/Q5f/juAGMvP7a2Fd0H7IhAR6F7/i78A==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.2.tgz", + "integrity": "sha512-9ma7C4g8Sq3cBlRJD2yrsHXB1mnnEBdpy7PhvFrylQWQb4PoyCmPucdX7frvsSBQuFtIiKCrolPl/8tCZrKvgQ==", "cpu": [ "x64" ], @@ -1177,9 +1145,9 @@ } }, "node_modules/@csstools/selector-resolve-nested": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", - "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-4.0.0.tgz", + "integrity": "sha512-9vAPxmp+Dx3wQBIUwc1v7Mdisw1kbbaGqXUM8QLTgWg7SoPGYtXBsMXvsFs/0Bn5yoFhcktzxNZGNaUt0VjgjA==", "dev": true, "funding": [ { @@ -1193,16 +1161,16 @@ ], "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "postcss-selector-parser": "^7.0.0" + "postcss-selector-parser": "^7.1.1" } }, "node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-6.0.0.tgz", + "integrity": "sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA==", "dev": true, "funding": [ { @@ -1216,10 +1184,10 @@ ], "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "postcss-selector-parser": "^7.0.0" + "postcss-selector-parser": "^7.1.1" } }, "node_modules/@emnapi/core": { @@ -1845,9 +1813,9 @@ "license": "0BSD" }, "node_modules/@graphql-codegen/cli": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-6.1.0.tgz", - "integrity": "sha512-7w3Zq5IFONVOBcyOiP01Nv9WRxGS/TEaBCAb/ALYA3xHq95dqKCpoGnxt/Ut9R18jiS+aMgT0gc8Tr8sHy44jA==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-6.1.2.tgz", + "integrity": "sha512-BQ49LF0jnQNL12rU1RucTemoX1bHx8slR4B11nOrp4k5NTojhcc1A1czzU5wXCK/1+ezNHrVGONWg3jxZUy08w==", "dev": true, "license": "MIT", "dependencies": { @@ -1906,21 +1874,21 @@ } }, "node_modules/@graphql-codegen/client-preset": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-5.2.2.tgz", - "integrity": "sha512-1xufIJZr04ylx0Dnw49m8Jrx1s1kujUNVm+Tp5cPRsQmgPN9VjB7wWY7CGD8ArStv6Vjb0a31Xnm5I+VzZM+Rw==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-5.2.3.tgz", + "integrity": "sha512-zgbk0dTY+KC/8TG00RGct6HnXWJU6jQaty3wAXKl1CvCXTKO73pW8Npph+RSJMTEEXb+QuJL3vyaPiGM1gw8sw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", "@babel/template": "^7.20.7", "@graphql-codegen/add": "^6.0.0", - "@graphql-codegen/gql-tag-operations": "5.1.2", + "@graphql-codegen/gql-tag-operations": "5.1.3", "@graphql-codegen/plugin-helpers": "^6.1.0", - "@graphql-codegen/typed-document-node": "^6.1.5", - "@graphql-codegen/typescript": "^5.0.7", - "@graphql-codegen/typescript-operations": "^5.0.7", - "@graphql-codegen/visitor-plugin-common": "^6.2.2", + "@graphql-codegen/typed-document-node": "^6.1.6", + "@graphql-codegen/typescript": "^5.0.8", + "@graphql-codegen/typescript-operations": "^5.0.8", + "@graphql-codegen/visitor-plugin-common": "^6.2.3", "@graphql-tools/documents": "^1.0.0", "@graphql-tools/utils": "^10.0.0", "@graphql-typed-document-node/core": "3.2.0", @@ -1973,14 +1941,14 @@ "license": "0BSD" }, "node_modules/@graphql-codegen/gql-tag-operations": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-5.1.2.tgz", - "integrity": "sha512-BIv66VJ2bKlpfXBeVakJxihBSKnBIdGFLMaFdnGPxqYlKIzaGffjsGbhViPwwBinmBChW4Se6PU4Py7eysYEiA==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-5.1.3.tgz", + "integrity": "sha512-yh/GTGW5Nf8f/zaCHZwWb04ItWAm+UfUJf7pb6n4SrqRxvWOSJk36LJ4l8UuDW1tmAOobjeXB8HSKSJsUjmA1g==", "dev": true, "license": "MIT", "dependencies": { "@graphql-codegen/plugin-helpers": "^6.1.0", - "@graphql-codegen/visitor-plugin-common": "6.2.2", + "@graphql-codegen/visitor-plugin-common": "6.2.3", "@graphql-tools/utils": "^10.0.0", "auto-bind": "~4.0.0", "tslib": "~2.6.0" @@ -2053,14 +2021,14 @@ "license": "0BSD" }, "node_modules/@graphql-codegen/typed-document-node": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-6.1.5.tgz", - "integrity": "sha512-6dgEPz+YRMzSPpATj7tsKh/L6Y8OZImiyXIUzvSq/dRAEgoinahrES5y/eZQyc7CVxfoFCyHF9KMQQ9jiLn7lw==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-6.1.6.tgz", + "integrity": "sha512-USuQdUWBXij9HQl+GWXuLm05kjpOVwViBfnNi7ijES4HFwAmt/EDAnYSCfUoOHCfFQeWcfqYbtcUGJO9iXiSYQ==", "dev": true, "license": "MIT", "dependencies": { "@graphql-codegen/plugin-helpers": "^6.1.0", - "@graphql-codegen/visitor-plugin-common": "6.2.2", + "@graphql-codegen/visitor-plugin-common": "6.2.3", "auto-bind": "~4.0.0", "change-case-all": "1.0.15", "tslib": "~2.6.0" @@ -2080,15 +2048,15 @@ "license": "0BSD" }, "node_modules/@graphql-codegen/typescript": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-5.0.7.tgz", - "integrity": "sha512-kZwcu9Iat5RWXxLGPnDbG6qVbGTigF25/aGqCG/DCQ1Al8RufSjVXhIOkJBp7QWAqXn3AupHXL1WTMXP7xs4dQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-5.0.8.tgz", + "integrity": "sha512-lUW6ari+rXP6tz5B0LXjmV9rEMOphoCZAkt+SJGObLQ6w6544ZsXSsRga/EJiSvZ1fRfm9yaFoErOZ56IVThyg==", "dev": true, "license": "MIT", "dependencies": { "@graphql-codegen/plugin-helpers": "^6.1.0", "@graphql-codegen/schema-ast": "^5.0.0", - "@graphql-codegen/visitor-plugin-common": "6.2.2", + "@graphql-codegen/visitor-plugin-common": "6.2.3", "auto-bind": "~4.0.0", "tslib": "~2.6.0" }, @@ -2395,15 +2363,15 @@ } }, "node_modules/@graphql-codegen/typescript-operations": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-5.0.7.tgz", - "integrity": "sha512-5N3myNse1putRQlp8+l1k9ayvc98oq2mPJx0zN8MTOlTBxcb2grVPFRLy5wJJjuv9NffpyCkVJ9LvUaf8mqQgg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-5.0.8.tgz", + "integrity": "sha512-5H58DnDIy59Q+wcPRu13UnAS7fkMCW/vPI1+g8rHBmxuV9YGyGlVL9lE/fmJ06181hI7G9YGuUaoFYMJFU6bxQ==", "dev": true, "license": "MIT", "dependencies": { "@graphql-codegen/plugin-helpers": "^6.1.0", - "@graphql-codegen/typescript": "^5.0.7", - "@graphql-codegen/visitor-plugin-common": "6.2.2", + "@graphql-codegen/typescript": "^5.0.8", + "@graphql-codegen/visitor-plugin-common": "6.2.3", "auto-bind": "~4.0.0", "tslib": "~2.6.0" }, @@ -2435,9 +2403,9 @@ "license": "0BSD" }, "node_modules/@graphql-codegen/visitor-plugin-common": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-6.2.2.tgz", - "integrity": "sha512-wEJ4zJj58PKlXISItZfr0xIHyM1lAuRfoflPegsb1L17Mx5+YzNOy0WAlLele3yzyV89WvCiprFKMcVQ7KfDXg==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-6.2.3.tgz", + "integrity": "sha512-Rewl/QRFfIOXHFK3i/ts4VodsaB4N22kckH1zweTzq7SFodkfrqGrLa/MrGLJ/q6aUuqGiqao7f4Za2IjjkCxw==", "dev": true, "license": "MIT", "dependencies": { @@ -2854,14 +2822,33 @@ } }, "node_modules/@graphql-tools/relay-operation-optimizer": { - "version": "7.0.26", - "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.0.26.tgz", - "integrity": "sha512-cVdS2Hw4hg/WgPVV2wRIzZM975pW5k4vdih3hR4SvEDQVr6MmozmlTQSqzMyi9yg8LKTq540Oz3bYQa286yGmg==", + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.0.27.tgz", + "integrity": "sha512-rdkL1iDMFaGDiHWd7Bwv7hbhrhnljkJaD0MXeqdwQlZVgVdUDlMot2WuF7CEKVgijpH6eSC6AxXMDeqVgSBS2g==", "dev": true, "license": "MIT", "dependencies": { "@ardatan/relay-compiler": "^12.0.3", - "@graphql-tools/utils": "^10.11.0", + "@graphql-tools/utils": "^11.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/relay-operation-optimizer/node_modules/@graphql-tools/utils": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-11.0.0.tgz", + "integrity": "sha512-bM1HeZdXA2C3LSIeLOnH/bcqSgbQgKEDrjxODjqi3y58xai2TkNrtYcQSoWzGbt9VMN1dORGjR7Vem8SPnUFQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@whatwg-node/promise-helpers": "^1.0.0", + "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "engines": { @@ -3315,98 +3302,6 @@ } } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@joshwooding/vite-plugin-react-docgen-typescript": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.6.3.tgz", - "integrity": "sha512-9TGZuAX+liGkNKkwuo3FYJu7gHWT0vkBcf7GkOe7s7fmC19XwH/4u5u7sDIFrMooe558ORcmuBvBz7Ur5PlbHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob": "^11.1.0", - "react-docgen-typescript": "^2.2.2" - }, - "peerDependencies": { - "typescript": ">= 4.3.x", - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -3476,9 +3371,9 @@ } }, "node_modules/@mswjs/interceptors": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.40.0.tgz", - "integrity": "sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.0.tgz", + "integrity": "sha512-edAo9bW53BLYeSK+UPRr2Iz1Fj9DeGMjytvVM0HXRoo750ElWUgPsZPAOTQa12EUiwgDErH2PsFNTLvk1jBxjQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3494,9 +3389,9 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz", - "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", "dev": true, "license": "MIT", "optional": true, @@ -3504,6 +3399,10 @@ "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@nodelib/fs.scandir": { @@ -3569,6 +3468,26 @@ "dev": true, "license": "MIT" }, + "node_modules/@oxc-project/runtime": { + "version": "0.115.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.115.0.tgz", + "integrity": "sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.115.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.115.0.tgz", + "integrity": "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@oxc-resolver/binding-android-arm-eabi": { "version": "11.15.0", "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.15.0.tgz", @@ -4681,10 +4600,265 @@ "dev": true, "license": "MIT" }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.9.tgz", + "integrity": "sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.9.tgz", + "integrity": "sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.9.tgz", + "integrity": "sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.9.tgz", + "integrity": "sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.9.tgz", + "integrity": "sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.9.tgz", + "integrity": "sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.9.tgz", + "integrity": "sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.9.tgz", + "integrity": "sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", - "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", "dev": true, "license": "MIT" }, @@ -4711,314 +4885,6 @@ } } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz", - "integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz", - "integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz", - "integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz", - "integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz", - "integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz", - "integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz", - "integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz", - "integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz", - "integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz", - "integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz", - "integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz", - "integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz", - "integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz", - "integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz", - "integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz", - "integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz", - "integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz", - "integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz", - "integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz", - "integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz", - "integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz", - "integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@scarf/scarf": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", @@ -5054,16 +4920,16 @@ "license": "MIT" }, "node_modules/@storybook/addon-docs": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.1.11.tgz", - "integrity": "sha512-Jwm291Fhim2eVcZIVlkG1B2skb0ZI9oru6nqMbJxceQZlvZmcIa4oxvS1oaMTKw2DJnCv97gLm57P/YvRZ8eUg==", + "version": "10.2.19", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.2.19.tgz", + "integrity": "sha512-tXugthdzjX5AkGWDSP4pnRgA/CWlOaEKp/+y9JOGXHLQmm1GHjW+4brNvNkKbjBl06LALXwlcTOyU4lyVRDLAw==", "dev": true, "license": "MIT", "dependencies": { "@mdx-js/react": "^3.0.0", - "@storybook/csf-plugin": "10.1.11", - "@storybook/icons": "^2.0.0", - "@storybook/react-dom-shim": "10.1.11", + "@storybook/csf-plugin": "10.2.19", + "@storybook/icons": "^2.0.1", + "@storybook/react-dom-shim": "10.2.19", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" @@ -5073,18 +4939,17 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.1.11" + "storybook": "^10.2.19" } }, "node_modules/@storybook/builder-vite": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.1.11.tgz", - "integrity": "sha512-MMD09Ap7FyzDfWG961pkIMv/w684XXe1bBEi+wCEpHxvrgAd3j3A9w/Rqp9Am2uRDPCEdi1QgSzS3SGW3aGThQ==", + "version": "10.2.19", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.2.19.tgz", + "integrity": "sha512-a59xALzM9GeYh6p+wzAeBbDyIe+qyrC4nxS3QNzb5i2ZOhrq1iIpvnDaOWe80NC8mV3IlqUEGY8Uawkf//1Rmg==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf-plugin": "10.1.11", - "@vitest/mocker": "3.2.4", + "@storybook/csf-plugin": "10.2.19", "ts-dedent": "^2.0.0" }, "funding": { @@ -5092,14 +4957,14 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.1.11", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" + "storybook": "^10.2.19", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/@storybook/csf-plugin": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.1.11.tgz", - "integrity": "sha512-Ant0NhgqHKzQsseeVTSetZCuDHHs0W2HRkHt51Kg/sUl0T/sDtfVA+fWZT8nGzGZqYSFkxqYPWjauPmIhPtaRw==", + "version": "10.2.19", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.2.19.tgz", + "integrity": "sha512-BpjYIOdyQn/Rm6MjUAc5Gl8HlARZrskD/OhUNShiOh2fznb523dHjiE5mbU1kKM/+L1uvRlEqqih40rTx+xCrg==", "dev": true, "license": "MIT", "dependencies": { @@ -5112,7 +4977,7 @@ "peerDependencies": { "esbuild": "*", "rollup": "*", - "storybook": "^10.1.11", + "storybook": "^10.2.19", "vite": "*", "webpack": "*" }, @@ -5150,14 +5015,14 @@ } }, "node_modules/@storybook/react": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.1.11.tgz", - "integrity": "sha512-rmMGmEwBaM2YpB8oDk2moM0MNjNMqtwyoPPZxjyruY9WVhYca8EDPGKEdRzUlb4qZJsTgLi7VU4eqg6LD/mL3Q==", + "version": "10.2.19", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.2.19.tgz", + "integrity": "sha512-gm2qxLyYSsGp7fee5i+d8jSVUKMla8yRaTJ1wxPEnyaJMd0QUu6U2v3p2rW7PH1DWop3D6NqWOY8kmZjmSZKlA==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "10.1.11", + "@storybook/react-dom-shim": "10.2.19", "react-docgen": "^8.0.2" }, "funding": { @@ -5167,7 +5032,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.1.11", + "storybook": "^10.2.19", "typescript": ">= 4.9.x" }, "peerDependenciesMeta": { @@ -5177,9 +5042,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.1.11.tgz", - "integrity": "sha512-o8WPhRlZbORUWG9lAgDgJP0pi905VHJUFJr1Kp8980gHqtlemtnzjPxKy5vFwj6glNhAlK8SS8OOYzWP7hloTQ==", + "version": "10.2.19", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.2.19.tgz", + "integrity": "sha512-BXCEfBGVBRYBTYeBeH/PJsy0Bq5MERe/HiaylR+ah/XrvIr2Z9bkne1J8yYiXCjiyq5HQa7Bj11roz0+vyUaEw==", "dev": true, "license": "MIT", "funding": { @@ -5189,20 +5054,20 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.1.11" + "storybook": "^10.2.19" } }, "node_modules/@storybook/react-vite": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.1.11.tgz", - "integrity": "sha512-qh1BCD25nIoiDfqwha+qBkl7pcG4WuzM+c8tsE63YEm8AFIbNKg5K8lVUoclF+4CpFz7IwBpWe61YUTDfp+91w==", + "version": "10.2.19", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.2.19.tgz", + "integrity": "sha512-2/yMKrK4IqMIZicRpPMoIg+foBuWnkaEWt0R4V4hjErDj/SC3D9ov+GUqhjKJ81TegijhKzNpwnSD7Nf87haKw==", "dev": true, "license": "MIT", "dependencies": { - "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.3", + "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.4", "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "10.1.11", - "@storybook/react": "10.1.11", + "@storybook/builder-vite": "10.2.19", + "@storybook/react": "10.2.19", "empathic": "^2.0.0", "magic-string": "^0.30.0", "react-docgen": "^8.0.0", @@ -5216,14 +5081,34 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.1.11", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" + "storybook": "^10.2.19", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@storybook/react-vite/node_modules/@joshwooding/vite-plugin-react-docgen-typescript": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.6.4.tgz", + "integrity": "sha512-6PyZBYKnnVNqOSB0YFly+62R7dmov8segT27A+RVTBVd4iAE6kbW9QBJGlyR2yG4D4ohzhZSTIu7BK1UTtmFFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^13.0.1", + "react-docgen-typescript": "^2.2.2" + }, + "peerDependencies": { + "typescript": ">= 4.3.x", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@swc/core": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.4.tgz", - "integrity": "sha512-fH81BPo6EiJ7BUb6Qa5SY/NLWIRVambqU3740g0XPFPEz5KFPnzRYpR6zodQNOcEb9XUtZzRO1Y0WyIJP7iBxQ==", + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.18.tgz", + "integrity": "sha512-z87aF9GphWp//fnkRsqvtY+inMVPgYW3zSlXH1kJFvRT5H/wiAn+G32qW5l3oEk63KSF1x3Ov0BfHCObAmT8RA==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -5239,16 +5124,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.4", - "@swc/core-darwin-x64": "1.15.4", - "@swc/core-linux-arm-gnueabihf": "1.15.4", - "@swc/core-linux-arm64-gnu": "1.15.4", - "@swc/core-linux-arm64-musl": "1.15.4", - "@swc/core-linux-x64-gnu": "1.15.4", - "@swc/core-linux-x64-musl": "1.15.4", - "@swc/core-win32-arm64-msvc": "1.15.4", - "@swc/core-win32-ia32-msvc": "1.15.4", - "@swc/core-win32-x64-msvc": "1.15.4" + "@swc/core-darwin-arm64": "1.15.18", + "@swc/core-darwin-x64": "1.15.18", + "@swc/core-linux-arm-gnueabihf": "1.15.18", + "@swc/core-linux-arm64-gnu": "1.15.18", + "@swc/core-linux-arm64-musl": "1.15.18", + "@swc/core-linux-x64-gnu": "1.15.18", + "@swc/core-linux-x64-musl": "1.15.18", + "@swc/core-win32-arm64-msvc": "1.15.18", + "@swc/core-win32-ia32-msvc": "1.15.18", + "@swc/core-win32-x64-msvc": "1.15.18" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -5260,9 +5145,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.4.tgz", - "integrity": "sha512-NU/Of+ShFGG/i0lXKsF6GaGeTBNsr9iD8uUzdXxFfGbEjTeuKNXc5CWn3/Uo4Gr4LMAGD3hsRwG2Jq5iBDMalw==", + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.18.tgz", + "integrity": "sha512-+mIv7uBuSaywN3C9LNuWaX1jJJ3SKfiJuE6Lr3bd+/1Iv8oMU7oLBjYMluX1UrEPzwN2qCdY6Io0yVicABoCwQ==", "cpu": [ "arm64" ], @@ -5277,9 +5162,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.4.tgz", - "integrity": "sha512-9oWYMZHiEfHLqjjRGrXL17I8HdAOpWK/Rps34RKQ74O+eliygi1Iyq1TDUzYqUXcNvqN2K5fHgoMLRIni41ClQ==", + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.18.tgz", + "integrity": "sha512-wZle0eaQhnzxWX5V/2kEOI6Z9vl/lTFEC6V4EWcn+5pDjhemCpQv9e/TDJ0GIoiClX8EDWRvuZwh+Z3dhL1NAg==", "cpu": [ "x64" ], @@ -5294,9 +5179,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.4.tgz", - "integrity": "sha512-I1dPxXli3N1Vr71JXogUTLcspM5ICgCYaA16RE+JKchj3XKKmxLlYjwAHAA4lh/Cy486ikzACaG6pIBcegoGkg==", + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.18.tgz", + "integrity": "sha512-ao61HGXVqrJFHAcPtF4/DegmwEkVCo4HApnotLU8ognfmU8x589z7+tcf3hU+qBiU1WOXV5fQX6W9Nzs6hjxDw==", "cpu": [ "arm" ], @@ -5311,9 +5196,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.4.tgz", - "integrity": "sha512-iGpuS/2PDZ68ioAlhkxiN5M4+pB9uDJolTKk4mZ0JM29uFf9YIkiyk7Bbr2y1QtmD82rF0tDHhoG9jtnV8mZMg==", + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.18.tgz", + "integrity": "sha512-3xnctOBLIq3kj8PxOCgPrGjBLP/kNOddr6f5gukYt/1IZxsITQaU9TDyjeX6jG+FiCIHjCuWuffsyQDL5Ew1bg==", "cpu": [ "arm64" ], @@ -5328,9 +5213,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.4.tgz", - "integrity": "sha512-Ly95wc+VXDhl08pjAoPUhVu5vNbuPMbURknRZa5QOZuiizJ6DkaSI0/zsEc26PpC6HTc4prNLY3ARVwZ7j/IJQ==", + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.18.tgz", + "integrity": "sha512-0a+Lix+FSSHBSBOA0XznCcHo5/1nA6oLLjcnocvzXeqtdjnPb+SvchItHI+lfeiuj1sClYPDvPMLSLyXFaiIKw==", "cpu": [ "arm64" ], @@ -5345,9 +5230,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.4.tgz", - "integrity": "sha512-7pIG0BnaMn4zTpHeColPwyrWoTY9Drr+ISZQIgYHUKh3oaPtNCrXb289ScGbPPPjLsSfcGTeOy2pXmNczMC+yg==", + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.18.tgz", + "integrity": "sha512-wG9J8vReUlpaHz4KOD/5UE1AUgirimU4UFT9oZmupUDEofxJKYb1mTA/DrMj0s78bkBiNI+7Fo2EgPuvOJfuAA==", "cpu": [ "x64" ], @@ -5362,9 +5247,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.4.tgz", - "integrity": "sha512-oaqTV25V9H+PpSkvTcK25q6Q56FvXc6d2xBu486dv9LAPCHWgeAworE8WpBLV26g8rubcN5nGhO5HwSunXA7Ww==", + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.18.tgz", + "integrity": "sha512-4nwbVvCphKzicwNWRmvD5iBaZj8JYsRGa4xOxJmOyHlMDpsvvJ2OR2cODlvWyGFH6BYL1MfIAK3qph3hp0Az6g==", "cpu": [ "x64" ], @@ -5379,9 +5264,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.4.tgz", - "integrity": "sha512-VcPuUJw27YbGo1HcOaAriI50dpM3ZZeDW3x2cMnJW6vtkeyzUFk1TADmTwFax0Fn+yicCxhaWjnFE3eAzGAxIQ==", + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.18.tgz", + "integrity": "sha512-zk0RYO+LjiBCat2RTMHzAWaMky0cra9loH4oRrLKLLNuL+jarxKLFDA8xTZWEkCPLjUTwlRN7d28eDLLMgtUcQ==", "cpu": [ "arm64" ], @@ -5396,9 +5281,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.4.tgz", - "integrity": "sha512-dREjghAZEuKAK9nQzJETAiCSihSpAVS6Vk9+y2ElaoeTj68tNB1txV/m1RTPPD/+Kgbz6ITPNyXRWxPdkP5aXw==", + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.18.tgz", + "integrity": "sha512-yVuTrZ0RccD5+PEkpcLOBAuPbYBXS6rslENvIXfvJGXSdX5QGi1ehC4BjAMl5FkKLiam4kJECUI0l7Hq7T1vwg==", "cpu": [ "ia32" ], @@ -5413,9 +5298,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.4.tgz", - "integrity": "sha512-o/odIBuQkoxKbRweJWOMI9LeRSOenFKN2zgPeaaNQ/cyuVk2r6DCAobKMOodvDdZWlMn6N1xJrldeCRSTZIgiQ==", + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.18.tgz", + "integrity": "sha512-7NRmE4hmUQNCbYU3Hn9Tz57mK9Qq4c97ZS+YlamlK6qG9Fb5g/BB3gPDe0iLlJkns/sYv2VWSkm8c3NmbEGjbg==", "cpu": [ "x64" ], @@ -5447,12 +5332,12 @@ } }, "node_modules/@tanstack/history": { - "version": "1.145.7", - "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.145.7.tgz", - "integrity": "sha512-gMo/ReTUp0a3IOcZoI3hH6PLDC2R/5ELQ7P2yu9F6aEkA0wSQh+Q4qzMrtcKvF2ut0oE+16xWCGDo/TdYd6cEQ==", + "version": "1.161.6", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.161.6.tgz", + "integrity": "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=20.19" }, "funding": { "type": "github", @@ -5460,9 +5345,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.90.16", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.16.tgz", - "integrity": "sha512-MvtWckSVufs/ja463/K4PyJeqT+HMlJWtw6PrCpywznd2NSgO3m4KwO9RqbFqGg6iDE8vVMFWMeQI4Io3eEYww==", + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", + "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", "license": "MIT", "funding": { "type": "github", @@ -5470,9 +5355,9 @@ } }, "node_modules/@tanstack/query-devtools": { - "version": "5.92.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.92.0.tgz", - "integrity": "sha512-N8D27KH1vEpVacvZgJL27xC6yPFUy0Zkezn5gnB3L3gRCxlDeSuiya7fKge8Y91uMTnC8aSxBQhcK6ocY7alpQ==", + "version": "5.93.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.93.0.tgz", + "integrity": "sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg==", "dev": true, "license": "MIT", "funding": { @@ -5481,12 +5366,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.90.16", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.16.tgz", - "integrity": "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==", + "version": "5.90.21", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz", + "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.90.16" + "@tanstack/query-core": "5.90.20" }, "funding": { "type": "github", @@ -5497,38 +5382,38 @@ } }, "node_modules/@tanstack/react-query-devtools": { - "version": "5.91.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.91.2.tgz", - "integrity": "sha512-ZJ1503ay5fFeEYFUdo7LMNFzZryi6B0Cacrgr2h1JRkvikK1khgIq6Nq2EcblqEdIlgB/r7XDW8f8DQ89RuUgg==", + "version": "5.91.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.91.3.tgz", + "integrity": "sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA==", "dev": true, "license": "MIT", "dependencies": { - "@tanstack/query-devtools": "5.92.0" + "@tanstack/query-devtools": "5.93.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-query": "^5.90.14", + "@tanstack/react-query": "^5.90.20", "react": "^18 || ^19" } }, "node_modules/@tanstack/react-router": { - "version": "1.150.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.150.0.tgz", - "integrity": "sha512-k/oycTCpBT2XoEk9dNd/nNYhF0X9fLSB10lT40+NVX1TjOtBq5whksk8MT6oRnSoQ8KWeb7La3G9kFaAeSULkA==", + "version": "1.167.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.167.4.tgz", + "integrity": "sha512-VpbZh382zX3WF4+X2Z+EUyd8eJhJyjg9C6ByYwrVZiWbhgbMK4+zQQIG2+lCAlIlDi7SV8fDcGL09NA8Z2kpGQ==", "license": "MIT", "dependencies": { - "@tanstack/history": "1.145.7", - "@tanstack/react-store": "^0.8.0", - "@tanstack/router-core": "1.150.0", + "@tanstack/history": "1.161.6", + "@tanstack/react-store": "^0.9.1", + "@tanstack/router-core": "1.167.4", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "engines": { - "node": ">=12" + "node": ">=20.19" }, "funding": { "type": "github", @@ -5540,24 +5425,24 @@ } }, "node_modules/@tanstack/react-router-devtools": { - "version": "1.150.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-router-devtools/-/react-router-devtools-1.150.0.tgz", - "integrity": "sha512-TlvTE+XK5XVCfYjazoMWkjyyPKe4kMw2nCA7EuWoYUJKOqRW5oKvBY7auViGWxp51FKDEjV3bbok3wPKBYwZww==", + "version": "1.166.9", + "resolved": "https://registry.npmjs.org/@tanstack/react-router-devtools/-/react-router-devtools-1.166.9.tgz", + "integrity": "sha512-O49eZmaeEKB5YnKH/qd61AbxV/lW8ICm4stfZ4GNQNpzQQ6rhPIB0p3PMZDIgX+6DoMivdNvLRmXAOOpzpIpDg==", "dev": true, "license": "MIT", "dependencies": { - "@tanstack/router-devtools-core": "1.150.0" + "@tanstack/router-devtools-core": "1.166.9" }, "engines": { - "node": ">=12" + "node": ">=20.19" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-router": "^1.150.0", - "@tanstack/router-core": "^1.150.0", + "@tanstack/react-router": "^1.167.2", + "@tanstack/router-core": "^1.167.2", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, @@ -5568,12 +5453,12 @@ } }, "node_modules/@tanstack/react-store": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.8.0.tgz", - "integrity": "sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.9.2.tgz", + "integrity": "sha512-Vt5usJE5sHG/cMechQfmwvwne6ktGCELe89Lmvoxe3LKRoFrhPa8OCKWs0NliG8HTJElEIj7PLtaBQIcux5pAQ==", "license": "MIT", "dependencies": { - "@tanstack/store": "0.8.0", + "@tanstack/store": "0.9.2", "use-sync-external-store": "^1.6.0" }, "funding": { @@ -5586,21 +5471,24 @@ } }, "node_modules/@tanstack/router-core": { - "version": "1.150.0", - "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.150.0.tgz", - "integrity": "sha512-cAm44t/tUbfyzaDH+rE/WO4u3AgaZdpJp00xjQ4gNkC2O95ntVHq5fx+4fhtrkKpgdXoKldgk8OK66djiWpuGQ==", + "version": "1.167.4", + "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.167.4.tgz", + "integrity": "sha512-Gk5V9Zr5JFJ4SbLyCheQLJ3MnXddccENPA+DJRz+9g3QxtN8DJB8w8KCUCgDeYlWp4LvmO4nX3fy3tupqVP2Pw==", "license": "MIT", "dependencies": { - "@tanstack/history": "1.145.7", - "@tanstack/store": "^0.8.0", + "@tanstack/history": "1.161.6", + "@tanstack/store": "^0.9.1", "cookie-es": "^2.0.0", - "seroval": "^1.4.1", - "seroval-plugins": "^1.4.0", + "seroval": "^1.4.2", + "seroval-plugins": "^1.4.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, + "bin": { + "intent": "bin/intent.js" + }, "engines": { - "node": ">=12" + "node": ">=20.19" }, "funding": { "type": "github", @@ -5608,9 +5496,9 @@ } }, "node_modules/@tanstack/router-devtools-core": { - "version": "1.150.0", - "resolved": "https://registry.npmjs.org/@tanstack/router-devtools-core/-/router-devtools-core-1.150.0.tgz", - "integrity": "sha512-61V+4fq2fOPru/48cuojKvWhQx2h/nuj4nVHwzu9E7O8h391h4Hks6axxRbY98/rIz96mn5TCoc0aYuoga53bg==", + "version": "1.166.9", + "resolved": "https://registry.npmjs.org/@tanstack/router-devtools-core/-/router-devtools-core-1.166.9.tgz", + "integrity": "sha512-PNlA7GmOUX9wY7LUG709Pk3Lg33dfHBztQwzjzrOiOsuf4ggp2R6bwarF8nYGNjG79z/MaB5PN+5yvkCVk8jGw==", "dev": true, "license": "MIT", "dependencies": { @@ -5619,14 +5507,14 @@ "tiny-invariant": "^1.3.3" }, "engines": { - "node": ">=12" + "node": ">=20.19" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/router-core": "^1.150.0", + "@tanstack/router-core": "^1.167.2", "csstype": "^3.0.10" }, "peerDependenciesMeta": { @@ -5636,15 +5524,15 @@ } }, "node_modules/@tanstack/router-generator": { - "version": "1.150.0", - "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.150.0.tgz", - "integrity": "sha512-WsA1bN5/I+cxE6V1DkU5ABIPBQxZLlxszElYgnIhs884tzukv76rYMFOy6Xqd51YIFdYtjDrxZbp4/vfkrVCug==", + "version": "1.166.12", + "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.166.12.tgz", + "integrity": "sha512-2HdxSTbCkbU9JeYogKVigIlXoLtIJE1x5rbEov+ZLTPjGCO9kicNQuljqg9Js+u2/ahtWewNrE5u1QCAyxmpIg==", "dev": true, "license": "MIT", "dependencies": { - "@tanstack/router-core": "1.150.0", - "@tanstack/router-utils": "1.143.11", - "@tanstack/virtual-file-routes": "1.145.4", + "@tanstack/router-core": "1.167.4", + "@tanstack/router-utils": "1.161.6", + "@tanstack/virtual-file-routes": "1.161.7", "prettier": "^3.5.0", "recast": "^0.23.11", "source-map": "^0.7.4", @@ -5652,7 +5540,7 @@ "zod": "^3.24.2" }, "engines": { - "node": ">=12" + "node": ">=20.19" }, "funding": { "type": "github", @@ -5660,9 +5548,9 @@ } }, "node_modules/@tanstack/router-plugin": { - "version": "1.150.0", - "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.150.0.tgz", - "integrity": "sha512-k2NLysBXO4Wpt4Oo0xeBhNtFsMwHOU8ud48/cWNWbV89QAjlk0XU5CGNj2JEaFMT0zlF3H/aM5/h0+vYnDjFFA==", + "version": "1.166.13", + "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.166.13.tgz", + "integrity": "sha512-xG3ND3AlMe6DN9PihJAYUbQJevqJvVdzN1QpZbfU1/jkHurL97ynP2yXfmMTh8Qgi1K+SWRko4bi7iZlYP9SUw==", "dev": true, "license": "MIT", "dependencies": { @@ -5672,17 +5560,19 @@ "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", - "@tanstack/router-core": "1.150.0", - "@tanstack/router-generator": "1.150.0", - "@tanstack/router-utils": "1.143.11", - "@tanstack/virtual-file-routes": "1.145.4", - "babel-dead-code-elimination": "^1.0.11", + "@tanstack/router-core": "1.167.4", + "@tanstack/router-generator": "1.166.12", + "@tanstack/router-utils": "1.161.6", + "@tanstack/virtual-file-routes": "1.161.7", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, + "bin": { + "intent": "bin/intent.js" + }, "engines": { - "node": ">=12" + "node": ">=20.19" }, "funding": { "type": "github", @@ -5690,7 +5580,7 @@ }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", - "@tanstack/react-router": "^1.150.0", + "@tanstack/react-router": "^1.167.4", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", "vite-plugin-solid": "^2.11.10", "webpack": ">=5.92.0" @@ -5714,22 +5604,24 @@ } }, "node_modules/@tanstack/router-utils": { - "version": "1.143.11", - "resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.143.11.tgz", - "integrity": "sha512-N24G4LpfyK8dOlnP8BvNdkuxg1xQljkyl6PcrdiPSA301pOjatRT1y8wuCCJZKVVD8gkd0MpCZ0VEjRMGILOtA==", + "version": "1.161.6", + "resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.161.6.tgz", + "integrity": "sha512-nRcYw+w2OEgK6VfjirYvGyPLOK+tZQz1jkYcmH5AjMamQ9PycnlxZF2aEZtPpNoUsaceX2bHptn6Ub5hGXqNvw==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", "ansis": "^4.1.0", + "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" }, "engines": { - "node": ">=12" + "node": ">=20.19" }, "funding": { "type": "github", @@ -5737,9 +5629,9 @@ } }, "node_modules/@tanstack/store": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.8.0.tgz", - "integrity": "sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.9.2.tgz", + "integrity": "sha512-K013lUJEFJK2ofFQ/hZKJUmCnpcV00ebLyOyFOWQvyQHUOZp/iYO84BM6aOGiV81JzwbX0APTVmW8YI7yiG5oA==", "license": "MIT", "funding": { "type": "github", @@ -5747,13 +5639,16 @@ } }, "node_modules/@tanstack/virtual-file-routes": { - "version": "1.145.4", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-file-routes/-/virtual-file-routes-1.145.4.tgz", - "integrity": "sha512-CI75JrfqSluhdGwLssgVeQBaCphgfkMQpi8MCY3UJX1hoGzXa8kHYJcUuIFMOLs1q7zqHy++EVVtMK03osR5wQ==", + "version": "1.161.7", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-file-routes/-/virtual-file-routes-1.161.7.tgz", + "integrity": "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ==", "dev": true, "license": "MIT", + "bin": { + "intent": "bin/intent.js" + }, "engines": { - "node": ">=12" + "node": ">=20.19" }, "funding": { "type": "github", @@ -5973,19 +5868,19 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", - "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", + "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", - "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", "dependencies": { @@ -6041,9 +5936,9 @@ } }, "node_modules/@vector-im/compound-design-tokens": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/@vector-im/compound-design-tokens/-/compound-design-tokens-6.4.3.tgz", - "integrity": "sha512-Zyqag2QCAfpRipLHSX/LEXj6OGFgBpilMQvNdr3a0iXeGASfkM3HZGid077kMRp9lvyHiQfPIs1BdllENwDSNQ==", + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/@vector-im/compound-design-tokens/-/compound-design-tokens-6.10.1.tgz", + "integrity": "sha512-rbb+aUQhB6DppZrfZpwg5o2K4Uzil5QGmAoOSBmAdfgFMwQWkhwk1jnZozbH/vbsqwTLtRdX6tztXRZ1W9sJ0A==", "license": "SEE LICENSE IN README.md", "peerDependencies": { "@types/react": "*", @@ -6088,51 +5983,55 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", - "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", + "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.28.5", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.53", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.18.0" + "@rolldown/pluginutils": "1.0.0-rc.7" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } } }, "node_modules/@vitest/coverage-v8": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.16.tgz", - "integrity": "sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.0.tgz", + "integrity": "sha512-nDWulKeik2bL2Va/Wl4x7DLuTKAXa906iRFooIRPR+huHkcvp9QDkPQ2RJdmjOFrqOqvNfoSQLF68deE3xC3CQ==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.16", - "ast-v8-to-istanbul": "^0.3.8", + "@vitest/utils": "4.1.0", + "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.2.0", - "magicast": "^0.5.1", + "magicast": "^0.5.2", "obug": "^2.1.1", - "std-env": "^3.10.0", + "std-env": "^4.0.0-rc.1", "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.0.16", - "vitest": "4.0.16" + "@vitest/browser": "4.1.0", + "vitest": "4.1.0" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -6196,22 +6095,22 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz", + "integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.4", + "@vitest/spy": "4.1.0", "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" + "magic-string": "^0.30.21" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" }, "peerDependenciesMeta": { "msw": { @@ -6222,6 +6121,16 @@ } } }, + "node_modules/@vitest/mocker/node_modules/@vitest/spy": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz", + "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vitest/mocker/node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -6233,9 +6142,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz", - "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz", + "integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==", "dev": true, "license": "MIT", "dependencies": { @@ -6246,13 +6155,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz", - "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz", + "integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.16", + "@vitest/utils": "4.1.0", "pathe": "^2.0.3" }, "funding": { @@ -6260,13 +6169,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz", - "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz", + "integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.16", + "@vitest/pretty-format": "4.1.0", + "@vitest/utils": "4.1.0", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -6288,13 +6198,14 @@ } }, "node_modules/@vitest/utils": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz", - "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz", + "integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.16", + "@vitest/pretty-format": "4.1.0", + "convert-source-map": "^2.0.0", "tinyrainbow": "^3.0.3" }, "funding": { @@ -6386,19 +6297,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ansi-escapes": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", @@ -6562,15 +6460,15 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.9.tgz", - "integrity": "sha512-dSC6tJeOJxbZrPzPbv5mMd6CMiQ1ugaVXXPRad2fXUSsy1kstFn9XQWemV9VW7Y7kpxgQ/4WMoZfwdH8XSU48w==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", - "js-tokens": "^9.0.1" + "js-tokens": "^10.0.0" } }, "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": { @@ -6584,9 +6482,9 @@ } }, "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, "license": "MIT" }, @@ -6641,9 +6539,9 @@ } }, "node_modules/babel-dead-code-elimination": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.11.tgz", - "integrity": "sha512-mwq3W3e/pKSI6TG8lXMiDWvEi1VXYlSBlJlB3l+I0bAb5u1RNUl88udos85eOPNK3m5EXK9uO7d2g08pesTySQ==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.12.tgz", + "integrity": "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==", "dev": true, "license": "MIT", "dependencies": { @@ -7045,9 +6943,9 @@ } }, "node_modules/cli-spinners": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.3.0.tgz", - "integrity": "sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", + "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", "dev": true, "license": "MIT", "engines": { @@ -7217,9 +7115,9 @@ "license": "MIT" }, "node_modules/commander": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "license": "MIT", "engines": { @@ -7525,6 +7423,16 @@ "node": ">=8" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -7610,13 +7518,6 @@ "node": ">=4" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, "node_modules/electron-to-chromium": { "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", @@ -7624,13 +7525,6 @@ "dev": true, "license": "ISC" }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, "node_modules/empathic": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", @@ -7641,6 +7535,19 @@ "node": ">=14" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -7675,9 +7582,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, "license": "MIT" }, @@ -7825,6 +7732,33 @@ "node": ">=8.6.0" } }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz", + "integrity": "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -7972,23 +7906,6 @@ "node": ">=8" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/formatly": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz", @@ -8137,24 +8054,18 @@ } }, "node_modules/glob": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -8527,37 +8438,23 @@ } }, "node_modules/happy-dom": { - "version": "20.0.11", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.0.11.tgz", - "integrity": "sha512-QsCdAUHAmiDeKeaNojb1OHOPF7NjcWPBR7obdu3NwH2a/oyQaLg5d0aaCy/9My6CdPChYF07dvz5chaXBGaD4g==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.5.0.tgz", + "integrity": "sha512-VQe+Q5CYiGOgcCERXhcfNsbnrN92FDEKciMH/x6LppU9dd0j4aTjCTlqONFOIMcAm/5JxS3+utowbXV1OoFr+g==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "^20.0.0", + "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", - "whatwg-mimetype": "^3.0.0" + "@types/ws": "^8.18.1", + "entities": "^4.5.0", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.18.3" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/happy-dom/node_modules/@types/node": { - "version": "20.19.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", - "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/happy-dom/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -8626,9 +8523,9 @@ } }, "node_modules/i18next": { - "version": "25.7.4", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.4.tgz", - "integrity": "sha512-hRkpEblXXcXSNbw8mBNq9042OEetgyB/ahc/X17uV/khPwzV+uB8RHceHh3qavyrkPJvmXFKXME2Sy1E0KjAfw==", + "version": "25.8.18", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.18.tgz", + "integrity": "sha512-lzY5X83BiL5AP77+9DydbrqkQHFN9hUzWGjqjLpPcp5ZOzuu1aSoKaU3xbBLSjWx9dAzW431y+d+aogxOZaKRA==", "funding": [ { "type": "individual", @@ -8645,7 +8542,7 @@ ], "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4" + "@babel/runtime": "^7.28.6" }, "peerDependencies": { "typescript": "^5" @@ -8657,28 +8554,28 @@ } }, "node_modules/i18next-cli": { - "version": "1.36.1", - "resolved": "https://registry.npmjs.org/i18next-cli/-/i18next-cli-1.36.1.tgz", - "integrity": "sha512-q4lRuqVPTNI6Ip5YFCRNNGiTh/s/5nfl18coOnjDu7Cb1BiJeRXShFGL5Tcka4ii0k5ndOMjxIAkoNN6EXCwyQ==", + "version": "1.50.3", + "resolved": "https://registry.npmjs.org/i18next-cli/-/i18next-cli-1.50.3.tgz", + "integrity": "sha512-sXyEydTInPfWDGzbGEpSTIrIe0vNl0aLd4qw6lqk+g21CNImZ9t+eTPSkDRRBReBUOdgGC+TS2myqxZmwCPYIA==", "dev": true, "license": "MIT", "dependencies": { - "@croct/json5-parser": "0.2.2", - "@swc/core": "1.15.4", - "chalk": "5.6.2", - "chokidar": "5.0.0", - "commander": "14.0.2", - "execa": "9.6.1", - "glob": "13.0.0", - "i18next-resources-for-ts": "2.0.0", - "inquirer": "13.1.0", - "jiti": "2.6.1", - "jsonc-parser": "3.3.1", - "minimatch": "10.1.1", - "ora": "9.0.0", - "react": "^19.2.3", - "react-i18next": "^16.5.1", - "swc-walk": "1.0.1" + "@croct/json5-parser": "^0.2.2", + "@swc/core": "^1.15.18", + "chokidar": "^5.0.0", + "commander": "^14.0.3", + "execa": "^9.6.1", + "glob": "^13.0.6", + "i18next-resources-for-ts": "^2.0.2", + "inquirer": "^13.3.2", + "jiti": "^2.6.1", + "jsonc-parser": "^3.3.1", + "magic-string": "^0.30.21", + "minimatch": "^10.2.4", + "ora": "^9.3.0", + "react": "^19.2.4", + "react-i18next": "^16.5.8", + "yaml": "^2.8.2" }, "bin": { "i18next-cli": "dist/esm/cli.js" @@ -8687,19 +8584,6 @@ "node": ">=22" } }, - "node_modules/i18next-cli/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/i18next-cli/node_modules/chokidar": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", @@ -8716,24 +8600,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/i18next-cli/node_modules/glob": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", - "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "path-scurry": "^2.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/i18next-cli/node_modules/readdirp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", @@ -8749,14 +8615,14 @@ } }, "node_modules/i18next-resources-for-ts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/i18next-resources-for-ts/-/i18next-resources-for-ts-2.0.0.tgz", - "integrity": "sha512-RvATolbJlxrwpZh2+R7ZcNtg0ewmXFFx6rdu9i2bUEBvn6ThgA82rxDe3rJQa3hFS0SopX0qPaABqVDN3TUVpw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/i18next-resources-for-ts/-/i18next-resources-for-ts-2.0.2.tgz", + "integrity": "sha512-BGPebhvjrvU+pgKNsIzb3cxl6/8LJw1pxNkIlUpnxCC4SXjbIwXeHuNxFnOkTAryRfMqYf81lPjYD2gngDyOfQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", - "@swc/core": "^1.15.3", + "@babel/runtime": "^7.28.6", + "@swc/core": "^1.15.18", "chokidar": "^5.0.0", "yaml": "^2.8.2" }, @@ -8795,9 +8661,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", - "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "dev": true, "license": "MIT", "dependencies": { @@ -8901,16 +8767,16 @@ "license": "ISC" }, "node_modules/inquirer": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-13.1.0.tgz", - "integrity": "sha512-4vv4GS/9HLnn0radvmHlXUXiNkd2gYCBQ4U1rxZWBJDisu2Z06bzUM9CFU8pcu1vwuAQjo6O+CFiqCYNsEi6qQ==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-13.3.2.tgz", + "integrity": "sha512-bh/OjBGxNR9qvfQj1n5bxtIF58mbOTp2InN5dKuwUK03dXcDGFsjlDinQRuXMZ4EGiJaFieUWHCAaxH2p7iUBw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.2", - "@inquirer/core": "^11.1.0", - "@inquirer/prompts": "^8.1.0", - "@inquirer/type": "^4.0.2", + "@inquirer/ansi": "^2.0.4", + "@inquirer/core": "^11.1.7", + "@inquirer/prompts": "^8.3.2", + "@inquirer/type": "^4.0.4", "mute-stream": "^3.0.0", "run-async": "^4.0.6", "rxjs": "^7.8.2" @@ -8928,9 +8794,9 @@ } }, "node_modules/inquirer/node_modules/@inquirer/ansi": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.2.tgz", - "integrity": "sha512-SYLX05PwJVnW+WVegZt1T4Ip1qba1ik+pNJPDiqvk6zS5Y/i8PhRzLpGEtVd7sW0G8cMtkD8t4AZYhQwm8vnww==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.4.tgz", + "integrity": "sha512-DpcZrQObd7S0R/U3bFdkcT5ebRwbTTC4D3tCc1vsJizmgPLxNJBo+AAFmrZwe8zk30P2QzgzGWZ3Q9uJwWuhIg==", "dev": true, "license": "MIT", "engines": { @@ -8938,16 +8804,16 @@ } }, "node_modules/inquirer/node_modules/@inquirer/checkbox": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.0.3.tgz", - "integrity": "sha512-xtQP2eXMFlOcAhZ4ReKP2KZvDIBb1AnCfZ81wWXG3DXLVH0f0g4obE0XDPH+ukAEMRcZT0kdX2AS1jrWGXbpxw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.1.2.tgz", + "integrity": "sha512-PubpMPO2nJgMufkoB3P2wwxNXEMUXnBIKi/ACzDUYfaoPuM7gSTmuxJeMscoLVEsR4qqrCMf5p0SiYGWnVJ8kw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.2", - "@inquirer/core": "^11.1.0", - "@inquirer/figures": "^2.0.2", - "@inquirer/type": "^4.0.2" + "@inquirer/ansi": "^2.0.4", + "@inquirer/core": "^11.1.7", + "@inquirer/figures": "^2.0.4", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -8962,14 +8828,14 @@ } }, "node_modules/inquirer/node_modules/@inquirer/confirm": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.3.tgz", - "integrity": "sha512-lyEvibDFL+NA5R4xl8FUmNhmu81B+LDL9L/MpKkZlQDJZXzG8InxiqYxiAlQYa9cqLLhYqKLQwZqXmSTqCLjyw==", + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.10.tgz", + "integrity": "sha512-tiNyA73pgpQ0FQ7axqtoLUe4GDYjNCDcVsbgcA5anvwg2z6i+suEngLKKJrWKJolT//GFPZHwN30binDIHgSgQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.0", - "@inquirer/type": "^4.0.2" + "@inquirer/core": "^11.1.7", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -8984,19 +8850,19 @@ } }, "node_modules/inquirer/node_modules/@inquirer/core": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.0.tgz", - "integrity": "sha512-+jD/34T1pK8M5QmZD/ENhOfXdl9Zr+BrQAUc5h2anWgi7gggRq15ZbiBeLoObj0TLbdgW7TAIQRU2boMc9uOKQ==", + "version": "11.1.7", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.7.tgz", + "integrity": "sha512-1BiBNDk9btIwYIzNZpkikIHXWeNzNncJePPqwDyVMhXhD1ebqbpn1mKGctpoqAbzywZfdG0O4tvmsGIcOevAPQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.2", - "@inquirer/figures": "^2.0.2", - "@inquirer/type": "^4.0.2", + "@inquirer/ansi": "^2.0.4", + "@inquirer/figures": "^2.0.4", + "@inquirer/type": "^4.0.4", "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^9.0.2" + "signal-exit": "^4.1.0" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -9011,15 +8877,15 @@ } }, "node_modules/inquirer/node_modules/@inquirer/editor": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.0.3.tgz", - "integrity": "sha512-wYyQo96TsAqIciP/r5D3cFeV8h4WqKQ/YOvTg5yOfP2sqEbVVpbxPpfV3LM5D0EP4zUI3EZVHyIUIllnoIa8OQ==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.0.10.tgz", + "integrity": "sha512-VJx4XyaKea7t8hEApTw5dxeIyMtWXre2OiyJcICCRZI4hkoHsMoCnl/KbUnJJExLbH9csLLHMVR144ZhFE1CwA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.0", - "@inquirer/external-editor": "^2.0.2", - "@inquirer/type": "^4.0.2" + "@inquirer/core": "^11.1.7", + "@inquirer/external-editor": "^2.0.4", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -9034,14 +8900,14 @@ } }, "node_modules/inquirer/node_modules/@inquirer/expand": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.0.3.tgz", - "integrity": "sha512-2oINvuL27ujjxd95f6K2K909uZOU2x1WiAl7Wb1X/xOtL8CgQ1kSxzykIr7u4xTkXkXOAkCuF45T588/YKee7w==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.0.10.tgz", + "integrity": "sha512-fC0UHJPXsTRvY2fObiwuQYaAnHrp3aDqfwKUJSdfpgv18QUG054ezGbaRNStk/BKD5IPijeMKWej8VV8O5Q/eQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.0", - "@inquirer/type": "^4.0.2" + "@inquirer/core": "^11.1.7", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -9056,14 +8922,14 @@ } }, "node_modules/inquirer/node_modules/@inquirer/external-editor": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-2.0.2.tgz", - "integrity": "sha512-X/fMXK7vXomRWEex1j8mnj7s1mpnTeP4CO/h2gysJhHLT2WjBnLv4ZQEGpm/kcYI8QfLZ2fgW+9kTKD+jeopLg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-2.0.4.tgz", + "integrity": "sha512-Prenuv9C1PHj2Itx0BcAOVBTonz02Hc2Nd2DbU67PdGUaqn0nPCnV34oDyyoaZHnmfRxkpuhh/u51ThkrO+RdA==", "dev": true, "license": "MIT", "dependencies": { "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" + "iconv-lite": "^0.7.2" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -9078,9 +8944,9 @@ } }, "node_modules/inquirer/node_modules/@inquirer/figures": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.2.tgz", - "integrity": "sha512-qXm6EVvQx/FmnSrCWCIGtMHwqeLgxABP8XgcaAoywsL0NFga9gD5kfG0gXiv80GjK9Hsoz4pgGwF/+CjygyV9A==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.4.tgz", + "integrity": "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ==", "dev": true, "license": "MIT", "engines": { @@ -9088,14 +8954,14 @@ } }, "node_modules/inquirer/node_modules/@inquirer/input": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.0.3.tgz", - "integrity": "sha512-4R0TdWl53dtp79Vs6Df2OHAtA2FVNqya1hND1f5wjHWxZJxwDMSNB1X5ADZJSsQKYAJ5JHCTO+GpJZ42mK0Otw==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.0.10.tgz", + "integrity": "sha512-nvZ6qEVeX/zVtZ1dY2hTGDQpVGD3R7MYPLODPgKO8Y+RAqxkrP3i/3NwF3fZpLdaMiNuK0z2NaYIx9tPwiSegQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.0", - "@inquirer/type": "^4.0.2" + "@inquirer/core": "^11.1.7", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -9110,14 +8976,14 @@ } }, "node_modules/inquirer/node_modules/@inquirer/number": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.0.3.tgz", - "integrity": "sha512-TjQLe93GGo5snRlu83JxE38ZPqj5ZVggL+QqqAF2oBA5JOJoxx25GG3EGH/XN/Os5WOmKfO8iLVdCXQxXRZIMQ==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.0.10.tgz", + "integrity": "sha512-Ht8OQstxiS3APMGjHV0aYAjRAysidWdwurWEo2i8yI5xbhOBWqizT0+MU1S2GCcuhIBg+3SgWVjEoXgfhY+XaA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.0", - "@inquirer/type": "^4.0.2" + "@inquirer/core": "^11.1.7", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -9132,15 +8998,15 @@ } }, "node_modules/inquirer/node_modules/@inquirer/password": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.0.3.tgz", - "integrity": "sha512-rCozGbUMAHedTeYWEN8sgZH4lRCdgG/WinFkit6ZPsp8JaNg2T0g3QslPBS5XbpORyKP/I+xyBO81kFEvhBmjA==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.0.10.tgz", + "integrity": "sha512-QbNyvIE8q2GTqKLYSsA8ATG+eETo+m31DSR0+AU7x3d2FhaTWzqQek80dj3JGTo743kQc6mhBR0erMjYw5jQ0A==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.2", - "@inquirer/core": "^11.1.0", - "@inquirer/type": "^4.0.2" + "@inquirer/ansi": "^2.0.4", + "@inquirer/core": "^11.1.7", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -9155,22 +9021,22 @@ } }, "node_modules/inquirer/node_modules/@inquirer/prompts": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.1.0.tgz", - "integrity": "sha512-LsZMdKcmRNF5LyTRuZE5nWeOjganzmN3zwbtNfcs6GPh3I2TsTtF1UYZlbxVfhxd+EuUqLGs/Lm3Xt4v6Az1wA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.3.2.tgz", + "integrity": "sha512-yFroiSj2iiBFlm59amdTvAcQFvWS6ph5oKESls/uqPBect7rTU2GbjyZO2DqxMGuIwVA8z0P4K6ViPcd/cp+0w==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^5.0.3", - "@inquirer/confirm": "^6.0.3", - "@inquirer/editor": "^5.0.3", - "@inquirer/expand": "^5.0.3", - "@inquirer/input": "^5.0.3", - "@inquirer/number": "^4.0.3", - "@inquirer/password": "^5.0.3", - "@inquirer/rawlist": "^5.1.0", - "@inquirer/search": "^4.0.3", - "@inquirer/select": "^5.0.3" + "@inquirer/checkbox": "^5.1.2", + "@inquirer/confirm": "^6.0.10", + "@inquirer/editor": "^5.0.10", + "@inquirer/expand": "^5.0.10", + "@inquirer/input": "^5.0.10", + "@inquirer/number": "^4.0.10", + "@inquirer/password": "^5.0.10", + "@inquirer/rawlist": "^5.2.6", + "@inquirer/search": "^4.1.6", + "@inquirer/select": "^5.1.2" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -9185,14 +9051,14 @@ } }, "node_modules/inquirer/node_modules/@inquirer/rawlist": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.1.0.tgz", - "integrity": "sha512-yUCuVh0jW026Gr2tZlG3kHignxcrLKDR3KBp+eUgNz+BAdSeZk0e18yt2gyBr+giYhj/WSIHCmPDOgp1mT2niQ==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.2.6.tgz", + "integrity": "sha512-jfw0MLJ5TilNsa9zlJ6nmRM0ZFVZhhTICt4/6CU2Dv1ndY7l3sqqo1gIYZyMMDw0LvE1u1nzJNisfHEhJIxq5w==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.0", - "@inquirer/type": "^4.0.2" + "@inquirer/core": "^11.1.7", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -9207,15 +9073,15 @@ } }, "node_modules/inquirer/node_modules/@inquirer/search": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.0.3.tgz", - "integrity": "sha512-lzqVw0YwuKYetk5VwJ81Ba+dyVlhseHPx9YnRKQgwXdFS0kEavCz2gngnNhnMIxg8+j1N/rUl1t5s1npwa7bqg==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.1.6.tgz", + "integrity": "sha512-3/6kTRae98hhDevENScy7cdFEuURnSpM3JbBNg8yfXLw88HgTOl+neUuy/l9W0No5NzGsLVydhBzTIxZP7yChQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.0", - "@inquirer/figures": "^2.0.2", - "@inquirer/type": "^4.0.2" + "@inquirer/core": "^11.1.7", + "@inquirer/figures": "^2.0.4", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -9230,16 +9096,16 @@ } }, "node_modules/inquirer/node_modules/@inquirer/select": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.0.3.tgz", - "integrity": "sha512-M+ynbwS0ecQFDYMFrQrybA0qL8DV0snpc4kKevCCNaTpfghsRowRY7SlQBeIYNzHqXtiiz4RG9vTOeb/udew7w==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.1.2.tgz", + "integrity": "sha512-kTK8YIkHV+f02y7bWCh7E0u2/11lul5WepVTclr3UMBtBr05PgcZNWfMa7FY57ihpQFQH/spLMHTcr0rXy50tA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.2", - "@inquirer/core": "^11.1.0", - "@inquirer/figures": "^2.0.2", - "@inquirer/type": "^4.0.2" + "@inquirer/ansi": "^2.0.4", + "@inquirer/core": "^11.1.7", + "@inquirer/figures": "^2.0.4", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -9254,9 +9120,9 @@ } }, "node_modules/inquirer/node_modules/@inquirer/type": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.2.tgz", - "integrity": "sha512-cae7mzluplsjSdgFA6ACLygb5jC8alO0UUnFPyu0E7tNRPrL+q/f8VcSXp+cjZQ7l5CMpDpi2G1+IQvkOiL1Lw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.4.tgz", + "integrity": "sha512-PamArxO3cFJZoOzspzo6cxVlLeIftyBsZw/S9bKY5DzxqJVZgjoj1oP8d0rskKtp7sZxBycsoer1g6UeJV1BBA==", "dev": true, "license": "MIT", "engines": { @@ -9271,26 +9137,6 @@ } } }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, "node_modules/inquirer/node_modules/mute-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", @@ -9301,42 +9147,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/inquirer/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inquirer/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -9679,21 +9489,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -9708,22 +9503,6 @@ "node": ">=8" } }, - "node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -9860,6 +9639,267 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -9968,9 +10008,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, @@ -10159,14 +10199,14 @@ } }, "node_modules/magicast": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", - "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, @@ -10301,21 +10341,44 @@ } }, "node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimatch/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -10327,11 +10390,11 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -10344,15 +10407,15 @@ "license": "MIT" }, "node_modules/msw": { - "version": "2.12.4", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.4.tgz", - "integrity": "sha512-rHNiVfTyKhzc0EjoXUBVGteNKBevdjOlVC6GlIRXpy+/3LHEIGRovnB5WPjcvmNODVQ1TNFnoa7wsGbd0V3epg==", + "version": "2.12.8", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.8.tgz", + "integrity": "sha512-KOriJUhjefCO+liF7Ie1KlSXcBAQEzuLhPZ4EKuEUSEmAR4YhuuzT9YuGxTipjqDrg6eWQ6oMoGVhvEnqukFGg==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "@inquirer/confirm": "^5.0.0", - "@mswjs/interceptors": "^0.40.0", + "@mswjs/interceptors": "^0.41.0", "@open-draft/deferred-promise": "^2.2.0", "@types/statuses": "^2.0.6", "cookie": "^1.0.2", @@ -10362,7 +10425,7 @@ "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", - "rettime": "^0.7.0", + "rettime": "^0.10.1", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.0", @@ -10633,9 +10696,9 @@ } }, "node_modules/ora": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-9.0.0.tgz", - "integrity": "sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-9.3.0.tgz", + "integrity": "sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==", "dev": true, "license": "MIT", "dependencies": { @@ -10645,9 +10708,8 @@ "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", - "stdin-discarder": "^0.2.2", - "string-width": "^8.1.0", - "strip-ansi": "^7.1.2" + "stdin-discarder": "^0.3.1", + "string-width": "^8.1.0" }, "engines": { "node": ">=20" @@ -10687,9 +10749,9 @@ } }, "node_modules/ora/node_modules/string-width": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", + "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", "dev": true, "license": "MIT", "dependencies": { @@ -10958,9 +11020,9 @@ } }, "node_modules/path-scurry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", - "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -10968,16 +11030,16 @@ "minipass": "^7.1.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -11059,9 +11121,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "dev": true, "funding": [ { @@ -11215,9 +11277,9 @@ } }, "node_modules/postcss-nesting": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", - "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-14.0.0.tgz", + "integrity": "sha512-YGFOfVrjxYfeGTS5XctP1WCI5hu8Lr9SmntjfRC+iX5hCihEO+QZl9Ra+pkjqkgoVdDKvb2JccpElcowhZtzpw==", "dev": true, "funding": [ { @@ -11231,12 +11293,12 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/selector-resolve-nested": "^3.1.0", - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" + "@csstools/selector-resolve-nested": "^4.0.0", + "@csstools/selector-specificity": "^6.0.0", + "postcss-selector-parser": "^7.1.1" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { "postcss": "^8.4" @@ -11357,18 +11419,18 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-docgen": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-8.0.2.tgz", - "integrity": "sha512-+NRMYs2DyTP4/tqWz371Oo50JqmWltR1h2gcdgUMAWZJIAvrd0/SqlCfx7tpzpl/s36rzw6qH2MjoNrxtRNYhA==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-8.0.3.tgz", + "integrity": "sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w==", "dev": true, "license": "MIT", "dependencies": { @@ -11398,21 +11460,21 @@ } }, "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.3" + "react": "^19.2.4" } }, "node_modules/react-i18next": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.5.3.tgz", - "integrity": "sha512-fo+/NNch37zqxOzlBYrWMx0uy/yInPkRfjSuy4lqKdaecR17nvCHnEUt3QyzA8XjQ2B/0iW/5BhaHR3ZmukpGw==", + "version": "16.5.8", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.5.8.tgz", + "integrity": "sha512-2ABeHHlakxVY+LSirD+OiERxFL6+zip0PaHo979bgwzeHg27Sqc82xxXWIrSFmfWX0ZkrvXMHwhsi/NGUf5VQg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.4", @@ -11444,16 +11506,6 @@ "license": "MIT", "peer": true }, - "node_modules/react-refresh": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", - "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-remove-scroll": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", @@ -11725,9 +11777,9 @@ } }, "node_modules/rettime": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.7.0.tgz", - "integrity": "sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.10.1.tgz", + "integrity": "sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==", "dev": true, "license": "MIT" }, @@ -11769,66 +11821,47 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/glob": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", - "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "path-scurry": "^2.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.5.tgz", - "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==", + "node_modules/rolldown": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.9.tgz", + "integrity": "sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@oxc-project/types": "=0.115.0", + "@rolldown/pluginutils": "1.0.0-rc.9" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.5", - "@rollup/rollup-android-arm64": "4.53.5", - "@rollup/rollup-darwin-arm64": "4.53.5", - "@rollup/rollup-darwin-x64": "4.53.5", - "@rollup/rollup-freebsd-arm64": "4.53.5", - "@rollup/rollup-freebsd-x64": "4.53.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.5", - "@rollup/rollup-linux-arm-musleabihf": "4.53.5", - "@rollup/rollup-linux-arm64-gnu": "4.53.5", - "@rollup/rollup-linux-arm64-musl": "4.53.5", - "@rollup/rollup-linux-loong64-gnu": "4.53.5", - "@rollup/rollup-linux-ppc64-gnu": "4.53.5", - "@rollup/rollup-linux-riscv64-gnu": "4.53.5", - "@rollup/rollup-linux-riscv64-musl": "4.53.5", - "@rollup/rollup-linux-s390x-gnu": "4.53.5", - "@rollup/rollup-linux-x64-gnu": "4.53.5", - "@rollup/rollup-linux-x64-musl": "4.53.5", - "@rollup/rollup-openharmony-arm64": "4.53.5", - "@rollup/rollup-win32-arm64-msvc": "4.53.5", - "@rollup/rollup-win32-ia32-msvc": "4.53.5", - "@rollup/rollup-win32-x64-gnu": "4.53.5", - "@rollup/rollup-win32-x64-msvc": "4.53.5", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm64": "1.0.0-rc.9", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.9", + "@rolldown/binding-darwin-x64": "1.0.0-rc.9", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.9", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.9", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.9", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.9", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.9", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.9", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.9", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.9" } }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.9.tgz", + "integrity": "sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==", + "dev": true, + "license": "MIT" + }, "node_modules/run-applescript": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", @@ -11931,9 +11964,9 @@ } }, "node_modules/seroval-plugins": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.4.0.tgz", - "integrity": "sha512-zir1aWzoiax6pbBVjoYVd0O1QQXgIL3eVGBMsBsNmM8Ukq90yGaWlfx0AB9dTS8GPqrOrbXn79vmItCUP9U3BQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.1.tgz", + "integrity": "sha512-4FbuZ/TMl02sqv0RTFexu0SP6V+ywaIe5bAWCCEik0fk17BhALgwvUDVF7e3Uvf9pxmwCEJsRPmlkUE6HdzLAw==", "license": "MIT", "engines": { "node": ">=10" @@ -12131,16 +12164,16 @@ } }, "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", "dev": true, "license": "MIT" }, "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.1.tgz", + "integrity": "sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==", "dev": true, "license": "MIT", "engines": { @@ -12151,14 +12184,14 @@ } }, "node_modules/storybook": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.1.11.tgz", - "integrity": "sha512-pKP5jXJYM4OjvNklGuHKO53wOCAwfx79KvZyOWHoi9zXUH5WVMFUe/ZfWyxXG/GTcj0maRgHGUjq/0I43r0dDQ==", + "version": "10.2.19", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.2.19.tgz", + "integrity": "sha512-UUm5eGSm6BLhkcFP0WbxkmAHJZfVN2ViLpIZOqiIPS++q32VYn+CLFC0lrTYTDqYvaG7i4BK4uowXYujzE4NdQ==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "^2.0.0", + "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", @@ -12166,7 +12199,7 @@ "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", - "semver": "^7.6.2", + "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, @@ -12213,70 +12246,6 @@ "dev": true, "license": "MIT" }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", @@ -12293,20 +12262,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-ansi/node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -12447,19 +12402,6 @@ "tslib": "^2.0.3" } }, - "node_modules/swc-walk": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/swc-walk/-/swc-walk-1.0.1.tgz", - "integrity": "sha512-bHR0Zs+MdFxKKq5QXmPZuvbXybAJh4wV56zZT7n7hQC55eHpGvL1TeeHxNwL5XlXYSAXKK57GsKY0aEttGDuWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn-walk": "^8.3.4" - }, - "engines": { - "node": ">=20.2.0" - } - }, "node_modules/sync-fetch": { "version": "0.6.0-2", "resolved": "https://registry.npmjs.org/sync-fetch/-/sync-fetch-0.6.0-2.tgz", @@ -12911,9 +12853,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, @@ -13127,17 +13069,17 @@ } }, "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.0.tgz", + "integrity": "sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", + "@oxc-project/runtime": "0.115.0", + "lightningcss": "^1.32.0", "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.9", "tinyglobby": "^0.2.15" }, "bin": { @@ -13154,9 +13096,10 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.0.0-alpha.31", + "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", - "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", @@ -13169,15 +13112,18 @@ "@types/node": { "optional": true }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, "jiti": { "optional": true }, "less": { "optional": true }, - "lightningcss": { - "optional": true - }, "sass": { "optional": true }, @@ -13214,31 +13160,31 @@ } }, "node_modules/vitest": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", - "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz", + "integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.0.16", - "@vitest/mocker": "4.0.16", - "@vitest/pretty-format": "4.0.16", - "@vitest/runner": "4.0.16", - "@vitest/snapshot": "4.0.16", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", - "es-module-lexer": "^1.7.0", - "expect-type": "^1.2.2", + "@vitest/expect": "4.1.0", + "@vitest/mocker": "4.1.0", + "@vitest/pretty-format": "4.1.0", + "@vitest/runner": "4.1.0", + "@vitest/snapshot": "4.1.0", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", - "std-env": "^3.10.0", + "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", "why-is-node-running": "^2.3.0" }, "bin": { @@ -13254,12 +13200,13 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.16", - "@vitest/browser-preview": "4.0.16", - "@vitest/browser-webdriverio": "4.0.16", - "@vitest/ui": "4.0.16", + "@vitest/browser-playwright": "4.1.0", + "@vitest/browser-preview": "4.1.0", + "@vitest/browser-webdriverio": "4.1.0", + "@vitest/ui": "4.1.0", "happy-dom": "*", - "jsdom": "*" + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -13288,58 +13235,34 @@ }, "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, "node_modules/vitest/node_modules/@vitest/expect": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", - "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz", + "integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==", "dev": true, "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.0.0", + "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", - "chai": "^6.2.1", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "chai": "^6.2.2", "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", - "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.0.16", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, "node_modules/vitest/node_modules/@vitest/spy": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", - "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz", + "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==", "dev": true, "license": "MIT", "funding": { @@ -13356,16 +13279,6 @@ "node": ">=18" } }, - "node_modules/vitest/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", @@ -13485,70 +13398,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index b4a80273b..7e39b47fc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,61 +22,61 @@ "@fontsource/inter": "^5.2.8", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", - "@tanstack/react-query": "^5.90.16", - "@tanstack/react-router": "^1.150.0", - "@vector-im/compound-design-tokens": "6.4.3", + "@tanstack/react-query": "^5.90.21", + "@tanstack/react-router": "^1.167.4", + "@vector-im/compound-design-tokens": "6.10.1", "@vector-im/compound-web": "^8.3.5", "@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4", "classnames": "^2.5.1", "date-fns": "^4.1.0", - "i18next": "^25.7.4", - "react": "^19.2.3", - "react-dom": "^19.2.3", - "react-i18next": "^16.5.3", + "i18next": "^25.8.18", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-i18next": "^16.5.8", "swagger-ui-dist": "^5.31.0", "valibot": "^1.2.0", "vaul": "^1.1.2" }, "devDependencies": { - "@biomejs/biome": "^2.3.9", + "@biomejs/biome": "^2.4.2", "@browser-logos/chrome": "^2.0.0", "@browser-logos/firefox": "^3.0.10", "@browser-logos/safari": "^2.1.0", - "@graphql-codegen/cli": "^6.1.0", - "@graphql-codegen/client-preset": "^5.2.2", + "@graphql-codegen/cli": "^6.1.2", + "@graphql-codegen/client-preset": "^5.2.3", "@graphql-codegen/typescript-msw": "^3.0.1", - "@storybook/addon-docs": "^10.1.11", - "@storybook/react-vite": "^10.1.11", - "@tanstack/react-query-devtools": "^5.91.2", - "@tanstack/react-router-devtools": "^1.150.0", - "@tanstack/router-plugin": "^1.150.0", + "@storybook/addon-docs": "^10.2.19", + "@storybook/react-vite": "^10.2.19", + "@tanstack/react-query-devtools": "^5.91.3", + "@tanstack/react-router-devtools": "^1.166.9", + "@tanstack/router-plugin": "^1.166.13", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", "@testing-library/user-event": "^14.6.1", - "@types/node": "^25.0.3", - "@types/react": "19.2.7", + "@types/node": "^25.3.0", + "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", - "@vitejs/plugin-react": "^5.1.2", - "@vitest/coverage-v8": "^4.0.16", + "@vitejs/plugin-react": "^6.0.1", + "@vitest/coverage-v8": "^4.1.0", "autoprefixer": "^10.4.23", "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.12.0", - "happy-dom": "^20.0.11", - "i18next-cli": "^1.36.1", + "happy-dom": "^20.5.0", + "i18next-cli": "^1.50.3", "knip": "^5.81.0", - "msw": "^2.12.4", + "msw": "^2.12.8", "msw-storybook-addon": "^2.0.6", "postcss": "^8.5.6", "postcss-import": "^16.1.1", - "postcss-nesting": "^13.0.2", + "postcss-nesting": "^14.0.0", "rimraf": "^6.1.2", "storybook": "^10.1.11", "tailwindcss": "^3.4.19", "tinyglobby": "^0.2.15", "typescript": "^5.9.3", - "vite": "7.3.1", + "vite": "8.0.0", "vite-plugin-graphql-codegen": "^3.8.0", "vitest": "^4.0.15" }, diff --git a/frontend/src/components/AccountDeleteButton.tsx b/frontend/src/components/AccountDeleteButton.tsx index 6f1b80bec..a5ab30991 100644 --- a/frontend/src/components/AccountDeleteButton.tsx +++ b/frontend/src/components/AccountDeleteButton.tsx @@ -109,8 +109,8 @@ const AccountDeleteButton: React.FC = (props) => { setAllowSubmitting(false); }, [isMaybeValid]); - const onPasswordChange = useCallback( - (e: React.ChangeEvent) => { + const onPasswordInput = useCallback( + (e: React.InputEvent) => { // We don't know if the password is correct, so we consider the form as // valid if the field is not empty setIsMaybeValid(e.currentTarget.value !== ""); @@ -118,8 +118,8 @@ const AccountDeleteButton: React.FC = (props) => { [], ); - const onMxidChange = useCallback( - (e: React.ChangeEvent) => { + const onMxidInput = useCallback( + (e: React.InputEvent) => { setIsMaybeValid(e.currentTarget.value === user.matrix.mxid); }, [user.matrix.mxid], @@ -208,7 +208,7 @@ const AccountDeleteButton: React.FC = (props) => { @@ -232,7 +232,7 @@ const AccountDeleteButton: React.FC = (props) => { diff --git a/frontend/src/entrypoints/templates.ts b/frontend/src/entrypoints/templates.ts index 2e0cf93d2..77587ee58 100644 --- a/frontend/src/entrypoints/templates.ts +++ b/frontend/src/entrypoints/templates.ts @@ -10,7 +10,7 @@ // Ideally later on we could find a way to hydrate full React components instead // of doing this, as this can very quickly get out of hands. -const VALID_USERNAME_RE = /^\s*([a-z0-9.=_/-]+|@[a-z0-9.=_/-]+(:.*)?)\s*$/g; +const VALID_USERNAME_RE = /^\s*([a-z0-9.=_/+-]+|@[a-z0-9.=_/+-]+(:.*)?)\s*$/g; /** Grab the nearest error message inserted by the templates by error kind and code */ function grabErrorMessage( diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 4338a8008..bd0bdbc7f 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -117,22 +117,22 @@ const AccountSessionsBrowsersRoute = AccountSessionsBrowsersRouteImport.update({ } as any) export interface FileRoutesByFullPath { + '/': typeof AccountIndexRoute '/reset-cross-signing': typeof ResetCrossSigningRouteWithChildren '/clients/$id': typeof ClientsIdRoute '/devices/$': typeof DevicesSplatRoute '/reset-cross-signing/cancelled': typeof ResetCrossSigningCancelledRoute '/reset-cross-signing/success': typeof ResetCrossSigningSuccessRoute '/sessions/$id': typeof SessionsIdRoute - '/': typeof AccountIndexRoute '/reset-cross-signing/': typeof ResetCrossSigningIndexRoute '/sessions/browsers': typeof AccountSessionsBrowsersRoute '/emails/$id/in-use': typeof EmailsIdInUseRoute '/emails/$id/verify': typeof EmailsIdVerifyRoute '/password/change/success': typeof PasswordChangeSuccessRoute - '/plan': typeof AccountPlanIndexRoute - '/sessions': typeof AccountSessionsIndexRoute - '/password/change': typeof PasswordChangeIndexRoute - '/password/recovery': typeof PasswordRecoveryIndexRoute + '/plan/': typeof AccountPlanIndexRoute + '/sessions/': typeof AccountSessionsIndexRoute + '/password/change/': typeof PasswordChangeIndexRoute + '/password/recovery/': typeof PasswordRecoveryIndexRoute } export interface FileRoutesByTo { '/clients/$id': typeof ClientsIdRoute @@ -174,22 +174,22 @@ export interface FileRoutesById { export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: + | '/' | '/reset-cross-signing' | '/clients/$id' | '/devices/$' | '/reset-cross-signing/cancelled' | '/reset-cross-signing/success' | '/sessions/$id' - | '/' | '/reset-cross-signing/' | '/sessions/browsers' | '/emails/$id/in-use' | '/emails/$id/verify' | '/password/change/success' - | '/plan' - | '/sessions' - | '/password/change' - | '/password/recovery' + | '/plan/' + | '/sessions/' + | '/password/change/' + | '/password/recovery/' fileRoutesByTo: FileRoutesByTo to: | '/clients/$id' @@ -253,7 +253,7 @@ declare module '@tanstack/react-router' { '/_account': { id: '/_account' path: '' - fullPath: '' + fullPath: '/' preLoaderRoute: typeof AccountRouteImport parentRoute: typeof rootRouteImport } @@ -309,28 +309,28 @@ declare module '@tanstack/react-router' { '/password/recovery/': { id: '/password/recovery/' path: '/password/recovery' - fullPath: '/password/recovery' + fullPath: '/password/recovery/' preLoaderRoute: typeof PasswordRecoveryIndexRouteImport parentRoute: typeof rootRouteImport } '/password/change/': { id: '/password/change/' path: '/password/change' - fullPath: '/password/change' + fullPath: '/password/change/' preLoaderRoute: typeof PasswordChangeIndexRouteImport parentRoute: typeof rootRouteImport } '/_account/sessions/': { id: '/_account/sessions/' path: '/sessions' - fullPath: '/sessions' + fullPath: '/sessions/' preLoaderRoute: typeof AccountSessionsIndexRouteImport parentRoute: typeof AccountRoute } '/_account/plan/': { id: '/_account/plan/' path: '/plan' - fullPath: '/plan' + fullPath: '/plan/' preLoaderRoute: typeof AccountPlanIndexRouteImport parentRoute: typeof AccountRoute } diff --git a/frontend/src/test-utils/router.tsx b/frontend/src/test-utils/router.tsx index 0a312899a..a6faa924b 100644 --- a/frontend/src/test-utils/router.tsx +++ b/frontend/src/test-utils/router.tsx @@ -9,9 +9,7 @@ import { createRootRoute, createRoute, createRouter, - matchContext, RouterContextProvider, - useRouterState, } from "@tanstack/react-router"; const rootRoute = createRootRoute(); @@ -27,22 +25,6 @@ const router = createRouter({ }); router.load(); -const InnerProvider: React.FC = ({ children }) => { - const matchId = useRouterState({ - select: (s) => { - return s.matches[0]?.id; - }, - }); - - return ( - {children} - ); -}; - export const DummyRouter: React.FC = ({ children, -}) => ( - - {children} - -); +}) => {children}; diff --git a/policies/Makefile b/policies/Makefile index db5991672..de4d063ad 100644 --- a/policies/Makefile +++ b/policies/Makefile @@ -7,9 +7,9 @@ DOCKER := 0 PODMAN := 0 # Keep in sync with Dockerfile and .github/actions/build-policies/action.yml -OPA_DOCKER_IMAGE := docker.io/openpolicyagent/opa:1.8.0-debug +OPA_DOCKER_IMAGE := docker.io/openpolicyagent/opa:1.13.1 # Keep in sync with .github/workflows/ci.yaml -REGAL_DOCKER_IMAGE := ghcr.io/open-policy-agent/regal:0.36.1 +REGAL_DOCKER_IMAGE := ghcr.io/open-policy-agent/regal:0.38.1 INPUTS := \ common/common.rego \ diff --git a/policies/register/register.rego b/policies/register/register.rego index 89002a075..93ef86a0d 100644 --- a/policies/register/register.rego +++ b/policies/register/register.rego @@ -49,7 +49,7 @@ violation contains { "field": "username", "code": "username-invalid-chars", "msg": "username contains invalid characters", } if { - not regex.match(`^[a-z0-9.=_/-]+$`, input.username) + not regex.match(`^[a-z0-9.=_/+-]+$`, input.username) } violation contains { diff --git a/templates/components/captcha.html b/templates/components/captcha.html index b21fa5f67..42b5e12b2 100644 --- a/templates/components/captcha.html +++ b/templates/components/captcha.html @@ -29,7 +29,7 @@ Please see LICENSE files in the repository root for full details. {% macro head() -%} {%- if captcha|default(False) -%} {%- if captcha.service == "recaptcha_v2" -%} - + {%- elif captcha.service == "cloudflare_turnstile" -%} {%- elif captcha.service == "hcaptcha" -%} diff --git a/translations/pt-BR.json b/translations/pt-BR.json new file mode 100644 index 000000000..16e725081 --- /dev/null +++ b/translations/pt-BR.json @@ -0,0 +1,104 @@ +{ + "action": { + "back": "Voltar", + "cancel": "Cancelar", + "continue": "Continuar", + "create_account": "Criar Conta", + "sign_in": "Entrar", + "sign_out": "Sair", + "submit": "Enviar" + }, + "common": { + "display_name": "Nome e sobrenome", + "email_address": "Endereço de e-mail", + "loading": "Carregando…", + "password": "Senha", + "password_confirm": "Confirme a nova senha", + "username": "Nome de usuário" + }, + "error": { + "unexpected": "Erro inesperado" + }, + "mas": { + "back_to_homepage": "Voltar à página inicial", + "change_password": { + "change": "Alterar a senha", + "confirm": "Confirme a nova senha", + "current": "Senha atual", + "heading": "Alterar minha senha", + "new": "Nova senha" + }, + "emails": { + "greeting": "Olá, %(username)s,", + "verify": { + "body_html": "Seu código de verificação para este endereço de e-mail é: %(code)s", + "body_text": "Seu código de verificação para este endereço de e-mail é: %(code)s", + "subject": "Seu código de verificação de e-mail é: %(code)s" + } + }, + "errors": { + "denied_policy": "Negado de acordo com a norma: %(policy)s", + "field_required": "Este campo é obrigatório", + "invalid_credentials": "Credenciais inválidas", + "password_mismatch": "As senhas digitadas não são iguais", + "username_taken": "Este nome de usuário já está em uso" + }, + "login": { + "call_to_register": "Ainda não tem uma conta?", + "continue_with_provider": "Continuar com %(provider)s", + "description": "Faça login para continuar:", + "headline": "Entrar", + "no_login_methods": "Não há métodos de login disponíveis.", + "separator": "Ou" + }, + "navbar": { + "my_account": "Minha conta", + "register": "Criar uma conta", + "signed_in_as": "Entrou como %(username)s." + }, + "not_found": { + "description": "A página que você estava procurando não existe ou foi removida.", + "heading": "Página não encontrada" + }, + "not_you": "Não é %(username)s?", + "or_separator": "Ou", + "recovery": { + "finish": { + "new": "Nova senha" + } + }, + "register": { + "call_to_login": "Já tem uma conta?", + "create_account": { + "description": "Escolha um nome de usuário para continuar.", + "heading": "Criar uma conta" + }, + "sign_in_instead": "Em vez disso, faça login" + }, + "scope": { + "edit_profile": "Edite seu perfil e seus dados de contato.", + "manage_sessions": "Gerencie seus dispositivos e sessões", + "view_profile": "Veja as informações do seu perfil e os seus dados de contato." + }, + "upstream_oauth2": { + "login_link": { + "action": "Continuar" + }, + "register": { + "choose_username": { + "heading": "Escolha seu nome de usuário" + }, + "create_account": "Criar uma nova conta", + "suggested_display_name": "Importar nome de exibição", + "suggested_email": "Importar endereço de e-mail" + }, + "suggest_link": { + "action": "Ligação" + } + }, + "verify_email": { + "description": "Digite o código de 6 dígitos enviado para %(email)s", + "headline": "Verifique seu e-mail" + } + } +} \ No newline at end of file diff --git a/translations/sk.json b/translations/sk.json new file mode 100644 index 000000000..8cf921aa0 --- /dev/null +++ b/translations/sk.json @@ -0,0 +1,122 @@ +{ + "action": { + "back": "Naspäť", + "cancel": "Zrušiť", + "continue": "Pokračovať", + "create_account": "Vytvoriť účet", + "sign_in": "Prihlásiť sa", + "sign_out": "Odhlásiť sa", + "submit": "Odoslať" + }, + "app": { + "human_name": "Matrix Authentication Service", + "name": "matrix-authentication-service", + "technical_description": "Dokument zisťovania OpenID Connect: %(discovery_url)s" + }, + "common": { + "display_name": "Zobrazované meno", + "email_address": "Emailová adresa", + "loading": "Načítavanie…", + "password": "Heslo", + "password_confirm": "Potvrdiť heslo", + "username": "Meno používateľa" + }, + "error": { + "unexpected": "Neočakávaná chyba" + }, + "mas": { + "add_email": { + "heading": "Pridať email" + }, + "back_to_homepage": "Späť na domovskú stránku", + "change_password": { + "change": "Zmeniť heslo", + "confirm": "Potvrdiť heslo", + "current": "Súčasné heslo", + "heading": "Zmena hesla", + "new": "Nové heslo" + }, + "emails": { + "greeting": "Ahoj %(username)s,", + "verify": { + "body_html": "Váš overovací kód na potvrdenie tejto e-mailovej adresy je: %(code)s", + "body_text": "Váš kód na potvrdenie tejto e-mailovej adresy je: %(code)s", + "subject": "Váš kód e-mailu je: %(code)s" + } + }, + "errors": { + "denied_policy": "Zamietnuté pravidlami:%(policy)s", + "field_required": "Toto pole je povinné", + "invalid_credentials": "Neplatné poverenia", + "password_mismatch": "Heslá sa nezhodujú.", + "username_taken": "Toto používateľské meno je už obsadené" + }, + "login": { + "call_to_register": "Ešte nemáte účet?", + "continue_with_provider": "Pokračovať s %(provider)s", + "description": "Ak chcete pokračovať, prihláste sa:", + "headline": "Prihlásiť sa", + "link": { + "description": "Prepojenie vášho %(provider)s účtu" + }, + "no_login_methods": "Nie sú k dispozícii žiadne metódy prihlásenia.", + "separator": "Alebo" + }, + "navbar": { + "my_account": "Môj účet", + "register": "Vytvoriť účet", + "signed_in_as": "Prihlásený ako %(username)s." + }, + "not_found": { + "description": "Stránka, ktorú hľadáte, neexistuje alebo bola presunutá.", + "heading": "Stránka nenájdená" + }, + "not_you": "Nie ste prihlásený ako %(username)s?", + "or_separator": "Alebo", + "policy_violation": { + "description": "Môže to byť spôsobené klientom, ktorý požiadavku vytvoril, aktuálne prihláseným používateľom alebo samotnou požiadavkou.", + "heading": "Žiadosť o autorizáciu bola zamietnutá na základe pravidiel uplatňovaných touto službou.", + "logged_as": "Prihlásený ako %(username)s" + }, + "recovery": { + "finish": { + "new": "Nové heslo" + } + }, + "register": { + "call_to_login": "Už máte účet?", + "create_account": { + "description": "Pre pokračovanie vyberte používateľské meno.", + "heading": "Vytvoriť účet" + }, + "sign_in_instead": "Radšej sa prihlásiť" + }, + "scope": { + "edit_profile": "Upravte si profil a kontaktné údaje", + "manage_sessions": "Spravujte svoje zariadenia a prihlásenia.", + "mas_admin": "Spravovať ľubovoľného používateľa v matrix-authentication-service", + "send_messages": "Odosielať nové správy vo vašom mene", + "synapse_admin": "Spravovať domovský server", + "view_messages": "Zobraziť existujúce správy a údaje", + "view_profile": "Zobraziť informácie o vašom profile a kontaktné údaje" + }, + "upstream_oauth2": { + "login_link": { + "action": "Pokračovať" + }, + "register": { + "choose_username": { + "heading": "Vyberte si svoje používateľské meno" + }, + "create_account": "Vytvoriť nový účet" + }, + "suggest_link": { + "action": "Odkaz" + } + }, + "verify_email": { + "description": "Zadajte 6-ciferný kód, ktorý bol odoslaný na: %(email)s", + "headline": "Overte svoj e-mail" + } + } +} \ No newline at end of file diff --git a/translations/uz.json b/translations/uz.json new file mode 100644 index 000000000..205cd5ed4 --- /dev/null +++ b/translations/uz.json @@ -0,0 +1,195 @@ +{ + "action": { + "back": "Orqaga", + "cancel": "Bekor qilish", + "continue": "Davom etish", + "create_account": "Ro'yxatdan o'tish", + "sign_in": "Kirish", + "sign_out": "Chiqish", + "submit": "Yuborish" + }, + "app": { + "human_name": "Matrix Authentication Service", + "name": "matritsa-autentifikatsiya-xizmati", + "technical_description": "OpenID Connect kashfiyot hujjati:%(discovery_url)s" + }, + "branding": { + "privacy_policy": { + "alt": "Xizmat maxfiylik siyosatiga havola", + "link": "Maxfiylik siyosati" + }, + "terms_and_conditions": { + "alt": "Xizmat ko‘rsatish shartlari va qoidalariga havola", + "link": "Foydalanish shartlari" + } + }, + "common": { + "display_name": "Ko'rsatiladigan ism", + "email_address": "Email manzili", + "loading": "Yuklanmoqda…", + "mxid": "Matrix ID", + "password": "Parol", + "password_confirm": "Parolni tasdiqlash", + "username": "Foydalanuvchi nomi" + }, + "error": { + "unexpected": "Kutilmagan xato" + }, + "mas": { + "add_email": { + "description": "Agar hisobingizga kira olmasangiz, uni tiklash uchun email manzilini kiriting.", + "heading": "Email manzilini qo‘shish" + }, + "back_to_homepage": "Bosh sahifaga qaytish", + "change_password": { + "change": "Parolni o‘zgartirish", + "confirm": "Parolni tasdiqlash", + "current": "Joriy parol", + "description": "Bunda hisobingizdagi parol o‘zgaradi.", + "heading": "Parolni o‘zgartirish", + "new": "Yangi parol" + }, + "consent": { + "client_wants_access": "%(client_name)s da %(redirect_uri)s hisobingizga kirmoqchi.", + "heading": "Hisobingizga kirishga ruxsat berilsinmi?", + "make_sure_you_trust": "%(client_name)sga ishonch hosil qiling.", + "this_will_allow": "Bu %(client_name)sga quyidagilarga ruxsat beradi:", + "you_may_be_sharing": "Bu sayt yoki ilovaga maxfiy axborotlarni ulashayotgan bo‘lishingiz mumkin." + }, + "device_card": { + "access_requested": "Ruxsat so‘raldi", + "device_code": "Kod", + "generic_device": "Qurilma", + "ip_address": "IP manzili" + }, + "device_code_link": { + "description": "Qurilmani ulang", + "headline": "Qurilmangizda chiqqan kodni kiriting" + }, + "device_consent": { + "another_device_access": "Boshqa qurilma hisobingizga kirishni xohlamoqda.", + "denied": { + "description": "Siz %(client_name)s uchun ruxsatni rad etdingiz. Bu oynani yopishingiz mumkin.", + "heading": "Ruxsat berilmadi" + }, + "granted": { + "description": "Siz %(client_name)s uchun ruxsat berdingiz. Bu oynani yopishingiz mumkin.", + "heading": "Ruxsat berildi" + } + }, + "device_display_name": { + "unknown_device": "Noma’lum qurilma." + }, + "emails": { + "greeting": "Salom %(username)s ,", + "verify": { + "body_html": "Ushbu elektron pochta manzilini tasdiqlash uchun tasdiqlash kodingiz: %(code)s", + "body_text": "Ushbu elektron pochta manzilini tasdiqlash uchun tasdiqlash kodingiz: %(code)s", + "subject": "Elektron pochtangizni tasdiqlash kodingiz: %(code)s" + } + }, + "errors": { + "denied_policy": "Siyosat tomonidan rad etilgan: %(policy)s", + "field_required": "Ushbu qator toʻldirilishi shart", + "invalid_credentials": "Hisob ma’lumotlari yaroqsiz", + "password_mismatch": "Parol maydonlari mos kelmayapti", + "username_taken": "Bu foydalanuvchi nomi allaqachon band" + }, + "login": { + "call_to_register": "Hali hisobingiz yo‘qmi?", + "continue_with_provider": "%(provider)s bilan davom etish", + "description": "Davom etish uchun tizimga kiring:", + "headline": "Kirish", + "link": { + "description": "%(provider)s hisobingiz ulanmoqda", + "headline": "Havola uchun tizimga kiring" + }, + "no_login_methods": "Kirish usullari mavjud emas.", + "separator": "Yoki" + }, + "navbar": { + "my_account": "Mening hisobim", + "register": "Hisob yaratish", + "signed_in_as": "Tizimga kirgan:%(username)s ." + }, + "not_found": { + "description": "Siz qidirayotgan sahifa mavjud emas yoki boshqa joyga ko‘chirilgan.", + "heading": "Sahifa topilmadi" + }, + "not_you": "%(username)s emasmi ?", + "or_separator": "Yoki", + "policy_violation": { + "description": "Bu so‘rovni yaratgan mijoz, hozirda tizimga kirgan foydalanuvchi yoki so‘rovning o‘zi bilan bog‘liq bo‘lishi mumkin.", + "heading": "Avtorizatsiya so‘rovi ushbu xizmatda amalda bo‘lgan siyosat tufayli rad etildi.", + "logged_as": "%(username)s sifatida kirildi" + }, + "recovery": { + "expired": { + "resend_email": "Elektron pochtani qayta yuborish" + }, + "finish": { + "new": "Yangi parol" + }, + "progress": { + "resend_email": "Elektron pochtani qayta yuborish" + } + }, + "register": { + "call_to_login": "Hisobingiz allaqachon bormi?", + "create_account": { + "description": "Davom etish uchun foydalanuvchi nomini tanlang.", + "heading": "Hisob yaratish" + }, + "sign_in_instead": "Buning o‘rniga tizimga kiring", + "terms_of_service": "Men roziman Foydalanish shartlari" + }, + "scope": { + "edit_profile": "Profilingiz va aloqa maʼlumotlaringizni tahrirlang", + "manage_sessions": "Qurilmalaringiz va sessiyalaringizni boshqaring", + "mas_admin": "Foydalanuvchilarni boshqarish (urn:mas:admin)", + "send_messages": "Sizning nomingizdan yangi xabarlar yuborish", + "synapse_admin": "Serverni boshqarish (urn:synapse:admin:*)", + "view_messages": "Mavjud xabar va ma’lumotlaringizni ko‘rish", + "view_profile": "Profilingiz ma’lumotlari va kontakt tafsilotlarini ko‘rish" + }, + "upstream_oauth2": { + "link_mismatch": { + "heading": "Bu yuqori oqim hisobi allaqachon boshqa hisobga ulangan." + }, + "login_link": { + "action": "Davom etish", + "heading": "Mavjud hisobingizga havola" + }, + "register": { + "choose_username": { + "description": "Buni keyin o‘zgartirib bo‘lmaydi.", + "heading": "Foydalanuvchi nomingizni tanlang" + }, + "create_account": "Yangi hisob yaratish", + "enforced_by_policy": "Server siyosati tomonidan amalga oshiriladi", + "forced_display_name": "Quyidagi ko‘rsatish nomidan foydalanadi", + "forced_email": "Quyidagi elektron pochta manzilidan foydalaniladi", + "forced_localpart": "Quyidagi foydalanuvchi nomidan foydalaniladi", + "import_data": { + "description": "Yangi %(server_name)s hisobingizga ulanadigan ma’lumotlarni tasdiqlang.", + "heading": "Ma’lumotlaringizni import qiling" + }, + "imported_from_upstream": "Yuqori darajadagi hisobingizdan import qilindi", + "link_existing": "Mavjud hisobga havola", + "suggested_display_name": "Ko‘rsatish nomini import qilish", + "suggested_email": "Elektron pochta manzilini import qilish", + "use": "Foydalanish" + }, + "suggest_link": { + "action": "Havola", + "heading": "Mavjud hisobingizga havola" + } + }, + "verify_email": { + "6_digit_code": "6 xonali kod", + "code": "Kod", + "description": "Yuborilgan 6 xonali kodni kiriting: %(email)s", + "headline": "Elektron pochtangizni tasdiqlang" + } + } +} \ No newline at end of file diff --git a/translations/zh-Hans.json b/translations/zh-Hans.json index 9aec3c264..1dd0dcdfc 100644 --- a/translations/zh-Hans.json +++ b/translations/zh-Hans.json @@ -74,9 +74,13 @@ }, "consent": { "client_wants_access": "%(client_name)s 位于 %(redirect_uri)s,请求访问您的账户。", + "continue_to": "继续前往 %(client_name)s?", "heading": "允许访问你的账户?", "make_sure_you_trust": "请确保你信任 %(client_name)s 。", + "scope_list_preface": "一旦继续你将允许 %(client_name)s:", "this_will_allow": "这将允许 %(client_name)s :", + "this_will_setup": "这将使用你在 %(server_name)s 上的账户配置 %(client_name)s(%(client_uri)s)。", + "use_another_account": "使用另一账户", "you_may_be_sharing": "你可能正在与本网站或 app 分享敏感信息。" }, "device_card": { @@ -98,7 +102,8 @@ "granted": { "description": "您已授予 %(client_name)s 访问权限。您可以关闭此窗口。", "heading": "已授予访问权限" - } + }, + "this_will_setup": "另一设备尝试使用你在上的账户 %(server_name)s 配置 %(client_name)s(%(client_uri)s)。请确保你认识该设备。" }, "device_display_name": { "client_on_device": "运行在 %(device_name)s 的 %(client_name)s", @@ -145,6 +150,9 @@ "username_too_long": "用户名太长", "username_too_short": "用户名太短" }, + "legacy_consent": { + "this_will_setup": "这将设置你位于 %(server_name)s 上的账户:%(client_name)s。" + }, "login": { "call_to_register": "还没有账户?", "continue_with_provider": "以 %(provider)s 继续", @@ -275,7 +283,7 @@ "6_digit_code": "6位数代码", "code": "代码", "description": "输入发送至以下邮箱的6位数代码:%(email)s", - "headline": "验证邮箱" + "headline": "验证邮件地址" } } } \ No newline at end of file