From c622d966b54e3dd59b558714d50aeec60f1f3ac7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:13:35 +0000 Subject: [PATCH 01/86] build(deps): bump convert_case from 0.8.0 to 0.9.0 Bumps [convert_case](https://github.com/rutrum/convert-case) from 0.8.0 to 0.9.0. - [Commits](https://github.com/rutrum/convert-case/commits) --- updated-dependencies: - dependency-name: convert_case dependency-version: 0.9.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6bfb0009b..de1d91359 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1089,9 +1089,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "convert_case" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +checksum = "db05ffb6856bf0ecdf6367558a76a0e8a77b1713044eb92845c692100ed50190" dependencies = [ "unicode-segmentation", ] diff --git a/Cargo.toml b/Cargo.toml index ca8f5508a..3e2dcae65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,7 +177,7 @@ features = ["std"] # Utility for converting between different cases [workspace.dependencies.convert_case] -version = "0.8.0" +version = "0.9.0" # CRC calculation [workspace.dependencies.crc] From 312e31e231d24a9af2cdc8ea0d33d309aa342a4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:12:43 +0000 Subject: [PATCH 02/86] build(deps): bump actions/checkout from 5 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yaml | 10 +++++----- .github/workflows/ci.yaml | 20 ++++++++++---------- .github/workflows/coverage.yaml | 6 +++--- .github/workflows/docs.yaml | 2 +- .github/workflows/merge-back.yaml | 2 +- .github/workflows/release-branch.yaml | 6 +++--- .github/workflows/release-bump.yaml | 4 ++-- .github/workflows/tag.yaml | 2 +- .github/workflows/translations-download.yaml | 2 +- .github/workflows/translations-upload.yaml | 2 +- 10 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 548e53eca..4ea1c43fe 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -46,7 +46,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: # Need a full clone so that `git describe` reports the right version fetch-depth: 0 @@ -67,7 +67,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - uses: ./.github/actions/build-frontend - uses: ./.github/actions/build-policies @@ -112,7 +112,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable @@ -376,7 +376,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: sparse-checkout: | .github/scripts @@ -454,7 +454,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: sparse-checkout: | .github/scripts diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ed119c2e3..a6297f016 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -34,7 +34,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - uses: ./.github/actions/build-policies @@ -61,7 +61,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Node uses: actions/setup-node@v6.0.0 @@ -85,7 +85,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Node uses: actions/setup-node@v6.0.0 @@ -109,7 +109,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Node uses: actions/setup-node@v6.0.0 @@ -133,7 +133,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@nightly @@ -156,7 +156,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Run `cargo-deny` uses: EmbarkStudios/cargo-deny-action@v2.0.13 @@ -172,7 +172,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Rust toolchain run: | @@ -213,7 +213,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@1.89.0 @@ -238,7 +238,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable @@ -291,7 +291,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 3046f45cc..74eeef3b4 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - uses: ./.github/actions/build-policies @@ -54,7 +54,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - uses: ./.github/actions/build-frontend env: @@ -99,7 +99,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 84074d85e..1fa2f56a7 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/merge-back.yaml b/.github/workflows/merge-back.yaml index d28d68c20..8239442fc 100644 --- a/.github/workflows/merge-back.yaml +++ b/.github/workflows/merge-back.yaml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: sparse-checkout: | .github/scripts diff --git a/.github/workflows/release-branch.yaml b/.github/workflows/release-branch.yaml index e31fa2c82..b78c9363f 100644 --- a/.github/workflows/release-branch.yaml +++ b/.github/workflows/release-branch.yaml @@ -34,7 +34,7 @@ jobs: run: exit 1 - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable @@ -61,7 +61,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Node uses: actions/setup-node@v6.0.0 @@ -106,7 +106,7 @@ jobs: needs: [tag, compute-version, localazy] steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: sparse-checkout: | .github/scripts diff --git a/.github/workflows/release-bump.yaml b/.github/workflows/release-bump.yaml index 46251a410..a2a20791a 100644 --- a/.github/workflows/release-bump.yaml +++ b/.github/workflows/release-bump.yaml @@ -33,7 +33,7 @@ jobs: run: exit 1 - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable @@ -76,7 +76,7 @@ jobs: needs: [tag, compute-version] steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: sparse-checkout: | .github/scripts diff --git a/.github/workflows/tag.yaml b/.github/workflows/tag.yaml index ec3ea290d..c6c394c81 100644 --- a/.github/workflows/tag.yaml +++ b/.github/workflows/tag.yaml @@ -30,7 +30,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/translations-download.yaml b/.github/workflows/translations-download.yaml index a022203fc..fe9518ab7 100644 --- a/.github/workflows/translations-download.yaml +++ b/.github/workflows/translations-download.yaml @@ -19,7 +19,7 @@ jobs: run: exit 1 - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Node uses: actions/setup-node@v6.0.0 diff --git a/.github/workflows/translations-upload.yaml b/.github/workflows/translations-upload.yaml index 063a228dd..b09a7d63c 100644 --- a/.github/workflows/translations-upload.yaml +++ b/.github/workflows/translations-upload.yaml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Node uses: actions/setup-node@v6.0.0 From b73d396aa65013c689dd05397b27502f10c75cd8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:13:36 +0000 Subject: [PATCH 03/86] build(deps-dev): bump msw-storybook-addon in /frontend Bumps [msw-storybook-addon](https://github.com/mswjs/msw-storybook-addon/tree/HEAD/packages/msw-addon) from 2.0.5 to 2.0.6. - [Release notes](https://github.com/mswjs/msw-storybook-addon/releases) - [Changelog](https://github.com/mswjs/msw-storybook-addon/blob/main/packages/msw-addon/CHANGELOG.md) - [Commits](https://github.com/mswjs/msw-storybook-addon/commits/v2.0.6/packages/msw-addon) --- updated-dependencies: - dependency-name: msw-storybook-addon dependency-version: 2.0.6 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 8 ++++---- frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 76e6c4c9a..fa29bd201 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -57,7 +57,7 @@ "i18next-cli": "^1.24.20", "knip": "^5.66.4", "msw": "^2.11.6", - "msw-storybook-addon": "^2.0.5", + "msw-storybook-addon": "^2.0.6", "postcss": "^8.5.6", "postcss-import": "^16.1.1", "postcss-nesting": "^13.0.2", @@ -10025,9 +10025,9 @@ } }, "node_modules/msw-storybook-addon": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/msw-storybook-addon/-/msw-storybook-addon-2.0.5.tgz", - "integrity": "sha512-uum2gtprDBoUb8GV/rPMwPytHmB8+AUr25BQUY0MpjYey5/ujaew2Edt+4oHiXpLTd0ThyMqmEvGy/sRpDV4lg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/msw-storybook-addon/-/msw-storybook-addon-2.0.6.tgz", + "integrity": "sha512-ExCwDbcJoM2V3iQU+fZNp+axVfNc7DWMRh4lyTXebDO8IbpUNYKGFUrA8UqaeWiRGKVuS7+fU+KXEa9b0OP6uA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index f3fbe5c4b..d53f80835 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -67,7 +67,7 @@ "i18next-cli": "^1.24.20", "knip": "^5.66.4", "msw": "^2.11.6", - "msw-storybook-addon": "^2.0.5", + "msw-storybook-addon": "^2.0.6", "postcss": "^8.5.6", "postcss-import": "^16.1.1", "postcss-nesting": "^13.0.2", From 18665bff9dd2a324f4a368aa7d2824181bc26aa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:14:55 +0000 Subject: [PATCH 04/86] build(deps-dev): bump @biomejs/biome from 2.3.2 to 2.3.7 in /frontend Bumps [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) from 2.3.2 to 2.3.7. - [Release notes](https://github.com/biomejs/biome/releases) - [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md) - [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.3.7/packages/@biomejs/biome) --- updated-dependencies: - dependency-name: "@biomejs/biome" dependency-version: 2.3.7 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 72 +++++++++++++++++++------------------- frontend/package.json | 2 +- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 76e6c4c9a..cba401458 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -29,7 +29,7 @@ "vaul": "^1.1.2" }, "devDependencies": { - "@biomejs/biome": "^2.3.2", + "@biomejs/biome": "^2.3.7", "@browser-logos/chrome": "^2.0.0", "@browser-logos/firefox": "^3.0.10", "@browser-logos/safari": "^2.1.0", @@ -1035,9 +1035,9 @@ } }, "node_modules/@biomejs/biome": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.2.tgz", - "integrity": "sha512-8e9tzamuDycx7fdrcJ/F/GDZ8SYukc5ud6tDicjjFqURKYFSWMl0H0iXNXZEGmcmNUmABgGuHThPykcM41INgg==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.7.tgz", + "integrity": "sha512-CTbAS/jNAiUc6rcq94BrTB8z83O9+BsgWj2sBCQg9rD6Wkh2gjfR87usjx0Ncx0zGXP1NKgT7JNglay5Zfs9jw==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -1051,20 +1051,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.3.2", - "@biomejs/cli-darwin-x64": "2.3.2", - "@biomejs/cli-linux-arm64": "2.3.2", - "@biomejs/cli-linux-arm64-musl": "2.3.2", - "@biomejs/cli-linux-x64": "2.3.2", - "@biomejs/cli-linux-x64-musl": "2.3.2", - "@biomejs/cli-win32-arm64": "2.3.2", - "@biomejs/cli-win32-x64": "2.3.2" + "@biomejs/cli-darwin-arm64": "2.3.7", + "@biomejs/cli-darwin-x64": "2.3.7", + "@biomejs/cli-linux-arm64": "2.3.7", + "@biomejs/cli-linux-arm64-musl": "2.3.7", + "@biomejs/cli-linux-x64": "2.3.7", + "@biomejs/cli-linux-x64-musl": "2.3.7", + "@biomejs/cli-win32-arm64": "2.3.7", + "@biomejs/cli-win32-x64": "2.3.7" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.2.tgz", - "integrity": "sha512-4LECm4kc3If0JISai4c3KWQzukoUdpxy4fRzlrPcrdMSRFksR9ZoXK7JBcPuLBmd2SoT4/d7CQS33VnZpgBjew==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.7.tgz", + "integrity": "sha512-LirkamEwzIUULhXcf2D5b+NatXKeqhOwilM+5eRkbrnr6daKz9rsBL0kNZ16Hcy4b8RFq22SG4tcLwM+yx/wFA==", "cpu": [ "arm64" ], @@ -1079,9 +1079,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.2.tgz", - "integrity": "sha512-jNMnfwHT4N3wi+ypRfMTjLGnDmKYGzxVr1EYAPBcauRcDnICFXN81wD6wxJcSUrLynoyyYCdfW6vJHS/IAoTDA==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.7.tgz", + "integrity": "sha512-Q4TO633kvrMQkKIV7wmf8HXwF0dhdTD9S458LGE24TYgBjSRbuhvio4D5eOQzirEYg6eqxfs53ga/rbdd8nBKg==", "cpu": [ "x64" ], @@ -1096,9 +1096,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.2.tgz", - "integrity": "sha512-amnqvk+gWybbQleRRq8TMe0rIv7GHss8mFJEaGuEZYWg1Tw14YKOkeo8h6pf1c+d3qR+JU4iT9KXnBKGON4klw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.7.tgz", + "integrity": "sha512-inHOTdlstUBzgjDcx0ge71U4SVTbwAljmkfi3MC5WzsYCRhancqfeL+sa4Ke6v2ND53WIwCFD5hGsYExoI3EZQ==", "cpu": [ "arm64" ], @@ -1113,9 +1113,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.2.tgz", - "integrity": "sha512-2Zz4usDG1GTTPQnliIeNx6eVGGP2ry5vE/v39nT73a3cKN6t5H5XxjcEoZZh62uVZvED7hXXikclvI64vZkYqw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.7.tgz", + "integrity": "sha512-/afy8lto4CB8scWfMdt+NoCZtatBUF62Tk3ilWH2w8ENd5spLhM77zKlFZEvsKJv9AFNHknMl03zO67CiklL2Q==", "cpu": [ "arm64" ], @@ -1130,9 +1130,9 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.2.tgz", - "integrity": "sha512-8BG/vRAhFz1pmuyd24FQPhNeueLqPtwvZk6yblABY2gzL2H8fLQAF/Z2OPIc+BPIVPld+8cSiKY/KFh6k81xfA==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.7.tgz", + "integrity": "sha512-fJMc3ZEuo/NaMYo5rvoWjdSS5/uVSW+HPRQujucpZqm2ZCq71b8MKJ9U4th9yrv2L5+5NjPF0nqqILCl8HY/fg==", "cpu": [ "x64" ], @@ -1147,9 +1147,9 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.2.tgz", - "integrity": "sha512-gzB19MpRdTuOuLtPpFBGrV3Lq424gHyq2lFj8wfX9tvLMLdmA/R9C7k/mqBp/spcbWuHeIEKgEs3RviOPcWGBA==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.7.tgz", + "integrity": "sha512-CQUtgH1tIN6e5wiYSJqzSwJumHYolNtaj1dwZGCnZXm2PZU1jOJof9TsyiP3bXNDb+VOR7oo7ZvY01If0W3iFQ==", "cpu": [ "x64" ], @@ -1164,9 +1164,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.2.tgz", - "integrity": "sha512-lCruqQlfWjhMlOdyf5pDHOxoNm4WoyY2vZ4YN33/nuZBRstVDuqPPjS0yBkbUlLEte11FbpW+wWSlfnZfSIZvg==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.7.tgz", + "integrity": "sha512-aJAE8eCNyRpcfx2JJAtsPtISnELJ0H4xVVSwnxm13bzI8RwbXMyVtxy2r5DV1xT3WiSP+7LxORcApWw0LM8HiA==", "cpu": [ "arm64" ], @@ -1181,9 +1181,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.2.tgz", - "integrity": "sha512-6Ee9P26DTb4D8sN9nXxgbi9Dw5vSOfH98M7UlmkjKB2vtUbrRqCbZiNfryGiwnPIpd6YUoTl7rLVD2/x1CyEHQ==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.7.tgz", + "integrity": "sha512-pulzUshqv9Ed//MiE8MOUeeEkbkSHVDVY5Cz5wVAnH1DUqliCQG3j6s1POaITTFqFfo7AVIx2sWdKpx/GS+Nqw==", "cpu": [ "x64" ], diff --git a/frontend/package.json b/frontend/package.json index f3fbe5c4b..201814dca 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -39,7 +39,7 @@ "vaul": "^1.1.2" }, "devDependencies": { - "@biomejs/biome": "^2.3.2", + "@biomejs/biome": "^2.3.7", "@browser-logos/chrome": "^2.0.0", "@browser-logos/firefox": "^3.0.10", "@browser-logos/safari": "^2.1.0", From 2186354c6dfa0f4667103fb91c9842825edce46f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:46:21 +0000 Subject: [PATCH 05/86] build(deps): bump peter-evans/create-pull-request from 7.0.8 to 7.0.9 Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.8 to 7.0.9. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/v7.0.8...v7.0.9) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 7.0.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/translations-download.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/translations-download.yaml b/.github/workflows/translations-download.yaml index a022203fc..d01a6fde4 100644 --- a/.github/workflows/translations-download.yaml +++ b/.github/workflows/translations-download.yaml @@ -42,7 +42,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@v7.0.8 + uses: peter-evans/create-pull-request@v7.0.9 with: sign-commits: true token: ${{ secrets.BOT_GITHUB_TOKEN }} From 2cc75f1da3b024f3b1e19a5b94836660d44bbe16 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 25 Nov 2025 15:43:01 +0000 Subject: [PATCH 06/86] Add a little bit of explanation to the documentation about keys --- docs/reference/configuration.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index f614791dd..ac70432cc 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -196,7 +196,7 @@ secrets: # Signing keys keys: - # It needs at least an RSA key to work properly + # At least one RSA key must be configured - key_file: keys/rsa_key - kid: "iv1aShae" key: | @@ -238,9 +238,21 @@ The following key formats are supported: - PKCS#8 PEM or DER-encoded RSA or ECDSA private key, encrypted or not - SEC1 PEM or DER-encoded ECDSA private key +The signing keys are used for signing ID Tokens (as returned in the [Token Endpoint] +at `/oauth2/token`) and for signing the response of the [UserInfo Endpoint] at +`/oauth2/userinfo` if the client requests a signed response. + +At a minimum, an RSA key must be configured in order to be compliant with the +[OpenID Connect Core specification][oidc-core-rs256] which specifies the RS256 algorithm +as mandatory to implement by servers for interoperability reasons. + The keys can be given as a directory path via `secrets.keys_dir` or, alternatively, as an inline configuration list via `secrets.keys`. +[Token Endpoint]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint +[UserInfo Endpoint]: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo +[oidc-core-rs256]: https://openid.net/specs/openid-connect-core-1_0.html#ServerMTI + #### `secrets.keys_dir` Path to the directory containing MAS signing key files. From 53cd956250bd9fe819ac3a07874e9ebe2cc0a436 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 25 Nov 2025 16:59:21 +0000 Subject: [PATCH 07/86] drive-by: Singing -> Signing --- docs/reference/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index ac70432cc..5b40e8370 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -222,7 +222,7 @@ The secret is not updated when the content of the file changes. > Changing the encryption secret afterwards will lead to a loss of all encrypted > information in the database. -### Singing Keys +### Signing Keys The service can use a number of key types for signing. The following key types are supported: From 069b57758b9d4153b6cb9b005778a958be7bf71a Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 25 Nov 2025 18:20:14 +0000 Subject: [PATCH 08/86] Introduce compat login policy --- crates/policy/src/bin/schema.rs | 3 +- crates/policy/src/model.rs | 26 ++++++ policies/Makefile | 2 + policies/compat_login/compat_login.rego | 61 ++++++++++++++ policies/compat_login/compat_login_test.rego | 72 ++++++++++++++++ policies/schema/compat_login_input.json | 88 ++++++++++++++++++++ 6 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 policies/compat_login/compat_login.rego create mode 100644 policies/compat_login/compat_login_test.rego create mode 100644 policies/schema/compat_login_input.json diff --git a/crates/policy/src/bin/schema.rs b/crates/policy/src/bin/schema.rs index 8e9c81a07..be778f6e1 100644 --- a/crates/policy/src/bin/schema.rs +++ b/crates/policy/src/bin/schema.rs @@ -12,7 +12,7 @@ use std::path::{Path, PathBuf}; use mas_policy::model::{ - AuthorizationGrantInput, ClientRegistrationInput, EmailInput, RegisterInput, + AuthorizationGrantInput, ClientRegistrationInput, CompatLoginInput, EmailInput, RegisterInput, }; use schemars::{JsonSchema, generate::SchemaSettings}; @@ -42,5 +42,6 @@ fn main() { write_schema::(output_root, "register_input.json"); write_schema::(output_root, "client_registration_input.json"); write_schema::(output_root, "authorization_grant_input.json"); + write_schema::(output_root, "compat_login_input.json"); write_schema::(output_root, "email_input.json"); } diff --git a/crates/policy/src/model.rs b/crates/policy/src/model.rs index b85170025..85b05d317 100644 --- a/crates/policy/src/model.rs +++ b/crates/policy/src/model.rs @@ -187,6 +187,32 @@ pub struct AuthorizationGrantInput<'a> { pub requester: Requester, } +/// Input for the compatibility login policy. +#[derive(Serialize, Debug, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct CompatLoginInput<'a> { + #[schemars(with = "std::collections::HashMap")] + pub user: &'a User, + + /// How many sessions the user has. + pub session_counts: SessionCounts, + + // TODO is this actually what we care about? Don't we care a bit more about whether we're in an + // interactive context or a non-interactive context? (SSO type has both phases :() + pub login_type: CompatLoginType, + + pub requester: Requester, +} + +#[derive(Serialize, Debug, JsonSchema)] +pub enum CompatLoginType { + #[serde(rename = "m.login.sso")] + WebSso, + + #[serde(rename = "m.login.password")] + Password, +} + /// Information about how many sessions the user has #[derive(Serialize, Debug, JsonSchema)] pub struct SessionCounts { diff --git a/policies/Makefile b/policies/Makefile index 0d515b904..db5991672 100644 --- a/policies/Makefile +++ b/policies/Makefile @@ -16,6 +16,7 @@ INPUTS := \ client_registration/client_registration.rego \ register/register.rego \ authorization_grant/authorization_grant.rego \ + compat_login/compat_login.rego \ email/email.rego ifeq ($(DOCKER), 1) @@ -38,6 +39,7 @@ policy.wasm: $(INPUTS) -e "client_registration/violation" \ -e "register/violation" \ -e "authorization_grant/violation" \ + -e "compat_login/violation" \ -e "email/violation" \ $^ tar xzf bundle.tar.gz /policy.wasm diff --git a/policies/compat_login/compat_login.rego b/policies/compat_login/compat_login.rego new file mode 100644 index 000000000..3aeb566cf --- /dev/null +++ b/policies/compat_login/compat_login.rego @@ -0,0 +1,61 @@ +# Copyright 2025 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. + +# METADATA +# schemas: +# - input: schema["compat_login_input"] +package compat_login + +import rego.v1 + +import data.common + +default allow := false + +allow if { + count(violation) == 0 +} + +violation contains {"msg": sprintf( + "Requester [%s] isn't allowed to do this action", + [common.format_requester(input.requester)], +)} if { + common.requester_banned(input.requester, data.requester) +} + +violation contains { + "code": "too-many-sessions", + "msg": "user has too many active sessions (soft limit)", +} if { + # Only apply if session limits are enabled in the config + data.session_limit != null + + # This is a web-based interactive login + # TODO not strictly correct... + input.login_type == "m.login.sso" + + # For web-based 'compat SSO' login, a violation occurs when the soft limit has already been + # reached or exceeded. + # We use the soft limit because the user will be able to interactively remove + # sessions to return under the limit. + data.session_limit.soft_limit <= input.session_counts.total +} + +violation contains { + "code": "too-many-sessions", + "msg": "user has too many active sessions (hard limit)", +} if { + # Only apply if session limits are enabled in the config + data.session_limit != null + + # This is not a web-based interactive login + input.login_type == "m.login.password" + + # For `m.login.password` login, a violation occurs when the hard limit has already been + # reached or exceeded. + # We don't use the soft limit because the user won't be able to interactively remove + # sessions to return under the limit. + data.session_limit.hard_limit <= input.session_counts.total +} diff --git a/policies/compat_login/compat_login_test.rego b/policies/compat_login/compat_login_test.rego new file mode 100644 index 000000000..46ec1b0a2 --- /dev/null +++ b/policies/compat_login/compat_login_test.rego @@ -0,0 +1,72 @@ +# Copyright 2025 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. + +package compat_login_test + +import data.compat_login +import rego.v1 + +user := {"username": "john"} + +test_session_limiting_sso if { + compat_login.allow with input.user as user + with input.session_counts as {"total": 1} + with input.login_type as "m.login.sso" + with data.session_limit as {"soft_limit": 32, "hard_limit": 64} + + compat_login.allow with input.user as user + with input.session_counts as {"total": 31} + with input.login_type as "m.login.sso" + with data.session_limit as {"soft_limit": 32, "hard_limit": 64} + + not compat_login.allow with input.user as user + with input.session_counts as {"total": 32} + with input.login_type as "m.login.sso" + with data.session_limit as {"soft_limit": 32, "hard_limit": 64} + + not compat_login.allow with input.user as user + with input.session_counts as {"total": 42} + with input.login_type as "m.login.sso" + with data.session_limit as {"soft_limit": 32, "hard_limit": 64} + + not compat_login.allow with input.user as user + with input.session_counts as {"total": 65} + with input.login_type as "m.login.sso" + with data.session_limit as {"soft_limit": 32, "hard_limit": 64} + + # No limit configured + compat_login.allow with input.user as user + with input.session_counts as {"total": 1} + with input.login_type as "m.login.sso" + with data.session_limit as null +} + +test_session_limiting_password if { + compat_login.allow with input.user as user + with input.session_counts as {"total": 1} + with input.login_type as "m.login.password" + with data.session_limit as {"soft_limit": 32, "hard_limit": 64} + + compat_login.allow with input.user as user + with input.session_counts as {"total": 63} + with input.login_type as "m.login.password" + with data.session_limit as {"soft_limit": 32, "hard_limit": 64} + + not compat_login.allow with input.user as user + with input.session_counts as {"total": 64} + with input.login_type as "m.login.password" + with data.session_limit as {"soft_limit": 32, "hard_limit": 64} + + not compat_login.allow with input.user as user + with input.session_counts as {"total": 65} + with input.login_type as "m.login.password" + with data.session_limit as {"soft_limit": 32, "hard_limit": 64} + + # No limit configured + compat_login.allow with input.user as user + with input.session_counts as {"total": 1} + with input.login_type as "m.login.password" + with data.session_limit as null +} diff --git a/policies/schema/compat_login_input.json b/policies/schema/compat_login_input.json new file mode 100644 index 000000000..c28c79388 --- /dev/null +++ b/policies/schema/compat_login_input.json @@ -0,0 +1,88 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CompatLoginInput", + "description": "Input for the compatibility login policy.", + "type": "object", + "required": [ + "login_type", + "requester", + "session_counts", + "user" + ], + "properties": { + "user": { + "type": "object", + "additionalProperties": true + }, + "session_counts": { + "description": "How many sessions the user has.", + "allOf": [ + { + "$ref": "#/definitions/SessionCounts" + } + ] + }, + "login_type": { + "$ref": "#/definitions/CompatLoginType" + }, + "requester": { + "$ref": "#/definitions/Requester" + } + }, + "definitions": { + "SessionCounts": { + "description": "Information about how many sessions the user has", + "type": "object", + "required": [ + "compat", + "oauth2", + "personal", + "total" + ], + "properties": { + "total": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "oauth2": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "compat": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "personal": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "CompatLoginType": { + "type": "string", + "enum": [ + "m.login.sso", + "m.login.password" + ] + }, + "Requester": { + "description": "Identity of the requester", + "type": "object", + "properties": { + "ip_address": { + "description": "IP address of the entity making the request", + "type": "string", + "format": "ip" + }, + "user_agent": { + "description": "User agent of the entity making the request", + "type": "string" + } + } + } + } +} \ No newline at end of file From 2c95c0a9a032764f249edf2da66fe347842b5d0d Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 25 Nov 2025 18:20:14 +0000 Subject: [PATCH 09/86] Expose the compat login policy from the policy engine --- crates/cli/src/util.rs | 1 + crates/config/src/sections/policy.rs | 16 ++++++ crates/policy/src/lib.rs | 76 ++++++++++++++++++---------- docs/config.schema.json | 4 ++ 4 files changed, 70 insertions(+), 27 deletions(-) diff --git a/crates/cli/src/util.rs b/crates/cli/src/util.rs index c0f31557b..454276150 100644 --- a/crates/cli/src/util.rs +++ b/crates/cli/src/util.rs @@ -145,6 +145,7 @@ pub async fn policy_factory_from_config( register: config.register_entrypoint.clone(), client_registration: config.client_registration_entrypoint.clone(), authorization_grant: config.authorization_grant_entrypoint.clone(), + compat_login: config.compat_login_entrypoint.clone(), email: config.email_entrypoint.clone(), }; diff --git a/crates/config/src/sections/policy.rs b/crates/config/src/sections/policy.rs index 37d052ade..3b816b713 100644 --- a/crates/config/src/sections/policy.rs +++ b/crates/config/src/sections/policy.rs @@ -62,6 +62,14 @@ fn is_default_password_entrypoint(value: &String) -> bool { *value == default_password_entrypoint() } +fn default_compat_login_entrypoint() -> String { + "compat_login/violation".to_owned() +} + +fn is_default_compat_login_entrypoint(value: &String) -> bool { + *value == default_compat_login_entrypoint() +} + fn default_email_entrypoint() -> String { "email/violation".to_owned() } @@ -111,6 +119,13 @@ pub struct PolicyConfig { )] pub authorization_grant_entrypoint: String, + /// Entrypoint to use when evaluating compatibility logins + #[serde( + default = "default_compat_login_entrypoint", + skip_serializing_if = "is_default_compat_login_entrypoint" + )] + pub compat_login_entrypoint: String, + /// Entrypoint to use when changing password #[serde( default = "default_password_entrypoint", @@ -137,6 +152,7 @@ impl Default for PolicyConfig { client_registration_entrypoint: default_client_registration_entrypoint(), register_entrypoint: default_register_entrypoint(), authorization_grant_entrypoint: default_authorization_grant_entrypoint(), + compat_login_entrypoint: default_compat_login_entrypoint(), password_entrypoint: default_password_entrypoint(), email_entrypoint: default_email_entrypoint(), data: default_data(), diff --git a/crates/policy/src/lib.rs b/crates/policy/src/lib.rs index 8a038aea8..dcb68dd36 100644 --- a/crates/policy/src/lib.rs +++ b/crates/policy/src/lib.rs @@ -19,8 +19,9 @@ use thiserror::Error; use tokio::io::{AsyncRead, AsyncReadExt}; pub use self::model::{ - AuthorizationGrantInput, ClientRegistrationInput, Code as ViolationCode, EmailInput, - EvaluationResult, GrantType, RegisterInput, RegistrationMethod, Requester, Violation, + AuthorizationGrantInput, ClientRegistrationInput, Code as ViolationCode, CompatLoginInput, + EmailInput, EvaluationResult, GrantType, RegisterInput, RegistrationMethod, Requester, + Violation, }; #[derive(Debug, Error)] @@ -72,15 +73,17 @@ pub struct Entrypoints { pub register: String, pub client_registration: String, pub authorization_grant: String, + pub compat_login: String, pub email: String, } impl Entrypoints { - fn all(&self) -> [&str; 4] { + fn all(&self) -> [&str; 5] { [ self.register.as_str(), self.client_registration.as_str(), self.authorization_grant.as_str(), + self.compat_login.as_str(), self.email.as_str(), ] } @@ -459,6 +462,30 @@ impl Policy { Ok(res) } + + /// Evaluate the `compat_login` entrypoint. + /// + /// # Errors + /// + /// Returns an error if the policy engine fails to evaluate the entrypoint. + #[tracing::instrument( + name = "policy.evaluate.compat_login", + skip_all, + fields( + %input.user.id, + ), + )] + pub async fn evaluate_compat_login( + &mut self, + input: CompatLoginInput<'_>, + ) -> Result { + let [res]: [EvaluationResult; 1] = self + .instance + .evaluate(&mut self.store, &self.entrypoints.compat_login, &input) + .await?; + + Ok(res) + } } #[cfg(test)] @@ -468,6 +495,16 @@ mod tests { use super::*; + fn make_entrypoints() -> Entrypoints { + Entrypoints { + register: "register/violation".to_owned(), + client_registration: "client_registration/violation".to_owned(), + authorization_grant: "authorization_grant/violation".to_owned(), + compat_login: "compat_login/violation".to_owned(), + email: "email/violation".to_owned(), + } + } + #[tokio::test] async fn test_register() { let data = Data::new("example.com".to_owned(), None).with_rest(serde_json::json!({ @@ -484,14 +521,9 @@ mod tests { let file = tokio::fs::File::open(path).await.unwrap(); - let entrypoints = Entrypoints { - register: "register/violation".to_owned(), - client_registration: "client_registration/violation".to_owned(), - authorization_grant: "authorization_grant/violation".to_owned(), - email: "email/violation".to_owned(), - }; - - let factory = PolicyFactory::load(file, data, entrypoints).await.unwrap(); + let factory = PolicyFactory::load(file, data, make_entrypoints()) + .await + .unwrap(); let mut policy = factory.instantiate().await.unwrap(); @@ -551,14 +583,9 @@ mod tests { let file = tokio::fs::File::open(path).await.unwrap(); - let entrypoints = Entrypoints { - register: "register/violation".to_owned(), - client_registration: "client_registration/violation".to_owned(), - authorization_grant: "authorization_grant/violation".to_owned(), - email: "email/violation".to_owned(), - }; - - let factory = PolicyFactory::load(file, data, entrypoints).await.unwrap(); + let factory = PolicyFactory::load(file, data, make_entrypoints()) + .await + .unwrap(); let mut policy = factory.instantiate().await.unwrap(); @@ -620,14 +647,9 @@ mod tests { let file = tokio::fs::File::open(path).await.unwrap(); - let entrypoints = Entrypoints { - register: "register/violation".to_owned(), - client_registration: "client_registration/violation".to_owned(), - authorization_grant: "authorization_grant/violation".to_owned(), - email: "email/violation".to_owned(), - }; - - let factory = PolicyFactory::load(file, data, entrypoints).await.unwrap(); + let factory = PolicyFactory::load(file, data, make_entrypoints()) + .await + .unwrap(); // That is around 1 MB of JSON data. Each element is a 5-digit string, so 8 // characters including the quotes and a comma. diff --git a/docs/config.schema.json b/docs/config.schema.json index cda68f145..496cd2c5b 100644 --- a/docs/config.schema.json +++ b/docs/config.schema.json @@ -1883,6 +1883,10 @@ "description": "Entrypoint to use when evaluating authorization grants", "type": "string" }, + "compat_login_entrypoint": { + "description": "Entrypoint to use when evaluating compatibility logins", + "type": "string" + }, "password_entrypoint": { "description": "Entrypoint to use when changing password", "type": "string" From 31c3fe2b392553467123bd0698f5cb8e048cf8bc Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 25 Nov 2025 18:20:14 +0000 Subject: [PATCH 10/86] Add a 'compat login policy violation' page --- crates/templates/src/context.rs | 38 +++++++++++++++++++ crates/templates/src/lib.rs | 20 ++++++---- .../pages/compat_login_policy_violation.html | 34 +++++++++++++++++ translations/en.json | 10 ++--- 4 files changed, 89 insertions(+), 13 deletions(-) create mode 100644 templates/pages/compat_login_policy_violation.html diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index 4ed09c3e1..85d3f6e3e 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -860,6 +860,44 @@ impl PolicyViolationContext { } } +/// Context used by the `compat_login_policy_violation.html` template +#[derive(Serialize)] +pub struct CompatLoginPolicyViolationContext { + violation_codes: Vec<&'static str>, +} + +impl TemplateContext for CompatLoginPolicyViolationContext { + fn sample( + _now: chrono::DateTime, + _rng: &mut R, + _locales: &[DataLocale], + ) -> BTreeMap + where + Self: Sized, + { + sample_list(vec![ + CompatLoginPolicyViolationContext { + violation_codes: vec![], + }, + CompatLoginPolicyViolationContext { + violation_codes: vec!["too-many-sessions"], + }, + ]) + } +} + +impl CompatLoginPolicyViolationContext { + /// Constructs a context for the compatibility login policy violation page + /// given the list of violations' codes. + /// + /// TODO maybe this is not very nice, not sure what the API boundary should + /// be + #[must_use] + pub const fn for_violations(violation_codes: Vec<&'static str>) -> Self { + Self { violation_codes } + } +} + /// Context used by the `sso.html` template #[derive(Serialize)] pub struct CompatSsoContext { diff --git a/crates/templates/src/lib.rs b/crates/templates/src/lib.rs index 32a41e8b2..dc0e1e714 100644 --- a/crates/templates/src/lib.rs +++ b/crates/templates/src/lib.rs @@ -37,14 +37,15 @@ mod macros; pub use self::{ context::{ - AccountInactiveContext, ApiDocContext, AppContext, CompatSsoContext, ConsentContext, - DeviceConsentContext, DeviceLinkContext, DeviceLinkFormField, DeviceNameContext, - EmailRecoveryContext, EmailVerificationContext, EmptyContext, ErrorContext, - FormPostContext, IndexContext, LoginContext, LoginFormField, NotFoundContext, - PasswordRegisterContext, PolicyViolationContext, PostAuthContext, PostAuthContextInner, - RecoveryExpiredContext, RecoveryFinishContext, RecoveryFinishFormField, - RecoveryProgressContext, RecoveryStartContext, RecoveryStartFormField, RegisterContext, - RegisterFormField, RegisterStepsDisplayNameContext, RegisterStepsDisplayNameFormField, + AccountInactiveContext, ApiDocContext, AppContext, CompatLoginPolicyViolationContext, + CompatSsoContext, ConsentContext, DeviceConsentContext, DeviceLinkContext, + DeviceLinkFormField, DeviceNameContext, EmailRecoveryContext, EmailVerificationContext, + EmptyContext, ErrorContext, FormPostContext, IndexContext, LoginContext, LoginFormField, + NotFoundContext, PasswordRegisterContext, PolicyViolationContext, PostAuthContext, + PostAuthContextInner, RecoveryExpiredContext, RecoveryFinishContext, + RecoveryFinishFormField, RecoveryProgressContext, RecoveryStartContext, + RecoveryStartFormField, RegisterContext, RegisterFormField, + RegisterStepsDisplayNameContext, RegisterStepsDisplayNameFormField, RegisterStepsEmailInUseContext, RegisterStepsRegistrationTokenContext, RegisterStepsRegistrationTokenFormField, RegisterStepsVerifyEmailContext, RegisterStepsVerifyEmailFormField, SiteBranding, SiteConfigExt, SiteFeatures, @@ -391,6 +392,9 @@ register_templates! { /// Render the policy violation page pub fn render_policy_violation(WithLanguage>>) { "pages/policy_violation.html" } + /// Render the compatibility login policy violation page + pub fn render_compat_login_policy_violation(WithLanguage>>) { "pages/compat_login_policy_violation.html" } + /// Render the legacy SSO login consent page pub fn render_sso_login(WithLanguage>>) { "pages/sso.html" } diff --git a/templates/pages/compat_login_policy_violation.html b/templates/pages/compat_login_policy_violation.html new file mode 100644 index 000000000..c4187336f --- /dev/null +++ b/templates/pages/compat_login_policy_violation.html @@ -0,0 +1,34 @@ +{# +Copyright 2024, 2025 New Vector Ltd. +Copyright 2022-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. +-#} + +{% extends "base.html" %} + +{% block content %} +
+
+ {{ icon.error_solid() }} +
+ +
+

{{ _("mas.policy_violation.heading") }}

+

{{ _("mas.policy_violation.description") }}

+
+
+ +
+
+

+ {{ _("mas.policy_violation.logged_as", username=current_session.user.username) }} +

+ + {{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token, post_logout_action=action, as_link=True) }} +
+ + {{ _("action.cancel") }} +
+{% endblock content %} diff --git a/translations/en.json b/translations/en.json index cdf2df82d..af406ba66 100644 --- a/translations/en.json +++ b/translations/en.json @@ -6,7 +6,7 @@ }, "cancel": "Cancel", "@cancel": { - "context": "pages/consent.html:69:11-29, pages/device_consent.html:127:13-31, pages/policy_violation.html:44:13-31" + "context": "pages/compat_login_policy_violation.html:32:89-107, pages/consent.html:69:11-29, pages/device_consent.html:127:13-31, pages/policy_violation.html:44:13-31" }, "continue": "Continue", "@continue": { @@ -22,7 +22,7 @@ }, "sign_out": "Sign out", "@sign_out": { - "context": "pages/account/logged_out.html:22:28-48, pages/consent.html:65:28-48, pages/device_consent.html:136:30-50, pages/index.html:28:28-48, pages/policy_violation.html:38:28-48, pages/sso.html:45:28-48, pages/upstream_oauth2/link_mismatch.html:24:24-44, pages/upstream_oauth2/suggest_link.html:32:26-46" + "context": "pages/account/logged_out.html:22:28-48, pages/compat_login_policy_violation.html:29:28-48, pages/consent.html:65:28-48, pages/device_consent.html:136:30-50, pages/index.html:28:28-48, pages/policy_violation.html:38:28-48, pages/sso.html:45:28-48, pages/upstream_oauth2/link_mismatch.html:24:24-44, pages/upstream_oauth2/suggest_link.html:32:26-46" }, "skip": "Skip", "@skip": { @@ -496,17 +496,17 @@ "policy_violation": { "description": "This might be because of the client which authored the request, the currently logged in user, or the request itself.", "@description": { - "context": "pages/policy_violation.html:19:25-62", + "context": "pages/compat_login_policy_violation.html:19:25-62, pages/policy_violation.html:19:25-62", "description": "Displayed when an authorization request is denied by the policy" }, "heading": "The authorization request was denied by the policy enforced by this service", "@heading": { - "context": "pages/policy_violation.html:18:27-60", + "context": "pages/compat_login_policy_violation.html:18:27-60, pages/policy_violation.html:18:27-60", "description": "Displayed when an authorization request is denied by the policy" }, "logged_as": "Logged as %(username)s", "@logged_as": { - "context": "pages/policy_violation.html:35:11-86" + "context": "pages/compat_login_policy_violation.html:26:11-86, pages/policy_violation.html:35:11-86" } }, "recovery": { From 985ea0b30a7ce4d328aeb10a1bea17caaf219a50 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 25 Nov 2025 18:20:14 +0000 Subject: [PATCH 11/86] Enforce policy on compat login --- crates/handlers/src/compat/login.rs | 76 +++++++++++++++++ .../handlers/src/compat/login_sso_complete.rs | 83 ++++++++++++++++++- crates/handlers/src/lib.rs | 1 + crates/policy/src/model.rs | 2 +- 4 files changed, 157 insertions(+), 5 deletions(-) diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index d3c7c979f..f0be20ca9 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -16,6 +16,7 @@ use mas_data_model::{ User, }; use mas_matrix::HomeserverConnection; +use mas_policy::{Policy, Requester, ViolationCode, model::CompatLoginType}; use mas_storage::{ BoxRepository, BoxRepositoryFactory, RepositoryAccess, compat::{ @@ -37,6 +38,7 @@ use crate::{ BoundActivityTracker, Limiter, METER, RequesterFingerprint, impl_from_error_for_route, passwords::{PasswordManager, PasswordVerificationResult}, rate_limit::PasswordCheckLimitedError, + session::count_user_sessions_for_limiting, }; static LOGIN_COUNTER: LazyLock> = LazyLock::new(|| { @@ -213,9 +215,16 @@ pub enum RouteError { #[error("failed to provision device")] ProvisionDeviceFailed(#[source] anyhow::Error), + + #[error("login rejected by policy")] + PolicyRejected, + + #[error("login rejected by policy (hard session limit reached)")] + PolicyHardSessionLimitReached, } impl_from_error_for_route!(mas_storage::RepositoryError); +impl_from_error_for_route!(mas_policy::EvaluationError); impl From for RouteError { fn from(err: anyhow::Error) -> Self { @@ -274,6 +283,16 @@ impl IntoResponse for RouteError { error: "User account has been locked", status: StatusCode::UNAUTHORIZED, }, + Self::PolicyRejected => MatrixError { + errcode: "M_FORBIDDEN", + error: "Login denied by the policy enforced by this service", + status: StatusCode::FORBIDDEN, + }, + Self::PolicyHardSessionLimitReached => MatrixError { + errcode: "M_FORBIDDEN", + error: "You have reached your hard device limit. Please visit your account page to sign some out.", + status: StatusCode::FORBIDDEN, + }, }; (sentry_event_id, response).into_response() @@ -290,6 +309,7 @@ pub(crate) async fn post( State(homeserver): State>, State(site_config): State, State(limiter): State, + mut policy: Policy, requester: RequesterFingerprint, user_agent: Option>, MatrixJsonBody(input): MatrixJsonBody, @@ -329,6 +349,11 @@ pub(crate) async fn post( &limiter, requester, &mut repo, + &mut policy, + Requester { + ip_address: activity_tracker.ip(), + user_agent: user_agent.clone(), + }, username, password, input.device_id, // TODO check for validity @@ -342,6 +367,11 @@ pub(crate) async fn post( &mut rng, &clock, &mut repo, + &mut policy, + Requester { + ip_address: activity_tracker.ip(), + user_agent: user_agent.clone(), + }, &token, input.device_id, input.initial_device_display_name, @@ -459,6 +489,8 @@ async fn token_login( rng: &mut (dyn RngCore + Send), clock: &dyn Clock, repo: &mut BoxRepository, + policy: &mut Policy, + requester: Requester, token: &str, requested_device_id: Option, initial_device_display_name: Option, @@ -548,6 +580,27 @@ async fn token_login( .finish_sessions_to_replace_device(clock, &browser_session.user, &device) .await?; + let session_counts = count_user_sessions_for_limiting(repo, &browser_session.user).await?; + + let res = policy + .evaluate_compat_login(mas_policy::CompatLoginInput { + user: &browser_session.user, + login_type: CompatLoginType::WebSso, + session_counts, + requester, + }) + .await?; + if !res.valid() { + if res.violations.len() == 1 { + let violation = &res.violations[0]; + if violation.code == Some(ViolationCode::TooManySessions) { + // The only violation is having reached the session limit. + return Err(RouteError::PolicyHardSessionLimitReached); + } + } + return Err(RouteError::PolicyRejected); + } + // We first create the session in the database, commit the transaction, then // create it on the homeserver, scheduling a device sync job afterwards to // make sure we don't end up in an inconsistent state. @@ -578,6 +631,8 @@ async fn user_password_login( limiter: &Limiter, requester: RequesterFingerprint, repo: &mut BoxRepository, + policy: &mut Policy, + policy_requester: Requester, username: &str, password: String, requested_device_id: Option, @@ -651,6 +706,27 @@ async fn user_password_login( .finish_sessions_to_replace_device(clock, &user, &device) .await?; + let session_counts = count_user_sessions_for_limiting(repo, &user).await?; + + let res = policy + .evaluate_compat_login(mas_policy::CompatLoginInput { + user: &user, + login_type: CompatLoginType::Password, + session_counts, + requester: policy_requester, + }) + .await?; + if !res.valid() { + if res.violations.len() == 1 { + let violation = &res.violations[0]; + if violation.code == Some(ViolationCode::TooManySessions) { + // The only violation is having reached the session limit. + return Err(RouteError::PolicyHardSessionLimitReached); + } + } + return Err(RouteError::PolicyRejected); + } + let session = repo .compat_session() .add( diff --git a/crates/handlers/src/compat/login_sso_complete.rs b/crates/handlers/src/compat/login_sso_complete.rs index a4fbb24fb..3c33b5d46 100644 --- a/crates/handlers/src/compat/login_sso_complete.rs +++ b/crates/handlers/src/compat/login_sso_complete.rs @@ -11,23 +11,28 @@ use axum::{ extract::{Form, Path, State}, response::{Html, IntoResponse, Redirect, Response}, }; -use axum_extra::extract::Query; +use axum_extra::{TypedHeader, extract::Query}; use chrono::Duration; +use hyper::StatusCode; use mas_axum_utils::{ InternalError, cookies::CookieJar, csrf::{CsrfExt, ProtectedForm}, }; use mas_data_model::{BoxClock, BoxRng, Clock}; +use mas_policy::{Policy, ViolationCode, model::CompatLoginType}; use mas_router::{CompatLoginSsoAction, UrlBuilder}; use mas_storage::{BoxRepository, RepositoryAccess, compat::CompatSsoLoginRepository}; -use mas_templates::{CompatSsoContext, ErrorContext, TemplateContext, Templates}; +use mas_templates::{ + CompatLoginPolicyViolationContext, CompatSsoContext, EmptyContext, ErrorContext, + PolicyViolationContext, TemplateContext, Templates, +}; use serde::{Deserialize, Serialize}; use ulid::Ulid; use crate::{ - PreferredLanguage, - session::{SessionOrFallback, load_session_or_fallback}, + BoundActivityTracker, PreferredLanguage, + session::{SessionOrFallback, count_user_sessions_for_limiting, load_session_or_fallback}, }; #[derive(Serialize)] @@ -56,10 +61,15 @@ pub async fn get( mut repo: BoxRepository, State(templates): State, State(url_builder): State, + mut policy: Policy, + activity_tracker: BoundActivityTracker, + user_agent: Option>, cookie_jar: CookieJar, Path(id): Path, Query(params): Query, ) -> Result { + let user_agent = user_agent.map(|ua| ua.to_string()); + let (cookie_jar, maybe_session) = match load_session_or_fallback( cookie_jar, &clock, &mut rng, &templates, &locale, &mut repo, ) @@ -107,6 +117,35 @@ pub async fn get( return Ok((cookie_jar, Html(content)).into_response()); } + let session_counts = count_user_sessions_for_limiting(&mut repo, &session.user).await?; + + let res = policy + .evaluate_compat_login(mas_policy::CompatLoginInput { + user: &session.user, + login_type: CompatLoginType::WebSso, + session_counts, + requester: mas_policy::Requester { + ip_address: activity_tracker.ip(), + user_agent, + }, + }) + .await?; + if !res.valid() { + let ctx = CompatLoginPolicyViolationContext::for_violations( + res.violations + .into_iter() + .filter_map(|v| Some(v.code?.as_str())) + .collect(), + ) + .with_session(session) + .with_csrf(csrf_token.form_value()) + .with_language(locale); + + let content = templates.render_compat_login_policy_violation(&ctx)?; + + return Ok((StatusCode::FORBIDDEN, cookie_jar, Html(content)).into_response()); + } + let ctx = CompatSsoContext::new(login) .with_session(session) .with_csrf(csrf_token.form_value()) @@ -129,11 +168,16 @@ pub async fn post( PreferredLanguage(locale): PreferredLanguage, State(templates): State, State(url_builder): State, + mut policy: Policy, + activity_tracker: BoundActivityTracker, + user_agent: Option>, cookie_jar: CookieJar, Path(id): Path, Query(params): Query, Form(form): Form>, ) -> Result { + let user_agent = user_agent.map(|ua| ua.to_string()); + let (cookie_jar, maybe_session) = match load_session_or_fallback( cookie_jar, &clock, &mut rng, &templates, &locale, &mut repo, ) @@ -200,6 +244,37 @@ pub async fn post( redirect_uri }; + let session_counts = count_user_sessions_for_limiting(&mut repo, &session.user).await?; + + let res = policy + .evaluate_compat_login(mas_policy::CompatLoginInput { + user: &session.user, + login_type: CompatLoginType::WebSso, + session_counts, + requester: mas_policy::Requester { + ip_address: activity_tracker.ip(), + user_agent, + }, + }) + .await?; + + if !res.valid() { + let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng); + let ctx = CompatLoginPolicyViolationContext::for_violations( + res.violations + .into_iter() + .filter_map(|v| Some(v.code?.as_str())) + .collect(), + ) + .with_session(session) + .with_csrf(csrf_token.form_value()) + .with_language(locale); + + let content = templates.render_compat_login_policy_violation(&ctx)?; + + return Ok((StatusCode::FORBIDDEN, cookie_jar, Html(content)).into_response()); + } + // Note that if the login is not Pending, // this fails and aborts the transaction. repo.compat_sso_login() diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index 65a75f550..ebd223e4a 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -272,6 +272,7 @@ where BoxRepository: FromRequestParts, BoxClock: FromRequestParts, BoxRng: FromRequestParts, + Policy: FromRequestParts, { // A sub-router for human-facing routes with error handling let human_router = Router::new() diff --git a/crates/policy/src/model.rs b/crates/policy/src/model.rs index 85b05d317..81c37a6e5 100644 --- a/crates/policy/src/model.rs +++ b/crates/policy/src/model.rs @@ -17,7 +17,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; /// A well-known policy code. -#[derive(Deserialize, Debug, Clone, Copy, JsonSchema)] +#[derive(Deserialize, Debug, Clone, Copy, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub enum Code { /// The username is too short. From 3b04fd5621efc10b101fd3bde09972d1d53b0652 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 25 Nov 2025 18:20:14 +0000 Subject: [PATCH 12/86] Make `finish_sessions_to_replace_device` return whether any were finished --- crates/storage-pg/src/app_session.rs | 17 +++++++++++------ crates/storage/src/app_session.rs | 6 ++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/crates/storage-pg/src/app_session.rs b/crates/storage-pg/src/app_session.rs index 4e12810cc..2867534c3 100644 --- a/crates/storage-pg/src/app_session.rs +++ b/crates/storage-pg/src/app_session.rs @@ -487,14 +487,15 @@ impl AppSessionRepository for PgAppSessionRepository<'_> { clock: &dyn Clock, user: &User, device: &Device, - ) -> Result<(), Self::Error> { + ) -> Result { + let mut affected = false; // TODO need to invoke this from all the oauth2 login sites let span = tracing::info_span!( "db.app_session.finish_sessions_to_replace_device.compat_sessions", { DB_QUERY_TEXT } = tracing::field::Empty, ); let finished_at = clock.now(); - sqlx::query!( + let compat_affected = sqlx::query!( " UPDATE compat_sessions SET finished_at = $3 WHERE user_id = $1 AND device_id = $2 AND finished_at IS NULL ", @@ -505,7 +506,9 @@ impl AppSessionRepository for PgAppSessionRepository<'_> { .record(&span) .execute(&mut *self.conn) .instrument(span) - .await?; + .await? + .rows_affected(); + affected |= compat_affected > 0; if let Ok([stable_device_as_scope_token, unstable_device_as_scope_token]) = device.to_scope_token() @@ -514,7 +517,7 @@ impl AppSessionRepository for PgAppSessionRepository<'_> { "db.app_session.finish_sessions_to_replace_device.oauth2_sessions", { DB_QUERY_TEXT } = tracing::field::Empty, ); - sqlx::query!( + let oauth2_affected = sqlx::query!( " UPDATE oauth2_sessions SET finished_at = $4 @@ -530,10 +533,12 @@ impl AppSessionRepository for PgAppSessionRepository<'_> { .record(&span) .execute(&mut *self.conn) .instrument(span) - .await?; + .await? + .rows_affected(); + affected |= oauth2_affected > 0; } - Ok(()) + Ok(affected) } } diff --git a/crates/storage/src/app_session.rs b/crates/storage/src/app_session.rs index d649ff35e..4c0b7703a 100644 --- a/crates/storage/src/app_session.rs +++ b/crates/storage/src/app_session.rs @@ -196,12 +196,14 @@ pub trait AppSessionRepository: Send + Sync { /// replacing a device). /// /// Should be called *before* creating a new session for the device. + /// + /// Returns true if a session was finished. async fn finish_sessions_to_replace_device( &mut self, clock: &dyn Clock, user: &User, device: &Device, - ) -> Result<(), Self::Error>; + ) -> Result; } repository_impl!(AppSessionRepository: @@ -218,5 +220,5 @@ repository_impl!(AppSessionRepository: clock: &dyn Clock, user: &User, device: &Device, - ) -> Result<(), Self::Error>; + ) -> Result; ); From 6fdb63b361ee5da32e7d2dde4baeea685f4d5bc8 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 25 Nov 2025 18:20:14 +0000 Subject: [PATCH 13/86] Don't apply a session limit when genuinely replacing a session --- crates/handlers/src/compat/login.rs | 8 ++++-- .../handlers/src/compat/login_sso_complete.rs | 4 +++ crates/policy/src/model.rs | 3 +++ policies/compat_login/compat_login.rego | 8 ++++++ policies/compat_login/compat_login_test.rego | 25 +++++++++++++++++++ policies/schema/compat_login_input.json | 5 ++++ 6 files changed, 51 insertions(+), 2 deletions(-) diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index f0be20ca9..23a13d59e 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -576,7 +576,8 @@ async fn token_login( Device::generate(rng) }; - repo.app_session() + let session_replaced = repo + .app_session() .finish_sessions_to_replace_device(clock, &browser_session.user, &device) .await?; @@ -586,6 +587,7 @@ async fn token_login( .evaluate_compat_login(mas_policy::CompatLoginInput { user: &browser_session.user, login_type: CompatLoginType::WebSso, + session_replaced, session_counts, requester, }) @@ -702,7 +704,8 @@ async fn user_password_login( Device::generate(&mut rng) }; - repo.app_session() + let session_replaced = repo + .app_session() .finish_sessions_to_replace_device(clock, &user, &device) .await?; @@ -712,6 +715,7 @@ async fn user_password_login( .evaluate_compat_login(mas_policy::CompatLoginInput { user: &user, login_type: CompatLoginType::Password, + session_replaced, session_counts, requester: policy_requester, }) diff --git a/crates/handlers/src/compat/login_sso_complete.rs b/crates/handlers/src/compat/login_sso_complete.rs index 3c33b5d46..4c34ca7cd 100644 --- a/crates/handlers/src/compat/login_sso_complete.rs +++ b/crates/handlers/src/compat/login_sso_complete.rs @@ -123,6 +123,8 @@ pub async fn get( .evaluate_compat_login(mas_policy::CompatLoginInput { user: &session.user, login_type: CompatLoginType::WebSso, + // TODO should we predict a replacement? + session_replaced: false, session_counts, requester: mas_policy::Requester { ip_address: activity_tracker.ip(), @@ -251,6 +253,8 @@ pub async fn post( user: &session.user, login_type: CompatLoginType::WebSso, session_counts, + // TODO should we predict a replacement? + session_replaced: false, requester: mas_policy::Requester { ip_address: activity_tracker.ip(), user_agent, diff --git a/crates/policy/src/model.rs b/crates/policy/src/model.rs index 81c37a6e5..f8dc8e129 100644 --- a/crates/policy/src/model.rs +++ b/crates/policy/src/model.rs @@ -197,6 +197,9 @@ pub struct CompatLoginInput<'a> { /// How many sessions the user has. pub session_counts: SessionCounts, + /// Whether a session will be replaced by this login + pub session_replaced: bool, + // TODO is this actually what we care about? Don't we care a bit more about whether we're in an // interactive context or a non-interactive context? (SSO type has both phases :() pub login_type: CompatLoginType, diff --git a/policies/compat_login/compat_login.rego b/policies/compat_login/compat_login.rego index 3aeb566cf..d81fe6e6e 100644 --- a/policies/compat_login/compat_login.rego +++ b/policies/compat_login/compat_login.rego @@ -36,6 +36,10 @@ violation contains { # TODO not strictly correct... input.login_type == "m.login.sso" + # Only apply if this login doesn't replace a session + # (As then this login is not actually increasing the number of devices) + not input.session_replaced + # For web-based 'compat SSO' login, a violation occurs when the soft limit has already been # reached or exceeded. # We use the soft limit because the user will be able to interactively remove @@ -53,6 +57,10 @@ violation contains { # This is not a web-based interactive login input.login_type == "m.login.password" + # Only apply if this login doesn't replace a session + # (As then this login is not actually increasing the number of devices) + not input.session_replaced + # For `m.login.password` login, a violation occurs when the hard limit has already been # reached or exceeded. # We don't use the soft limit because the user won't be able to interactively remove diff --git a/policies/compat_login/compat_login_test.rego b/policies/compat_login/compat_login_test.rego index 46ec1b0a2..070e0b7e8 100644 --- a/policies/compat_login/compat_login_test.rego +++ b/policies/compat_login/compat_login_test.rego @@ -14,32 +14,38 @@ test_session_limiting_sso if { compat_login.allow with input.user as user with input.session_counts as {"total": 1} with input.login_type as "m.login.sso" + with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} compat_login.allow with input.user as user with input.session_counts as {"total": 31} with input.login_type as "m.login.sso" + with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 32} with input.login_type as "m.login.sso" + with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 42} with input.login_type as "m.login.sso" + with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 65} with input.login_type as "m.login.sso" + with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} # No limit configured compat_login.allow with input.user as user with input.session_counts as {"total": 1} with input.login_type as "m.login.sso" + with input.session_replaced as false with data.session_limit as null } @@ -47,26 +53,45 @@ test_session_limiting_password if { compat_login.allow with input.user as user with input.session_counts as {"total": 1} with input.login_type as "m.login.password" + with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} compat_login.allow with input.user as user with input.session_counts as {"total": 63} with input.login_type as "m.login.password" + with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 64} with input.login_type as "m.login.password" + with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 65} with input.login_type as "m.login.password" + with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} # No limit configured compat_login.allow with input.user as user with input.session_counts as {"total": 1} with input.login_type as "m.login.password" + with input.session_replaced as false with data.session_limit as null } + +test_no_session_limiting_upon_replacement if { + not compat_login.allow with input.user as user + with input.session_counts as {"total": 65} + with input.login_type as "m.login.password" + with input.session_replaced as false + with data.session_limit as {"soft_limit": 32, "hard_limit": 64} + + not compat_login.allow with input.user as user + with input.session_counts as {"total": 65} + with input.login_type as "m.login.sso" + with input.session_replaced as false + with data.session_limit as {"soft_limit": 32, "hard_limit": 64} +} diff --git a/policies/schema/compat_login_input.json b/policies/schema/compat_login_input.json index c28c79388..a7814c059 100644 --- a/policies/schema/compat_login_input.json +++ b/policies/schema/compat_login_input.json @@ -7,6 +7,7 @@ "login_type", "requester", "session_counts", + "session_replaced", "user" ], "properties": { @@ -22,6 +23,10 @@ } ] }, + "session_replaced": { + "description": "Whether a session will be replaced by this login", + "type": "boolean" + }, "login_type": { "$ref": "#/definitions/CompatLoginType" }, From f450d0449c7cf69e31d2ca48abb151f2d2cfb1ad Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 25 Nov 2025 18:20:14 +0000 Subject: [PATCH 14/86] Make policy depend on whether the login is interactive or not --- crates/handlers/src/compat/login.rs | 2 ++ crates/handlers/src/compat/login_sso_complete.rs | 7 ++++--- crates/policy/src/model.rs | 11 +++++++++-- policies/compat_login/compat_login.rego | 5 ++--- policies/compat_login/compat_login_test.rego | 15 +++++++++++++++ policies/schema/compat_login_input.json | 5 +++++ 6 files changed, 37 insertions(+), 8 deletions(-) diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index 23a13d59e..f159ee791 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -586,6 +586,7 @@ async fn token_login( let res = policy .evaluate_compat_login(mas_policy::CompatLoginInput { user: &browser_session.user, + is_interactive: false, login_type: CompatLoginType::WebSso, session_replaced, session_counts, @@ -714,6 +715,7 @@ async fn user_password_login( let res = policy .evaluate_compat_login(mas_policy::CompatLoginInput { user: &user, + is_interactive: false, login_type: CompatLoginType::Password, session_replaced, session_counts, diff --git a/crates/handlers/src/compat/login_sso_complete.rs b/crates/handlers/src/compat/login_sso_complete.rs index 4c34ca7cd..229eb993c 100644 --- a/crates/handlers/src/compat/login_sso_complete.rs +++ b/crates/handlers/src/compat/login_sso_complete.rs @@ -20,12 +20,11 @@ use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, }; use mas_data_model::{BoxClock, BoxRng, Clock}; -use mas_policy::{Policy, ViolationCode, model::CompatLoginType}; +use mas_policy::{Policy, model::CompatLoginType}; use mas_router::{CompatLoginSsoAction, UrlBuilder}; use mas_storage::{BoxRepository, RepositoryAccess, compat::CompatSsoLoginRepository}; use mas_templates::{ - CompatLoginPolicyViolationContext, CompatSsoContext, EmptyContext, ErrorContext, - PolicyViolationContext, TemplateContext, Templates, + CompatLoginPolicyViolationContext, CompatSsoContext, ErrorContext, TemplateContext, Templates, }; use serde::{Deserialize, Serialize}; use ulid::Ulid; @@ -122,6 +121,7 @@ pub async fn get( let res = policy .evaluate_compat_login(mas_policy::CompatLoginInput { user: &session.user, + is_interactive: true, login_type: CompatLoginType::WebSso, // TODO should we predict a replacement? session_replaced: false, @@ -251,6 +251,7 @@ pub async fn post( let res = policy .evaluate_compat_login(mas_policy::CompatLoginInput { user: &session.user, + is_interactive: true, login_type: CompatLoginType::WebSso, session_counts, // TODO should we predict a replacement? diff --git a/crates/policy/src/model.rs b/crates/policy/src/model.rs index f8dc8e129..81609eb47 100644 --- a/crates/policy/src/model.rs +++ b/crates/policy/src/model.rs @@ -200,8 +200,15 @@ pub struct CompatLoginInput<'a> { /// Whether a session will be replaced by this login pub session_replaced: bool, - // TODO is this actually what we care about? Don't we care a bit more about whether we're in an - // interactive context or a non-interactive context? (SSO type has both phases :() + /// Whether the user is currently in an interactive context. + /// For `m.login.password`: false + /// For `m.login.sso`: + /// - true when asking for consent, + /// - false when actually performing the login (at which point we create the + /// compat session, but it's too late to show a web page) + pub is_interactive: bool, + + // TODO I don't know if we should keep this anymore, not used by current policy pub login_type: CompatLoginType, pub requester: Requester, diff --git a/policies/compat_login/compat_login.rego b/policies/compat_login/compat_login.rego index d81fe6e6e..913ef38c4 100644 --- a/policies/compat_login/compat_login.rego +++ b/policies/compat_login/compat_login.rego @@ -33,8 +33,7 @@ violation contains { data.session_limit != null # This is a web-based interactive login - # TODO not strictly correct... - input.login_type == "m.login.sso" + input.is_interactive # Only apply if this login doesn't replace a session # (As then this login is not actually increasing the number of devices) @@ -55,7 +54,7 @@ violation contains { data.session_limit != null # This is not a web-based interactive login - input.login_type == "m.login.password" + not input.is_interactive # Only apply if this login doesn't replace a session # (As then this login is not actually increasing the number of devices) diff --git a/policies/compat_login/compat_login_test.rego b/policies/compat_login/compat_login_test.rego index 070e0b7e8..9a246a6a5 100644 --- a/policies/compat_login/compat_login_test.rego +++ b/policies/compat_login/compat_login_test.rego @@ -10,33 +10,39 @@ import rego.v1 user := {"username": "john"} +# Tests session limiting when using (the interactive part of) `m.login.sso` test_session_limiting_sso if { compat_login.allow with input.user as user with input.session_counts as {"total": 1} + with input.is_interactive as true with input.login_type as "m.login.sso" with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} compat_login.allow with input.user as user with input.session_counts as {"total": 31} + with input.is_interactive as true with input.login_type as "m.login.sso" with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 32} + with input.is_interactive as true with input.login_type as "m.login.sso" with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 42} + with input.is_interactive as true with input.login_type as "m.login.sso" with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 65} + with input.is_interactive as true with input.login_type as "m.login.sso" with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} @@ -44,32 +50,38 @@ test_session_limiting_sso if { # No limit configured compat_login.allow with input.user as user with input.session_counts as {"total": 1} + with input.is_interactive as true with input.login_type as "m.login.sso" with input.session_replaced as false with data.session_limit as null } +# Test session limiting when using `m.login.password` test_session_limiting_password if { compat_login.allow with input.user as user with input.session_counts as {"total": 1} + with input.is_interactive as false with input.login_type as "m.login.password" with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} compat_login.allow with input.user as user with input.session_counts as {"total": 63} + with input.is_interactive as false with input.login_type as "m.login.password" with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 64} + with input.is_interactive as false with input.login_type as "m.login.password" with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 65} + with input.is_interactive as false with input.login_type as "m.login.password" with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} @@ -77,6 +89,7 @@ test_session_limiting_password if { # No limit configured compat_login.allow with input.user as user with input.session_counts as {"total": 1} + with input.is_interactive as false with input.login_type as "m.login.password" with input.session_replaced as false with data.session_limit as null @@ -85,12 +98,14 @@ test_session_limiting_password if { test_no_session_limiting_upon_replacement if { not compat_login.allow with input.user as user with input.session_counts as {"total": 65} + with input.is_interactive as false with input.login_type as "m.login.password" with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 65} + with input.is_interactive as true with input.login_type as "m.login.sso" with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} diff --git a/policies/schema/compat_login_input.json b/policies/schema/compat_login_input.json index a7814c059..99d33b7e5 100644 --- a/policies/schema/compat_login_input.json +++ b/policies/schema/compat_login_input.json @@ -4,6 +4,7 @@ "description": "Input for the compatibility login policy.", "type": "object", "required": [ + "is_interactive", "login_type", "requester", "session_counts", @@ -27,6 +28,10 @@ "description": "Whether a session will be replaced by this login", "type": "boolean" }, + "is_interactive": { + "description": "Whether the user is currently in an interactive context. For `m.login.password`: false For `m.login.sso`: - true when asking for consent, - false when actually performing the login (at which point we create the compat session, but it's too late to show a web page)", + "type": "boolean" + }, "login_type": { "$ref": "#/definitions/CompatLoginType" }, From 05c087d0461d97475b0bd639e2b30a5b7cc35ec4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:12:33 +0000 Subject: [PATCH 15/86] build(deps): bump EmbarkStudios/cargo-deny-action from 2.0.13 to 2.0.14 Bumps [EmbarkStudios/cargo-deny-action](https://github.com/embarkstudios/cargo-deny-action) from 2.0.13 to 2.0.14. - [Release notes](https://github.com/embarkstudios/cargo-deny-action/releases) - [Commits](https://github.com/embarkstudios/cargo-deny-action/compare/v2.0.13...v2.0.14) --- updated-dependencies: - dependency-name: EmbarkStudios/cargo-deny-action dependency-version: 2.0.14 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ed119c2e3..61a9f3052 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -159,7 +159,7 @@ jobs: uses: actions/checkout@v5 - name: Run `cargo-deny` - uses: EmbarkStudios/cargo-deny-action@v2.0.13 + uses: EmbarkStudios/cargo-deny-action@v2.0.14 with: rust-version: stable From d2ac79d4c8e20cdf1702109501a9dda4341aec6b Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Wed, 26 Nov 2025 12:30:21 +0000 Subject: [PATCH 16/86] fixup! Introduce compat login policy --- crates/handlers/src/test_utils.rs | 1 + policies/schema/compat_login_input.json | 50 ++++++++++++++----------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/crates/handlers/src/test_utils.rs b/crates/handlers/src/test_utils.rs index 4b93177de..521a4848d 100644 --- a/crates/handlers/src/test_utils.rs +++ b/crates/handlers/src/test_utils.rs @@ -82,6 +82,7 @@ pub(crate) async fn policy_factory( register: "register/violation".to_owned(), client_registration: "client_registration/violation".to_owned(), authorization_grant: "authorization_grant/violation".to_owned(), + compat_login: "compat_login/violation".to_owned(), email: "email/violation".to_owned(), }; diff --git a/policies/schema/compat_login_input.json b/policies/schema/compat_login_input.json index 99d33b7e5..eb036014d 100644 --- a/policies/schema/compat_login_input.json +++ b/policies/schema/compat_login_input.json @@ -3,14 +3,6 @@ "title": "CompatLoginInput", "description": "Input for the compatibility login policy.", "type": "object", - "required": [ - "is_interactive", - "login_type", - "requester", - "session_counts", - "session_replaced", - "user" - ], "properties": { "user": { "type": "object", @@ -29,7 +21,7 @@ "type": "boolean" }, "is_interactive": { - "description": "Whether the user is currently in an interactive context. For `m.login.password`: false For `m.login.sso`: - true when asking for consent, - false when actually performing the login (at which point we create the compat session, but it's too late to show a web page)", + "description": "Whether the user is currently in an interactive context.\n For `m.login.password`: false\n For `m.login.sso`:\n - true when asking for consent,\n - false when actually performing the login (at which point we create the\n compat session, but it's too late to show a web page)", "type": "boolean" }, "login_type": { @@ -39,38 +31,46 @@ "$ref": "#/definitions/Requester" } }, + "required": [ + "user", + "session_counts", + "session_replaced", + "is_interactive", + "login_type", + "requester" + ], "definitions": { "SessionCounts": { "description": "Information about how many sessions the user has", "type": "object", - "required": [ - "compat", - "oauth2", - "personal", - "total" - ], "properties": { "total": { "type": "integer", "format": "uint64", - "minimum": 0.0 + "minimum": 0 }, "oauth2": { "type": "integer", "format": "uint64", - "minimum": 0.0 + "minimum": 0 }, "compat": { "type": "integer", "format": "uint64", - "minimum": 0.0 + "minimum": 0 }, "personal": { "type": "integer", "format": "uint64", - "minimum": 0.0 + "minimum": 0 } - } + }, + "required": [ + "total", + "oauth2", + "compat", + "personal" + ] }, "CompatLoginType": { "type": "string", @@ -85,12 +85,18 @@ "properties": { "ip_address": { "description": "IP address of the entity making the request", - "type": "string", + "type": [ + "string", + "null" + ], "format": "ip" }, "user_agent": { "description": "User agent of the entity making the request", - "type": "string" + "type": [ + "string", + "null" + ] } } } From 6f1c10f32b5fdb3e50f8fb95aa878f6ab19289b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 19:43:42 +0000 Subject: [PATCH 17/86] build(deps): bump valibot from 1.1.0 to 1.2.0 in /frontend Bumps [valibot](https://github.com/open-circle/valibot) from 1.1.0 to 1.2.0. - [Release notes](https://github.com/open-circle/valibot/releases) - [Commits](https://github.com/open-circle/valibot/compare/v1.1.0...v1.2.0) --- updated-dependencies: - dependency-name: valibot dependency-version: 1.2.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 52 +++++++++++++++++++++++++++----------- frontend/package.json | 2 +- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 76e6c4c9a..c3e903de1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,7 +25,7 @@ "react-dom": "^19.2.0", "react-i18next": "^16.3.5", "swagger-ui-dist": "^5.29.5", - "valibot": "^1.1.0", + "valibot": "^1.2.0", "vaul": "^1.1.2" }, "devDependencies": { @@ -163,6 +163,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1846,6 +1847,7 @@ "resolved": "https://registry.npmjs.org/@fontsource/inconsolata/-/inconsolata-5.2.8.tgz", "integrity": "sha512-lIZW+WOZYpUH91g9r6rYYhfTmptF3YPPM54ZOs8IYVeeL4SeiAu4tfj7mdr8llYEq31DLYgi6JtGIJa192gB0Q==", "license": "OFL-1.1", + "peer": true, "funding": { "url": "https://github.com/sponsors/ayuhito" } @@ -1855,6 +1857,7 @@ "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.8.tgz", "integrity": "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==", "license": "OFL-1.1", + "peer": true, "funding": { "url": "https://github.com/sponsors/ayuhito" } @@ -5808,6 +5811,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.10.tgz", "integrity": "sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/query-core": "5.90.10" }, @@ -5842,6 +5846,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.131.44.tgz", "integrity": "sha512-LREJfrl8lSedXHCRAAt0HvnHFP9ikAQWnVhYRM++B26w4ZYQBbLvgCT1BCDZVY7MR6rslcd4OfgpZMOyVhNzFg==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.131.2", "@tanstack/react-store": "^0.7.0", @@ -5937,6 +5942,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.131.44.tgz", "integrity": "sha512-Npi9xB3GSYZhRW8+gPhP6bEbyx0vNc8ZNwsi0JapdiFpIiszgRJ57pesy/rklruv46gYQjLVA5KDOsuaCT/urA==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.131.2", "@tanstack/store": "^0.7.0", @@ -6203,8 +6209,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -6295,6 +6300,7 @@ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -6305,6 +6311,7 @@ "integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6315,6 +6322,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6362,6 +6370,7 @@ "resolved": "https://registry.npmjs.org/@vector-im/compound-design-tokens/-/compound-design-tokens-6.4.0.tgz", "integrity": "sha512-93nYQZMgUt6apjCwwnMhMxN8VYQXN3GYOnwovwJjavImwsCGwI/e853BV/DstrWumYh6k5pZsP9e6AF+nz3SIQ==", "license": "SEE LICENSE IN README.md", + "peer": true, "peerDependencies": { "@types/react": "*", "react": "^17 || ^18 || ^19.0.0" @@ -7056,6 +7065,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.2", "caniuse-lite": "^1.0.30001741", @@ -7619,7 +7629,8 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", @@ -7790,8 +7801,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dot-case": { "version": "3.0.4", @@ -7892,6 +7902,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -8417,6 +8428,7 @@ "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -8628,6 +8640,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -9779,7 +9792,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -9986,6 +9998,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.40.0", @@ -10706,6 +10719,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -10863,6 +10877,7 @@ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -10900,7 +10915,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -10916,7 +10930,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -10976,6 +10989,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11017,6 +11031,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -11056,8 +11071,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-refresh": { "version": "0.18.0", @@ -11473,6 +11487,7 @@ "integrity": "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -11592,6 +11607,7 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -11850,6 +11866,7 @@ "integrity": "sha512-vQMufKKA9TxgoEDHJv3esrqUkjszuuRiDkThiHxENFPdQawHhm2Dei+iwNRwH5W671zTDy9iRT9P1KDjcU5Iyw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^1.6.0", @@ -12309,7 +12326,8 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tiny-warning": { "version": "1.0.3", @@ -12512,6 +12530,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12757,9 +12776,9 @@ "license": "MIT" }, "node_modules/valibot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.1.0.tgz", - "integrity": "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz", + "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==", "license": "MIT", "peerDependencies": { "typescript": ">=5" @@ -12789,6 +12808,7 @@ "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -12936,6 +12956,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -13154,6 +13175,7 @@ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/frontend/package.json b/frontend/package.json index f3fbe5c4b..066840864 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,7 +35,7 @@ "react-dom": "^19.2.0", "react-i18next": "^16.3.5", "swagger-ui-dist": "^5.29.5", - "valibot": "^1.1.0", + "valibot": "^1.2.0", "vaul": "^1.1.2" }, "devDependencies": { From 5782efcf60bb1f4ae0d7f397a2f9160473baa1b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 13:12:26 +0000 Subject: [PATCH 18/86] build(deps): bump docker/bake-action from 6.9.0 to 6.10.0 Bumps [docker/bake-action](https://github.com/docker/bake-action) from 6.9.0 to 6.10.0. - [Release notes](https://github.com/docker/bake-action/releases) - [Commits](https://github.com/docker/bake-action/compare/v6.9.0...v6.10.0) --- updated-dependencies: - dependency-name: docker/bake-action dependency-version: 6.10.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 548e53eca..97a729ee3 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -276,7 +276,7 @@ jobs: - name: Build and push id: bake - uses: docker/bake-action@v6.9.0 + uses: docker/bake-action@v6.10.0 with: files: | ./docker-bake.hcl From f97f56ed118e386cefd297cc65e49f559c454d7b Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 28 Nov 2025 10:53:28 +0100 Subject: [PATCH 19/86] Add more options to deal with localpart conflicts on upstream OAuth 2.0 logins --- crates/cli/src/sync.rs | 6 ++ crates/config/src/sections/upstream_oauth2.rs | 13 ++- .../src/upstream_oauth2/provider.rs | 13 ++- crates/handlers/src/upstream_oauth2/link.rs | 92 ++++++++++++++++++- docs/config.schema.json | 14 ++- 5 files changed, 128 insertions(+), 10 deletions(-) diff --git a/crates/cli/src/sync.rs b/crates/cli/src/sync.rs index 1aa8f7402..9d810a7f3 100644 --- a/crates/cli/src/sync.rs +++ b/crates/cli/src/sync.rs @@ -45,6 +45,12 @@ fn map_import_on_conflict( mas_config::UpstreamOAuth2OnConflict::Add => { mas_data_model::UpstreamOAuthProviderOnConflict::Add } + mas_config::UpstreamOAuth2OnConflict::Replace => { + mas_data_model::UpstreamOAuthProviderOnConflict::Replace + } + mas_config::UpstreamOAuth2OnConflict::Set => { + mas_data_model::UpstreamOAuthProviderOnConflict::Set + } mas_config::UpstreamOAuth2OnConflict::Fail => { mas_data_model::UpstreamOAuthProviderOnConflict::Fail } diff --git a/crates/config/src/sections/upstream_oauth2.rs b/crates/config/src/sections/upstream_oauth2.rs index 53eae7a1b..8c7d6dda2 100644 --- a/crates/config/src/sections/upstream_oauth2.rs +++ b/crates/config/src/sections/upstream_oauth2.rs @@ -206,13 +206,20 @@ impl ImportAction { #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum OnConflict { - /// Fails the sso login on conflict + /// Fails the upstream OAuth 2.0 login on conflict #[default] Fail, - /// Adds the oauth identity link, regardless of whether there is an existing - /// link or not + /// Adds the upstream OAuth 2.0 identity link, regardless of whether there + /// is an existing link or not Add, + + /// Replace any existing upstream OAuth 2.0 identity link + Replace, + + /// Adds the upstream OAuth 2.0 identity link *only* if there is no existing + /// link for this provider on the matching user + Set, } impl OnConflict { diff --git a/crates/data-model/src/upstream_oauth2/provider.rs b/crates/data-model/src/upstream_oauth2/provider.rs index be42cb5a5..671393910 100644 --- a/crates/data-model/src/upstream_oauth2/provider.rs +++ b/crates/data-model/src/upstream_oauth2/provider.rs @@ -415,11 +415,18 @@ impl ImportAction { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")] pub enum OnConflict { - /// Fails the upstream OAuth 2.0 login + /// Fails the upstream OAuth 2.0 login on conflict #[default] Fail, - /// Adds the upstream account link, regardless of whether there is an - /// existing link or not + /// Adds the upstream OAuth 2.0 identity link, regardless of whether there + /// is an existing link or not Add, + + /// Replace any existing upstream OAuth 2.0 identity link + Replace, + + /// Adds the upstream OAuth 2.0 identity link *only* if there is no existing + /// link for this provider on the matching user + Set, } diff --git a/crates/handlers/src/upstream_oauth2/link.rs b/crates/handlers/src/upstream_oauth2/link.rs index c794eff3e..b0eef5530 100644 --- a/crates/handlers/src/upstream_oauth2/link.rs +++ b/crates/handlers/src/upstream_oauth2/link.rs @@ -25,8 +25,10 @@ use mas_matrix::HomeserverConnection; use mas_policy::Policy; use mas_router::UrlBuilder; use mas_storage::{ - BoxRepository, RepositoryAccess, - upstream_oauth2::{UpstreamOAuthLinkRepository, UpstreamOAuthSessionRepository}, + BoxRepository, Pagination, RepositoryAccess, + upstream_oauth2::{ + UpstreamOAuthLinkFilter, UpstreamOAuthLinkRepository, UpstreamOAuthSessionRepository, + }, user::{BrowserSessionRepository, UserEmailRepository, UserRepository}, }; use mas_templates::{ @@ -585,6 +587,92 @@ pub(crate) async fn get( .associate_to_user(&link, &existing_user) .await?; } + + // We matched anexisting user and the conflict resolution is to replace any + // link on the existing user with this one + UpstreamOAuthProviderOnConflict::Replace => { + // Find existing links for this provider and user + let filter = UpstreamOAuthLinkFilter::new() + .for_provider(&provider) + .for_user(&existing_user); + let mut cursor = Pagination::first(100); + let mut removed = 0; + loop { + let page = repo.upstream_oauth_link().list(filter, cursor).await?; + for edge in page.edges { + // Remove any existing links for this provider and user + repo.upstream_oauth_link().remove(&clock, edge.node).await?; + cursor = cursor.after(edge.cursor); + removed += 1; + } + + if !page.has_next_page { + break; + } + } + + if removed > 0 { + tracing::warn!( + user.id = %existing_user.id, + upstream_oauth_provider.id = %provider.id, + upstream_oauth_link.id = %link.id, + upstream_oauth_link.subject = link.subject, + "Upstream account mapped localpart {localpart:?} matched an existing user, replaced {removed} links" + ); + } else { + tracing::info!( + user.id = %existing_user.id, + upstream_oauth_provider.id = %provider.id, + upstream_oauth_link.id = %link.id, + upstream_oauth_link.subject = link.subject, + "Upstream account mapped localpart {localpart:?} matched an existing user, linking" + ); + } + + // Add link to the user + repo.upstream_oauth_link() + .associate_to_user(&link, &existing_user) + .await?; + } + + // We matched an existing user and the conflict resolution is link to the + // existing user *only if* there is no existing link on that user + UpstreamOAuthProviderOnConflict::Set => { + // Find existing links for this provider and user + let filter = UpstreamOAuthLinkFilter::new() + .for_provider(&provider) + .for_user(&existing_user); + + let count = repo.upstream_oauth_link().count(filter).await?; + if count > 0 { + tracing::warn!( + upstream_oauth_provider.id = %provider.id, + upstream_oauth_link.id = %link.id, + user.id = %existing_user.id, + "Upstream provider returned a localpart {localpart:?} which is already used by another user. That user already has a ({count}) link to this provider, which isn't allowed by the conflict resolution" + ); + + // TODO: translate + let ctx = ErrorContext::new() + .with_code("User exists") + .with_description(format!( + r"Upstream account provider returned {localpart:?} as username, + which is not linked to another existing upstream account. + Your homeserver does not allow replacing upstream account links automatically." + )) + .with_language(&locale); + + return Ok(( + cookie_jar, + Html(templates.render_error(&ctx)?).into_response(), + )); + } + + // Add link to the user + repo.upstream_oauth_link() + .associate_to_user(&link, &existing_user) + .await?; + } } // Now that we've resolved the conflict, log in that existing user diff --git a/docs/config.schema.json b/docs/config.schema.json index cda68f145..9c5f39313 100644 --- a/docs/config.schema.json +++ b/docs/config.schema.json @@ -2572,14 +2572,24 @@ "description": "How to handle an existing localpart claim", "oneOf": [ { - "description": "Fails the sso login on conflict", + "description": "Fails the upstream OAuth 2.0 login on conflict", "type": "string", "const": "fail" }, { - "description": "Adds the oauth identity link, regardless of whether there is an existing\n link or not", + "description": "Adds the upstream OAuth 2.0 identity link, regardless of whether there\n is an existing link or not", "type": "string", "const": "add" + }, + { + "description": "Replace any existing upstream OAuth 2.0 identity link", + "type": "string", + "const": "replace" + }, + { + "description": "Adds the upstream OAuth 2.0 identity link *only* if there is no existing\n link for this provider on the matching user", + "type": "string", + "const": "set" } ] }, From 868a50030a807622519543f74ccecda74c28b024 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 28 Nov 2025 11:55:34 +0100 Subject: [PATCH 20/86] Add tests for the new on_conflict options --- crates/handlers/src/upstream_oauth2/link.rs | 423 ++++++++++++++++++++ 1 file changed, 423 insertions(+) diff --git a/crates/handlers/src/upstream_oauth2/link.rs b/crates/handlers/src/upstream_oauth2/link.rs index b0eef5530..3bfdc4d33 100644 --- a/crates/handlers/src/upstream_oauth2/link.rs +++ b/crates/handlers/src/upstream_oauth2/link.rs @@ -1671,4 +1671,427 @@ mod tests { Ok((link, session)) } + + #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] + async fn test_link_existing_account_replace_conflict(pool: PgPool) { + let existing_username = "john"; + let subject = "subject"; + let old_subject = "old_subject"; + + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + let mut rng = state.rng(); + let cookies = CookieHelper::new(); + + let claims_imports = UpstreamOAuthProviderClaimsImports { + localpart: UpstreamOAuthProviderLocalpartPreference { + action: mas_data_model::UpstreamOAuthProviderImportAction::Require, + template: None, + // This will replace any existing links for this provider and user + on_conflict: mas_data_model::UpstreamOAuthProviderOnConflict::Replace, + }, + email: UpstreamOAuthProviderImportPreference { + action: mas_data_model::UpstreamOAuthProviderImportAction::Require, + template: None, + }, + ..UpstreamOAuthProviderClaimsImports::default() + }; + + let id_token_claims = serde_json::json!({ + "preferred_username": existing_username, + "email": "any@example.com", + "email_verified": true, + }); + + let id_token = sign_token(&mut rng, &state.key_store, id_token_claims.clone()).unwrap(); + + // Provision a provider and a link + let mut repo = state.repository().await.unwrap(); + let provider = repo + .upstream_oauth_provider() + .add( + &mut rng, + &state.clock, + UpstreamOAuthProviderParams { + issuer: Some("https://example.com/".to_owned()), + human_name: Some("Example Ltd.".to_owned()), + brand_name: None, + scope: Scope::from_iter([OPENID]), + token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::None, + token_endpoint_signing_alg: None, + id_token_signed_response_alg: JsonWebSignatureAlg::Rs256, + client_id: "client".to_owned(), + encrypted_client_secret: None, + claims_imports, + authorization_endpoint_override: None, + token_endpoint_override: None, + userinfo_endpoint_override: None, + fetch_userinfo: false, + userinfo_signed_response_alg: None, + jwks_uri_override: None, + discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc, + pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto, + response_mode: None, + additional_authorization_parameters: Vec::new(), + forward_login_hint: false, + on_backchannel_logout: + mas_data_model::UpstreamOAuthProviderOnBackchannelLogout::DoNothing, + ui_order: 0, + }, + ) + .await + .unwrap(); + + // Create an existing user + let user = repo + .user() + .add(&mut rng, &state.clock, existing_username.to_owned()) + .await + .unwrap(); + + // Create an existing link for this user and provider with a different subject + let old_link = repo + .upstream_oauth_link() + .add( + &mut rng, + &state.clock, + &provider, + old_subject.to_owned(), + None, + ) + .await + .unwrap(); + + repo.upstream_oauth_link() + .associate_to_user(&old_link, &user) + .await + .unwrap(); + + // Provision upstream authorization session to setup cookies + let (link, session) = add_linked_upstream_session( + &mut rng, + &state.clock, + &mut repo, + &provider, + subject, + &id_token.into_string(), + id_token_claims, + ) + .await + .unwrap(); + + repo.save().await.unwrap(); + + let cookie_jar = state.cookie_jar(); + let upstream_sessions = UpstreamSessionsCookie::default() + .add(session.id, provider.id, "state".to_owned(), None) + .add_link_to_session(session.id, link.id) + .unwrap(); + let cookie_jar = upstream_sessions.save(cookie_jar, &state.clock); + cookies.import(cookie_jar); + + let request = Request::get(&*mas_router::UpstreamOAuth2Link::new(link.id).path()).empty(); + let request = cookies.with_cookies(request); + let response = state.request(request).await; + cookies.save_cookies(&response); + response.assert_status(StatusCode::SEE_OTHER); + + // Check that the new link is associated with the existing user + let mut repo = state.repository().await.unwrap(); + + let new_link = repo + .upstream_oauth_link() + .find_by_subject(&provider, subject) + .await + .unwrap() + .expect("new link exists"); + + assert_eq!(new_link.user_id, Some(user.id)); + + // Check that the old link was removed + let old_link_result = repo + .upstream_oauth_link() + .find_by_subject(&provider, old_subject) + .await + .unwrap(); + + assert!( + old_link_result.is_none(), + "Old link should have been removed" + ); + } + + #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] + async fn test_link_existing_account_set_conflict_success(pool: PgPool) { + let existing_username = "john"; + let subject = "subject"; + + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + let mut rng = state.rng(); + let cookies = CookieHelper::new(); + + let claims_imports = UpstreamOAuthProviderClaimsImports { + localpart: UpstreamOAuthProviderLocalpartPreference { + action: mas_data_model::UpstreamOAuthProviderImportAction::Require, + template: None, + // This will only link if there are no existing links for this provider and user + on_conflict: mas_data_model::UpstreamOAuthProviderOnConflict::Set, + }, + email: UpstreamOAuthProviderImportPreference { + action: mas_data_model::UpstreamOAuthProviderImportAction::Require, + template: None, + }, + ..UpstreamOAuthProviderClaimsImports::default() + }; + + let id_token_claims = serde_json::json!({ + "preferred_username": existing_username, + "email": "any@example.com", + "email_verified": true, + }); + + let id_token = sign_token(&mut rng, &state.key_store, id_token_claims.clone()).unwrap(); + + // Provision a provider and a link + let mut repo = state.repository().await.unwrap(); + let provider = repo + .upstream_oauth_provider() + .add( + &mut rng, + &state.clock, + UpstreamOAuthProviderParams { + issuer: Some("https://example.com/".to_owned()), + human_name: Some("Example Ltd.".to_owned()), + brand_name: None, + scope: Scope::from_iter([OPENID]), + token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::None, + token_endpoint_signing_alg: None, + id_token_signed_response_alg: JsonWebSignatureAlg::Rs256, + client_id: "client".to_owned(), + encrypted_client_secret: None, + claims_imports, + authorization_endpoint_override: None, + token_endpoint_override: None, + userinfo_endpoint_override: None, + fetch_userinfo: false, + userinfo_signed_response_alg: None, + jwks_uri_override: None, + discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc, + pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto, + response_mode: None, + additional_authorization_parameters: Vec::new(), + forward_login_hint: false, + on_backchannel_logout: + mas_data_model::UpstreamOAuthProviderOnBackchannelLogout::DoNothing, + ui_order: 0, + }, + ) + .await + .unwrap(); + + // Create an existing user (with no existing links for this provider) + let user = repo + .user() + .add(&mut rng, &state.clock, existing_username.to_owned()) + .await + .unwrap(); + + // Provision upstream authorization session to setup cookies + let (link, session) = add_linked_upstream_session( + &mut rng, + &state.clock, + &mut repo, + &provider, + subject, + &id_token.into_string(), + id_token_claims, + ) + .await + .unwrap(); + + repo.save().await.unwrap(); + + let cookie_jar = state.cookie_jar(); + let upstream_sessions = UpstreamSessionsCookie::default() + .add(session.id, provider.id, "state".to_owned(), None) + .add_link_to_session(session.id, link.id) + .unwrap(); + let cookie_jar = upstream_sessions.save(cookie_jar, &state.clock); + cookies.import(cookie_jar); + + let request = Request::get(&*mas_router::UpstreamOAuth2Link::new(link.id).path()).empty(); + let request = cookies.with_cookies(request); + let response = state.request(request).await; + cookies.save_cookies(&response); + response.assert_status(StatusCode::SEE_OTHER); + + // Check that the new link is associated with the existing user + let mut repo = state.repository().await.unwrap(); + + let new_link = repo + .upstream_oauth_link() + .find_by_subject(&provider, subject) + .await + .unwrap() + .expect("new link exists"); + + assert_eq!(new_link.user_id, Some(user.id)); + } + + #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] + async fn test_link_existing_account_set_conflict_failure(pool: PgPool) { + let existing_username = "john"; + let subject = "subject"; + let old_subject = "old_subject"; + + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + let mut rng = state.rng(); + let cookies = CookieHelper::new(); + + let claims_imports = UpstreamOAuthProviderClaimsImports { + localpart: UpstreamOAuthProviderLocalpartPreference { + action: mas_data_model::UpstreamOAuthProviderImportAction::Require, + template: None, + // This will only link if there are no existing links for this provider and user + on_conflict: mas_data_model::UpstreamOAuthProviderOnConflict::Set, + }, + email: UpstreamOAuthProviderImportPreference { + action: mas_data_model::UpstreamOAuthProviderImportAction::Require, + template: None, + }, + ..UpstreamOAuthProviderClaimsImports::default() + }; + + let id_token_claims = serde_json::json!({ + "preferred_username": existing_username, + "email": "any@example.com", + "email_verified": true, + }); + + let id_token = sign_token(&mut rng, &state.key_store, id_token_claims.clone()).unwrap(); + + // Provision a provider and a link + let mut repo = state.repository().await.unwrap(); + let provider = repo + .upstream_oauth_provider() + .add( + &mut rng, + &state.clock, + UpstreamOAuthProviderParams { + issuer: Some("https://example.com/".to_owned()), + human_name: Some("Example Ltd.".to_owned()), + brand_name: None, + scope: Scope::from_iter([OPENID]), + token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::None, + token_endpoint_signing_alg: None, + id_token_signed_response_alg: JsonWebSignatureAlg::Rs256, + client_id: "client".to_owned(), + encrypted_client_secret: None, + claims_imports, + authorization_endpoint_override: None, + token_endpoint_override: None, + userinfo_endpoint_override: None, + fetch_userinfo: false, + userinfo_signed_response_alg: None, + jwks_uri_override: None, + discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc, + pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto, + response_mode: None, + additional_authorization_parameters: Vec::new(), + forward_login_hint: false, + on_backchannel_logout: + mas_data_model::UpstreamOAuthProviderOnBackchannelLogout::DoNothing, + ui_order: 0, + }, + ) + .await + .unwrap(); + + // Create an existing user + let user = repo + .user() + .add(&mut rng, &state.clock, existing_username.to_owned()) + .await + .unwrap(); + + // Create an existing link for this user and provider with a different subject + let old_link = repo + .upstream_oauth_link() + .add( + &mut rng, + &state.clock, + &provider, + old_subject.to_owned(), + None, + ) + .await + .unwrap(); + + repo.upstream_oauth_link() + .associate_to_user(&old_link, &user) + .await + .unwrap(); + + // Provision upstream authorization session to setup cookies + let (link, session) = add_linked_upstream_session( + &mut rng, + &state.clock, + &mut repo, + &provider, + subject, + &id_token.into_string(), + id_token_claims, + ) + .await + .unwrap(); + + repo.save().await.unwrap(); + + let cookie_jar = state.cookie_jar(); + let upstream_sessions = UpstreamSessionsCookie::default() + .add(session.id, provider.id, "state".to_owned(), None) + .add_link_to_session(session.id, link.id) + .unwrap(); + let cookie_jar = upstream_sessions.save(cookie_jar, &state.clock); + cookies.import(cookie_jar); + + let request = Request::get(&*mas_router::UpstreamOAuth2Link::new(link.id).path()).empty(); + let request = cookies.with_cookies(request); + let response = state.request(request).await; + cookies.save_cookies(&response); + + // Should return an error page because the user already has a link for this + // provider + response.assert_status(StatusCode::OK); + response.assert_header_value(CONTENT_TYPE, "text/html; charset=utf-8"); + + // Verify the error message is displayed + assert!(response.body().contains("User exists")); + assert!(response.body().contains("replacing upstream account links")); + + // Check that the new link was NOT associated with the existing user + let mut repo = state.repository().await.unwrap(); + + let new_link = repo + .upstream_oauth_link() + .find_by_subject(&provider, subject) + .await + .unwrap() + .expect("new link exists"); + + // The new link should still not be associated with the user + assert_eq!(new_link.user_id, None); + + // Check that the old link is still there + let old_link_result = repo + .upstream_oauth_link() + .find_by_subject(&provider, old_subject) + .await + .unwrap(); + + assert!(old_link_result.is_some(), "Old link should still exist"); + assert_eq!(old_link_result.unwrap().user_id, Some(user.id)); + } } From bdffeb6ef5b18e78ff3d60a8c3068a0d3e193a6b Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 28 Nov 2025 11:57:46 +0100 Subject: [PATCH 21/86] Document the new conflict options --- docs/reference/configuration.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index f614791dd..d18e5a44d 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -780,8 +780,10 @@ upstream_oauth2: # How to handle when localpart already exists. # Possible values are (default: fail): - # - `add` : Adds the upstream account link to the existing user, regardless of whether there is an existing link or not. # - `fail` : Fails the upstream OAuth 2.0 login. + # - `add` : Adds the upstream account link to the existing user, regardless of whether there is an existing link or not. + # - `replace` : Replace any existing upstream OAuth 2.0 identity link for this provider on the matching user. + # - `set` : Adds the upstream account link *only* if there is no existing link for this provider on the matching user. #on_conflict: fail # The display name is the user's display name. From 5c7ff7b8dcee31b7c30ca6724295e8c1a9ca99ce Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Thu, 27 Nov 2025 15:13:16 +0000 Subject: [PATCH 22/86] We can't show a cancel button, so don't show one --- templates/pages/compat_login_policy_violation.html | 2 -- translations/en.json | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/templates/pages/compat_login_policy_violation.html b/templates/pages/compat_login_policy_violation.html index c4187336f..5953faefb 100644 --- a/templates/pages/compat_login_policy_violation.html +++ b/templates/pages/compat_login_policy_violation.html @@ -28,7 +28,5 @@ Please see LICENSE files in the repository root for full details. {{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token, post_logout_action=action, as_link=True) }} - - {{ _("action.cancel") }} {% endblock content %} diff --git a/translations/en.json b/translations/en.json index af406ba66..06ae76773 100644 --- a/translations/en.json +++ b/translations/en.json @@ -6,7 +6,7 @@ }, "cancel": "Cancel", "@cancel": { - "context": "pages/compat_login_policy_violation.html:32:89-107, pages/consent.html:69:11-29, pages/device_consent.html:127:13-31, pages/policy_violation.html:44:13-31" + "context": "pages/consent.html:69:11-29, pages/device_consent.html:127:13-31, pages/policy_violation.html:44:13-31" }, "continue": "Continue", "@continue": { From ffec0020a4e821723bae270d7424fb425afd716c Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Thu, 27 Nov 2025 15:13:24 +0000 Subject: [PATCH 23/86] We don't know if there's a device ID --- crates/handlers/src/compat/login_sso_complete.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/handlers/src/compat/login_sso_complete.rs b/crates/handlers/src/compat/login_sso_complete.rs index 229eb993c..020ddd707 100644 --- a/crates/handlers/src/compat/login_sso_complete.rs +++ b/crates/handlers/src/compat/login_sso_complete.rs @@ -123,7 +123,8 @@ pub async fn get( user: &session.user, is_interactive: true, login_type: CompatLoginType::WebSso, - // TODO should we predict a replacement? + // We don't know if there's going to be a replacement until we received the device ID, + // which happens too late. session_replaced: false, session_counts, requester: mas_policy::Requester { @@ -254,7 +255,8 @@ pub async fn post( is_interactive: true, login_type: CompatLoginType::WebSso, session_counts, - // TODO should we predict a replacement? + // We don't know if there's going to be a replacement until we received the device ID, + // which happens too late. session_replaced: false, requester: mas_policy::Requester { ip_address: activity_tracker.ip(), From d38662e3954b8bc6d9c0619803f4f0e93bb296e1 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 28 Nov 2025 15:51:43 +0100 Subject: [PATCH 24/86] Option to skip confirmation when registering through an upstream OAuth provider --- crates/cli/src/sync.rs | 1 + crates/config/src/sections/upstream_oauth2.rs | 34 +++++++++++++++++-- .../src/upstream_oauth2/provider.rs | 3 ++ docs/config.schema.json | 6 +++- docs/reference/configuration.md | 8 +++++ 5 files changed, 48 insertions(+), 4 deletions(-) diff --git a/crates/cli/src/sync.rs b/crates/cli/src/sync.rs index 1aa8f7402..6c250d9c8 100644 --- a/crates/cli/src/sync.rs +++ b/crates/cli/src/sync.rs @@ -58,6 +58,7 @@ fn map_claims_imports( subject: mas_data_model::UpstreamOAuthProviderSubjectPreference { template: config.subject.template.clone(), }, + skip_confirmation: config.skip_confirmation, localpart: mas_data_model::UpstreamOAuthProviderLocalpartPreference { action: map_import_action(config.localpart.action), template: config.localpart.template.clone(), diff --git a/crates/config/src/sections/upstream_oauth2.rs b/crates/config/src/sections/upstream_oauth2.rs index 53eae7a1b..4fc5f16b8 100644 --- a/crates/config/src/sections/upstream_oauth2.rs +++ b/crates/config/src/sections/upstream_oauth2.rs @@ -118,6 +118,26 @@ impl ConfigurationSection for UpstreamOAuth2Config { } } + if provider.claims_imports.skip_confirmation { + if provider.claims_imports.localpart.action != ImportAction::Require { + return Err(annotate(figment::Error::custom( + "The field `action` must be `require` when `skip_confirmation` is set to `true`", + )).with_path("claims_imports.localpart").into()); + } + + if provider.claims_imports.email.action == ImportAction::Suggest { + return Err(annotate(figment::Error::custom( + "The field `action` must not be `suggest` when `skip_confirmation` is set to `true`", + )).with_path("claims_imports.email").into()); + } + + if provider.claims_imports.displayname.action == ImportAction::Suggest { + return Err(annotate(figment::Error::custom( + "The field `action` must not be `suggest` when `skip_confirmation` is set to `true`", + )).with_path("claims_imports.displayname").into()); + } + } + if matches!( provider.claims_imports.localpart.on_conflict, OnConflict::Add @@ -127,7 +147,7 @@ impl ConfigurationSection for UpstreamOAuth2Config { ) { return Err(annotate(figment::Error::custom( "The field `action` must be either `force` or `require` when `on_conflict` is set to `add`", - )).into()); + )).with_path("claims_imports.localpart").into()); } } @@ -326,6 +346,13 @@ pub struct ClaimsImports { #[serde(default, skip_serializing_if = "SubjectImportPreference::is_default")] pub subject: SubjectImportPreference, + /// Whether to skip the interactive screen prompting the user to confirm the + /// attributes that are being imported. This requires `localpart.action` to + /// be `require` and other attribute actions to be either `ignore`, `force` + /// or `require` + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub skip_confirmation: bool, + /// Import the localpart of the MXID #[serde(default, skip_serializing_if = "LocalpartImportPreference::is_default")] pub localpart: LocalpartImportPreference, @@ -337,8 +364,7 @@ pub struct ClaimsImports { )] pub displayname: DisplaynameImportPreference, - /// Import the email address of the user based on the `email` and - /// `email_verified` claims + /// Import the email address of the user #[serde(default, skip_serializing_if = "EmailImportPreference::is_default")] pub email: EmailImportPreference, @@ -354,8 +380,10 @@ impl ClaimsImports { const fn is_default(&self) -> bool { self.subject.is_default() && self.localpart.is_default() + && !self.skip_confirmation && self.displayname.is_default() && self.email.is_default() + && self.account_name.is_default() } } diff --git a/crates/data-model/src/upstream_oauth2/provider.rs b/crates/data-model/src/upstream_oauth2/provider.rs index be42cb5a5..318f1e680 100644 --- a/crates/data-model/src/upstream_oauth2/provider.rs +++ b/crates/data-model/src/upstream_oauth2/provider.rs @@ -312,6 +312,9 @@ pub struct ClaimsImports { #[serde(default)] pub subject: SubjectPreference, + #[serde(default)] + pub skip_confirmation: bool, + #[serde(default)] pub localpart: LocalpartPreference, diff --git a/docs/config.schema.json b/docs/config.schema.json index cda68f145..510c0e765 100644 --- a/docs/config.schema.json +++ b/docs/config.schema.json @@ -2467,6 +2467,10 @@ } ] }, + "skip_confirmation": { + "description": "Whether to skip the interactive screen prompting the user to confirm the\n attributes that are being imported. This requires `localpart.action` to\n be `require` and other attribute actions to be either `ignore`, `force`\n or `require`", + "type": "boolean" + }, "localpart": { "description": "Import the localpart of the MXID", "allOf": [ @@ -2484,7 +2488,7 @@ ] }, "email": { - "description": "Import the email address of the user based on the `email` and\n `email_verified` claims", + "description": "Import the email address of the user", "allOf": [ { "$ref": "#/definitions/EmailImportPreference" diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 58637f2d8..d8bb41129 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -771,6 +771,14 @@ upstream_oauth2: subject: #template: "{{ user.sub }}" + # By default, new users will see a screen confirming the attributes they + # are about to have on their account. + # + # Setting this to `true` allows skipping this screen, but requires the + # `localpart.action` to be set to `require` and the other attributes + # actions to be set to `ignore`, `force` or `require`. + #skip_confirmation: false + # The localpart is the local part of the user's Matrix ID. # For example, on the `example.com` server, if the localpart is `alice`, # the user's Matrix ID will be `@alice:example.com`. From 450b1fa24cfbf6c495a08846e54a423e8772d052 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 28 Nov 2025 16:10:07 +0100 Subject: [PATCH 25/86] Check for the new on_conflict options & update docs --- crates/config/src/sections/upstream_oauth2.rs | 6 ++--- docs/setup/sso.md | 25 +++++++++++-------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/crates/config/src/sections/upstream_oauth2.rs b/crates/config/src/sections/upstream_oauth2.rs index 8c7d6dda2..caee97294 100644 --- a/crates/config/src/sections/upstream_oauth2.rs +++ b/crates/config/src/sections/upstream_oauth2.rs @@ -120,14 +120,14 @@ impl ConfigurationSection for UpstreamOAuth2Config { if matches!( provider.claims_imports.localpart.on_conflict, - OnConflict::Add + OnConflict::Add | OnConflict::Replace | OnConflict::Set ) && !matches!( provider.claims_imports.localpart.action, ImportAction::Force | ImportAction::Require ) { return Err(annotate(figment::Error::custom( - "The field `action` must be either `force` or `require` when `on_conflict` is set to `add`", - )).into()); + "The field `action` must be either `force` or `require` when `on_conflict` is set to `add`, `replace` or `set`", + )).with_path("claims_imports.localpart").into()); } } diff --git a/docs/setup/sso.md b/docs/setup/sso.md index 3442d06bd..62835bb77 100644 --- a/docs/setup/sso.md +++ b/docs/setup/sso.md @@ -69,20 +69,25 @@ The template has the following variables available: ## Allow linking existing user accounts -The authentication service supports linking external provider identities to existing local user accounts. +The authentication service supports linking external provider identities to existing local user accounts if the `localpart` matches. -To enable this behavior, the following option must be explicitly set in the provider configuration: +If the `localpart` given by the upstream provider matches an existing user and the `claims_imports.localpart.action` is set to `force` or `require`, by default the service will refuse to link to that existing account. +This behaviour is controlled by the `claims_imports.localpart.on_conflict` option, which can be set to: + + * `fail` *(default)*: fails the upstream OAuth 2.0 login + * `add`: automatically adds the upstream account to the existing user, regardless of whether the existing user already has another upstream account or not + * `set`: automatically adds the upstream account to the existing user only if there are no other upstream accounts for that provider linked to the user + * `replace`: automatically replaces any upstream account for that provider linked to the user ```yaml -claims_imports: - localpart: - on_conflict: add +upstream_oauth2: + providers: + - id: … + claims_imports: + localpart: + action: force + on_conflict: set ``` -`on_conflict` configuration is specific to `localpart` claim_imports, it can be either: -* `add` : when a user authenticates with the provider for the first time, the system checks whether a local user already exists with a `localpart` matching the attribute mapping `localpart` , _by default `{{ user.preferred_username }}`_. If a match is found, the external identity is linked to the existing local account. -* `fail` *(default)* : fails the sso login. - -To enable this option, the `localpart` mapping must be set to either `force` or `require`. > ⚠️ **Security Notice** > Enabling this option can introduce a risk of account takeover. From 7c16212c4c7325fde2fc8c67df89501f26e499a2 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 28 Nov 2025 18:01:49 +0100 Subject: [PATCH 26/86] Skip the attributes confirmation screen if configured to do so --- crates/handlers/src/upstream_oauth2/link.rs | 225 ++++++++++++++------ 1 file changed, 154 insertions(+), 71 deletions(-) diff --git a/crates/handlers/src/upstream_oauth2/link.rs b/crates/handlers/src/upstream_oauth2/link.rs index c6c1de95d..5125f24ce 100644 --- a/crates/handlers/src/upstream_oauth2/link.rs +++ b/crates/handlers/src/upstream_oauth2/link.rs @@ -4,7 +4,10 @@ // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial // Please see LICENSE files in the repository root for full details. -use std::sync::{Arc, LazyLock}; +use std::{ + net::IpAddr, + sync::{Arc, LazyLock}, +}; use axum::{ Form, @@ -19,7 +22,10 @@ use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, record_error, }; -use mas_data_model::{BoxClock, BoxRng, UpstreamOAuthProviderOnConflict}; +use mas_data_model::{ + BoxClock, BoxRng, UpstreamOAuthAuthorizationSession, UpstreamOAuthProviderOnConflict, + UserRegistration, +}; use mas_jose::jwt::Jwt; use mas_matrix::HomeserverConnection; use mas_policy::Policy; @@ -238,10 +244,6 @@ pub(crate) async fn get( .lookup_link(link_id) .map_err(|_| RouteError::MissingCookie)?; - let post_auth_action = OptionalPostAuthAction { - post_auth_action: post_auth_action.cloned(), - }; - let link = repo .upstream_oauth_link() .lookup(link_id) @@ -285,6 +287,10 @@ pub(crate) async fn get( repo.save().await?; + let post_auth_action = OptionalPostAuthAction { + post_auth_action: post_auth_action.cloned(), + }; + post_auth_action.go_next(&url_builder).into_response() } @@ -357,6 +363,10 @@ pub(crate) async fn get( .authenticate_with_upstream(&mut rng, &clock, &session, &upstream_session) .await?; + let post_auth_action = OptionalPostAuthAction { + post_auth_action: post_auth_action.cloned(), + }; + cookie_jar = sessions_cookie .consume_link(link_id)? .save(cookie_jar, &clock); @@ -386,8 +396,6 @@ pub(crate) async fn get( .await? .ok_or(RouteError::ProviderNotFound(link.provider_id))?; - let mut ctx = UpstreamRegister::new(link.clone(), provider.clone()); - let env = environment(); let mut context = AttributeMappingContext::new(); @@ -421,13 +429,6 @@ pub(crate) async fn get( )? }; - if let Some(displayname) = displayname { - ctx = ctx.with_display_name( - displayname, - provider.claims_imports.displayname.is_forced_or_required(), - ); - } - let email = if provider.claims_imports.email.ignore() { None } else { @@ -446,13 +447,6 @@ pub(crate) async fn get( )? }; - if let Some(ref email) = email { - ctx = ctx.with_email( - email.clone(), - provider.claims_imports.email.is_forced_or_required(), - ); - } - // We do a bunch of checks for the localpart. Instead of using nested ifs all // the way, we use a labelled block, and use `break` for 'exiting' early when // needed @@ -622,6 +616,10 @@ pub(crate) async fn get( .authenticate_with_upstream(&mut rng, &clock, &session, &upstream_session) .await?; + let post_auth_action = OptionalPostAuthAction { + post_auth_action: post_auth_action.cloned(), + }; + let cookie_jar = sessions_cookie .consume_link(link_id)? .save(cookie_jar, &clock) @@ -679,6 +677,53 @@ pub(crate) async fn get( Some(localpart) }; + if provider.claims_imports.skip_confirmation { + let Some(localpart) = localpart else { + return Err(RouteError::Internal( + "No localpart available even though the provider is configured to skip confirmation, this is a bug!".into() + )); + }; + + // Register on the fly + REGISTRATION_COUNTER.add(1, &[KeyValue::new(PROVIDER, provider.id.to_string())]); + + let registration = prepare_user_registration( + &mut rng, + &clock, + &mut repo, + upstream_session, + localpart, + displayname, + email, + activity_tracker.ip(), + user_agent, + post_auth_action.map(|action| serde_json::json!(action)), + ) + .await?; + + let registrations = UserRegistrationSessionsCookie::load(&cookie_jar); + + let cookie_jar = sessions_cookie + .consume_link(link_id)? + .save(cookie_jar, &clock); + + let cookie_jar = registrations.add(®istration).save(cookie_jar, &clock); + + repo.save().await?; + + // Redirect to the user registration flow, in case we have any other step to + // finish + return Ok(( + cookie_jar, + url_builder + .redirect(&mas_router::RegisterFinish::new(registration.id)) + .into_response(), + )); + } + + // Else we show the upstream registration screen + let mut ctx = UpstreamRegister::new(link.clone(), provider.clone()); + if let Some(localpart) = localpart { ctx = ctx.with_localpart( localpart, @@ -686,6 +731,17 @@ pub(crate) async fn get( ); } + if let Some(displayname) = displayname { + ctx = ctx.with_display_name( + displayname, + provider.claims_imports.displayname.is_forced_or_required(), + ); + } + + if let Some(email) = email { + ctx = ctx.with_email(email, provider.claims_imports.email.is_forced_or_required()); + } + let ctx = ctx.with_csrf(csrf_token.form_value()).with_language(locale); Html(templates.render_upstream_oauth2_do_register(&ctx)?).into_response() @@ -1002,17 +1058,19 @@ pub(crate) async fn post( REGISTRATION_COUNTER.add(1, &[KeyValue::new(PROVIDER, provider.id.to_string())]); - let mut registration = repo - .user_registration() - .add( - &mut rng, - &clock, - username, - activity_tracker.ip(), - user_agent, - post_auth_action.map(|action| serde_json::json!(action)), - ) - .await?; + let mut registration = prepare_user_registration( + &mut rng, + &clock, + &mut repo, + upstream_session, + username, + display_name, + email, + activity_tracker.ip(), + user_agent, + post_auth_action.map(|action| serde_json::json!(action)), + ) + .await?; if let Some(terms_url) = &site_config.tos_uri { registration = repo @@ -1021,44 +1079,6 @@ pub(crate) async fn post( .await?; } - // If we have an email, add an email authentication and complete it - if let Some(email) = email { - let authentication = repo - .user_email() - .add_authentication_for_registration(&mut rng, &clock, email, ®istration) - .await?; - let authentication = repo - .user_email() - .complete_authentication_with_upstream( - &clock, - authentication, - &upstream_session, - ) - .await?; - - registration = repo - .user_registration() - .set_email_authentication(registration, &authentication) - .await?; - } - - // If we have a display name, add it to the registration - if let Some(name) = display_name { - registration = repo - .user_registration() - .set_display_name(registration, name) - .await?; - } - - let registration = repo - .user_registration() - .set_upstream_oauth_authorization_session(registration, &upstream_session) - .await?; - - repo.upstream_oauth_session() - .consume(&clock, upstream_session) - .await?; - let registrations = UserRegistrationSessionsCookie::load(&cookie_jar); let cookie_jar = sessions_cookie @@ -1082,6 +1102,69 @@ pub(crate) async fn post( } } +/// Create a user registration using attributes got from the upstream +/// authorization session +async fn prepare_user_registration( + rng: &mut BoxRng, + clock: &BoxClock, + repo: &mut BoxRepository, + upstream_session: UpstreamOAuthAuthorizationSession, + localpart: String, + displayname: Option, + email: Option, + ip_address: Option, + user_agent: Option, + post_auth_action: Option, +) -> Result { + let mut registration = repo + .user_registration() + .add( + rng, + clock, + localpart, + ip_address, + user_agent, + post_auth_action, + ) + .await?; + + // If we have an email, add an email authentication and complete it + if let Some(email) = email { + let authentication = repo + .user_email() + .add_authentication_for_registration(rng, clock, email, ®istration) + .await?; + let authentication = repo + .user_email() + .complete_authentication_with_upstream(clock, authentication, &upstream_session) + .await?; + + registration = repo + .user_registration() + .set_email_authentication(registration, &authentication) + .await?; + } + + // If we have a display name, add it to the registration + if let Some(name) = displayname { + registration = repo + .user_registration() + .set_display_name(registration, name) + .await?; + } + + let registration = repo + .user_registration() + .set_upstream_oauth_authorization_session(registration, &upstream_session) + .await?; + + repo.upstream_oauth_session() + .consume(clock, upstream_session) + .await?; + + Ok(registration) +} + #[cfg(test)] mod tests { use hyper::{Request, StatusCode, header::CONTENT_TYPE}; From e484a810a7e1eae2ea4b4c7d3e4bc1d8420cbffd Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Mon, 1 Dec 2025 11:03:04 +0100 Subject: [PATCH 27/86] Typos and error message rewording Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/handlers/src/upstream_oauth2/link.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/handlers/src/upstream_oauth2/link.rs b/crates/handlers/src/upstream_oauth2/link.rs index 08281291f..15c065be3 100644 --- a/crates/handlers/src/upstream_oauth2/link.rs +++ b/crates/handlers/src/upstream_oauth2/link.rs @@ -588,7 +588,7 @@ pub(crate) async fn get( .await?; } - // We matched anexisting user and the conflict resolution is to replace any + // We matched an existing user and the conflict resolution is to replace any // link on the existing user with this one UpstreamOAuthProviderOnConflict::Replace => { // Find existing links for this provider and user @@ -635,7 +635,7 @@ pub(crate) async fn get( .await?; } - // We matched an existing user and the conflict resolution is link to the + // We matched an existing user and the conflict resolution is to link to the // existing user *only if* there is no existing link on that user UpstreamOAuthProviderOnConflict::Set => { // Find existing links for this provider and user @@ -649,7 +649,7 @@ pub(crate) async fn get( upstream_oauth_provider.id = %provider.id, upstream_oauth_link.id = %link.id, user.id = %existing_user.id, - "Upstream provider returned a localpart {localpart:?} which is already used by another user. That user already has a ({count}) link to this provider, which isn't allowed by the conflict resolution" + "Upstream provider returned a localpart {localpart:?} matching an existing user who already has {count} link(s) to this provider, which isn't allowed by the conflict resolution" ); // TODO: translate @@ -657,7 +657,7 @@ pub(crate) async fn get( .with_code("User exists") .with_description(format!( r"Upstream account provider returned {localpart:?} as username, - which is not linked to another existing upstream account. + but this user already has an existing link to this provider. Your homeserver does not allow replacing upstream account links automatically." )) .with_language(&locale); From 70f3efc0b8cdb9946ad3f466f823f6cca93b99a8 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Fri, 28 Nov 2025 17:37:28 +0000 Subject: [PATCH 28/86] Remove `is_interactive` and carry on with login types --- crates/handlers/src/compat/login.rs | 8 +-- .../handlers/src/compat/login_sso_complete.rs | 12 ++-- crates/policy/src/model.rs | 24 +++---- policies/compat_login/compat_login.rego | 10 ++- policies/compat_login/compat_login_test.rego | 39 ++++------- policies/schema/compat_login_input.json | 66 +++++++++++++++---- 6 files changed, 96 insertions(+), 63 deletions(-) diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index f159ee791..0fbcf5e0f 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -16,7 +16,7 @@ use mas_data_model::{ User, }; use mas_matrix::HomeserverConnection; -use mas_policy::{Policy, Requester, ViolationCode, model::CompatLoginType}; +use mas_policy::{Policy, Requester, ViolationCode, model::CompatLogin}; use mas_storage::{ BoxRepository, BoxRepositoryFactory, RepositoryAccess, compat::{ @@ -586,8 +586,7 @@ async fn token_login( let res = policy .evaluate_compat_login(mas_policy::CompatLoginInput { user: &browser_session.user, - is_interactive: false, - login_type: CompatLoginType::WebSso, + login: CompatLogin::Token, session_replaced, session_counts, requester, @@ -715,8 +714,7 @@ async fn user_password_login( let res = policy .evaluate_compat_login(mas_policy::CompatLoginInput { user: &user, - is_interactive: false, - login_type: CompatLoginType::Password, + login: CompatLogin::Password, session_replaced, session_counts, requester: policy_requester, diff --git a/crates/handlers/src/compat/login_sso_complete.rs b/crates/handlers/src/compat/login_sso_complete.rs index 020ddd707..9ede9a923 100644 --- a/crates/handlers/src/compat/login_sso_complete.rs +++ b/crates/handlers/src/compat/login_sso_complete.rs @@ -20,7 +20,7 @@ use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, }; use mas_data_model::{BoxClock, BoxRng, Clock}; -use mas_policy::{Policy, model::CompatLoginType}; +use mas_policy::{Policy, model::CompatLogin}; use mas_router::{CompatLoginSsoAction, UrlBuilder}; use mas_storage::{BoxRepository, RepositoryAccess, compat::CompatSsoLoginRepository}; use mas_templates::{ @@ -121,8 +121,9 @@ pub async fn get( let res = policy .evaluate_compat_login(mas_policy::CompatLoginInput { user: &session.user, - is_interactive: true, - login_type: CompatLoginType::WebSso, + login: CompatLogin::Sso { + redirect_uri: login.redirect_uri.to_string(), + }, // We don't know if there's going to be a replacement until we received the device ID, // which happens too late. session_replaced: false, @@ -252,8 +253,9 @@ pub async fn post( let res = policy .evaluate_compat_login(mas_policy::CompatLoginInput { user: &session.user, - is_interactive: true, - login_type: CompatLoginType::WebSso, + login: CompatLogin::Sso { + redirect_uri: login.redirect_uri.to_string(), + }, session_counts, // We don't know if there's going to be a replacement until we received the device ID, // which happens too late. diff --git a/crates/policy/src/model.rs b/crates/policy/src/model.rs index 81609eb47..3f6a72d66 100644 --- a/crates/policy/src/model.rs +++ b/crates/policy/src/model.rs @@ -200,25 +200,25 @@ pub struct CompatLoginInput<'a> { /// Whether a session will be replaced by this login pub session_replaced: bool, - /// Whether the user is currently in an interactive context. - /// For `m.login.password`: false - /// For `m.login.sso`: - /// - true when asking for consent, - /// - false when actually performing the login (at which point we create the - /// compat session, but it's too late to show a web page) - pub is_interactive: bool, - - // TODO I don't know if we should keep this anymore, not used by current policy - pub login_type: CompatLoginType, + /// What type of login is being performed. + /// This also determines whether the login is interactive. + pub login: CompatLogin, pub requester: Requester, } #[derive(Serialize, Debug, JsonSchema)] -pub enum CompatLoginType { +#[serde(tag = "type")] +pub enum CompatLogin { + /// Used as the interactive part of SSO login. #[serde(rename = "m.login.sso")] - WebSso, + Sso { redirect_uri: String }, + /// Used as the final (non-interactive) stage of SSO login. + #[serde(rename = "m.login.token")] + Token, + + /// Non-interactive password-over-the-API login. #[serde(rename = "m.login.password")] Password, } diff --git a/policies/compat_login/compat_login.rego b/policies/compat_login/compat_login.rego index 913ef38c4..c856aaf05 100644 --- a/policies/compat_login/compat_login.rego +++ b/policies/compat_login/compat_login.rego @@ -14,6 +14,12 @@ import data.common default allow := false +is_interactive if { + # Only `m.login.sso` (the interactive web form) is interactive; + # `m.login.password` and `m.login.token` (including the finalisation of an SSO login) are not + input.login.type == "m.login.sso" +} + allow if { count(violation) == 0 } @@ -33,7 +39,7 @@ violation contains { data.session_limit != null # This is a web-based interactive login - input.is_interactive + is_interactive # Only apply if this login doesn't replace a session # (As then this login is not actually increasing the number of devices) @@ -54,7 +60,7 @@ violation contains { data.session_limit != null # This is not a web-based interactive login - not input.is_interactive + not is_interactive # Only apply if this login doesn't replace a session # (As then this login is not actually increasing the number of devices) diff --git a/policies/compat_login/compat_login_test.rego b/policies/compat_login/compat_login_test.rego index 9a246a6a5..1b8049844 100644 --- a/policies/compat_login/compat_login_test.rego +++ b/policies/compat_login/compat_login_test.rego @@ -14,44 +14,38 @@ user := {"username": "john"} test_session_limiting_sso if { compat_login.allow with input.user as user with input.session_counts as {"total": 1} - with input.is_interactive as true - with input.login_type as "m.login.sso" + with input.login as {"type": "m.login.sso"} with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} compat_login.allow with input.user as user with input.session_counts as {"total": 31} - with input.is_interactive as true - with input.login_type as "m.login.sso" + with input.login as {"type": "m.login.sso"} with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 32} - with input.is_interactive as true - with input.login_type as "m.login.sso" + with input.login as {"type": "m.login.sso"} with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 42} - with input.is_interactive as true - with input.login_type as "m.login.sso" + with input.login as {"type": "m.login.sso"} with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 65} - with input.is_interactive as true - with input.login_type as "m.login.sso" + with input.login as {"type": "m.login.sso"} with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} # No limit configured compat_login.allow with input.user as user with input.session_counts as {"total": 1} - with input.is_interactive as true - with input.login_type as "m.login.sso" + with input.login as {"type": "m.login.sso"} with input.session_replaced as false with data.session_limit as null } @@ -60,37 +54,32 @@ test_session_limiting_sso if { test_session_limiting_password if { compat_login.allow with input.user as user with input.session_counts as {"total": 1} - with input.is_interactive as false - with input.login_type as "m.login.password" + with input.login as {"type": "m.login.password"} with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} compat_login.allow with input.user as user with input.session_counts as {"total": 63} - with input.is_interactive as false - with input.login_type as "m.login.password" + with input.login as {"type": "m.login.password"} with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 64} - with input.is_interactive as false - with input.login_type as "m.login.password" + with input.login as {"type": "m.login.password"} with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 65} - with input.is_interactive as false - with input.login_type as "m.login.password" + with input.login as {"type": "m.login.password"} with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} # No limit configured compat_login.allow with input.user as user with input.session_counts as {"total": 1} - with input.is_interactive as false - with input.login_type as "m.login.password" + with input.login as {"type": "m.login.password"} with input.session_replaced as false with data.session_limit as null } @@ -98,15 +87,13 @@ test_session_limiting_password if { test_no_session_limiting_upon_replacement if { not compat_login.allow with input.user as user with input.session_counts as {"total": 65} - with input.is_interactive as false - with input.login_type as "m.login.password" + with input.login as {"type": "m.login.password"} with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} not compat_login.allow with input.user as user with input.session_counts as {"total": 65} - with input.is_interactive as true - with input.login_type as "m.login.sso" + with input.login as {"type": "m.login.sso"} with input.session_replaced as false with data.session_limit as {"soft_limit": 32, "hard_limit": 64} } diff --git a/policies/schema/compat_login_input.json b/policies/schema/compat_login_input.json index eb036014d..ffb182de4 100644 --- a/policies/schema/compat_login_input.json +++ b/policies/schema/compat_login_input.json @@ -20,12 +20,13 @@ "description": "Whether a session will be replaced by this login", "type": "boolean" }, - "is_interactive": { - "description": "Whether the user is currently in an interactive context.\n For `m.login.password`: false\n For `m.login.sso`:\n - true when asking for consent,\n - false when actually performing the login (at which point we create the\n compat session, but it's too late to show a web page)", - "type": "boolean" - }, - "login_type": { - "$ref": "#/definitions/CompatLoginType" + "login": { + "description": "What type of login is being performed.\n This also determines whether the login is interactive.", + "allOf": [ + { + "$ref": "#/definitions/CompatLogin" + } + ] }, "requester": { "$ref": "#/definitions/Requester" @@ -35,8 +36,7 @@ "user", "session_counts", "session_replaced", - "is_interactive", - "login_type", + "login", "requester" ], "definitions": { @@ -72,11 +72,51 @@ "personal" ] }, - "CompatLoginType": { - "type": "string", - "enum": [ - "m.login.sso", - "m.login.password" + "CompatLogin": { + "oneOf": [ + { + "description": "Used as the interactive part of SSO login.", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "m.login.sso" + }, + "redirect_uri": { + "type": "string" + } + }, + "required": [ + "type", + "redirect_uri" + ] + }, + { + "description": "Used as the final (non-interactive) stage of SSO login.", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "m.login.token" + } + }, + "required": [ + "type" + ] + }, + { + "description": "Non-interactive password-over-the-API login.", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "m.login.password" + } + }, + "required": [ + "type" + ] + } ] }, "Requester": { From 65b7cdc40959e9f2f914a562a6208120ef386976 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Mon, 1 Dec 2025 11:47:37 +0000 Subject: [PATCH 29/86] Expose `Violation`s directly to the compat policy violation template --- Cargo.lock | 1 + .../handlers/src/compat/login_sso_complete.rs | 26 ++++++------------- crates/policy/src/model.rs | 4 +-- crates/templates/Cargo.toml | 1 + crates/templates/src/context.rs | 23 ++++++++-------- 5 files changed, 24 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b67a4a65f..61fc0b4ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3723,6 +3723,7 @@ dependencies = [ "mas-data-model", "mas-i18n", "mas-iana", + "mas-policy", "mas-router", "mas-spa", "minijinja", diff --git a/crates/handlers/src/compat/login_sso_complete.rs b/crates/handlers/src/compat/login_sso_complete.rs index 9ede9a923..b0735ffce 100644 --- a/crates/handlers/src/compat/login_sso_complete.rs +++ b/crates/handlers/src/compat/login_sso_complete.rs @@ -135,15 +135,10 @@ pub async fn get( }) .await?; if !res.valid() { - let ctx = CompatLoginPolicyViolationContext::for_violations( - res.violations - .into_iter() - .filter_map(|v| Some(v.code?.as_str())) - .collect(), - ) - .with_session(session) - .with_csrf(csrf_token.form_value()) - .with_language(locale); + let ctx = CompatLoginPolicyViolationContext::for_violations(res.violations) + .with_session(session) + .with_csrf(csrf_token.form_value()) + .with_language(locale); let content = templates.render_compat_login_policy_violation(&ctx)?; @@ -269,15 +264,10 @@ pub async fn post( if !res.valid() { let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng); - let ctx = CompatLoginPolicyViolationContext::for_violations( - res.violations - .into_iter() - .filter_map(|v| Some(v.code?.as_str())) - .collect(), - ) - .with_session(session) - .with_csrf(csrf_token.form_value()) - .with_language(locale); + let ctx = CompatLoginPolicyViolationContext::for_violations(res.violations) + .with_session(session) + .with_csrf(csrf_token.form_value()) + .with_language(locale); let content = templates.render_compat_login_policy_violation(&ctx)?; diff --git a/crates/policy/src/model.rs b/crates/policy/src/model.rs index 3f6a72d66..a9f5fb502 100644 --- a/crates/policy/src/model.rs +++ b/crates/policy/src/model.rs @@ -17,7 +17,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; /// A well-known policy code. -#[derive(Deserialize, Debug, Clone, Copy, JsonSchema, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub enum Code { /// The username is too short. @@ -75,7 +75,7 @@ impl Code { } /// A single violation of a policy. -#[derive(Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema)] pub struct Violation { pub msg: String, pub redirect_uri: Option, diff --git a/crates/templates/Cargo.toml b/crates/templates/Cargo.toml index 46ff80c74..d9c1bb019 100644 --- a/crates/templates/Cargo.toml +++ b/crates/templates/Cargo.toml @@ -41,6 +41,7 @@ oauth2-types.workspace = true mas-data-model.workspace = true mas-i18n.workspace = true mas-iana.workspace = true +mas-policy.workspace = true mas-router.workspace = true mas-spa.workspace = true diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index 85d3f6e3e..bee16efbd 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -28,6 +28,7 @@ use mas_data_model::{ }; use mas_i18n::DataLocale; use mas_iana::jose::JsonWebSignatureAlg; +use mas_policy::{Violation, ViolationCode}; use mas_router::{Account, GraphQL, PostAuthAction, UrlBuilder}; use oauth2_types::scope::{OPENID, Scope}; use rand::{ @@ -863,7 +864,7 @@ impl PolicyViolationContext { /// Context used by the `compat_login_policy_violation.html` template #[derive(Serialize)] pub struct CompatLoginPolicyViolationContext { - violation_codes: Vec<&'static str>, + violations: Vec, } impl TemplateContext for CompatLoginPolicyViolationContext { @@ -876,11 +877,14 @@ impl TemplateContext for CompatLoginPolicyViolationContext { Self: Sized, { sample_list(vec![ + CompatLoginPolicyViolationContext { violations: vec![] }, CompatLoginPolicyViolationContext { - violation_codes: vec![], - }, - CompatLoginPolicyViolationContext { - violation_codes: vec!["too-many-sessions"], + violations: vec![Violation { + msg: "user has too many active sessions".to_owned(), + redirect_uri: None, + field: None, + code: Some(ViolationCode::TooManySessions), + }], }, ]) } @@ -888,13 +892,10 @@ impl TemplateContext for CompatLoginPolicyViolationContext { impl CompatLoginPolicyViolationContext { /// Constructs a context for the compatibility login policy violation page - /// given the list of violations' codes. - /// - /// TODO maybe this is not very nice, not sure what the API boundary should - /// be + /// given the list of violations #[must_use] - pub const fn for_violations(violation_codes: Vec<&'static str>) -> Self { - Self { violation_codes } + pub const fn for_violations(violations: Vec) -> Self { + Self { violations } } } From a59d38fc0b53e424d2bdf0eaf888a3227df176e7 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Mon, 1 Dec 2025 11:47:37 +0000 Subject: [PATCH 30/86] Comment on why we special-case 'only violation is too-many-sessions' --- crates/handlers/src/compat/login.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index 0fbcf5e0f..9f10d0373 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -593,6 +593,11 @@ async fn token_login( }) .await?; if !res.valid() { + // If the only violation is that we have too many sessions, then handle that + // separately. + // In the future, we intend to evict some sessions automatically instead. We + // don't trigger this if there was some other violation anyway, since that means + // that removing a session wouldn't actually unblock the login. if res.violations.len() == 1 { let violation = &res.violations[0]; if violation.code == Some(ViolationCode::TooManySessions) { @@ -721,6 +726,11 @@ async fn user_password_login( }) .await?; if !res.valid() { + // If the only violation is that we have too many sessions, then handle that + // separately. + // In the future, we intend to evict some sessions automatically instead. We + // don't trigger this if there was some other violation anyway, since that means + // that removing a session wouldn't actually unblock the login. if res.violations.len() == 1 { let violation = &res.violations[0]; if violation.code == Some(ViolationCode::TooManySessions) { From fa9ff0c67bb1b4f7ec31eb263ddb80a347068e6f Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Mon, 1 Dec 2025 11:51:51 +0000 Subject: [PATCH 31/86] (delint: Is this a less messy rule?) --- policies/compat_login/compat_login.rego | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/policies/compat_login/compat_login.rego b/policies/compat_login/compat_login.rego index c856aaf05..4f76842cd 100644 --- a/policies/compat_login/compat_login.rego +++ b/policies/compat_login/compat_login.rego @@ -14,12 +14,6 @@ import data.common default allow := false -is_interactive if { - # Only `m.login.sso` (the interactive web form) is interactive; - # `m.login.password` and `m.login.token` (including the finalisation of an SSO login) are not - input.login.type == "m.login.sso" -} - allow if { count(violation) == 0 } @@ -72,3 +66,9 @@ violation contains { # sessions to return under the limit. data.session_limit.hard_limit <= input.session_counts.total } + +is_interactive if { + # Only `m.login.sso` (the interactive web form) is interactive; + # `m.login.password` and `m.login.token` (including the finalisation of an SSO login) are not + input.login.type == "m.login.sso" +} From 8305a7e184f037f3925905b4e922c15ec6b5b9ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:53:04 +0000 Subject: [PATCH 32/86] build(deps-dev): bump the graphql-codegen group Bumps the graphql-codegen group in /frontend with 2 updates: [@graphql-codegen/cli](https://github.com/dotansimha/graphql-code-generator/tree/HEAD/packages/graphql-codegen-cli) and [@graphql-codegen/client-preset](https://github.com/dotansimha/graphql-code-generator/tree/HEAD/packages/presets/client). Updates `@graphql-codegen/cli` from 6.0.2 to 6.1.0 - [Release notes](https://github.com/dotansimha/graphql-code-generator/releases) - [Changelog](https://github.com/dotansimha/graphql-code-generator/blob/master/packages/graphql-codegen-cli/CHANGELOG.md) - [Commits](https://github.com/dotansimha/graphql-code-generator/commits/@graphql-codegen/cli@6.1.0/packages/graphql-codegen-cli) Updates `@graphql-codegen/client-preset` from 5.1.3 to 5.2.0 - [Release notes](https://github.com/dotansimha/graphql-code-generator/releases) - [Changelog](https://github.com/dotansimha/graphql-code-generator/blob/master/packages/presets/client/CHANGELOG.md) - [Commits](https://github.com/dotansimha/graphql-code-generator/commits/@graphql-codegen/client-preset@5.2.0/packages/presets/client) --- updated-dependencies: - dependency-name: "@graphql-codegen/cli" dependency-version: 6.1.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: graphql-codegen - dependency-name: "@graphql-codegen/client-preset" dependency-version: 5.2.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: graphql-codegen ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 144 +++++++++++++++++++++---------------- frontend/package.json | 2 +- 2 files changed, 84 insertions(+), 62 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 76e6c4c9a..a54b0cf15 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -33,7 +33,7 @@ "@browser-logos/chrome": "^2.0.0", "@browser-logos/firefox": "^3.0.10", "@browser-logos/safari": "^2.1.0", - "@graphql-codegen/cli": "^6.0.2", + "@graphql-codegen/cli": "^6.1.0", "@graphql-codegen/client-preset": "^5.1.1", "@graphql-codegen/typescript-msw": "^3.0.1", "@storybook/addon-docs": "^10.0.8", @@ -163,6 +163,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1846,6 +1847,7 @@ "resolved": "https://registry.npmjs.org/@fontsource/inconsolata/-/inconsolata-5.2.8.tgz", "integrity": "sha512-lIZW+WOZYpUH91g9r6rYYhfTmptF3YPPM54ZOs8IYVeeL4SeiAu4tfj7mdr8llYEq31DLYgi6JtGIJa192gB0Q==", "license": "OFL-1.1", + "peer": true, "funding": { "url": "https://github.com/sponsors/ayuhito" } @@ -1855,6 +1857,7 @@ "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.8.tgz", "integrity": "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==", "license": "OFL-1.1", + "peer": true, "funding": { "url": "https://github.com/sponsors/ayuhito" } @@ -1884,18 +1887,18 @@ "license": "0BSD" }, "node_modules/@graphql-codegen/cli": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-6.0.2.tgz", - "integrity": "sha512-W+0ime0xMrCyG77q+5xiPkkqPLuXJcTx0Zr9TTOxF4zIqWKVsuImS3qVxtpeTx+GRbb8VWv9IedWMtt91JGzQg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-6.1.0.tgz", + "integrity": "sha512-7w3Zq5IFONVOBcyOiP01Nv9WRxGS/TEaBCAb/ALYA3xHq95dqKCpoGnxt/Ut9R18jiS+aMgT0gc8Tr8sHy44jA==", "dev": true, "license": "MIT", "dependencies": { "@babel/generator": "^7.18.13", "@babel/template": "^7.18.10", "@babel/types": "^7.18.13", - "@graphql-codegen/client-preset": "^5.1.2", + "@graphql-codegen/client-preset": "^5.2.0", "@graphql-codegen/core": "^5.0.0", - "@graphql-codegen/plugin-helpers": "^6.0.0", + "@graphql-codegen/plugin-helpers": "^6.1.0", "@graphql-tools/apollo-engine-loader": "^8.0.0", "@graphql-tools/code-file-loader": "^8.0.0", "@graphql-tools/git-loader": "^8.0.0", @@ -2144,21 +2147,21 @@ } }, "node_modules/@graphql-codegen/client-preset": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-5.1.3.tgz", - "integrity": "sha512-8nlKt8/gO/BovWahLb96taMssHKPibBfslry1ed9DIJtbOrceFYF3yNbFZuTHmI644C7ZvoYK93JkE3VzDlCyg==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-5.2.1.tgz", + "integrity": "sha512-6qFjHQQUWrEH+MVvWs5sPUgme8X+Ivg3WfzaCESooRBQZ4/EnSFlXkPWUTbOKYLRUoMv4g6iTRcZQf6u1wtHZA==", "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.0.5", - "@graphql-codegen/plugin-helpers": "^6.0.0", - "@graphql-codegen/typed-document-node": "^6.1.2", - "@graphql-codegen/typescript": "^5.0.4", - "@graphql-codegen/typescript-operations": "^5.0.4", - "@graphql-codegen/visitor-plugin-common": "^6.1.2", + "@graphql-codegen/gql-tag-operations": "5.1.1", + "@graphql-codegen/plugin-helpers": "^6.1.0", + "@graphql-codegen/typed-document-node": "^6.1.4", + "@graphql-codegen/typescript": "^5.0.6", + "@graphql-codegen/typescript-operations": "^5.0.6", + "@graphql-codegen/visitor-plugin-common": "^6.2.1", "@graphql-tools/documents": "^1.0.0", "@graphql-tools/utils": "^10.0.0", "@graphql-typed-document-node/core": "3.2.0", @@ -2211,14 +2214,14 @@ "license": "0BSD" }, "node_modules/@graphql-codegen/gql-tag-operations": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-5.0.5.tgz", - "integrity": "sha512-DutUBwA3UMOB2AI6O1FDidYw7N0Br4d/ogGrYg6XOeeVuRYigc6i9wX4qiv4ofD34Ujfcfze0U2PI3ZOR33NKw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-5.1.1.tgz", + "integrity": "sha512-XewD0XxN2sgKieEIFeGWV5yT5X2aNy+eg+K8bHlUD7QfyrN2bi67rv/O5Edu7LVDOJR69uqVBp++18d742mn3Q==", "dev": true, "license": "MIT", "dependencies": { - "@graphql-codegen/plugin-helpers": "^6.0.0", - "@graphql-codegen/visitor-plugin-common": "6.1.2", + "@graphql-codegen/plugin-helpers": "^6.1.0", + "@graphql-codegen/visitor-plugin-common": "6.2.1", "@graphql-tools/utils": "^10.0.0", "auto-bind": "~4.0.0", "tslib": "~2.6.0" @@ -2238,9 +2241,9 @@ "license": "0BSD" }, "node_modules/@graphql-codegen/plugin-helpers": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-6.0.0.tgz", - "integrity": "sha512-Z7P89vViJvQakRyMbq/JF2iPLruRFOwOB6IXsuSvV/BptuuEd7fsGPuEf8bdjjDxUY0pJZnFN8oC7jIQ8p9GKA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-6.1.0.tgz", + "integrity": "sha512-JJypehWTcty9kxKiqH7TQOetkGdOYjY78RHlI+23qB59cV2wxjFFVf8l7kmuXS4cpGVUNfIjFhVr7A1W7JMtdA==", "dev": true, "license": "MIT", "dependencies": { @@ -2291,14 +2294,14 @@ "license": "0BSD" }, "node_modules/@graphql-codegen/typed-document-node": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-6.1.2.tgz", - "integrity": "sha512-DelLv7BY8Sx0toyCiEsc46W3FtqipiiqhprUnGnSalfKnKVB8KUodXKaf70migy6hWyDl5d1OJOp5wrttuIy2Q==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-6.1.4.tgz", + "integrity": "sha512-ITWsA+qvT7R64z7KmYHXfgyD5ff069FAGq/hpR0EWVfzXT4RW1Xn/3Biw7/jvwMGsS1BTjo8ZLSIMNM8KjE3GA==", "dev": true, "license": "MIT", "dependencies": { - "@graphql-codegen/plugin-helpers": "^6.0.0", - "@graphql-codegen/visitor-plugin-common": "6.1.2", + "@graphql-codegen/plugin-helpers": "^6.1.0", + "@graphql-codegen/visitor-plugin-common": "6.2.1", "auto-bind": "~4.0.0", "change-case-all": "1.0.15", "tslib": "~2.6.0" @@ -2318,15 +2321,15 @@ "license": "0BSD" }, "node_modules/@graphql-codegen/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-q6S8hX+aR4BzeGgolac4gp22rBnXbLhedmOwT1UBT9e3lGNmNpYC7WJUEzAPjWf6z1lRSNmojLlwEjTnffhKNA==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-5.0.6.tgz", + "integrity": "sha512-rKW3wYInAnmO/DmKjhW3/KLMxUauUCZuMEPQmuoHChnwIuMjn5kVXCdArGyQqv+vVtFj55aS+sJLN4MPNNjSNg==", "dev": true, "license": "MIT", "dependencies": { - "@graphql-codegen/plugin-helpers": "^6.0.0", + "@graphql-codegen/plugin-helpers": "^6.1.0", "@graphql-codegen/schema-ast": "^5.0.0", - "@graphql-codegen/visitor-plugin-common": "6.1.2", + "@graphql-codegen/visitor-plugin-common": "6.2.1", "auto-bind": "~4.0.0", "tslib": "~2.6.0" }, @@ -2655,15 +2658,15 @@ } }, "node_modules/@graphql-codegen/typescript-operations": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-5.0.4.tgz", - "integrity": "sha512-5Bu/BTmyNjdSfSLLBKjC0+4XWcY01uotVcnVIWIxxRdIHoRxnTW6PUkT5CoPHP5r/Uoo3OvIJxh+0LYSH5suwA==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-5.0.6.tgz", + "integrity": "sha512-pkR/82qWO50OHWeV3BiDuVxNFxiJerpmNjFep71VlabADXiU3GIeSaDd6G9a1/SCniVTXZQk2ivCb0ZJiuwo1A==", "dev": true, "license": "MIT", "dependencies": { - "@graphql-codegen/plugin-helpers": "^6.0.0", - "@graphql-codegen/typescript": "^5.0.4", - "@graphql-codegen/visitor-plugin-common": "6.1.2", + "@graphql-codegen/plugin-helpers": "^6.1.0", + "@graphql-codegen/typescript": "^5.0.6", + "@graphql-codegen/visitor-plugin-common": "6.2.1", "auto-bind": "~4.0.0", "tslib": "~2.6.0" }, @@ -2695,13 +2698,13 @@ "license": "0BSD" }, "node_modules/@graphql-codegen/visitor-plugin-common": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-6.1.2.tgz", - "integrity": "sha512-zYdrhJKgk8kqE1Xz5/m/Ua42zk+rIvYB/FHh3dE1AhZ6b1IDqgKjF3LnkT+K2qenf9EfT4yNjXd5CEKMeXfHyg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-6.2.1.tgz", + "integrity": "sha512-5QT1hCV3286mrmoIC7vlFXsTlwELMexhuFIkjh+oVGGL1E8hxkIPAU0kfH/lsPbQHKi8zKmic2pl3tAdyYxNyg==", "dev": true, "license": "MIT", "dependencies": { - "@graphql-codegen/plugin-helpers": "^6.0.0", + "@graphql-codegen/plugin-helpers": "^6.1.0", "@graphql-tools/optimize": "^2.0.0", "@graphql-tools/relay-operation-optimizer": "^7.0.0", "@graphql-tools/utils": "^10.0.0", @@ -3215,14 +3218,14 @@ } }, "node_modules/@graphql-tools/relay-operation-optimizer": { - "version": "7.0.25", - "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.0.25.tgz", - "integrity": "sha512-1S7qq9eyO6ygPNWX2lZd+oxbpl63OhnTTw8+t5OWprM2Tzws9HEosLUpsMR85z1gbezeKtUDt9a2bsSyu4MMFg==", + "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==", "dev": true, "license": "MIT", "dependencies": { "@ardatan/relay-compiler": "^12.0.3", - "@graphql-tools/utils": "^10.10.3", + "@graphql-tools/utils": "^10.11.0", "tslib": "^2.4.0" }, "engines": { @@ -3278,9 +3281,9 @@ } }, "node_modules/@graphql-tools/utils": { - "version": "10.10.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.10.3.tgz", - "integrity": "sha512-2EdYiefeLLxsoeZTukSNZJ0E/Z5NnWBUGK2VJa0DQj1scDhVd93HeT1eW9TszJOYmIh3eWAKLv58ri/1XUmdsQ==", + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.11.0.tgz", + "integrity": "sha512-iBFR9GXIs0gCD+yc3hoNswViL1O5josI33dUqiNStFI/MHLCEPduasceAcazRH77YONKNiviHBV8f7OgcT4o2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5808,6 +5811,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.10.tgz", "integrity": "sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/query-core": "5.90.10" }, @@ -5842,6 +5846,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.131.44.tgz", "integrity": "sha512-LREJfrl8lSedXHCRAAt0HvnHFP9ikAQWnVhYRM++B26w4ZYQBbLvgCT1BCDZVY7MR6rslcd4OfgpZMOyVhNzFg==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.131.2", "@tanstack/react-store": "^0.7.0", @@ -5937,6 +5942,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.131.44.tgz", "integrity": "sha512-Npi9xB3GSYZhRW8+gPhP6bEbyx0vNc8ZNwsi0JapdiFpIiszgRJ57pesy/rklruv46gYQjLVA5KDOsuaCT/urA==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.131.2", "@tanstack/store": "^0.7.0", @@ -6203,8 +6209,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -6295,6 +6300,7 @@ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -6305,6 +6311,7 @@ "integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6315,6 +6322,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6362,6 +6370,7 @@ "resolved": "https://registry.npmjs.org/@vector-im/compound-design-tokens/-/compound-design-tokens-6.4.0.tgz", "integrity": "sha512-93nYQZMgUt6apjCwwnMhMxN8VYQXN3GYOnwovwJjavImwsCGwI/e853BV/DstrWumYh6k5pZsP9e6AF+nz3SIQ==", "license": "SEE LICENSE IN README.md", + "peer": true, "peerDependencies": { "@types/react": "*", "react": "^17 || ^18 || ^19.0.0" @@ -7056,6 +7065,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.2", "caniuse-lite": "^1.0.30001741", @@ -7619,7 +7629,8 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", @@ -7790,8 +7801,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dot-case": { "version": "3.0.4", @@ -7892,6 +7902,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -8417,6 +8428,7 @@ "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -8628,6 +8640,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -9779,7 +9792,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -9986,6 +9998,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.40.0", @@ -10706,6 +10719,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -10863,6 +10877,7 @@ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -10900,7 +10915,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -10916,7 +10930,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -10976,6 +10989,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11017,6 +11031,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -11056,8 +11071,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-refresh": { "version": "0.18.0", @@ -11473,6 +11487,7 @@ "integrity": "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -11592,6 +11607,7 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -11850,6 +11866,7 @@ "integrity": "sha512-vQMufKKA9TxgoEDHJv3esrqUkjszuuRiDkThiHxENFPdQawHhm2Dei+iwNRwH5W671zTDy9iRT9P1KDjcU5Iyw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^1.6.0", @@ -12309,7 +12326,8 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tiny-warning": { "version": "1.0.3", @@ -12512,6 +12530,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12789,6 +12808,7 @@ "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -12936,6 +12956,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -13154,6 +13175,7 @@ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/frontend/package.json b/frontend/package.json index f3fbe5c4b..4af4b342f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,7 +43,7 @@ "@browser-logos/chrome": "^2.0.0", "@browser-logos/firefox": "^3.0.10", "@browser-logos/safari": "^2.1.0", - "@graphql-codegen/cli": "^6.0.2", + "@graphql-codegen/cli": "^6.1.0", "@graphql-codegen/client-preset": "^5.1.1", "@graphql-codegen/typescript-msw": "^3.0.1", "@storybook/addon-docs": "^10.0.8", From 034c18cbb2f6b6ae77de8156d6751024ac6b80ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:03:31 +0000 Subject: [PATCH 33/86] build(deps-dev): bump the vitest group in /frontend with 2 updates Bumps the vitest group in /frontend with 2 updates: [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) and [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest). Updates `@vitest/coverage-v8` from 3.2.4 to 4.0.1 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.1/packages/coverage-v8) Updates `vitest` from 3.2.4 to 4.0.1 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.1/packages/vitest) --- updated-dependencies: - dependency-name: "@vitest/coverage-v8" dependency-version: 4.0.1 dependency-type: direct:development update-type: version-update:semver-major dependency-group: vitest - dependency-name: vitest dependency-version: 4.0.1 dependency-type: direct:development update-type: version-update:semver-major dependency-group: vitest ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 477 +++++++++++++++++++++++-------------- frontend/package.json | 4 +- 2 files changed, 304 insertions(+), 177 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 39955cc30..23e1ceeee 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -49,7 +49,7 @@ "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.1", - "@vitest/coverage-v8": "^3.2.4", + "@vitest/coverage-v8": "^4.0.14", "autoprefixer": "^10.4.21", "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", @@ -70,7 +70,7 @@ "vite-plugin-compression": "^0.5.1", "vite-plugin-graphql-codegen": "^3.7.0", "vite-plugin-manifest-sri": "^0.2.0", - "vitest": "^3.2.4" + "vitest": "^4.0.14" } }, "node_modules/@adobe/css-tools": { @@ -93,20 +93,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@ardatan/relay-compiler": { "version": "12.0.3", "resolved": "https://registry.npmjs.org/@ardatan/relay-compiler/-/relay-compiler-12.0.3.tgz", @@ -3822,16 +3808,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@joshwooding/vite-plugin-react-docgen-typescript": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.6.1.tgz", @@ -5378,6 +5354,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@storybook/addon-docs": { "version": "10.0.8", "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.0.8.tgz", @@ -6435,32 +6418,30 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", - "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.14.tgz", + "integrity": "sha512-EYHLqN/BY6b47qHH7gtMxAg++saoGmsjWmAq9MlXxAz4M0NcHh9iOyKhBZyU4yxZqOd8Xnqp80/5saeitz4Cng==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", - "ast-v8-to-istanbul": "^0.3.3", - "debug": "^4.4.1", + "@vitest/utils": "4.0.14", + "ast-v8-to-istanbul": "^0.3.8", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "std-env": "^3.9.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.2.4", - "vitest": "3.2.4" + "@vitest/browser": "4.0.14", + "vitest": "4.0.14" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -6468,6 +6449,43 @@ } } }, + "node_modules/@vitest/coverage-v8/node_modules/@vitest/pretty-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz", + "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/@vitest/utils": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz", + "integrity": "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.14", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -6536,35 +6554,94 @@ } }, "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.14.tgz", + "integrity": "sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" + "@vitest/utils": "4.0.14", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "node_modules/@vitest/runner/node_modules/@vitest/pretty-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz", + "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/@vitest/utils": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz", + "integrity": "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.14", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.14.tgz", + "integrity": "sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.14", + "magic-string": "^0.30.21", "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz", + "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@vitest/spy": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", @@ -6854,13 +6931,13 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.5.tgz", - "integrity": "sha512-9SdXjNheSiE8bALAQCQQuT6fgQaoxJh7IRYrRGZ8/9nv8WhJeC1aXAwN8TbaOssGOukUvyvnkgD9+Yuykvl1aA==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.8.tgz", + "integrity": "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.30", + "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^9.0.1" } @@ -7109,16 +7186,6 @@ "node-int64": "^0.4.0" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -9797,9 +9864,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.19", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", - "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9807,15 +9874,15 @@ } }, "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" } }, "node_modules/make-dir": { @@ -10248,6 +10315,17 @@ "node": ">= 6" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -11841,9 +11919,9 @@ } }, "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, "license": "MIT" }, @@ -12030,26 +12108,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-literal": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", - "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/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==", - "dev": true, - "license": "MIT" - }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -12274,21 +12332,6 @@ "node": ">=4" } }, - "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -12366,16 +12409,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, "node_modules/tinyrainbow": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", @@ -12878,29 +12911,6 @@ } } }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/vite-plugin-compression": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz", @@ -12951,52 +12961,51 @@ "license": "MIT" }, "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.14.tgz", + "integrity": "sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", + "@vitest/expect": "4.0.14", + "@vitest/mocker": "4.0.14", + "@vitest/pretty-format": "4.0.14", + "@vitest/runner": "4.0.14", + "@vitest/snapshot": "4.0.14", + "@vitest/spy": "4.0.14", + "@vitest/utils": "4.0.14", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", "pathe": "^2.0.3", - "picomatch": "^4.0.2", - "std-env": "^3.9.0", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.14", + "@vitest/browser-preview": "4.0.14", + "@vitest/browser-webdriverio": "4.0.14", + "@vitest/ui": "4.0.14", "happy-dom": "*", "jsdom": "*" }, @@ -13004,13 +13013,19 @@ "@edge-runtime/vm": { "optional": true }, - "@types/debug": { + "@opentelemetry/api": { "optional": true }, "@types/node": { "optional": true }, - "@vitest/browser": { + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { "optional": true }, "@vitest/ui": { @@ -13024,6 +13039,118 @@ } } }, + "node_modules/vitest/node_modules/@vitest/expect": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.14.tgz", + "integrity": "sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.14", + "@vitest/utils": "4.0.14", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.14.tgz", + "integrity": "sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.14", + "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/pretty-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz", + "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/@vitest/spy": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.14.tgz", + "integrity": "sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/@vitest/utils": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz", + "integrity": "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.14", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/chai": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz", + "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", + "dev": true, + "license": "MIT", + "engines": { + "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/vitest/node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 45a7d1b77..cdb6a1e7a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -59,7 +59,7 @@ "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.1", - "@vitest/coverage-v8": "^3.2.4", + "@vitest/coverage-v8": "^4.0.14", "autoprefixer": "^10.4.21", "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", @@ -80,7 +80,7 @@ "vite-plugin-compression": "^0.5.1", "vite-plugin-graphql-codegen": "^3.7.0", "vite-plugin-manifest-sri": "^0.2.0", - "vitest": "^3.2.4" + "vitest": "^4.0.14" }, "msw": { "workerDirectory": [ From 0eb9f12a2967a71477f8de08721662c759357501 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:49:15 +0000 Subject: [PATCH 34/86] Initial plan From 8b1eb91f6e0fe249c29f7991a3aa89301c000dc6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:55:50 +0000 Subject: [PATCH 35/86] Fix mockLocale for vitest 4.x spyOn breaking change Co-authored-by: reivilibre <38398653+reivilibre@users.noreply.github.com> --- frontend/src/test-utils/mockLocale.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/frontend/src/test-utils/mockLocale.ts b/frontend/src/test-utils/mockLocale.ts index 2eb89ce60..07470369f 100644 --- a/frontend/src/test-utils/mockLocale.ts +++ b/frontend/src/test-utils/mockLocale.ts @@ -12,11 +12,22 @@ import { vi } from "vitest"; * Defaults to `en-GB` */ export const mockLocale = (defaultLocale = "en-GB"): void => { - const { DateTimeFormat } = Intl; + const OriginalDateTimeFormat = Intl.DateTimeFormat; + + // Vitest 4.x requires function/class implementations for spyOn mocks + function MockDateTimeFormat( + this: Intl.DateTimeFormat, + locales?: Intl.LocalesArgument, + options?: Intl.DateTimeFormatOptions, + ): Intl.DateTimeFormat { + return new OriginalDateTimeFormat(locales || defaultLocale, options); + } + + // Copy static methods and prototype from original + Object.setPrototypeOf(MockDateTimeFormat, OriginalDateTimeFormat); + MockDateTimeFormat.prototype = OriginalDateTimeFormat.prototype; + vi.spyOn(Intl, "DateTimeFormat").mockImplementation( - ( - locales?: Intl.LocalesArgument, - options?: Intl.DateTimeFormatOptions | undefined, - ) => new DateTimeFormat(locales || defaultLocale, options), + MockDateTimeFormat as typeof Intl.DateTimeFormat, ); }; From ce2b3af82a3b6e8baf44a3013077bd9fdc031806 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:58:03 +0000 Subject: [PATCH 36/86] Improve mockLocale prototype handling and fix formatting Co-authored-by: reivilibre <38398653+reivilibre@users.noreply.github.com> --- frontend/src/test-utils/mockLocale.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/frontend/src/test-utils/mockLocale.ts b/frontend/src/test-utils/mockLocale.ts index 07470369f..9f5517481 100644 --- a/frontend/src/test-utils/mockLocale.ts +++ b/frontend/src/test-utils/mockLocale.ts @@ -14,18 +14,25 @@ import { vi } from "vitest"; export const mockLocale = (defaultLocale = "en-GB"): void => { const OriginalDateTimeFormat = Intl.DateTimeFormat; - // Vitest 4.x requires function/class implementations for spyOn mocks + // Vitest 4.x requires function/class implementations for spyOn mocks. + // This function acts as a constructor wrapper that applies the default locale + // when no locale is specified. Returning the instance from the constructor + // is valid JavaScript - when a constructor returns an object, that object + // is used instead of the newly created `this`. function MockDateTimeFormat( - this: Intl.DateTimeFormat, locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions, ): Intl.DateTimeFormat { return new OriginalDateTimeFormat(locales || defaultLocale, options); } - // Copy static methods and prototype from original + // Inherit static methods from the original DateTimeFormat Object.setPrototypeOf(MockDateTimeFormat, OriginalDateTimeFormat); - MockDateTimeFormat.prototype = OriginalDateTimeFormat.prototype; + // Set up prototype chain so instanceof checks work correctly + Object.setPrototypeOf( + MockDateTimeFormat.prototype, + OriginalDateTimeFormat.prototype, + ); vi.spyOn(Intl, "DateTimeFormat").mockImplementation( MockDateTimeFormat as typeof Intl.DateTimeFormat, From b7b5dd53bf8ef2821e508804c585c785619fc7e9 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 2 Dec 2025 12:22:33 +0000 Subject: [PATCH 37/86] Convert use case list to bullet points and note the niche private_key_jwt method --- docs/reference/configuration.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 5b40e8370..2f2956451 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -238,9 +238,12 @@ The following key formats are supported: - PKCS#8 PEM or DER-encoded RSA or ECDSA private key, encrypted or not - SEC1 PEM or DER-encoded ECDSA private key -The signing keys are used for signing ID Tokens (as returned in the [Token Endpoint] -at `/oauth2/token`) and for signing the response of the [UserInfo Endpoint] at -`/oauth2/userinfo` if the client requests a signed response. +The signing keys are used for: +- signing ID Tokens (as returned in the [Token Endpoint] at `/oauth2/token`); +- signing the response of the [UserInfo Endpoint] at `/oauth2/userinfo` if the + client requests a signed response; +- (niche) signing a JWT for authenticating to an upstream OAuth provider when + the `private_key_jwt` client auth method is configured. At a minimum, an RSA key must be configured in order to be compliant with the [OpenID Connect Core specification][oidc-core-rs256] which specifies the RS256 algorithm From e2a96cf27ed4328a2c6ed9ca53f4e1cca08dcf23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:15:13 +0000 Subject: [PATCH 38/86] build(deps): bump docker/metadata-action from 5.9.0 to 5.10.0 Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.9.0 to 5.10.0. - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/v5.9.0...v5.10.0) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-version: 5.10.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1df6a6b9f..667481e6c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -226,7 +226,7 @@ jobs: steps: - name: Docker meta id: meta - uses: docker/metadata-action@v5.9.0 + uses: docker/metadata-action@v5.10.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.9.0 + uses: docker/metadata-action@v5.10.0 with: images: "${{ env.IMAGE }}" bake-target: docker-metadata-action-debug From f50207ccfe2ccb098a49657c56c18ae926ee7422 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:15:17 +0000 Subject: [PATCH 39/86] build(deps): bump softprops/action-gh-release from 2.4.2 to 2.5.0 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.4.2 to 2.5.0. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/v2.4.2...v2.5.0) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1df6a6b9f..5f8279127 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -327,7 +327,7 @@ jobs: merge-multiple: true - name: Prepare a release - uses: softprops/action-gh-release@v2.4.2 + uses: softprops/action-gh-release@v2.5.0 with: generate_release_notes: true body: | @@ -396,7 +396,7 @@ jobs: await script({ core, github, context }); - name: Update unstable release - uses: softprops/action-gh-release@v2.4.2 + uses: softprops/action-gh-release@v2.5.0 with: name: "Unstable build" tag_name: unstable From 56b3ba499c9b4f165c6d0bff35996468c4d2881a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:15:41 +0000 Subject: [PATCH 40/86] build(deps-dev): bump the vitest group in /frontend with 2 updates Bumps the vitest group in /frontend with 2 updates: [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) and [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest). Updates `@vitest/coverage-v8` from 3.2.4 to 4.0.1 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.1/packages/coverage-v8) Updates `vitest` from 3.2.4 to 4.0.1 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.1/packages/vitest) --- updated-dependencies: - dependency-name: "@vitest/coverage-v8" dependency-version: 4.0.1 dependency-type: direct:development update-type: version-update:semver-major dependency-group: vitest - dependency-name: vitest dependency-version: 4.0.1 dependency-type: direct:development update-type: version-update:semver-major dependency-group: vitest ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 477 +++++++++++++++++++++++-------------- frontend/package.json | 4 +- 2 files changed, 304 insertions(+), 177 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 39955cc30..23e1ceeee 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -49,7 +49,7 @@ "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.1", - "@vitest/coverage-v8": "^3.2.4", + "@vitest/coverage-v8": "^4.0.14", "autoprefixer": "^10.4.21", "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", @@ -70,7 +70,7 @@ "vite-plugin-compression": "^0.5.1", "vite-plugin-graphql-codegen": "^3.7.0", "vite-plugin-manifest-sri": "^0.2.0", - "vitest": "^3.2.4" + "vitest": "^4.0.14" } }, "node_modules/@adobe/css-tools": { @@ -93,20 +93,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@ardatan/relay-compiler": { "version": "12.0.3", "resolved": "https://registry.npmjs.org/@ardatan/relay-compiler/-/relay-compiler-12.0.3.tgz", @@ -3822,16 +3808,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@joshwooding/vite-plugin-react-docgen-typescript": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.6.1.tgz", @@ -5378,6 +5354,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@storybook/addon-docs": { "version": "10.0.8", "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.0.8.tgz", @@ -6435,32 +6418,30 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", - "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.14.tgz", + "integrity": "sha512-EYHLqN/BY6b47qHH7gtMxAg++saoGmsjWmAq9MlXxAz4M0NcHh9iOyKhBZyU4yxZqOd8Xnqp80/5saeitz4Cng==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", - "ast-v8-to-istanbul": "^0.3.3", - "debug": "^4.4.1", + "@vitest/utils": "4.0.14", + "ast-v8-to-istanbul": "^0.3.8", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "std-env": "^3.9.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.2.4", - "vitest": "3.2.4" + "@vitest/browser": "4.0.14", + "vitest": "4.0.14" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -6468,6 +6449,43 @@ } } }, + "node_modules/@vitest/coverage-v8/node_modules/@vitest/pretty-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz", + "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/@vitest/utils": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz", + "integrity": "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.14", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -6536,35 +6554,94 @@ } }, "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.14.tgz", + "integrity": "sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" + "@vitest/utils": "4.0.14", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "node_modules/@vitest/runner/node_modules/@vitest/pretty-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz", + "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/@vitest/utils": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz", + "integrity": "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.14", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.14.tgz", + "integrity": "sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.14", + "magic-string": "^0.30.21", "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz", + "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@vitest/spy": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", @@ -6854,13 +6931,13 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.5.tgz", - "integrity": "sha512-9SdXjNheSiE8bALAQCQQuT6fgQaoxJh7IRYrRGZ8/9nv8WhJeC1aXAwN8TbaOssGOukUvyvnkgD9+Yuykvl1aA==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.8.tgz", + "integrity": "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.30", + "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^9.0.1" } @@ -7109,16 +7186,6 @@ "node-int64": "^0.4.0" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -9797,9 +9864,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.19", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", - "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9807,15 +9874,15 @@ } }, "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" } }, "node_modules/make-dir": { @@ -10248,6 +10315,17 @@ "node": ">= 6" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -11841,9 +11919,9 @@ } }, "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, "license": "MIT" }, @@ -12030,26 +12108,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-literal": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", - "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/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==", - "dev": true, - "license": "MIT" - }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -12274,21 +12332,6 @@ "node": ">=4" } }, - "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -12366,16 +12409,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, "node_modules/tinyrainbow": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", @@ -12878,29 +12911,6 @@ } } }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/vite-plugin-compression": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz", @@ -12951,52 +12961,51 @@ "license": "MIT" }, "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.14.tgz", + "integrity": "sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", + "@vitest/expect": "4.0.14", + "@vitest/mocker": "4.0.14", + "@vitest/pretty-format": "4.0.14", + "@vitest/runner": "4.0.14", + "@vitest/snapshot": "4.0.14", + "@vitest/spy": "4.0.14", + "@vitest/utils": "4.0.14", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", "pathe": "^2.0.3", - "picomatch": "^4.0.2", - "std-env": "^3.9.0", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.14", + "@vitest/browser-preview": "4.0.14", + "@vitest/browser-webdriverio": "4.0.14", + "@vitest/ui": "4.0.14", "happy-dom": "*", "jsdom": "*" }, @@ -13004,13 +13013,19 @@ "@edge-runtime/vm": { "optional": true }, - "@types/debug": { + "@opentelemetry/api": { "optional": true }, "@types/node": { "optional": true }, - "@vitest/browser": { + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { "optional": true }, "@vitest/ui": { @@ -13024,6 +13039,118 @@ } } }, + "node_modules/vitest/node_modules/@vitest/expect": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.14.tgz", + "integrity": "sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.14", + "@vitest/utils": "4.0.14", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.14.tgz", + "integrity": "sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.14", + "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/pretty-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz", + "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/@vitest/spy": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.14.tgz", + "integrity": "sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/@vitest/utils": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz", + "integrity": "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.14", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/chai": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz", + "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", + "dev": true, + "license": "MIT", + "engines": { + "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/vitest/node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 45a7d1b77..cdb6a1e7a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -59,7 +59,7 @@ "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.1", - "@vitest/coverage-v8": "^3.2.4", + "@vitest/coverage-v8": "^4.0.14", "autoprefixer": "^10.4.21", "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", @@ -80,7 +80,7 @@ "vite-plugin-compression": "^0.5.1", "vite-plugin-graphql-codegen": "^3.7.0", "vite-plugin-manifest-sri": "^0.2.0", - "vitest": "^3.2.4" + "vitest": "^4.0.14" }, "msw": { "workerDirectory": [ From 8048574696f4fc79fed753b327b37a5ba16ea415 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:16:16 +0000 Subject: [PATCH 41/86] build(deps): bump the i18next group in /frontend with 2 updates Bumps the i18next group in /frontend with 2 updates: [i18next](https://github.com/i18next/i18next) and [i18next-cli](https://github.com/i18next/i18next-cli). Updates `i18next` from 25.6.3 to 25.7.1 - [Release notes](https://github.com/i18next/i18next/releases) - [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md) - [Commits](https://github.com/i18next/i18next/compare/v25.6.3...v25.7.1) Updates `i18next-cli` from 1.24.20 to 1.28.3 - [Changelog](https://github.com/i18next/i18next-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/i18next/i18next-cli/compare/v1.24.20...v1.28.3) --- updated-dependencies: - dependency-name: i18next dependency-version: 25.7.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: i18next - dependency-name: i18next-cli dependency-version: 1.28.3 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: i18next ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 34 ++++++++++++++++++++++++++-------- frontend/package.json | 4 ++-- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 39955cc30..b36264bdc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -20,7 +20,7 @@ "@zxcvbn-ts/language-common": "^3.0.4", "classnames": "^2.5.1", "date-fns": "^4.1.0", - "i18next": "^25.6.3", + "i18next": "^25.7.1", "react": "^19.2.0", "react-dom": "^19.2.0", "react-i18next": "^16.3.5", @@ -54,7 +54,7 @@ "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", "happy-dom": "^20.0.4", - "i18next-cli": "^1.24.20", + "i18next-cli": "^1.28.3", "knip": "^5.66.4", "msw": "^2.11.6", "msw-storybook-addon": "^2.0.6", @@ -1216,6 +1216,23 @@ "integrity": "sha512-diidPiK62E4hlAh0dyLfWQDZXi2SSAGiOuw6iqD1x8ztw7L/Sz3He46FhcxEzYa1hKi1blCkjnKDjqw6rQfgcA==", "dev": true }, + "node_modules/@croct/json": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@croct/json/-/json-2.1.0.tgz", + "integrity": "sha512-UrWfjNQVlBxN+OVcFwHmkjARMW55MBN04E9KfGac8ac8z1QnFVuiOOFtMWXCk3UwsyRqhsNaFoYLZC+xxqsVjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@croct/json5-parser": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@croct/json5-parser/-/json5-parser-0.2.1.tgz", + "integrity": "sha512-YB21uimsK6lZOUr8z+W0gsLI3TlUz4e1iWtN4qxiAsl7yCBgeg7Xfc3Y8kRJzWnZL/eOU5WaMgD09KOyuqenuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@croct/json": "^2.1.0" + } + }, "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", @@ -8622,9 +8639,9 @@ } }, "node_modules/i18next": { - "version": "25.6.3", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.6.3.tgz", - "integrity": "sha512-AEQvoPDljhp67a1+NsnG/Wb1Nh6YoSvtrmeEd24sfGn3uujCtXCF3cXpr7ulhMywKNFF7p3TX1u2j7y+caLOJg==", + "version": "25.7.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.1.tgz", + "integrity": "sha512-XbTnkh1yCZWSAZGnA9xcQfHcYNgZs2cNxm+c6v1Ma9UAUGCeJPplRe1ILia6xnDvXBjk0uXU+Z8FYWhA19SKFw==", "funding": [ { "type": "individual", @@ -8654,12 +8671,13 @@ } }, "node_modules/i18next-cli": { - "version": "1.24.20", - "resolved": "https://registry.npmjs.org/i18next-cli/-/i18next-cli-1.24.20.tgz", - "integrity": "sha512-WpHJD24Ghi3s0/nEL2Bv/RUeaflOWEGIdwLkg2QWwQdxCMZCRZzXkRcT2qQzjipUPwjz17Kfe5P8ke8pw0tLJg==", + "version": "1.28.3", + "resolved": "https://registry.npmjs.org/i18next-cli/-/i18next-cli-1.28.3.tgz", + "integrity": "sha512-E+nY2XmIaeC+JfSvd8UM4MxLaKNwS33eCghL+xm8rue0hXsWy5tJOWsHj1o/X76v/maYw5sgbSLRSBoPJzp5hA==", "dev": true, "license": "MIT", "dependencies": { + "@croct/json5-parser": "0.2.1", "@swc/core": "1.15.2", "chalk": "5.6.2", "chokidar": "4.0.3", diff --git a/frontend/package.json b/frontend/package.json index 45a7d1b77..8bbbdf3f7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,7 @@ "@zxcvbn-ts/language-common": "^3.0.4", "classnames": "^2.5.1", "date-fns": "^4.1.0", - "i18next": "^25.6.3", + "i18next": "^25.7.1", "react": "^19.2.0", "react-dom": "^19.2.0", "react-i18next": "^16.3.5", @@ -64,7 +64,7 @@ "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", "happy-dom": "^20.0.4", - "i18next-cli": "^1.24.20", + "i18next-cli": "^1.28.3", "knip": "^5.66.4", "msw": "^2.11.6", "msw-storybook-addon": "^2.0.6", From 31cb4085d51de372e5030aca2346ab7f64fabfbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:17:05 +0000 Subject: [PATCH 42/86] build(deps): bump the sentry group with 3 updates Bumps the sentry group with 3 updates: [sentry](https://github.com/getsentry/sentry-rust), [sentry-tower](https://github.com/getsentry/sentry-rust) and [sentry-tracing](https://github.com/getsentry/sentry-rust). Updates `sentry` from 0.45.0 to 0.46.0 - [Release notes](https://github.com/getsentry/sentry-rust/releases) - [Changelog](https://github.com/getsentry/sentry-rust/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-rust/compare/0.45.0...0.46.0) Updates `sentry-tower` from 0.45.0 to 0.46.0 - [Release notes](https://github.com/getsentry/sentry-rust/releases) - [Changelog](https://github.com/getsentry/sentry-rust/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-rust/compare/0.45.0...0.46.0) Updates `sentry-tracing` from 0.45.0 to 0.46.0 - [Release notes](https://github.com/getsentry/sentry-rust/releases) - [Changelog](https://github.com/getsentry/sentry-rust/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-rust/compare/0.45.0...0.46.0) --- updated-dependencies: - dependency-name: sentry dependency-version: 0.46.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: sentry - dependency-name: sentry-tower dependency-version: 0.46.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: sentry - dependency-name: sentry-tracing dependency-version: 0.46.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: sentry ... Signed-off-by: dependabot[bot] --- Cargo.lock | 44 ++++++++++++++++++++++---------------------- Cargo.toml | 6 +++--- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ffebb425a..f6b157f39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1736,7 +1736,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.0", + "windows-sys 0.59.0", ] [[package]] @@ -3008,7 +3008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-targets 0.48.5", ] [[package]] @@ -4084,7 +4084,7 @@ dependencies = [ "sha1", "sha2", "sprintf", - "thiserror 2.0.17", + "thiserror 1.0.69", "tokio", "tracing", "urlencoding", @@ -5102,7 +5102,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.0", + "windows-sys 0.59.0", ] [[package]] @@ -5383,9 +5383,9 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "sentry" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48b85e25e8a1fc13928885e8bf13abe8a09e15c46993aed05d6405f7755d6e20" +checksum = "d9794f69ad475e76c057e326175d3088509649e3aed98473106b9fe94ba59424" dependencies = [ "httpdate", "reqwest", @@ -5400,9 +5400,9 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3253a495ab536f6de1746a58d5d7824b77d75e08e1a4b8ca6fb356839077ae0" +checksum = "e81137ad53b8592bd0935459ad74c0376053c40084aa170451e74eeea8dbc6c3" dependencies = [ "backtrace", "regex", @@ -5411,9 +5411,9 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027f81a728836e66b88c07666a10f5ed5a35e2695b04eb7aa0fcbed93f814900" +checksum = "cfb403c66cc2651a01b9bacda2e7c22cd51f7e8f56f206aa4310147eb3259282" dependencies = [ "hostname", "libc", @@ -5425,9 +5425,9 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3b6729c8e71ac968edbe9bf2dd4109c162e552b52bacd2b07e24ede1aba84a5" +checksum = "cfc409727ae90765ca8ea76fe6c949d6f159a11d02e130b357fa652ee9efcada" dependencies = [ "rand 0.9.2", "sentry-types", @@ -5438,9 +5438,9 @@ dependencies = [ [[package]] name = "sentry-panic" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac0471f04f8f97af0c17eeca2c516e23faa1c0271a55bc64371d9ce488c2d40" +checksum = "3df79f4e1e72b2a8b75a0ebf49e78709ceb9b3f0b451f13adc92a0361b0aaabe" dependencies = [ "sentry-backtrace", "sentry-core", @@ -5448,9 +5448,9 @@ dependencies = [ [[package]] name = "sentry-tower" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "417bd48071863a65ca5f33d15af9aabd49a5cee7f97415d3f08ce8c90ed2c531" +checksum = "7eec9885bceb8ba374858d015bb6fa39dbb341d94ca088bc8f13bee2e64e2c68" dependencies = [ "axum", "http", @@ -5463,9 +5463,9 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428f780866a613142dcc81b7f8551ae4d1c056f4df22b6d7ddd9154a9974eb03" +checksum = "ff2046f527fd4b75e0b6ab3bd656c67dce42072f828dc4d03c206d15dca74a93" dependencies = [ "bitflags", "sentry-backtrace", @@ -5476,9 +5476,9 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c19d1d1967b55659c358886d0f1aa3076488d445f84c7d727d384c675adaec1" +checksum = "c7b9b4e4c03a4d3643c18c78b8aa91d2cbee5da047d2fa0ca4bb29bc67e6c55c" dependencies = [ "debugid", "hex", @@ -6156,7 +6156,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.61.0", + "windows-sys 0.59.0", ] [[package]] @@ -7283,7 +7283,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 84bc5f77e..cec19fa71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -567,18 +567,18 @@ features = [ # Sentry error tracking [workspace.dependencies.sentry] -version = "0.45.0" +version = "0.46.0" default-features = false features = ["backtrace", "contexts", "panic", "tower", "reqwest"] # Sentry tower layer [workspace.dependencies.sentry-tower] -version = "0.45.0" +version = "0.46.0" features = ["http", "axum-matched-path"] # Sentry tracing integration [workspace.dependencies.sentry-tracing] -version = "0.45.0" +version = "0.46.0" # Serialization and deserialization [workspace.dependencies.serde] From 6c066ca972b4e9259ef770adf098b6f6c80ac6fb Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 2 Dec 2025 14:53:55 +0000 Subject: [PATCH 43/86] Update templates/pages/compat_login_policy_violation.html Co-authored-by: Quentin Gliech --- templates/pages/compat_login_policy_violation.html | 3 +-- translations/en.json | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/templates/pages/compat_login_policy_violation.html b/templates/pages/compat_login_policy_violation.html index 5953faefb..80bb120c2 100644 --- a/templates/pages/compat_login_policy_violation.html +++ b/templates/pages/compat_login_policy_violation.html @@ -1,6 +1,5 @@ {# -Copyright 2024, 2025 New Vector Ltd. -Copyright 2022-2024 The Matrix.org Foundation C.I.C. +Copyright 2025 New Vector Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. diff --git a/translations/en.json b/translations/en.json index 06ae76773..4dd4c2702 100644 --- a/translations/en.json +++ b/translations/en.json @@ -22,7 +22,7 @@ }, "sign_out": "Sign out", "@sign_out": { - "context": "pages/account/logged_out.html:22:28-48, pages/compat_login_policy_violation.html:29:28-48, pages/consent.html:65:28-48, pages/device_consent.html:136:30-50, pages/index.html:28:28-48, pages/policy_violation.html:38:28-48, pages/sso.html:45:28-48, pages/upstream_oauth2/link_mismatch.html:24:24-44, pages/upstream_oauth2/suggest_link.html:32:26-46" + "context": "pages/account/logged_out.html:22:28-48, pages/compat_login_policy_violation.html:28:28-48, pages/consent.html:65:28-48, pages/device_consent.html:136:30-50, pages/index.html:28:28-48, pages/policy_violation.html:38:28-48, pages/sso.html:45:28-48, pages/upstream_oauth2/link_mismatch.html:24:24-44, pages/upstream_oauth2/suggest_link.html:32:26-46" }, "skip": "Skip", "@skip": { @@ -496,17 +496,17 @@ "policy_violation": { "description": "This might be because of the client which authored the request, the currently logged in user, or the request itself.", "@description": { - "context": "pages/compat_login_policy_violation.html:19:25-62, pages/policy_violation.html:19:25-62", + "context": "pages/compat_login_policy_violation.html:18:25-62, pages/policy_violation.html:19:25-62", "description": "Displayed when an authorization request is denied by the policy" }, "heading": "The authorization request was denied by the policy enforced by this service", "@heading": { - "context": "pages/compat_login_policy_violation.html:18:27-60, pages/policy_violation.html:18:27-60", + "context": "pages/compat_login_policy_violation.html:17:27-60, pages/policy_violation.html:18:27-60", "description": "Displayed when an authorization request is denied by the policy" }, "logged_as": "Logged as %(username)s", "@logged_as": { - "context": "pages/compat_login_policy_violation.html:26:11-86, pages/policy_violation.html:35:11-86" + "context": "pages/compat_login_policy_violation.html:25:11-86, pages/policy_violation.html:35:11-86" } }, "recovery": { From ff6b25061eb6dbe0cc24e8c860e9c09c2f9226c3 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 2 Dec 2025 17:51:23 +0100 Subject: [PATCH 44/86] Get the display name of the Matrix user on the consent screens --- crates/data-model/src/lib.rs | 4 +- crates/data-model/src/users.rs | 6 ++ .../src/oauth2/authorization/consent.rs | 41 +++++++++- crates/handlers/src/oauth2/device/consent.rs | 74 ++++++++++++++++++- crates/templates/src/context.rs | 35 +++++++-- 5 files changed, 145 insertions(+), 15 deletions(-) diff --git a/crates/data-model/src/lib.rs b/crates/data-model/src/lib.rs index fd5c0e633..05b2466b9 100644 --- a/crates/data-model/src/lib.rs +++ b/crates/data-model/src/lib.rs @@ -56,8 +56,8 @@ pub use self::{ }, user_agent::{DeviceType, UserAgent}, users::{ - Authentication, AuthenticationMethod, BrowserSession, Password, User, UserEmail, - UserEmailAuthentication, UserEmailAuthenticationCode, UserRecoverySession, + Authentication, AuthenticationMethod, BrowserSession, MatrixUser, Password, User, + UserEmail, UserEmailAuthentication, UserEmailAuthenticationCode, UserRecoverySession, UserRecoveryTicket, UserRegistration, UserRegistrationPassword, UserRegistrationToken, }, utils::{BoxClock, BoxRng}, diff --git a/crates/data-model/src/users.rs b/crates/data-model/src/users.rs index 541eb26d2..78c483e12 100644 --- a/crates/data-model/src/users.rs +++ b/crates/data-model/src/users.rs @@ -12,6 +12,12 @@ use serde::Serialize; use ulid::Ulid; use url::Url; +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct MatrixUser { + pub mxid: String, + pub display_name: Option, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct User { pub id: Ulid, diff --git a/crates/handlers/src/oauth2/authorization/consent.rs b/crates/handlers/src/oauth2/authorization/consent.rs index 2587828b5..ab51bef1c 100644 --- a/crates/handlers/src/oauth2/authorization/consent.rs +++ b/crates/handlers/src/oauth2/authorization/consent.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::{sync::Arc, time::Duration}; + use axum::{ extract::{Form, Path, State}, response::{Html, IntoResponse, Response}, @@ -15,8 +17,9 @@ use mas_axum_utils::{ cookies::CookieJar, csrf::{CsrfExt, ProtectedForm}, }; -use mas_data_model::{AuthorizationGrantStage, BoxClock, BoxRng}; +use mas_data_model::{AuthorizationGrantStage, BoxClock, BoxRng, MatrixUser}; use mas_keystore::Keystore; +use mas_matrix::HomeserverConnection; use mas_policy::Policy; use mas_router::{PostAuthAction, UrlBuilder}; use mas_storage::{ @@ -87,6 +90,7 @@ pub(crate) async fn get( PreferredLanguage(locale): PreferredLanguage, State(templates): State, State(url_builder): State, + State(homeserver): State>, mut policy: Policy, mut repo: BoxRepository, activity_tracker: BoundActivityTracker, @@ -138,6 +142,9 @@ pub(crate) async fn get( let session_counts = count_user_sessions_for_limiting(&mut repo, &session.user).await?; + // We can close the repository early, we don't need it at this point + repo.save().await?; + let res = policy .evaluate_authorization_grant(mas_policy::AuthorizationGrantInput { user: Some(&session.user), @@ -162,7 +169,37 @@ pub(crate) async fn get( return Ok((cookie_jar, Html(content)).into_response()); } - let ctx = ConsentContext::new(grant, client) + // Fetch informations about the user. This is purely cosmetic, so we let it + // fail and put a 1s timeout to it in case we fail to query it + // XXX: we're likely to need this in other places + let localpart = &session.user.username; + let display_name = match tokio::time::timeout( + Duration::from_secs(1), + homeserver.query_user(localpart), + ) + .await + { + Ok(Ok(user)) => user.displayname, + Ok(Err(err)) => { + tracing::warn!( + error = &*err as &dyn std::error::Error, + localpart, + "Failed to query user" + ); + None + } + Err(_) => { + tracing::warn!(localpart, "Timed out while querying user"); + None + } + }; + + let matrix_user = MatrixUser { + mxid: homeserver.mxid(localpart), + display_name, + }; + + let ctx = ConsentContext::new(grant, client, matrix_user) .with_session(session) .with_csrf(csrf_token.form_value()) .with_language(locale); diff --git a/crates/handlers/src/oauth2/device/consent.rs b/crates/handlers/src/oauth2/device/consent.rs index e1d32870f..3912d2dc1 100644 --- a/crates/handlers/src/oauth2/device/consent.rs +++ b/crates/handlers/src/oauth2/device/consent.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::{sync::Arc, time::Duration}; + use anyhow::Context; use axum::{ Form, @@ -16,7 +18,8 @@ use mas_axum_utils::{ cookies::CookieJar, csrf::{CsrfExt, ProtectedForm}, }; -use mas_data_model::{BoxClock, BoxRng}; +use mas_data_model::{BoxClock, BoxRng, MatrixUser}; +use mas_matrix::HomeserverConnection; use mas_policy::Policy; use mas_router::UrlBuilder; use mas_storage::BoxRepository; @@ -49,6 +52,7 @@ pub(crate) async fn get( PreferredLanguage(locale): PreferredLanguage, State(templates): State, State(url_builder): State, + State(homeserver): State>, mut repo: BoxRepository, mut policy: Policy, activity_tracker: BoundActivityTracker, @@ -105,6 +109,9 @@ pub(crate) async fn get( let session_counts = count_user_sessions_for_limiting(&mut repo, &session.user).await?; + // We can close the repository early, we don't need it at this point + repo.save().await?; + // Evaluate the policy let res = policy .evaluate_authorization_grant(mas_policy::AuthorizationGrantInput { @@ -133,7 +140,37 @@ pub(crate) async fn get( return Ok((cookie_jar, Html(content)).into_response()); } - let ctx = DeviceConsentContext::new(grant, client) + // Fetch informations about the user. This is purely cosmetic, so we let it + // fail and put a 1s timeout to it in case we fail to query it + // XXX: we're likely to need this in other places + let localpart = &session.user.username; + let display_name = match tokio::time::timeout( + Duration::from_secs(1), + homeserver.query_user(localpart), + ) + .await + { + Ok(Ok(user)) => user.displayname, + Ok(Err(err)) => { + tracing::warn!( + error = &*err as &dyn std::error::Error, + localpart, + "Failed to query user" + ); + None + } + Err(_) => { + tracing::warn!(localpart, "Timed out while querying user"); + None + } + }; + + let matrix_user = MatrixUser { + mxid: homeserver.mxid(localpart), + display_name, + }; + + let ctx = DeviceConsentContext::new(grant, client, matrix_user) .with_session(session) .with_csrf(csrf_token.form_value()) .with_language(locale); @@ -153,6 +190,7 @@ pub(crate) async fn post( PreferredLanguage(locale): PreferredLanguage, State(templates): State, State(url_builder): State, + State(homeserver): State>, mut repo: BoxRepository, mut policy: Policy, activity_tracker: BoundActivityTracker, @@ -265,7 +303,37 @@ pub(crate) async fn post( repo.save().await?; - let ctx = DeviceConsentContext::new(grant, client) + // Fetch informations about the user. This is purely cosmetic, so we let it + // fail and put a 1s timeout to it in case we fail to query it + // XXX: we're likely to need this in other places + let localpart = &session.user.username; + let display_name = match tokio::time::timeout( + Duration::from_secs(1), + homeserver.query_user(localpart), + ) + .await + { + Ok(Ok(user)) => user.displayname, + Ok(Err(err)) => { + tracing::warn!( + error = &*err as &dyn std::error::Error, + localpart, + "Failed to query user" + ); + None + } + Err(_) => { + tracing::warn!(localpart, "Timed out while querying user"); + None + } + }; + + let matrix_user = MatrixUser { + mxid: homeserver.mxid(localpart), + display_name, + }; + + let ctx = DeviceConsentContext::new(grant, client, matrix_user) .with_session(session) .with_csrf(csrf_token.form_value()) .with_language(locale); diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index f836d7c4b..73a38972f 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -21,10 +21,11 @@ use chrono::{DateTime, Duration, Utc}; use http::{Method, Uri, Version}; use mas_data_model::{ AuthorizationGrant, BrowserSession, Client, CompatSsoLogin, CompatSsoLoginState, - DeviceCodeGrant, UpstreamOAuthLink, UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports, - UpstreamOAuthProviderDiscoveryMode, UpstreamOAuthProviderOnBackchannelLogout, - UpstreamOAuthProviderPkceMode, UpstreamOAuthProviderTokenAuthMethod, User, - UserEmailAuthentication, UserEmailAuthenticationCode, UserRecoverySession, UserRegistration, + DeviceCodeGrant, MatrixUser, UpstreamOAuthLink, UpstreamOAuthProvider, + UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderDiscoveryMode, + UpstreamOAuthProviderOnBackchannelLogout, UpstreamOAuthProviderPkceMode, + UpstreamOAuthProviderTokenAuthMethod, User, UserEmailAuthentication, + UserEmailAuthenticationCode, UserRecoverySession, UserRegistration, }; use mas_i18n::DataLocale; use mas_iana::jose::JsonWebSignatureAlg; @@ -732,6 +733,7 @@ pub struct ConsentContext { grant: AuthorizationGrant, client: Client, action: PostAuthAction, + matrix_user: MatrixUser, } impl TemplateContext for ConsentContext { @@ -755,6 +757,10 @@ impl TemplateContext for ConsentContext { grant, client, action, + matrix_user: MatrixUser { + mxid: "@alice:example.com".to_owned(), + display_name: Some("Alice".to_owned()), + }, } }) .collect(), @@ -765,12 +771,13 @@ impl TemplateContext for ConsentContext { impl ConsentContext { /// Constructs a context for the client consent page #[must_use] - pub fn new(grant: AuthorizationGrant, client: Client) -> Self { + pub fn new(grant: AuthorizationGrant, client: Client, matrix_user: MatrixUser) -> Self { let action = PostAuthAction::continue_grant(grant.id); Self { grant, client, action, + matrix_user, } } } @@ -1748,13 +1755,18 @@ impl TemplateContext for DeviceLinkContext { pub struct DeviceConsentContext { grant: DeviceCodeGrant, client: Client, + matrix_user: MatrixUser, } impl DeviceConsentContext { /// Constructs a new context with an existing linked user #[must_use] - pub fn new(grant: DeviceCodeGrant, client: Client) -> Self { - Self { grant, client } + pub fn new(grant: DeviceCodeGrant, client: Client, matrix_user: MatrixUser) -> Self { + Self { + grant, + client, + matrix_user, + } } } @@ -1782,7 +1794,14 @@ impl TemplateContext for DeviceConsentContext { ip_address: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)), user_agent: Some("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Safari/537.36".to_owned()), }; - Self { grant, client } + Self { + grant, + client, + matrix_user: MatrixUser { + mxid: "@alice:example.com".to_owned(), + display_name: Some("Alice".to_owned()), + } + } }) .collect()) } From 29383dfd49717b804fd946a657c70e31b7aa8979 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 2 Dec 2025 17:51:51 +0100 Subject: [PATCH 45/86] Add a template function to compute the avatar color hash same as Compound Web --- crates/templates/src/functions.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/templates/src/functions.rs b/crates/templates/src/functions.rs index 9d764e032..cb4603de0 100644 --- a/crates/templates/src/functions.rs +++ b/crates/templates/src/functions.rs @@ -41,6 +41,7 @@ pub fn register( env.add_filter("simplify_url", filter_simplify_url); env.add_filter("add_slashes", filter_add_slashes); env.add_filter("parse_user_agent", filter_parse_user_agent); + env.add_filter("id_color_hash", filter_id_color_hash); env.add_function("add_params_to_url", function_add_params_to_url); env.add_function("counter", || Ok(Value::from_object(Counter::default()))); if let Some(vite_manifest) = vite_manifest { @@ -138,6 +139,12 @@ fn filter_simplify_url(url: &str, kwargs: Kwargs) -> Result u32 { + input.chars().fold(0, |hash, c| hash + c as u32) % 6 + 1 +} + /// Filter which parses a user-agent string fn filter_parse_user_agent(user_agent: String) -> Value { let user_agent = mas_data_model::UserAgent::parse(user_agent); From e7e8f9822df53b47155043e26b89b6e0c30d3bd4 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 2 Dec 2025 17:52:11 +0100 Subject: [PATCH 46/86] Define a few more font classes in the templates --- frontend/src/entrypoints/templates.css | 81 +++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/frontend/src/entrypoints/templates.css b/frontend/src/entrypoints/templates.css index 5dfe5986d..8e4870bc7 100644 --- a/frontend/src/entrypoints/templates.css +++ b/frontend/src/entrypoints/templates.css @@ -23,9 +23,9 @@ letter-spacing: var(--cpd-font-letter-spacing-body-lg); } -.cpd-text-heading-xl-semibold { - font: var(--cpd-font-heading-xl-semibold); - letter-spacing: var(--cpd-font-letter-spacing-heading-xl); +.cpd-text-body-lg-semibold { + font: var(--cpd-font-body-lg-semibold); + letter-spacing: var(--cpd-font-letter-spacing-body-lg); } .cpd-text-body-md-regular { @@ -33,6 +33,36 @@ letter-spacing: var(--cpd-font-letter-spacing-body-md); } +.cpd-text-body-md-semibold { + font: var(--cpd-font-body-md-semibold); + letter-spacing: var(--cpd-font-letter-spacing-body-md); +} + +.cpd-text-body-sm-regular { + font: var(--cpd-font-body-sm-regular); + letter-spacing: var(--cpd-font-letter-spacing-body-sm); +} + +.cpd-text-body-sm-semibold { + font: var(--cpd-font-body-sm-semibold); + letter-spacing: var(--cpd-font-letter-spacing-body-sm); +} + +.cpd-text-body-xs-regular { + font: var(--cpd-font-body-xs-regular); + letter-spacing: var(--cpd-font-letter-spacing-body-xs); +} + +.cpd-text-body-xs-semibold { + font: var(--cpd-font-body-xs-semibold); + letter-spacing: var(--cpd-font-letter-spacing-body-xs); +} + +.cpd-text-heading-xl-semibold { + font: var(--cpd-font-heading-xl-semibold); + letter-spacing: var(--cpd-font-letter-spacing-heading-xl); +} + .cpd-text-primary { color: var(--cpd-color-text-primary); } @@ -186,3 +216,48 @@ } } } + +.avatar-placeholder { + height: var(--cpd-space-14x); + width: var(--cpd-space-14x); + border-radius: 50%; + overflow: hidden; + user-select: none; + line-height: var(--cpd-space-14x); + font-size: 32px; + font-family: var(--cpd-font-family-sans); + font-weight: bold; + text-align: center; + background-color: var(--cpd-avatar-bg); + color: var(--cpd-avatar-color); + + &[data-color] { + --cpd-avatar-bg: var(--cpd-color-bg-decorative-1); + --cpd-avatar-color: var(--cpd-color-text-decorative-1); + } + + &[data-color="2"] { + --cpd-avatar-bg: var(--cpd-color-bg-decorative-2); + --cpd-avatar-color: var(--cpd-color-text-decorative-2); + } + + &[data-color="3"] { + --cpd-avatar-bg: var(--cpd-color-bg-decorative-3); + --cpd-avatar-color: var(--cpd-color-text-decorative-3); + } + + &[data-color="4"] { + --cpd-avatar-bg: var(--cpd-color-bg-decorative-4); + --cpd-avatar-color: var(--cpd-color-text-decorative-4); + } + + &[data-color="5"] { + --cpd-avatar-bg: var(--cpd-color-bg-decorative-5); + --cpd-avatar-color: var(--cpd-color-text-decorative-5); + } + + &[data-color="6"] { + --cpd-avatar-bg: var(--cpd-color-bg-decorative-6); + --cpd-avatar-color: var(--cpd-color-text-decorative-6); + } +} From 3410224315c6f2d0dbe379879b3c1868b01a9e1f Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 2 Dec 2025 17:55:16 +0100 Subject: [PATCH 47/86] Allow rendering a custom logout button in the templates --- templates/components/logout.html | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/templates/components/logout.html b/templates/components/logout.html index de4ee17dc..391a02500 100644 --- a/templates/components/logout.html +++ b/templates/components/logout.html @@ -6,16 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. -#} -{% macro button(text, csrf_token, as_link=false, post_logout_action={}) %} -
+{% macro button(csrf_token, text="", as_link=false, post_logout_action={}) %} + {% for key, value in post_logout_action|items %} {% endfor %} - {% if as_link %} - + {% if caller %} + {{ caller() }} + {% elif as_link %} + {% else %} - + {% endif %}
{% endmacro %} From f18b63ad64b1b9f649822fdcf34422710573572a Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 2 Dec 2025 17:58:22 +0100 Subject: [PATCH 48/86] Reword the MAS and Synapse admin scopes --- frontend/locales/en.json | 4 ++-- translations/en.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 731eab2f0..f3028c155 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -319,9 +319,9 @@ "scope": { "edit_profile": "Edit your profile and contact details", "manage_sessions": "Manage your devices and sessions", - "mas_admin": "Administer any user on the matrix-authentication-service", + "mas_admin": "Manage users (urn:mas:admin)", "send_messages": "Send new messages on your behalf", - "synapse_admin": "Administer the Synapse homeserver", + "synapse_admin": "Administer the server (urn:synapse:admin:*)", "view_messages": "View your existing messages and data", "view_profile": "See your profile info and contact details" } diff --git a/translations/en.json b/translations/en.json index c935542f3..601cb3534 100644 --- a/translations/en.json +++ b/translations/en.json @@ -664,7 +664,7 @@ "context": "components/scope.html:16:39-69", "description": "Displayed when the 'urn:mas:graphql:*' scope is requested" }, - "mas_admin": "Administer any user on the matrix-authentication-service", + "mas_admin": "Manage users (urn:mas:admin)", "@mas_admin": { "context": "components/scope.html:23:54-78", "description": "Displayed when the 'urn:mas:admin' scope is requested" @@ -673,7 +673,7 @@ "@send_messages": { "context": "components/scope.html:19:35-63" }, - "synapse_admin": "Administer the Synapse homeserver", + "synapse_admin": "Administer the server (urn:synapse:admin:*)", "@synapse_admin": { "context": "components/scope.html:21:53-81", "description": "Displayed when the 'urn:synapse:admin:*' scope is requested" From f9008f3184c056ecd43121444cbddcfaa2c0452a Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 2 Dec 2025 18:09:47 +0100 Subject: [PATCH 49/86] Add the Matrix user display name in the compat SSO login context --- .../handlers/src/compat/login_sso_complete.rs | 41 +++++++++++++++++-- crates/templates/src/context.rs | 29 +++++++++---- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/crates/handlers/src/compat/login_sso_complete.rs b/crates/handlers/src/compat/login_sso_complete.rs index a4fbb24fb..0062a65da 100644 --- a/crates/handlers/src/compat/login_sso_complete.rs +++ b/crates/handlers/src/compat/login_sso_complete.rs @@ -4,7 +4,7 @@ // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial // Please see LICENSE files in the repository root for full details. -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use anyhow::Context; use axum::{ @@ -18,7 +18,8 @@ use mas_axum_utils::{ cookies::CookieJar, csrf::{CsrfExt, ProtectedForm}, }; -use mas_data_model::{BoxClock, BoxRng, Clock}; +use mas_data_model::{BoxClock, BoxRng, Clock, MatrixUser}; +use mas_matrix::HomeserverConnection; use mas_router::{CompatLoginSsoAction, UrlBuilder}; use mas_storage::{BoxRepository, RepositoryAccess, compat::CompatSsoLoginRepository}; use mas_templates::{CompatSsoContext, ErrorContext, TemplateContext, Templates}; @@ -56,6 +57,7 @@ pub async fn get( mut repo: BoxRepository, State(templates): State, State(url_builder): State, + State(homeserver): State>, cookie_jar: CookieJar, Path(id): Path, Query(params): Query, @@ -96,6 +98,9 @@ pub async fn get( .context("Could not find compat SSO login") .map_err(InternalError::from_anyhow)?; + // We can close the repository early, we don't need it at this point + repo.save().await?; + // Bail out if that login session is more than 30min old if clock.now() > login.created_at + Duration::microseconds(30 * 60 * 1000 * 1000) { let ctx = ErrorContext::new() @@ -107,7 +112,37 @@ pub async fn get( return Ok((cookie_jar, Html(content)).into_response()); } - let ctx = CompatSsoContext::new(login) + // Fetch informations about the user. This is purely cosmetic, so we let it + // fail and put a 1s timeout to it in case we fail to query it + // XXX: we're likely to need this in other places + let localpart = &session.user.username; + let display_name = match tokio::time::timeout( + std::time::Duration::from_secs(1), + homeserver.query_user(localpart), + ) + .await + { + Ok(Ok(user)) => user.displayname, + Ok(Err(err)) => { + tracing::warn!( + error = &*err as &dyn std::error::Error, + localpart, + "Failed to query user" + ); + None + } + Err(_) => { + tracing::warn!(localpart, "Timed out while querying user"); + None + } + }; + + let matrix_user = MatrixUser { + mxid: homeserver.mxid(localpart), + display_name, + }; + + let ctx = CompatSsoContext::new(login, matrix_user) .with_session(session) .with_csrf(csrf_token.form_value()) .with_language(locale); diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index 73a38972f..c881198f8 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -872,6 +872,7 @@ impl PolicyViolationContext { pub struct CompatSsoContext { login: CompatSsoLogin, action: PostAuthAction, + matrix_user: MatrixUser, } impl TemplateContext for CompatSsoContext { @@ -884,23 +885,33 @@ impl TemplateContext for CompatSsoContext { Self: Sized, { let id = Ulid::from_datetime_with_source(now.into(), rng); - sample_list(vec![CompatSsoContext::new(CompatSsoLogin { - id, - redirect_uri: Url::parse("https://app.element.io/").unwrap(), - login_token: "abcdefghijklmnopqrstuvwxyz012345".into(), - created_at: now, - state: CompatSsoLoginState::Pending, - })]) + sample_list(vec![CompatSsoContext::new( + CompatSsoLogin { + id, + redirect_uri: Url::parse("https://app.element.io/").unwrap(), + login_token: "abcdefghijklmnopqrstuvwxyz012345".into(), + created_at: now, + state: CompatSsoLoginState::Pending, + }, + MatrixUser { + mxid: "@alice:example.com".to_owned(), + display_name: Some("Alice".to_owned()), + }, + )]) } } impl CompatSsoContext { /// Constructs a context for the legacy SSO login page #[must_use] - pub fn new(login: CompatSsoLogin) -> Self + pub fn new(login: CompatSsoLogin, matrix_user: MatrixUser) -> Self where { let action = PostAuthAction::continue_compat_sso_login(login.id); - Self { login, action } + Self { + login, + action, + matrix_user, + } } } From 5ed3c87818a783c45363b91b510b93acbf01e257 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 2 Dec 2025 18:10:12 +0100 Subject: [PATCH 50/86] Simplify the consent screens --- templates/components/scope.html | 29 ++++++++-- templates/pages/consent.html | 66 +++++++++++++---------- templates/pages/device_consent.html | 79 +++++++++++++++------------ templates/pages/sso.html | 39 ++++++++------ translations/en.json | 83 ++++++++++++++--------------- 5 files changed, 173 insertions(+), 123 deletions(-) diff --git a/templates/components/scope.html b/templates/components/scope.html index 96b5119cb..6ad4affd9 100644 --- a/templates/components/scope.html +++ b/templates/components/scope.html @@ -6,9 +6,32 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. -#} +{# Macro to remove 'unsafe' scope from a scope list. Usage: + + {% call(scopes) scope.unsafe_scopes(scopes=["openid", "urn:synapse:admin:*"]) %} + {{ scopes }} + {% endcall %} +#} +{% macro unsafe_scopes(scopes) -%} + {% set ns = namespace(unsafe_scopes=[]) %} + {% set safe_scope_prefixes = ["openid", "urn:matrix:client:api:", "urn:matrix:org.matrix.msc2967.client:api:", "urn:matrix:client:device:", "urn:matrix:org.matrix.msc2967.client:device:"] %} + {% for scope in scopes %} + {% set ns.is_safe = False %} + {% for safe_scope_prefix in safe_scope_prefixes %} + {% if scope.startswith(safe_scope_prefix) %} + {% set ns.is_safe = True %} + {% endif %} + {% endfor %} + {% if not ns.is_safe %} + {% set ns.unsafe_scopes = ns.unsafe_scopes + [scope] %} + {% endif %} + {% endfor %} + {{ caller(ns.unsafe_scopes) }} +{%- endmacro %} + {% macro list(scopes) %}
    - {% for scope in (scopes | split(" ")) %} + {% for scope in scopes %} {% if scope == "openid" %}
  • {{ icon.user_profile() }}

    {{ _("mas.scope.view_profile") }}

  • {% elif scope == "urn:mas:graphql:*" %} @@ -18,9 +41,9 @@ Please see LICENSE files in the repository root for full details.
  • {{ icon.chat() }}

    {{ _("mas.scope.view_messages") }}

  • {{ icon.send() }}

    {{ _("mas.scope.send_messages") }}

  • {% elif scope == "urn:synapse:admin:*" %} -
  • {{ icon.room() }}

    {{ _("mas.scope.synapse_admin") }}

  • +
  • {{ icon.room() }}

    {{ _("mas.scope.synapse_admin", scope=scope) }}

  • {% elif scope == "urn:mas:admin" %} -
  • {{ icon.admin() }}

    {{ _("mas.scope.mas_admin") }}

  • +
  • {{ icon.admin() }}

    {{ _("mas.scope.mas_admin", scope=scope) }}

  • {% elif scope is startingwith("urn:matrix:client:device:") or scope is startingwith("urn:matrix:org.matrix.msc2967.client:device:") %} {# We hide this scope #} {% else %} diff --git a/templates/pages/consent.html b/templates/pages/consent.html index 0eac9dc8b..cd0ee2d59 100644 --- a/templates/pages/consent.html +++ b/templates/pages/consent.html @@ -12,6 +12,7 @@ Please see LICENSE files in the repository root for full details. {% block content %} {% set client_name = client.client_name or client.client_id %} +
    {% if client.logo_uri %} @@ -22,33 +23,42 @@ Please see LICENSE files in the repository root for full details. {% endif %}
    -

    {{ _("mas.consent.heading") }}

    -

    - {{ _("mas.consent.client_wants_access", client_name=client_name, redirect_uri=(grant.redirect_uri | simplify_url)) }} - {{ _("mas.consent.this_will_allow", client_name=client_name) }} +

    + {{ _('mas.consent.continue_to', client_name=client_name) }} +

    +
    - - -
    - {{ _("mas.consent.make_sure_you_trust", client_name=client_name) }} - {{ _("mas.consent.you_may_be_sharing") }} - {% if client.policy_uri or client.tos_uri %} - Find out how {{ client_name }} will handle your data by reviewing its - {% if client.policy_uri %} - privacy policy{% if not client.tos_uri %}.{% endif %} - {% endif %} - {% if client.policy_uri and client.tos_uri%} - and - {% endif %} - {% if client.tos_uri %} - terms of service. - {% endif %} + {% call(scopes) scope.unsafe_scopes(scopes=grant.scope.split(" ")) %} + {% if scopes is not empty %} +
    +

    + {{ _('mas.consent.scope_list_preface', client_name=client_name) }} +

    + +
    {% endif %} + {% endcall %} + + {% set initial -%} + {%- if matrix_user.display_name -%} + {{- matrix_user.display_name[0] | upper -}} + {%- else -%} + {{- matrix_user.mxid[1] | upper -}} + {%- endif -%} + {%- endset %} + +
    +
    {{ initial }}
    +
    +
    {{ matrix_user.display_name or current_session.user.username }}
    +
    {{ matrix_user.mxid }}
    +
    @@ -57,13 +67,11 @@ Please see LICENSE files in the repository root for full details. {{ button.button(text=_("action.continue")) }} -
    -

    - {{ _("mas.not_you", username=current_session.user.username) }} -

    - - {{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token, post_logout_action=action, as_link=true) }} -
    + {% call logout.button(csrf_token=csrf_token, post_logout_action=action) %} + + {% endcall %} {{ back_to_client.link( text=_("action.cancel"), diff --git a/templates/pages/device_consent.html b/templates/pages/device_consent.html index 548e14e31..5433cadcf 100644 --- a/templates/pages/device_consent.html +++ b/templates/pages/device_consent.html @@ -25,9 +25,15 @@ Please see LICENSE files in the repository root for full details. {% endif %}
    -

    {{ _("mas.consent.heading") }}

    +

    + {{ _('mas.consent.continue_to', client_name=client_name) }} +

    -
    + + +
    {% if user_agent.device_type == "mobile" %} @@ -88,33 +94,36 @@ Please see LICENSE files in the repository root for full details.
    - -

    - {{ _("mas.device_consent.another_device_access") }} - {{ _("mas.consent.this_will_allow", client_name=client_name) }} -

    - - -
    - {{ _("mas.consent.make_sure_you_trust", client_name=client_name) }} - {{ _("mas.consent.you_may_be_sharing") }} - {% if client.policy_uri or client.tos_uri %} - Find out how {{ client_name }} will handle your data by reviewing its - {% if client.policy_uri %} - privacy policy{% if not client.tos_uri %}.{% endif %} - {% endif %} - {% if client.policy_uri and client.tos_uri%} - and - {% endif %} - {% if client.tos_uri %} - terms of service. - {% endif %} + {% call(scopes) scope.unsafe_scopes(scopes=grant.scope.split(" ")) %} + {% if scopes is not empty %} +
    +

    + {{ _('mas.consent.scope_list_preface', client_name=client_name) }} +

    + +
    {% endif %} + {% endcall %} + + {% set initial -%} + {%- if matrix_user.display_name -%} + {{- matrix_user.display_name[0] | upper -}} + {%- else -%} + {{- matrix_user.mxid[1] | upper -}} + {%- endif -%} + {%- endset %} + +
    +
    {{ initial }}
    +
    +
    {{ matrix_user.display_name or current_session.user.username }}
    +
    {{ matrix_user.mxid }}
    +
    @@ -123,18 +132,20 @@ Please see LICENSE files in the repository root for full details. - + {% endcall %} + +
    + +
    - -
    -

    - {{ _("mas.not_you", username=current_session.user.username) }} -

    - - {{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token, post_logout_action=action, as_link=true) }} -
    {% elif grant.state == "rejected" %}
    diff --git a/templates/pages/sso.html b/templates/pages/sso.html index cecd92baf..ce3321cfc 100644 --- a/templates/pages/sso.html +++ b/templates/pages/sso.html @@ -17,18 +17,29 @@ Please see LICENSE files in the repository root for full details.
    -

    Allow access to your account?

    -

    {{ client_name }} wants to access your account. This will allow {{ client_name }} to:

    +

    + {{ _('mas.consent.continue_to', client_name=client_name) }} +

    +
    - + {% set initial -%} + {%- if matrix_user.display_name -%} + {{- matrix_user.display_name[0] | upper -}} + {%- else -%} + {{- matrix_user.mxid[1] | upper -}} + {%- endif -%} + {%- endset %} -
    - Make sure that you trust {{ client_name }}. - You may be sharing sensitive information with this site or app. +
    +
    {{ initial }}
    +
    +
    {{ matrix_user.display_name or current_session.user.username }}
    +
    {{ matrix_user.mxid }}
    +
    @@ -37,12 +48,10 @@ Please see LICENSE files in the repository root for full details. {{ button.button(text=_("action.continue")) }} -
    -

    - {{ _("mas.not_you", username=current_session.user.username) }} -

    - - {{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token, post_logout_action=action, as_link=true) }} -
    + {% call logout.button(csrf_token=csrf_token, post_logout_action=action) %} + + {% endcall %}
    {% endblock content %} diff --git a/translations/en.json b/translations/en.json index 601cb3534..5afa415c9 100644 --- a/translations/en.json +++ b/translations/en.json @@ -6,11 +6,11 @@ }, "cancel": "Cancel", "@cancel": { - "context": "pages/consent.html:69:11-29, pages/device_consent.html:127:13-31, pages/policy_violation.html:44:13-31" + "context": "pages/consent.html:77:11-29, pages/device_consent.html:146:13-31, pages/policy_violation.html:44:13-31" }, "continue": "Continue", "@continue": { - "context": "form_post.html:25:28-48, pages/consent.html:57:28-48, pages/device_consent.html:124:13-33, pages/device_link.html:40:26-46, pages/login.html:68:30-50, pages/reauth.html:32:28-48, pages/recovery/start.html:38:26-46, pages/register/password.html:77:26-46, pages/register/steps/display_name.html:43:28-48, pages/register/steps/registration_token.html:41:28-48, pages/register/steps/verify_email.html:51:26-46, pages/sso.html:37:28-48" + "context": "form_post.html:25:28-48, pages/consent.html:67:28-48, pages/device_consent.html:133:13-33, pages/device_link.html:40:26-46, pages/login.html:68:30-50, pages/reauth.html:32:28-48, pages/recovery/start.html:38:26-46, pages/register/password.html:77:26-46, pages/register/steps/display_name.html:43:28-48, pages/register/steps/registration_token.html:41:28-48, pages/register/steps/verify_email.html:51:26-46, pages/sso.html:48:28-48" }, "create_account": "Create Account", "@create_account": { @@ -22,7 +22,7 @@ }, "sign_out": "Sign out", "@sign_out": { - "context": "pages/account/logged_out.html:22:28-48, pages/consent.html:65:28-48, pages/device_consent.html:136:30-50, pages/index.html:28:28-48, pages/policy_violation.html:38:28-48, pages/sso.html:45:28-48, pages/upstream_oauth2/link_mismatch.html:24:24-44, pages/upstream_oauth2/suggest_link.html:32:26-46" + "context": "pages/account/logged_out.html:22:28-48, pages/index.html:28:28-48, pages/policy_violation.html:38:28-48, pages/upstream_oauth2/link_mismatch.html:24:24-44, pages/upstream_oauth2/suggest_link.html:32:26-46" }, "skip": "Skip", "@skip": { @@ -165,8 +165,6 @@ "@current": { "description": "Field for the user's current password" }, - "description": "This will change the password on your account.", - "@description": {}, "heading": "Change my password", "@heading": { "description": "Heading on the change password page" @@ -189,43 +187,39 @@ } }, "consent": { - "client_wants_access": "%(client_name)s at %(redirect_uri)s wants to access your account.", - "@client_wants_access": { - "context": "pages/consent.html:27:11-122" + "continue_to": "Continue to %(client_name)s?", + "@continue_to": { + "context": "pages/consent.html:27:11-64, pages/device_consent.html:29:13-66, pages/sso.html:21:11-64" }, - "heading": "Allow access to your account?", - "@heading": { - "context": "pages/consent.html:25:27-51, pages/device_consent.html:28:29-53" + "scope_list_preface": "By continuing, you allow %(client_name)s to:", + "@scope_list_preface": { + "context": "pages/consent.html:39:13-73, pages/device_consent.html:104:15-75" }, - "make_sure_you_trust": "Make sure that you trust %(client_name)s.", - "@make_sure_you_trust": { - "context": "pages/consent.html:38:81-142, pages/device_consent.html:104:83-144" + "this_will_setup": "This will set up %(client_name)s (%(client_uri)s) with your %(server_name)s account.", + "@this_will_setup": { + "context": "pages/consent.html:30:11-149" }, - "this_will_allow": "This will allow %(client_name)s to:", - "@this_will_allow": { - "context": "pages/consent.html:28:11-68, pages/device_consent.html:94:13-70" - }, - "you_may_be_sharing": "You may be sharing sensitive information with this site or app.", - "@you_may_be_sharing": { - "context": "pages/consent.html:39:7-42, pages/device_consent.html:105:9-44" + "use_another_account": "Use another account", + "@use_another_account": { + "context": "pages/consent.html:72:11-47, pages/device_consent.html:139:13-49, pages/sso.html:53:11-47" } }, "device_card": { "access_requested": "Access requested", "@access_requested": { - "context": "pages/device_consent.html:82:34-71" + "context": "pages/device_consent.html:88:34-71" }, "device_code": "Code", "@device_code": { - "context": "pages/device_consent.html:86:34-66" + "context": "pages/device_consent.html:92:34-66" }, "generic_device": "Device", "@generic_device": { - "context": "pages/device_consent.html:70:22-57" + "context": "pages/device_consent.html:76:22-57" }, "ip_address": "IP address", "@ip_address": { - "context": "pages/device_consent.html:77:36-67" + "context": "pages/device_consent.html:83:36-67" } }, "device_code_link": { @@ -239,29 +233,29 @@ } }, "device_consent": { - "another_device_access": "Another device wants to access your account.", - "@another_device_access": { - "context": "pages/device_consent.html:93:13-58" - }, "denied": { "description": "You denied access to %(client_name)s. You can close this window.", "@description": { - "context": "pages/device_consent.html:147:27-94" + "context": "pages/device_consent.html:158:27-94" }, "heading": "Access denied", "@heading": { - "context": "pages/device_consent.html:146:29-67" + "context": "pages/device_consent.html:157:29-67" } }, "granted": { "description": "You granted access to %(client_name)s. You can close this window.", "@description": { - "context": "pages/device_consent.html:158:27-95" + "context": "pages/device_consent.html:169:27-95" }, "heading": "Access granted", "@heading": { - "context": "pages/device_consent.html:157:29-68" + "context": "pages/device_consent.html:168:29-68" } + }, + "this_will_setup": "Another device wants to set up %(client_name)s (%(client_uri)s) with your %(server_name)s account. Make sure you recognise that device.", + "@this_will_setup": { + "context": "pages/device_consent.html:33:13-158" } }, "device_display_name": { @@ -416,6 +410,12 @@ "context": "components/field.html:32:11-45" } }, + "legacy_consent": { + "this_will_setup": "This will set up %(client_name)s with your %(server_name)s account.", + "@this_will_setup": { + "context": "pages/sso.html:24:11-109" + } + }, "login": { "call_to_register": "Don't have an account yet?", "@call_to_register": { @@ -485,7 +485,6 @@ }, "not_you": "Not %(username)s?", "@not_you": { - "context": "pages/consent.html:62:11-67, pages/device_consent.html:133:13-69, pages/sso.html:42:11-67", "description": "Suggestions for the user to log in as a different user" }, "or_separator": "Or", @@ -656,36 +655,36 @@ "scope": { "edit_profile": "Edit your profile and contact details", "@edit_profile": { - "context": "components/scope.html:15:35-62", + "context": "components/scope.html:38:35-62", "description": "Displayed when the 'urn:mas:graphql:*' scope is requested" }, "manage_sessions": "Manage your devices and sessions", "@manage_sessions": { - "context": "components/scope.html:16:39-69", + "context": "components/scope.html:39:39-69", "description": "Displayed when the 'urn:mas:graphql:*' scope is requested" }, "mas_admin": "Manage users (urn:mas:admin)", "@mas_admin": { - "context": "components/scope.html:23:54-78", + "context": "components/scope.html:46:54-91", "description": "Displayed when the 'urn:mas:admin' scope is requested" }, "send_messages": "Send new messages on your behalf", "@send_messages": { - "context": "components/scope.html:19:35-63" + "context": "components/scope.html:42:35-63" }, "synapse_admin": "Administer the server (urn:synapse:admin:*)", "@synapse_admin": { - "context": "components/scope.html:21:53-81", + "context": "components/scope.html:44:53-94", "description": "Displayed when the 'urn:synapse:admin:*' scope is requested" }, "view_messages": "View your existing messages and data", "@view_messages": { - "context": "components/scope.html:18:35-63", + "context": "components/scope.html:41:35-63", "description": "Displayed when the 'urn:matrix:client:api:*' scope is requested" }, "view_profile": "See your profile info and contact details", "@view_profile": { - "context": "components/scope.html:13:43-70", + "context": "components/scope.html:36:43-70", "description": "Displayed when the 'openid' scope is requested" } }, @@ -800,4 +799,4 @@ } } } -} \ No newline at end of file +} From ebc30f3776dcf489ae336103d7fe049229b457d8 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 2 Dec 2025 18:13:02 +0100 Subject: [PATCH 51/86] Make the compat SSO login page use the same layout as other consent pages --- templates/pages/sso.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/pages/sso.html b/templates/pages/sso.html index ce3321cfc..f6d9aba99 100644 --- a/templates/pages/sso.html +++ b/templates/pages/sso.html @@ -6,6 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. -#} +{% set consent_page = true %} + {% extends "base.html" %} {% block content %} From 8dd1c3fa788c5512ea9d8c4a1d70c045916ba40d Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 2 Dec 2025 19:54:14 +0100 Subject: [PATCH 52/86] Fix rendering of the logout button in a few places --- templates/components/logout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/components/logout.html b/templates/components/logout.html index 391a02500..5db056120 100644 --- a/templates/components/logout.html +++ b/templates/components/logout.html @@ -12,7 +12,7 @@ Please see LICENSE files in the repository root for full details. {% for key, value in post_logout_action|items %} {% endfor %} - {% if caller %} + {% if caller is defined %} {{ caller() }} {% elif as_link %} From 8a39f4181fccfd7ef2c72ed5bbd9810eb2035376 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 3 Dec 2025 10:27:18 +0100 Subject: [PATCH 53/86] Fix the consent templates failing to render in tests --- templates/pages/consent.html | 2 +- templates/pages/device_consent.html | 2 +- translations/en.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/pages/consent.html b/templates/pages/consent.html index cd0ee2d59..c66e9c8a6 100644 --- a/templates/pages/consent.html +++ b/templates/pages/consent.html @@ -27,7 +27,7 @@ Please see LICENSE files in the repository root for full details. {{ _('mas.consent.continue_to', client_name=client_name) }} diff --git a/templates/pages/device_consent.html b/templates/pages/device_consent.html index 5433cadcf..23d56835d 100644 --- a/templates/pages/device_consent.html +++ b/templates/pages/device_consent.html @@ -30,7 +30,7 @@ Please see LICENSE files in the repository root for full details.
    diff --git a/translations/en.json b/translations/en.json index 2406b395a..be553656a 100644 --- a/translations/en.json +++ b/translations/en.json @@ -197,7 +197,7 @@ }, "this_will_setup": "This will set up %(client_name)s (%(client_uri)s) with your %(server_name)s account.", "@this_will_setup": { - "context": "pages/consent.html:30:11-149" + "context": "pages/consent.html:30:11-173" }, "use_another_account": "Use another account", "@use_another_account": { @@ -255,7 +255,7 @@ }, "this_will_setup": "Another device wants to set up %(client_name)s (%(client_uri)s) with your %(server_name)s account. Make sure you recognise that device.", "@this_will_setup": { - "context": "pages/device_consent.html:33:13-158" + "context": "pages/device_consent.html:33:13-166" } }, "device_display_name": { From 68e011443ec2c178470daf8eac302dfbcbb56d47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 09:36:14 +0000 Subject: [PATCH 54/86] Improve comments explaining function-based mock for Intl.DateTimeFormat Co-authored-by: sandhose <1549952+sandhose@users.noreply.github.com> --- frontend/src/test-utils/mockLocale.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/src/test-utils/mockLocale.ts b/frontend/src/test-utils/mockLocale.ts index 9f5517481..e42b5af08 100644 --- a/frontend/src/test-utils/mockLocale.ts +++ b/frontend/src/test-utils/mockLocale.ts @@ -14,15 +14,17 @@ import { vi } from "vitest"; export const mockLocale = (defaultLocale = "en-GB"): void => { const OriginalDateTimeFormat = Intl.DateTimeFormat; - // Vitest 4.x requires function/class implementations for spyOn mocks. - // This function acts as a constructor wrapper that applies the default locale - // when no locale is specified. Returning the instance from the constructor - // is valid JavaScript - when a constructor returns an object, that object - // is used instead of the newly created `this`. + // Vitest 4.x requires function/class implementations for spyOn mocks when + // mocking constructors. For built-in constructors like Intl.DateTimeFormat + // that have internal slots, we use a function that returns a new instance. + // This is valid JavaScript - when a constructor returns an object, that + // object becomes the instance (instead of `this`). function MockDateTimeFormat( + this: unknown, locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions, ): Intl.DateTimeFormat { + // Apply default locale when no locale is specified return new OriginalDateTimeFormat(locales || defaultLocale, options); } From 7c3e6701c1905817214d2b523e139e43d2ab7e1d Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 3 Dec 2025 11:00:32 +0100 Subject: [PATCH 55/86] Add a test for the new skip_confirmation option --- crates/handlers/src/upstream_oauth2/link.rs | 172 ++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/crates/handlers/src/upstream_oauth2/link.rs b/crates/handlers/src/upstream_oauth2/link.rs index e52ce4a26..37796ef77 100644 --- a/crates/handlers/src/upstream_oauth2/link.rs +++ b/crates/handlers/src/upstream_oauth2/link.rs @@ -1469,6 +1469,178 @@ mod tests { assert!(email_auth.completed_at.is_some()); } + #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] + async fn test_register_skip_confirmation(pool: PgPool) { + // Same test as test_register, but checks that we get straight to the + // registration flown skipping the confirmation + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + let mut rng = state.rng(); + let cookies = CookieHelper::new(); + + let claims_imports = UpstreamOAuthProviderClaimsImports { + skip_confirmation: true, + localpart: UpstreamOAuthProviderLocalpartPreference { + action: mas_data_model::UpstreamOAuthProviderImportAction::Force, + template: None, + on_conflict: mas_data_model::UpstreamOAuthProviderOnConflict::default(), + }, + email: UpstreamOAuthProviderImportPreference { + action: mas_data_model::UpstreamOAuthProviderImportAction::Force, + template: None, + }, + ..UpstreamOAuthProviderClaimsImports::default() + }; + + let id_token_claims = serde_json::json!({ + "preferred_username": "john", + "email": "john@example.com", + "email_verified": true, + }); + + // Grab a key to sign the id_token + // We could generate a key on the fly, but because we have one available here, + // why not use it? + let key = state + .key_store + .signing_key_for_algorithm(&JsonWebSignatureAlg::Rs256) + .unwrap(); + + let signer = key + .params() + .signing_key_for_alg(&JsonWebSignatureAlg::Rs256) + .unwrap(); + let header = JsonWebSignatureHeader::new(JsonWebSignatureAlg::Rs256); + let id_token = + Jwt::sign_with_rng(&mut rng, header, id_token_claims.clone(), &signer).unwrap(); + + // Provision a provider and a link + let mut repo = state.repository().await.unwrap(); + let provider = repo + .upstream_oauth_provider() + .add( + &mut rng, + &state.clock, + UpstreamOAuthProviderParams { + issuer: Some("https://example.com/".to_owned()), + human_name: Some("Example Ltd.".to_owned()), + brand_name: None, + scope: Scope::from_iter([OPENID]), + token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::None, + token_endpoint_signing_alg: None, + id_token_signed_response_alg: JsonWebSignatureAlg::Rs256, + client_id: "client".to_owned(), + encrypted_client_secret: None, + claims_imports, + authorization_endpoint_override: None, + token_endpoint_override: None, + userinfo_endpoint_override: None, + fetch_userinfo: false, + userinfo_signed_response_alg: None, + jwks_uri_override: None, + discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc, + pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto, + response_mode: None, + additional_authorization_parameters: Vec::new(), + forward_login_hint: false, + ui_order: 0, + on_backchannel_logout: + mas_data_model::UpstreamOAuthProviderOnBackchannelLogout::DoNothing, + }, + ) + .await + .unwrap(); + + let session = repo + .upstream_oauth_session() + .add( + &mut rng, + &state.clock, + &provider, + "state".to_owned(), + None, + None, + ) + .await + .unwrap(); + + let link = repo + .upstream_oauth_link() + .add( + &mut rng, + &state.clock, + &provider, + "subject".to_owned(), + None, + ) + .await + .unwrap(); + + let session = repo + .upstream_oauth_session() + .complete_with_link( + &state.clock, + session, + &link, + Some(id_token.into_string()), + Some(id_token_claims), + None, + None, + ) + .await + .unwrap(); + + repo.save().await.unwrap(); + + let cookie_jar = state.cookie_jar(); + let upstream_sessions = UpstreamSessionsCookie::default() + .add(session.id, provider.id, "state".to_owned(), None) + .add_link_to_session(session.id, link.id) + .unwrap(); + let cookie_jar = upstream_sessions.save(cookie_jar, &state.clock); + cookies.import(cookie_jar); + + let request = Request::get(&*mas_router::UpstreamOAuth2Link::new(link.id).path()).empty(); + let request = cookies.with_cookies(request); + let response = state.request(request).await; + cookies.save_cookies(&response); + let location = response.headers().get(hyper::header::LOCATION).unwrap(); + // Grab the registration ID from the redirected URL: + // /register/steps/{id}/finish + let registration_id: Ulid = str::from_utf8(location.as_bytes()) + .unwrap() + .rsplit('/') + .nth(1) + .expect("Location to have two slashes") + .parse() + .expect("last segment of location to be a ULID"); + + // Check that we have a registered user, with the email imported + let mut repo = state.repository().await.unwrap(); + let registration: UserRegistration = repo + .user_registration() + .lookup(registration_id) + .await + .unwrap() + .expect("user registration exists"); + + assert_eq!(registration.password, None); + assert_eq!(registration.completed_at, None); + assert_eq!(registration.username, "john"); + + let email_auth_id = registration + .email_authentication_id + .expect("registration should have an email authentication"); + let email_auth: UserEmailAuthentication = repo + .user_email() + .lookup_authentication(email_auth_id) + .await + .unwrap() + .expect("email authentication should exist"); + assert_eq!(email_auth.email, "john@example.com"); + assert!(email_auth.completed_at.is_some()); + } + #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] async fn test_link_existing_account(pool: PgPool) { let existing_username = "john"; From 445bc9b0f953eaa5694931cd69bcb18722c15d6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:39:13 +0000 Subject: [PATCH 56/86] build(deps-dev): bump the storybook group in /frontend with 3 updates Bumps the storybook group in /frontend with 3 updates: [@storybook/addon-docs](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/docs), [@storybook/react-vite](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/react-vite) and [storybook](https://github.com/storybookjs/storybook/tree/HEAD/code/core). Updates `@storybook/addon-docs` from 10.0.8 to 10.1.3 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v10.1.3/code/addons/docs) Updates `@storybook/react-vite` from 10.0.8 to 10.1.3 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v10.1.3/code/frameworks/react-vite) Updates `storybook` from 10.0.8 to 10.1.3 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v10.1.3/code/core) --- updated-dependencies: - dependency-name: "@storybook/addon-docs" dependency-version: 10.1.3 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: storybook - dependency-name: "@storybook/react-vite" dependency-version: 10.1.3 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: storybook - dependency-name: storybook dependency-version: 10.1.3 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: storybook ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 99 +++++++++++++++++++------------------- frontend/package.json | 4 +- 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 23e1ceeee..3503b99ae 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -36,8 +36,8 @@ "@graphql-codegen/cli": "^6.1.0", "@graphql-codegen/client-preset": "^5.1.1", "@graphql-codegen/typescript-msw": "^3.0.1", - "@storybook/addon-docs": "^10.0.8", - "@storybook/react-vite": "^10.0.8", + "@storybook/addon-docs": "^10.1.4", + "@storybook/react-vite": "^10.1.4", "@tanstack/react-query-devtools": "^5.90.2", "@tanstack/react-router-devtools": "^1.131.44", "@tanstack/router-plugin": "^1.131.44", @@ -5362,16 +5362,16 @@ "license": "MIT" }, "node_modules/@storybook/addon-docs": { - "version": "10.0.8", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.0.8.tgz", - "integrity": "sha512-PYuaGXGycsamK/7OrFoE4syHGy22mdqqArl67cfosRwmRxZEI9ManQK0jTjNQM9ZX14NpThMOSWNGoWLckkxog==", + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.1.4.tgz", + "integrity": "sha512-TWLDJNLS/S3AUyTf9x0Hb8k7d+VWMJCH9dWAS0QenvJG8ga9VaehO6r+e+3YyIDbO1ev3UST3GCjh9SY8tzwRA==", "dev": true, "license": "MIT", "dependencies": { "@mdx-js/react": "^3.0.0", - "@storybook/csf-plugin": "10.0.8", - "@storybook/icons": "^1.6.0", - "@storybook/react-dom-shim": "10.0.8", + "@storybook/csf-plugin": "10.1.4", + "@storybook/icons": "^2.0.0", + "@storybook/react-dom-shim": "10.1.4", "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" @@ -5381,17 +5381,18 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.0.8" + "storybook": "^10.1.4" } }, "node_modules/@storybook/builder-vite": { - "version": "10.0.8", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.0.8.tgz", - "integrity": "sha512-kaf/pUENzXxYgQMHGGPNiIk1ieb+SOMuSeLKx8wAUOlQOrzhtSH+ItACW/l43t+O6YZ8jYHoNBMF1kdQ1+Y5+w==", + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.1.4.tgz", + "integrity": "sha512-3mUQoCzMuhqAIjj8fdbGlwh+GgHaFpCvU+sxL8kIxnZqflW09SuwM5kS47Y5QDzYbHAPYCPqcBFyJ4EfRuf0rw==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf-plugin": "10.0.8", + "@storybook/csf-plugin": "10.1.4", + "@vitest/mocker": "3.2.4", "ts-dedent": "^2.0.0" }, "funding": { @@ -5399,14 +5400,14 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.0.8", + "storybook": "^10.1.4", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "node_modules/@storybook/csf-plugin": { - "version": "10.0.8", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.0.8.tgz", - "integrity": "sha512-OtLUWHIm3SDGtclQn6Mdd/YsWizLBgdEBRAdekGtwI/TvICfT7gpWYIycP53v2t9ufu2MIXjsxtV2maZKs8sZg==", + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.1.4.tgz", + "integrity": "sha512-nudIBYx8fBz+1j2Xn1pdfGcgMJ78N/1NFB4MYAxI3YEzxGnQwUjihOO1x3siAXPbjFGmnVHoBx7+6IpO3F70GA==", "dev": true, "license": "MIT", "dependencies": { @@ -5419,7 +5420,7 @@ "peerDependencies": { "esbuild": "*", "rollup": "*", - "storybook": "^10.0.8", + "storybook": "^10.1.4", "vite": "*", "webpack": "*" }, @@ -5446,28 +5447,26 @@ "license": "MIT" }, "node_modules/@storybook/icons": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-1.6.0.tgz", - "integrity": "sha512-hcFZIjW8yQz8O8//2WTIXylm5Xsgc+lW9ISLgUk1xGmptIJQRdlhVIXCpSyLrQaaRiyhQRaVg7l3BD9S216BHw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-2.0.1.tgz", + "integrity": "sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=14.0.0" - }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta" + "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" } }, "node_modules/@storybook/react": { - "version": "10.0.8", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.0.8.tgz", - "integrity": "sha512-PkuPb8sAqmjjkowSzm3rutiSuETvZI2F8SnjbHE6FRqZWWK4iFoaUrQbrg5kpPAtX//xIrqkdFwlbmQ3skhiPA==", + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.1.4.tgz", + "integrity": "sha512-ZBMPdQ99QBv/UtlIZBerDGNsQB30ffxk6twe45FIPutSlKXD6W9r0z7rGa5UWnqmmxa9HjARRhclOFsNGkhs9g==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "10.0.8" + "@storybook/react-dom-shim": "10.1.4", + "react-docgen": "^8.0.2" }, "funding": { "type": "opencollective", @@ -5476,7 +5475,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.0.8", + "storybook": "^10.1.4", "typescript": ">= 4.9.x" }, "peerDependenciesMeta": { @@ -5486,9 +5485,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "10.0.8", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.0.8.tgz", - "integrity": "sha512-ojuH22MB9Sz6rWbhTmC5IErZr0ZADbZijtPteUdydezY7scORT00UtbNoBcG0V6iVjdChgDtSKw2KHUUfchKqg==", + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.1.4.tgz", + "integrity": "sha512-PARu2HA5nYU1AkioNJNc430pz0oyaHFSSAdN3NEaWwkoGrCOo9ZpAXP9V7wlJANCi1pndbC84gSuHVnBXJBG6g==", "dev": true, "license": "MIT", "funding": { @@ -5498,20 +5497,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.0.8" + "storybook": "^10.1.4" } }, "node_modules/@storybook/react-vite": { - "version": "10.0.8", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.0.8.tgz", - "integrity": "sha512-HS2X4qlitrZr3/sN2+ollxAaNE813IasZRE8lOez1Ey1ISGBtYIb9rmJs82MK35+yDM0pHdiDjkFMD4SkNYh2g==", + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.1.4.tgz", + "integrity": "sha512-PneYbxBGArczDtDAvQu6Ug5oeDYM5SQiEDSF0i+TNN0ZKO2ROsmbGSI9/7YTFontXR2CqweIO8GyOGQOcz5K9A==", "dev": true, "license": "MIT", "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "0.6.1", "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "10.0.8", - "@storybook/react": "10.0.8", + "@storybook/builder-vite": "10.1.4", + "@storybook/react": "10.1.4", "empathic": "^2.0.0", "magic-string": "^0.30.0", "react-docgen": "^8.0.0", @@ -5525,7 +5524,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.0.8", + "storybook": "^10.1.4", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, @@ -11073,9 +11072,9 @@ } }, "node_modules/react-docgen": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-8.0.1.tgz", - "integrity": "sha512-kQKsqPLplY3Hx4jGnM3jpQcG3FQDt7ySz32uTHt3C9HAe45kNXG+3o16Eqn3Fw1GtMfHoN3b4J/z2e6cZJCmqQ==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-8.0.2.tgz", + "integrity": "sha512-+NRMYs2DyTP4/tqWz371Oo50JqmWltR1h2gcdgUMAWZJIAvrd0/SqlCfx7tpzpl/s36rzw6qH2MjoNrxtRNYhA==", "dev": true, "license": "MIT", "dependencies": { @@ -11939,23 +11938,23 @@ } }, "node_modules/storybook": { - "version": "10.0.8", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.0.8.tgz", - "integrity": "sha512-vQMufKKA9TxgoEDHJv3esrqUkjszuuRiDkThiHxENFPdQawHhm2Dei+iwNRwH5W671zTDy9iRT9P1KDjcU5Iyw==", + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.1.4.tgz", + "integrity": "sha512-FrBjm8I8O+pYEOPHcdW9xWwgXSZxte7lza9q2lN3jFN4vuW79m5j0OnTQeR8z9MmIbBTvkIpp3yMBebl53Yt5Q==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "^1.6.0", + "@storybook/icons": "^2.0.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", "@vitest/spy": "3.2.4", - "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", + "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", "recast": "^0.23.5", "semver": "^7.6.2", + "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "bin": { diff --git a/frontend/package.json b/frontend/package.json index cdb6a1e7a..8065e6b51 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -46,8 +46,8 @@ "@graphql-codegen/cli": "^6.1.0", "@graphql-codegen/client-preset": "^5.1.1", "@graphql-codegen/typescript-msw": "^3.0.1", - "@storybook/addon-docs": "^10.0.8", - "@storybook/react-vite": "^10.0.8", + "@storybook/addon-docs": "^10.1.4", + "@storybook/react-vite": "^10.1.4", "@tanstack/react-query-devtools": "^5.90.2", "@tanstack/react-router-devtools": "^1.131.44", "@tanstack/router-plugin": "^1.131.44", From c7bb887c658b5597297b3cfb9a099c547fec83b1 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 3 Dec 2025 13:39:34 +0100 Subject: [PATCH 57/86] Apply minor suggestions from Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/handlers/src/upstream_oauth2/link.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/handlers/src/upstream_oauth2/link.rs b/crates/handlers/src/upstream_oauth2/link.rs index 37796ef77..ba24ed311 100644 --- a/crates/handlers/src/upstream_oauth2/link.rs +++ b/crates/handlers/src/upstream_oauth2/link.rs @@ -1472,7 +1472,7 @@ mod tests { #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] async fn test_register_skip_confirmation(pool: PgPool) { // Same test as test_register, but checks that we get straight to the - // registration flown skipping the confirmation + // registration flow skipping the confirmation setup(); let state = TestState::from_pool(pool).await.unwrap(); let mut rng = state.rng(); @@ -1481,7 +1481,7 @@ mod tests { let claims_imports = UpstreamOAuthProviderClaimsImports { skip_confirmation: true, localpart: UpstreamOAuthProviderLocalpartPreference { - action: mas_data_model::UpstreamOAuthProviderImportAction::Force, + action: mas_data_model::UpstreamOAuthProviderImportAction::Require, template: None, on_conflict: mas_data_model::UpstreamOAuthProviderOnConflict::default(), }, From 27d96715a5f3cf7ae900bc7f729f29606948e331 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 3 Dec 2025 13:46:59 +0100 Subject: [PATCH 58/86] Explain how to use scope.unsafe_scopes() better --- templates/components/scope.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/templates/components/scope.html b/templates/components/scope.html index 6ad4affd9..92400a1b6 100644 --- a/templates/components/scope.html +++ b/templates/components/scope.html @@ -6,9 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. -#} -{# Macro to remove 'unsafe' scope from a scope list. Usage: +{# Macro to remove 'safe' scope from a scope list. Usage: - {% call(scopes) scope.unsafe_scopes(scopes=["openid", "urn:synapse:admin:*"]) %} + {% call(scopes) scope.unsafe_scopes(scopes=["openid", "urn:matrix:client:api:*", "urn:synapse:admin:*", "urn:mas:admin"]) %} + {# `scopes` only has unsafe scopes: ["urn:synapse:admin:*", "urn:mas:admin"] #} {{ scopes }} {% endcall %} #} From 9cc9fc8bbe5f380e9597b7fc8131da7583f97f6a Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 3 Dec 2025 13:47:59 +0100 Subject: [PATCH 59/86] Update comment on the `id_color_hash` template filter --- crates/templates/src/functions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/templates/src/functions.rs b/crates/templates/src/functions.rs index cb4603de0..3a8e3d43d 100644 --- a/crates/templates/src/functions.rs +++ b/crates/templates/src/functions.rs @@ -139,8 +139,8 @@ fn filter_simplify_url(url: &str, kwargs: Kwargs) -> Result u32 { input.chars().fold(0, |hash, c| hash + c as u32) % 6 + 1 } From 43388c46688ceceb6209cee574082779b5483d4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 12:53:13 +0000 Subject: [PATCH 60/86] build(deps-dev): bump vite in /frontend in the vite group Bumps the vite group in /frontend with 1 update: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite). Updates `vite` from 7.2.4 to 7.2.6 - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v7.2.6/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 7.2.6 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: vite ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 8 ++++---- frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 449f4141d..279d240d3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -66,7 +66,7 @@ "tailwindcss": "^3.4.18", "tinyglobby": "^0.2.15", "typescript": "^5.9.3", - "vite": "7.2.4", + "vite": "7.2.6", "vite-plugin-compression": "^0.5.1", "vite-plugin-graphql-codegen": "^3.7.0", "vite-plugin-manifest-sri": "^0.2.0", @@ -12853,9 +12853,9 @@ } }, "node_modules/vite": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz", - "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz", + "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==", "dev": true, "license": "MIT", "peer": true, diff --git a/frontend/package.json b/frontend/package.json index c0ba46743..45aae23e1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -76,7 +76,7 @@ "tailwindcss": "^3.4.18", "tinyglobby": "^0.2.15", "typescript": "^5.9.3", - "vite": "7.2.4", + "vite": "7.2.6", "vite-plugin-compression": "^0.5.1", "vite-plugin-graphql-codegen": "^3.7.0", "vite-plugin-manifest-sri": "^0.2.0", From 0cf481c6e70a3cd0fbc544d1185aa1960e6f4752 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 3 Dec 2025 14:04:46 +0100 Subject: [PATCH 61/86] Un-break the scope tempalte helpers --- templates/components/scope.html | 9 +++++++-- translations/en.json | 14 +++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/templates/components/scope.html b/templates/components/scope.html index 92400a1b6..e6c7b6261 100644 --- a/templates/components/scope.html +++ b/templates/components/scope.html @@ -9,8 +9,13 @@ Please see LICENSE files in the repository root for full details. {# Macro to remove 'safe' scope from a scope list. Usage: {% call(scopes) scope.unsafe_scopes(scopes=["openid", "urn:matrix:client:api:*", "urn:synapse:admin:*", "urn:mas:admin"]) %} - {# `scopes` only has unsafe scopes: ["urn:synapse:admin:*", "urn:mas:admin"] #} - {{ scopes }} + `scopes` only has unsafe scopes: ["urn:synapse:admin:*", "urn:mas:admin"] + +
      + {% for scope in scopes %} +
    • {{ scope }}
    • + {% endfor %} +
    {% endcall %} #} {% macro unsafe_scopes(scopes) -%} diff --git a/translations/en.json b/translations/en.json index be553656a..53ac7ad3d 100644 --- a/translations/en.json +++ b/translations/en.json @@ -655,36 +655,36 @@ "scope": { "edit_profile": "Edit your profile and contact details", "@edit_profile": { - "context": "components/scope.html:38:35-62", + "context": "components/scope.html:44:35-62", "description": "Displayed when the 'urn:mas:graphql:*' scope is requested" }, "manage_sessions": "Manage your devices and sessions", "@manage_sessions": { - "context": "components/scope.html:39:39-69", + "context": "components/scope.html:45:39-69", "description": "Displayed when the 'urn:mas:graphql:*' scope is requested" }, "mas_admin": "Manage users (urn:mas:admin)", "@mas_admin": { - "context": "components/scope.html:46:54-91", + "context": "components/scope.html:52:54-91", "description": "Displayed when the 'urn:mas:admin' scope is requested" }, "send_messages": "Send new messages on your behalf", "@send_messages": { - "context": "components/scope.html:42:35-63" + "context": "components/scope.html:48:35-63" }, "synapse_admin": "Administer the server (urn:synapse:admin:*)", "@synapse_admin": { - "context": "components/scope.html:44:53-94", + "context": "components/scope.html:50:53-94", "description": "Displayed when the 'urn:synapse:admin:*' scope is requested" }, "view_messages": "View your existing messages and data", "@view_messages": { - "context": "components/scope.html:41:35-63", + "context": "components/scope.html:47:35-63", "description": "Displayed when the 'urn:matrix:client:api:*' scope is requested" }, "view_profile": "See your profile info and contact details", "@view_profile": { - "context": "components/scope.html:36:43-70", + "context": "components/scope.html:42:43-70", "description": "Displayed when the 'openid' scope is requested" } }, From cd1cd192697d6eadbeadcb6336b9c92f49a33fe5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:13:24 +0000 Subject: [PATCH 62/86] build(deps-dev): bump the vitest group in /frontend with 2 updates Bumps the vitest group in /frontend with 2 updates: [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) and [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest). Updates `@vitest/coverage-v8` from 4.0.14 to 4.0.15 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.15/packages/coverage-v8) Updates `vitest` from 4.0.14 to 4.0.15 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.15/packages/vitest) --- updated-dependencies: - dependency-name: "@vitest/coverage-v8" dependency-version: 4.0.15 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: vitest - dependency-name: vitest dependency-version: 4.0.15 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: vitest ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 143 +++++++++++++++++++------------------ frontend/package.json | 2 +- 2 files changed, 74 insertions(+), 71 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 279d240d3..0e8ce5f93 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -49,7 +49,7 @@ "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.1", - "@vitest/coverage-v8": "^4.0.14", + "@vitest/coverage-v8": "^4.0.15", "autoprefixer": "^10.4.21", "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", @@ -6434,14 +6434,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.14.tgz", - "integrity": "sha512-EYHLqN/BY6b47qHH7gtMxAg++saoGmsjWmAq9MlXxAz4M0NcHh9iOyKhBZyU4yxZqOd8Xnqp80/5saeitz4Cng==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.15.tgz", + "integrity": "sha512-FUJ+1RkpTFW7rQITdgTi93qOCWJobWhBirEPCeXh2SW2wsTlFxy51apDz5gzG+ZEYt/THvWeNmhdAoS9DTwpCw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.14", + "@vitest/utils": "4.0.15", "ast-v8-to-istanbul": "^0.3.8", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -6456,8 +6456,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.0.14", - "vitest": "4.0.14" + "@vitest/browser": "4.0.15", + "vitest": "4.0.15" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -6466,9 +6466,9 @@ } }, "node_modules/@vitest/coverage-v8/node_modules/@vitest/pretty-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz", - "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.15.tgz", + "integrity": "sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==", "dev": true, "license": "MIT", "dependencies": { @@ -6479,13 +6479,13 @@ } }, "node_modules/@vitest/coverage-v8/node_modules/@vitest/utils": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz", - "integrity": "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.15.tgz", + "integrity": "sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.14", + "@vitest/pretty-format": "4.0.15", "tinyrainbow": "^3.0.3" }, "funding": { @@ -6570,13 +6570,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.14.tgz", - "integrity": "sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.15.tgz", + "integrity": "sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.14", + "@vitest/utils": "4.0.15", "pathe": "^2.0.3" }, "funding": { @@ -6584,9 +6584,9 @@ } }, "node_modules/@vitest/runner/node_modules/@vitest/pretty-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz", - "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.15.tgz", + "integrity": "sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==", "dev": true, "license": "MIT", "dependencies": { @@ -6597,13 +6597,13 @@ } }, "node_modules/@vitest/runner/node_modules/@vitest/utils": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz", - "integrity": "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.15.tgz", + "integrity": "sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.14", + "@vitest/pretty-format": "4.0.15", "tinyrainbow": "^3.0.3" }, "funding": { @@ -6621,13 +6621,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.14.tgz", - "integrity": "sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.15.tgz", + "integrity": "sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.14", + "@vitest/pretty-format": "4.0.15", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -6636,9 +6636,9 @@ } }, "node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz", - "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.15.tgz", + "integrity": "sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==", "dev": true, "license": "MIT", "dependencies": { @@ -12403,11 +12403,14 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { "version": "0.2.15", @@ -12978,20 +12981,20 @@ "license": "MIT" }, "node_modules/vitest": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.14.tgz", - "integrity": "sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.15.tgz", + "integrity": "sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@vitest/expect": "4.0.14", - "@vitest/mocker": "4.0.14", - "@vitest/pretty-format": "4.0.14", - "@vitest/runner": "4.0.14", - "@vitest/snapshot": "4.0.14", - "@vitest/spy": "4.0.14", - "@vitest/utils": "4.0.14", + "@vitest/expect": "4.0.15", + "@vitest/mocker": "4.0.15", + "@vitest/pretty-format": "4.0.15", + "@vitest/runner": "4.0.15", + "@vitest/snapshot": "4.0.15", + "@vitest/spy": "4.0.15", + "@vitest/utils": "4.0.15", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", @@ -13000,7 +13003,7 @@ "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", + "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", @@ -13019,10 +13022,10 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.14", - "@vitest/browser-preview": "4.0.14", - "@vitest/browser-webdriverio": "4.0.14", - "@vitest/ui": "4.0.14", + "@vitest/browser-playwright": "4.0.15", + "@vitest/browser-preview": "4.0.15", + "@vitest/browser-webdriverio": "4.0.15", + "@vitest/ui": "4.0.15", "happy-dom": "*", "jsdom": "*" }, @@ -13057,16 +13060,16 @@ } }, "node_modules/vitest/node_modules/@vitest/expect": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.14.tgz", - "integrity": "sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.15.tgz", + "integrity": "sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.14", - "@vitest/utils": "4.0.14", + "@vitest/spy": "4.0.15", + "@vitest/utils": "4.0.15", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" }, @@ -13075,13 +13078,13 @@ } }, "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.14.tgz", - "integrity": "sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.15.tgz", + "integrity": "sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.14", + "@vitest/spy": "4.0.15", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -13102,9 +13105,9 @@ } }, "node_modules/vitest/node_modules/@vitest/pretty-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz", - "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.15.tgz", + "integrity": "sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==", "dev": true, "license": "MIT", "dependencies": { @@ -13115,9 +13118,9 @@ } }, "node_modules/vitest/node_modules/@vitest/spy": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.14.tgz", - "integrity": "sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.15.tgz", + "integrity": "sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==", "dev": true, "license": "MIT", "funding": { @@ -13125,13 +13128,13 @@ } }, "node_modules/vitest/node_modules/@vitest/utils": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz", - "integrity": "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.15.tgz", + "integrity": "sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.14", + "@vitest/pretty-format": "4.0.15", "tinyrainbow": "^3.0.3" }, "funding": { diff --git a/frontend/package.json b/frontend/package.json index 45aae23e1..b26c5922f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -59,7 +59,7 @@ "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.1", - "@vitest/coverage-v8": "^4.0.14", + "@vitest/coverage-v8": "^4.0.15", "autoprefixer": "^10.4.21", "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", From fc956232617f16ba7ef337c6af640600bd33dedc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:13:37 +0000 Subject: [PATCH 63/86] build(deps-dev): bump i18next-cli in /frontend in the i18next group Bumps the i18next group in /frontend with 1 update: [i18next-cli](https://github.com/i18next/i18next-cli). Updates `i18next-cli` from 1.28.3 to 1.29.3 - [Changelog](https://github.com/i18next/i18next-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/i18next/i18next-cli/compare/v1.28.3...v1.29.3) --- updated-dependencies: - dependency-name: i18next-cli dependency-version: 1.29.3 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: i18next ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 8 ++++---- frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 279d240d3..192cae256 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -54,7 +54,7 @@ "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", "happy-dom": "^20.0.4", - "i18next-cli": "^1.28.3", + "i18next-cli": "^1.29.3", "knip": "^5.66.4", "msw": "^2.11.6", "msw-storybook-addon": "^2.0.6", @@ -8737,9 +8737,9 @@ } }, "node_modules/i18next-cli": { - "version": "1.28.3", - "resolved": "https://registry.npmjs.org/i18next-cli/-/i18next-cli-1.28.3.tgz", - "integrity": "sha512-E+nY2XmIaeC+JfSvd8UM4MxLaKNwS33eCghL+xm8rue0hXsWy5tJOWsHj1o/X76v/maYw5sgbSLRSBoPJzp5hA==", + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/i18next-cli/-/i18next-cli-1.29.3.tgz", + "integrity": "sha512-/gM1woZewtNBALcxjvTDihozFiCz9BKkSQjV58OGsy4mHK4ujIwjZXYsiHmH7UMUdnSeMBAXoK6xSkukMHcXrg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 45aae23e1..95ad02f59 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -64,7 +64,7 @@ "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", "happy-dom": "^20.0.4", - "i18next-cli": "^1.28.3", + "i18next-cli": "^1.29.3", "knip": "^5.66.4", "msw": "^2.11.6", "msw-storybook-addon": "^2.0.6", From 203b0ff661cc4d375440fd50bb9f90f8f0228a37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:14:04 +0000 Subject: [PATCH 64/86] build(deps): bump the tanstack-query group in /frontend with 2 updates Bumps the tanstack-query group in /frontend with 2 updates: [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) and [@tanstack/react-query-devtools](https://github.com/TanStack/query/tree/HEAD/packages/react-query-devtools). Updates `@tanstack/react-query` from 5.90.10 to 5.90.11 - [Release notes](https://github.com/TanStack/query/releases) - [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md) - [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.90.11/packages/react-query) Updates `@tanstack/react-query-devtools` from 5.90.2 to 5.91.1 - [Release notes](https://github.com/TanStack/query/releases) - [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query-devtools/CHANGELOG.md) - [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query-devtools@5.91.1/packages/react-query-devtools) --- updated-dependencies: - dependency-name: "@tanstack/react-query" dependency-version: 5.90.11 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: tanstack-query - dependency-name: "@tanstack/react-query-devtools" dependency-version: 5.91.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: tanstack-query ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 34 +++++++++++++++++----------------- frontend/package.json | 4 ++-- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 279d240d3..8242a9a0b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,7 +12,7 @@ "@fontsource/inter": "^5.2.8", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", - "@tanstack/react-query": "^5.90.10", + "@tanstack/react-query": "^5.90.11", "@tanstack/react-router": "^1.131.44", "@vector-im/compound-design-tokens": "6.4.0", "@vector-im/compound-web": "^8.2.5", @@ -38,7 +38,7 @@ "@graphql-codegen/typescript-msw": "^3.0.1", "@storybook/addon-docs": "^10.1.4", "@storybook/react-vite": "^10.1.4", - "@tanstack/react-query-devtools": "^5.90.2", + "@tanstack/react-query-devtools": "^5.91.1", "@tanstack/react-router-devtools": "^1.131.44", "@tanstack/router-plugin": "^1.131.44", "@testing-library/jest-dom": "^6.9.1", @@ -5785,9 +5785,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.90.10", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.10.tgz", - "integrity": "sha512-EhZVFu9rl7GfRNuJLJ3Y7wtbTnENsvzp+YpcAV7kCYiXni1v8qZh++lpw4ch4rrwC0u/EZRnBHIehzCGzwXDSQ==", + "version": "5.90.11", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.11.tgz", + "integrity": "sha512-f9z/nXhCgWDF4lHqgIE30jxLe4sYv15QodfdPDKYAk7nAEjNcndy4dHz3ezhdUaR23BpWa4I2EH4/DZ0//Uf8A==", "license": "MIT", "funding": { "type": "github", @@ -5795,9 +5795,9 @@ } }, "node_modules/@tanstack/query-devtools": { - "version": "5.90.1", - "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.90.1.tgz", - "integrity": "sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ==", + "version": "5.91.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.91.1.tgz", + "integrity": "sha512-l8bxjk6BMsCaVQH6NzQEE/bEgFy1hAs5qbgXl0xhzezlaQbPk6Mgz9BqEg2vTLPOHD8N4k+w/gdgCbEzecGyNg==", "dev": true, "license": "MIT", "funding": { @@ -5806,13 +5806,13 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.90.10", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.10.tgz", - "integrity": "sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw==", + "version": "5.90.11", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.11.tgz", + "integrity": "sha512-3uyzz01D1fkTLXuxF3JfoJoHQMU2fxsfJwE+6N5hHy0dVNoZOvwKP8Z2k7k1KDeD54N20apcJnG75TBAStIrBA==", "license": "MIT", "peer": true, "dependencies": { - "@tanstack/query-core": "5.90.10" + "@tanstack/query-core": "5.90.11" }, "funding": { "type": "github", @@ -5823,20 +5823,20 @@ } }, "node_modules/@tanstack/react-query-devtools": { - "version": "5.90.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.90.2.tgz", - "integrity": "sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ==", + "version": "5.91.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.91.1.tgz", + "integrity": "sha512-tRnJYwEbH0kAOuToy8Ew7bJw1lX3AjkkgSlf/vzb+NpnqmHPdWM+lA2DSdGQSLi1SU0PDRrrCI1vnZnci96CsQ==", "dev": true, "license": "MIT", "dependencies": { - "@tanstack/query-devtools": "5.90.1" + "@tanstack/query-devtools": "5.91.1" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-query": "^5.90.2", + "@tanstack/react-query": "^5.90.10", "react": "^18 || ^19" } }, diff --git a/frontend/package.json b/frontend/package.json index 45aae23e1..87b4368fc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,7 +22,7 @@ "@fontsource/inter": "^5.2.8", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", - "@tanstack/react-query": "^5.90.10", + "@tanstack/react-query": "^5.90.11", "@tanstack/react-router": "^1.131.44", "@vector-im/compound-design-tokens": "6.4.0", "@vector-im/compound-web": "^8.2.5", @@ -48,7 +48,7 @@ "@graphql-codegen/typescript-msw": "^3.0.1", "@storybook/addon-docs": "^10.1.4", "@storybook/react-vite": "^10.1.4", - "@tanstack/react-query-devtools": "^5.90.2", + "@tanstack/react-query-devtools": "^5.91.1", "@tanstack/react-router-devtools": "^1.131.44", "@tanstack/router-plugin": "^1.131.44", "@testing-library/jest-dom": "^6.9.1", From 294ae5e3f2a54a09e74649efbc41b41b2bdbc48c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:14:14 +0000 Subject: [PATCH 65/86] build(deps-dev): bump @types/react in /frontend in the types group Bumps the types group in /frontend with 1 update: [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react). Updates `@types/react` from 19.2.6 to 19.2.7 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) --- updated-dependencies: - dependency-name: "@types/react" dependency-version: 19.2.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 8 ++++---- frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 279d240d3..081b6450e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -45,7 +45,7 @@ "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", "@types/node": "^24.10.1", - "@types/react": "19.2.6", + "@types/react": "19.2.7", "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.1", @@ -6305,9 +6305,9 @@ } }, "node_modules/@types/react": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.6.tgz", - "integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "devOptional": true, "license": "MIT", "peer": true, diff --git a/frontend/package.json b/frontend/package.json index 45aae23e1..e348ecaad 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -55,7 +55,7 @@ "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", "@types/node": "^24.10.1", - "@types/react": "19.2.6", + "@types/react": "19.2.7", "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.1", From 0fd13d3d0700c107ab0a905cb37cae88b9fb7d59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:14:17 +0000 Subject: [PATCH 66/86] build(deps): bump tracing-appender in the tracing group Bumps the tracing group with 1 update: [tracing-appender](https://github.com/tokio-rs/tracing). Updates `tracing-appender` from 0.2.3 to 0.2.4 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-appender-0.2.3...tracing-appender-0.2.4) --- updated-dependencies: - dependency-name: tracing-appender dependency-version: 0.2.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: tracing ... Signed-off-by: dependabot[bot] --- Cargo.lock | 18 +++++++++--------- Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c1e9791eb..718567628 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1736,7 +1736,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -3008,7 +3008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.53.3", ] [[package]] @@ -4085,7 +4085,7 @@ dependencies = [ "sha1", "sha2", "sprintf", - "thiserror 1.0.69", + "thiserror 2.0.17", "tokio", "tracing", "urlencoding", @@ -5103,7 +5103,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -6157,7 +6157,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -6509,12 +6509,12 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" dependencies = [ "crossbeam-channel", - "thiserror 1.0.69", + "thiserror 2.0.17", "time", "tracing-subscriber", ] @@ -7284,7 +7284,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index cec19fa71..67a3eff57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -688,7 +688,7 @@ version = "0.1.41" version = "0.3.20" features = ["env-filter"] [workspace.dependencies.tracing-appender] -version = "0.2.3" +version = "0.2.4" # URL manipulation [workspace.dependencies.url] From 1407ae53907273ba85cb2d971f1d45857a2d25a0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 16:13:36 +0000 Subject: [PATCH 67/86] Translations updates --- frontend/.storybook/locales.ts | 66 +++++++++++++++++----------------- frontend/locales/de.json | 2 +- frontend/locales/fr.json | 4 +-- frontend/locales/nl.json | 4 +-- translations/fr.json | 14 ++++++-- translations/uk.json | 1 + 6 files changed, 50 insertions(+), 41 deletions(-) diff --git a/frontend/.storybook/locales.ts b/frontend/.storybook/locales.ts index 51c977c63..9a545003e 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.7", + projectUrl: "https://localazy.com/p/matrix-authentication-service", baseLocale: "en", languages: [ { @@ -181,22 +181,22 @@ const localazyMetadata: LocalazyMetadata = { file: "frontend.json", path: "", cdnFiles: { - "cs": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/cs/frontend.json", - "da": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/da/frontend.json", - "de": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/de/frontend.json", - "en": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/en/frontend.json", - "et": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/et/frontend.json", - "fi": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fi/frontend.json", - "fr": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fr/frontend.json", - "hu": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/hu/frontend.json", - "nb_NO": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nb-NO/frontend.json", - "nl": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nl/frontend.json", - "pl": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pl/frontend.json", - "pt": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt/frontend.json", - "ru": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json", - "sv": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json", - "uk": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json", - "zh#Hans": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_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", + "ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json", + "sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json", + "uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json", + "zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json" } }, { @@ -204,22 +204,22 @@ const localazyMetadata: LocalazyMetadata = { file: "file.json", path: "", cdnFiles: { - "cs": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/cs/file.json", - "da": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/da/file.json", - "de": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/de/file.json", - "en": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/en/file.json", - "et": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/et/file.json", - "fi": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fi/file.json", - "fr": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fr/file.json", - "hu": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/hu/file.json", - "nb_NO": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nb-NO/file.json", - "nl": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nl/file.json", - "pl": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pl/file.json", - "pt": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt/file.json", - "ru": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json", - "sv": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json", - "uk": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json", - "zh#Hans": "https://delivery.localazy.com/_a6730564787461928869ed6dbfd8/_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", + "ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json", + "sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json", + "uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json", + "zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json" } } ] diff --git a/frontend/locales/de.json b/frontend/locales/de.json index 75c8493ed..e8bf651ca 100644 --- a/frontend/locales/de.json +++ b/frontend/locales/de.json @@ -43,7 +43,7 @@ "alert_description": "Dieses Konto wird dauerhaft entfernt und du hast keinen Zugriff mehr auf deine Nachrichten.", "alert_title": "Du bist kurz davor, alle deine Daten zu verlieren.", "button": "Account löschen", - "dialog_description": "Bestätige, dass du dein Konto löschen möchtest:\n\nDu kannst dein Konto nicht reaktivieren\nDu kannst dich nicht mehr anmelden\nNiemand kann deinen Benutzernamen (MXID) wieder verwenden, auch du nicht.\nDu verlässt alle Gruppen und Chats\nDu wirst vom Identitätsserver entfernt und niemand kann dich mit deiner E-Mail-Adresse oder Telefonnummer finden\n\nDeine alten Nachrichten sind für Empfänger weiterhin sichtbar. Möchtest du deine gesendeten Nachrichten vor zukünftigen Gruppen-Besuchern verbergen?", + "dialog_description": "Bestätige, dass du dein Konto löschen möchtest:\n\n\nDu kannst dein Konto nicht reaktivieren\nDu kannst dich nicht mehr anmelden\nNiemand kann deinen Benutzernamen (MXID) wieder verwenden, auch du nicht.\nDu verlässt alle Gruppen und Chats\nDu wirst vom Identitätsserver entfernt und niemand kann dich mit deiner E-Mail-Adresse oder Telefonnummer finden\n\nDeine alten Nachrichten sind für die jeweiligen Empfänger weiterhin sichtbar. Möchtest du deine gesendeten Nachrichten vor zukünftigen Gruppen-Besuchern verbergen?", "dialog_title": "Dieses Konto löschen?", "erase_checkbox_label": "Ja, alle meine Nachrichten vor neuen Mitgliedern verbergen", "incorrect_password": "Falsches Passwort, versuch's nochmal", diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index eb4f0b3f9..d9219d9f9 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -391,9 +391,9 @@ "scope": { "edit_profile": "Modifier votre profil et vos coordonnées", "manage_sessions": "Gérer vos appareils et vos sessions", - "mas_admin": "Administrer n'importe quel utilisateur dans matrix-authentication-service", + "mas_admin": "Administrer les utilisateurs (urn:mas:admin)", "send_messages": "Envoyez de nouveaux messages en votre nom", - "synapse_admin": "Administrer le serveur d’accueil Synapse", + "synapse_admin": "Administrer le serveur (urn:synapse:admin:*)", "view_messages": "Afficher vos messages et données existants", "view_profile": "Voir les informations de votre profil et vos coordonnées" } diff --git a/frontend/locales/nl.json b/frontend/locales/nl.json index e52e20624..15b643f05 100644 --- a/frontend/locales/nl.json +++ b/frontend/locales/nl.json @@ -391,9 +391,9 @@ "scope": { "edit_profile": "Edit your profile and contact details", "manage_sessions": "Manage your devices and sessions", - "mas_admin": "Administer any user on the matrix-authentication-service", + "mas_admin": "Manage users (urn:mas:admin)", "send_messages": "Send new messages on your behalf", - "synapse_admin": "Administer the Synapse homeserver", + "synapse_admin": "Administer the server (urn:synapse:admin:*)", "view_messages": "View your existing messages and data", "view_profile": "See your profile info and contact details" } diff --git a/translations/fr.json b/translations/fr.json index c2cef20e0..9678dcc80 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -74,9 +74,13 @@ }, "consent": { "client_wants_access": "%(client_name)s à l'adresse %(redirect_uri)s souhaite accéder à votre compte.", + "continue_to": "Continuer vers %(client_name)s?", "heading": "Autoriser l'accès à votre compte ?", "make_sure_you_trust": "Assurez-vous de faire confiance %(client_name)s.", + "scope_list_preface": "En continuant, vous autorisez %(client_name)s à :", "this_will_allow": "Cela va permettre à %(client_name)s de :", + "this_will_setup": "Continuer connectera %(client_name)s (%(client_uri)s) avec votre compte %(server_name)s.", + "use_another_account": "Utiliser un autre compte", "you_may_be_sharing": "Vous partagez peut-être des informations sensibles avec ce site ou cette application." }, "device_card": { @@ -98,7 +102,8 @@ "granted": { "description": "Vous avez accordé l'accès à %(client_name)s. Vous pouvez fermer cette fenêtre.", "heading": "Accès accordé" - } + }, + "this_will_setup": "Un autre appareil souhaite connecter %(client_name)s (%(client_uri)s) avec votre compte %(server_name)s. Assurez-vous de reconnaître cet appareil." }, "device_display_name": { "client_on_device": "%(client_name)s sur %(device_name)s", @@ -145,6 +150,9 @@ "username_too_long": "Le nom d'utilisateur est trop long", "username_too_short": "Le nom d'utilisateur est trop court" }, + "legacy_consent": { + "this_will_setup": "Continuer connectera %(client_name)s avec votre compte %(server_name)s." + }, "login": { "call_to_register": "Vous n’avez pas encore de compte ?", "continue_with_provider": "Poursuivre avec %(provider)s", @@ -226,9 +234,9 @@ "scope": { "edit_profile": "Modifier votre profil et vos coordonnées", "manage_sessions": "Gérer vos appareils et vos sessions", - "mas_admin": "Administrer n'importe quel utilisateur dans matrix-authentication-service", + "mas_admin": "Administrer les utilisateurs (urn:mas:admin)", "send_messages": "Envoyez de nouveaux messages en votre nom", - "synapse_admin": "Administrer le serveur d’accueil Synapse", + "synapse_admin": "Administrer le serveur (urn:synapse:admin:*)", "view_messages": "Afficher vos messages et données existants", "view_profile": "Voir les informations de votre profil et vos coordonnées" }, diff --git a/translations/uk.json b/translations/uk.json index fcdf0ec6d..0aab7bf97 100644 --- a/translations/uk.json +++ b/translations/uk.json @@ -210,6 +210,7 @@ "register": { "call_to_login": "Вже маєте обліковий запис?", "continue_with_email": "Продовжити за допомогою е-пошти", + "continue_with_password": "Продовжити з паролем", "create_account": { "description": "Виберіть ім'я користувача, щоб продовжити.", "heading": "Створити обліковий запис" From 18c1ab1c74a926af2199c77971eac77964b151c8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 16:27:49 +0000 Subject: [PATCH 68/86] 1.8.0-rc.0 --- Cargo.lock | 56 +++++++++++++++++++++++++------------------------- Cargo.toml | 60 +++++++++++++++++++++++++++--------------------------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 718567628..6e9d36327 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3097,7 +3097,7 @@ dependencies = [ [[package]] name = "mas-axum-utils" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "anyhow", "axum", @@ -3131,7 +3131,7 @@ dependencies = [ [[package]] name = "mas-cli" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "anyhow", "axum", @@ -3204,7 +3204,7 @@ dependencies = [ [[package]] name = "mas-config" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "anyhow", "camino", @@ -3236,7 +3236,7 @@ dependencies = [ [[package]] name = "mas-context" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "console", "opentelemetry", @@ -3252,7 +3252,7 @@ dependencies = [ [[package]] name = "mas-data-model" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "base64ct", "chrono", @@ -3275,7 +3275,7 @@ dependencies = [ [[package]] name = "mas-email" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "async-trait", "lettre", @@ -3286,7 +3286,7 @@ dependencies = [ [[package]] name = "mas-handlers" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "aide", "anyhow", @@ -3366,7 +3366,7 @@ dependencies = [ [[package]] name = "mas-http" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "futures-util", "headers", @@ -3387,7 +3387,7 @@ dependencies = [ [[package]] name = "mas-i18n" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "camino", "icu_calendar", @@ -3409,7 +3409,7 @@ dependencies = [ [[package]] name = "mas-i18n-scan" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "camino", "clap", @@ -3423,7 +3423,7 @@ dependencies = [ [[package]] name = "mas-iana" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "schemars 0.9.0", "serde", @@ -3431,7 +3431,7 @@ dependencies = [ [[package]] name = "mas-iana-codegen" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "anyhow", "async-trait", @@ -3448,7 +3448,7 @@ dependencies = [ [[package]] name = "mas-jose" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "base64ct", "chrono", @@ -3478,7 +3478,7 @@ dependencies = [ [[package]] name = "mas-keystore" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "aead", "base64ct", @@ -3506,7 +3506,7 @@ dependencies = [ [[package]] name = "mas-listener" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "anyhow", "bytes", @@ -3531,7 +3531,7 @@ dependencies = [ [[package]] name = "mas-matrix" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "anyhow", "async-trait", @@ -3541,7 +3541,7 @@ dependencies = [ [[package]] name = "mas-matrix-synapse" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "anyhow", "async-trait", @@ -3558,7 +3558,7 @@ dependencies = [ [[package]] name = "mas-oidc-client" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "assert_matches", "async-trait", @@ -3594,7 +3594,7 @@ dependencies = [ [[package]] name = "mas-policy" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "anyhow", "arc-swap", @@ -3611,7 +3611,7 @@ dependencies = [ [[package]] name = "mas-router" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "axum", "serde", @@ -3622,7 +3622,7 @@ dependencies = [ [[package]] name = "mas-spa" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "camino", "serde", @@ -3631,7 +3631,7 @@ dependencies = [ [[package]] name = "mas-storage" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "async-trait", "chrono", @@ -3653,7 +3653,7 @@ dependencies = [ [[package]] name = "mas-storage-pg" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "async-trait", "chrono", @@ -3681,7 +3681,7 @@ dependencies = [ [[package]] name = "mas-tasks" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "anyhow", "async-trait", @@ -3713,7 +3713,7 @@ dependencies = [ [[package]] name = "mas-templates" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "anyhow", "arc-swap", @@ -3745,7 +3745,7 @@ dependencies = [ [[package]] name = "mas-tower" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "http", "opentelemetry", @@ -4015,7 +4015,7 @@ dependencies = [ [[package]] name = "oauth2-types" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "assert_matches", "base64ct", @@ -6086,7 +6086,7 @@ dependencies = [ [[package]] name = "syn2mas" -version = "1.7.0" +version = "1.8.0-rc.0" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index 67a3eff57..b49f4546e 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.7.0" +package.version = "1.8.0-rc.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.7.0" } -mas-cli = { path = "./crates/cli/", version = "=1.7.0" } -mas-config = { path = "./crates/config/", version = "=1.7.0" } -mas-context = { path = "./crates/context/", version = "=1.7.0" } -mas-data-model = { path = "./crates/data-model/", version = "=1.7.0" } -mas-email = { path = "./crates/email/", version = "=1.7.0" } -mas-graphql = { path = "./crates/graphql/", version = "=1.7.0" } -mas-handlers = { path = "./crates/handlers/", version = "=1.7.0" } -mas-http = { path = "./crates/http/", version = "=1.7.0" } -mas-i18n = { path = "./crates/i18n/", version = "=1.7.0" } -mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.7.0" } -mas-iana = { path = "./crates/iana/", version = "=1.7.0" } -mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.7.0" } -mas-jose = { path = "./crates/jose/", version = "=1.7.0" } -mas-keystore = { path = "./crates/keystore/", version = "=1.7.0" } -mas-listener = { path = "./crates/listener/", version = "=1.7.0" } -mas-matrix = { path = "./crates/matrix/", version = "=1.7.0" } -mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.7.0" } -mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.7.0" } -mas-policy = { path = "./crates/policy/", version = "=1.7.0" } -mas-router = { path = "./crates/router/", version = "=1.7.0" } -mas-spa = { path = "./crates/spa/", version = "=1.7.0" } -mas-storage = { path = "./crates/storage/", version = "=1.7.0" } -mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.7.0" } -mas-tasks = { path = "./crates/tasks/", version = "=1.7.0" } -mas-templates = { path = "./crates/templates/", version = "=1.7.0" } -mas-tower = { path = "./crates/tower/", version = "=1.7.0" } -oauth2-types = { path = "./crates/oauth2-types/", version = "=1.7.0" } -syn2mas = { path = "./crates/syn2mas", version = "=1.7.0" } +mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.8.0-rc.0" } +mas-cli = { path = "./crates/cli/", version = "=1.8.0-rc.0" } +mas-config = { path = "./crates/config/", version = "=1.8.0-rc.0" } +mas-context = { path = "./crates/context/", version = "=1.8.0-rc.0" } +mas-data-model = { path = "./crates/data-model/", version = "=1.8.0-rc.0" } +mas-email = { path = "./crates/email/", version = "=1.8.0-rc.0" } +mas-graphql = { path = "./crates/graphql/", version = "=1.8.0-rc.0" } +mas-handlers = { path = "./crates/handlers/", version = "=1.8.0-rc.0" } +mas-http = { path = "./crates/http/", version = "=1.8.0-rc.0" } +mas-i18n = { path = "./crates/i18n/", version = "=1.8.0-rc.0" } +mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.8.0-rc.0" } +mas-iana = { path = "./crates/iana/", version = "=1.8.0-rc.0" } +mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.8.0-rc.0" } +mas-jose = { path = "./crates/jose/", version = "=1.8.0-rc.0" } +mas-keystore = { path = "./crates/keystore/", version = "=1.8.0-rc.0" } +mas-listener = { path = "./crates/listener/", version = "=1.8.0-rc.0" } +mas-matrix = { path = "./crates/matrix/", version = "=1.8.0-rc.0" } +mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.8.0-rc.0" } +mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.8.0-rc.0" } +mas-policy = { path = "./crates/policy/", version = "=1.8.0-rc.0" } +mas-router = { path = "./crates/router/", version = "=1.8.0-rc.0" } +mas-spa = { path = "./crates/spa/", version = "=1.8.0-rc.0" } +mas-storage = { path = "./crates/storage/", version = "=1.8.0-rc.0" } +mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.8.0-rc.0" } +mas-tasks = { path = "./crates/tasks/", version = "=1.8.0-rc.0" } +mas-templates = { path = "./crates/templates/", version = "=1.8.0-rc.0" } +mas-tower = { path = "./crates/tower/", version = "=1.8.0-rc.0" } +oauth2-types = { path = "./crates/oauth2-types/", version = "=1.8.0-rc.0" } +syn2mas = { path = "./crates/syn2mas", version = "=1.8.0-rc.0" } # OpenAPI schema generation and validation [workspace.dependencies.aide] From d00217c4ea57c80626b212cdedea9d0119a20aa4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 13:16:43 +0000 Subject: [PATCH 69/86] build(deps-dev): bump i18next-cli in /frontend in the i18next group Bumps the i18next group in /frontend with 1 update: [i18next-cli](https://github.com/i18next/i18next-cli). Updates `i18next-cli` from 1.29.3 to 1.29.4 - [Changelog](https://github.com/i18next/i18next-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/i18next/i18next-cli/compare/v1.29.3...v1.29.4) --- updated-dependencies: - dependency-name: i18next-cli dependency-version: 1.29.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: i18next ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 8 ++++---- frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e69d1ce79..5ce8f87c9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -54,7 +54,7 @@ "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", "happy-dom": "^20.0.4", - "i18next-cli": "^1.29.3", + "i18next-cli": "^1.29.4", "knip": "^5.66.4", "msw": "^2.11.6", "msw-storybook-addon": "^2.0.6", @@ -8737,9 +8737,9 @@ } }, "node_modules/i18next-cli": { - "version": "1.29.3", - "resolved": "https://registry.npmjs.org/i18next-cli/-/i18next-cli-1.29.3.tgz", - "integrity": "sha512-/gM1woZewtNBALcxjvTDihozFiCz9BKkSQjV58OGsy4mHK4ujIwjZXYsiHmH7UMUdnSeMBAXoK6xSkukMHcXrg==", + "version": "1.29.4", + "resolved": "https://registry.npmjs.org/i18next-cli/-/i18next-cli-1.29.4.tgz", + "integrity": "sha512-EqnCI+OP7ljAt2XFWVE0Rf189Ci0qIIMCc+Q8oV7HSPQSkZFwohtIFrqMdTGOJ5O/znW3y83mGxgPuP25NP4wg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 2de787e18..544a89296 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -64,7 +64,7 @@ "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", "happy-dom": "^20.0.4", - "i18next-cli": "^1.29.3", + "i18next-cli": "^1.29.4", "knip": "^5.66.4", "msw": "^2.11.6", "msw-storybook-addon": "^2.0.6", From cca32770d508ed97861224c66676e6ac162a1b47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 13:17:02 +0000 Subject: [PATCH 70/86] build(deps): bump the react group in /frontend with 2 updates Bumps the react group in /frontend with 2 updates: [react](https://github.com/facebook/react/tree/HEAD/packages/react) and [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom). Updates `react` from 19.2.0 to 19.2.1 - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/v19.2.1/packages/react) Updates `react-dom` from 19.2.0 to 19.2.1 - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/v19.2.1/packages/react-dom) --- updated-dependencies: - dependency-name: react dependency-version: 19.2.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: react - dependency-name: react-dom dependency-version: 19.2.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: react ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 18 +++++++++--------- frontend/package.json | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e69d1ce79..41622bd45 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -21,8 +21,8 @@ "classnames": "^2.5.1", "date-fns": "^4.1.0", "i18next": "^25.7.1", - "react": "^19.2.0", - "react-dom": "^19.2.0", + "react": "^19.2.1", + "react-dom": "^19.2.1", "react-i18next": "^16.3.5", "swagger-ui-dist": "^5.29.5", "valibot": "^1.2.0", @@ -11080,9 +11080,9 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", - "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", + "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "license": "MIT", "peer": true, "engines": { @@ -11122,16 +11122,16 @@ } }, "node_modules/react-dom": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", - "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", + "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "license": "MIT", "peer": true, "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.0" + "react": "^19.2.1" } }, "node_modules/react-i18next": { diff --git a/frontend/package.json b/frontend/package.json index 2de787e18..4df9c46b6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,8 +31,8 @@ "classnames": "^2.5.1", "date-fns": "^4.1.0", "i18next": "^25.7.1", - "react": "^19.2.0", - "react-dom": "^19.2.0", + "react": "^19.2.1", + "react-dom": "^19.2.1", "react-i18next": "^16.3.5", "swagger-ui-dist": "^5.29.5", "valibot": "^1.2.0", From 123b1866991593ad0cc6a93508881472b2f6a769 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 13:17:16 +0000 Subject: [PATCH 71/86] build(deps): bump @tanstack/react-query Bumps the tanstack-query group in /frontend with 1 update: [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query). Updates `@tanstack/react-query` from 5.90.11 to 5.90.12 - [Release notes](https://github.com/TanStack/query/releases) - [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md) - [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.90.12/packages/react-query) --- updated-dependencies: - dependency-name: "@tanstack/react-query" dependency-version: 5.90.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: tanstack-query ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 16 ++++++++-------- frontend/package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e69d1ce79..a7ebf3727 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,7 +12,7 @@ "@fontsource/inter": "^5.2.8", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", - "@tanstack/react-query": "^5.90.11", + "@tanstack/react-query": "^5.90.12", "@tanstack/react-router": "^1.131.44", "@vector-im/compound-design-tokens": "6.4.0", "@vector-im/compound-web": "^8.2.5", @@ -5785,9 +5785,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.90.11", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.11.tgz", - "integrity": "sha512-f9z/nXhCgWDF4lHqgIE30jxLe4sYv15QodfdPDKYAk7nAEjNcndy4dHz3ezhdUaR23BpWa4I2EH4/DZ0//Uf8A==", + "version": "5.90.12", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.12.tgz", + "integrity": "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==", "license": "MIT", "funding": { "type": "github", @@ -5806,13 +5806,13 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.90.11", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.11.tgz", - "integrity": "sha512-3uyzz01D1fkTLXuxF3JfoJoHQMU2fxsfJwE+6N5hHy0dVNoZOvwKP8Z2k7k1KDeD54N20apcJnG75TBAStIrBA==", + "version": "5.90.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.12.tgz", + "integrity": "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==", "license": "MIT", "peer": true, "dependencies": { - "@tanstack/query-core": "5.90.11" + "@tanstack/query-core": "5.90.12" }, "funding": { "type": "github", diff --git a/frontend/package.json b/frontend/package.json index 2de787e18..973e2cbdf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,7 +22,7 @@ "@fontsource/inter": "^5.2.8", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", - "@tanstack/react-query": "^5.90.11", + "@tanstack/react-query": "^5.90.12", "@tanstack/react-router": "^1.131.44", "@vector-im/compound-design-tokens": "6.4.0", "@vector-im/compound-web": "^8.2.5", From 7a2c049c07b9a61e49876dd63241b88583244d5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 13:17:32 +0000 Subject: [PATCH 72/86] build(deps-dev): bump @biomejs/biome from 2.3.7 to 2.3.8 in /frontend Bumps [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) from 2.3.7 to 2.3.8. - [Release notes](https://github.com/biomejs/biome/releases) - [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md) - [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.3.8/packages/@biomejs/biome) --- updated-dependencies: - dependency-name: "@biomejs/biome" dependency-version: 2.3.8 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 72 +++++++++++++++++++------------------- frontend/package.json | 2 +- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e69d1ce79..71f8729bb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -29,7 +29,7 @@ "vaul": "^1.1.2" }, "devDependencies": { - "@biomejs/biome": "^2.3.7", + "@biomejs/biome": "^2.3.8", "@browser-logos/chrome": "^2.0.0", "@browser-logos/firefox": "^3.0.10", "@browser-logos/safari": "^2.1.0", @@ -1022,9 +1022,9 @@ } }, "node_modules/@biomejs/biome": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.7.tgz", - "integrity": "sha512-CTbAS/jNAiUc6rcq94BrTB8z83O9+BsgWj2sBCQg9rD6Wkh2gjfR87usjx0Ncx0zGXP1NKgT7JNglay5Zfs9jw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.8.tgz", + "integrity": "sha512-Qjsgoe6FEBxWAUzwFGFrB+1+M8y/y5kwmg5CHac+GSVOdmOIqsAiXM5QMVGZJ1eCUCLlPZtq4aFAQ0eawEUuUA==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -1038,20 +1038,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.3.7", - "@biomejs/cli-darwin-x64": "2.3.7", - "@biomejs/cli-linux-arm64": "2.3.7", - "@biomejs/cli-linux-arm64-musl": "2.3.7", - "@biomejs/cli-linux-x64": "2.3.7", - "@biomejs/cli-linux-x64-musl": "2.3.7", - "@biomejs/cli-win32-arm64": "2.3.7", - "@biomejs/cli-win32-x64": "2.3.7" + "@biomejs/cli-darwin-arm64": "2.3.8", + "@biomejs/cli-darwin-x64": "2.3.8", + "@biomejs/cli-linux-arm64": "2.3.8", + "@biomejs/cli-linux-arm64-musl": "2.3.8", + "@biomejs/cli-linux-x64": "2.3.8", + "@biomejs/cli-linux-x64-musl": "2.3.8", + "@biomejs/cli-win32-arm64": "2.3.8", + "@biomejs/cli-win32-x64": "2.3.8" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.7.tgz", - "integrity": "sha512-LirkamEwzIUULhXcf2D5b+NatXKeqhOwilM+5eRkbrnr6daKz9rsBL0kNZ16Hcy4b8RFq22SG4tcLwM+yx/wFA==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.8.tgz", + "integrity": "sha512-HM4Zg9CGQ3txTPflxD19n8MFPrmUAjaC7PQdLkugeeC0cQ+PiVrd7i09gaBS/11QKsTDBJhVg85CEIK9f50Qww==", "cpu": [ "arm64" ], @@ -1066,9 +1066,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.7.tgz", - "integrity": "sha512-Q4TO633kvrMQkKIV7wmf8HXwF0dhdTD9S458LGE24TYgBjSRbuhvio4D5eOQzirEYg6eqxfs53ga/rbdd8nBKg==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.8.tgz", + "integrity": "sha512-lUDQ03D7y/qEao7RgdjWVGCu+BLYadhKTm40HkpJIi6kn8LSv5PAwRlew/DmwP4YZ9ke9XXoTIQDO1vAnbRZlA==", "cpu": [ "x64" ], @@ -1083,9 +1083,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.7.tgz", - "integrity": "sha512-inHOTdlstUBzgjDcx0ge71U4SVTbwAljmkfi3MC5WzsYCRhancqfeL+sa4Ke6v2ND53WIwCFD5hGsYExoI3EZQ==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.8.tgz", + "integrity": "sha512-Uo1OJnIkJgSgF+USx970fsM/drtPcQ39I+JO+Fjsaa9ZdCN1oysQmy6oAGbyESlouz+rzEckLTF6DS7cWse95g==", "cpu": [ "arm64" ], @@ -1100,9 +1100,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.7.tgz", - "integrity": "sha512-/afy8lto4CB8scWfMdt+NoCZtatBUF62Tk3ilWH2w8ENd5spLhM77zKlFZEvsKJv9AFNHknMl03zO67CiklL2Q==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.8.tgz", + "integrity": "sha512-PShR4mM0sjksUMyxbyPNMxoKFPVF48fU8Qe8Sfx6w6F42verbwRLbz+QiKNiDPRJwUoMG1nPM50OBL3aOnTevA==", "cpu": [ "arm64" ], @@ -1117,9 +1117,9 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.7.tgz", - "integrity": "sha512-fJMc3ZEuo/NaMYo5rvoWjdSS5/uVSW+HPRQujucpZqm2ZCq71b8MKJ9U4th9yrv2L5+5NjPF0nqqILCl8HY/fg==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.8.tgz", + "integrity": "sha512-QDPMD5bQz6qOVb3kiBui0zKZXASLo0NIQ9JVJio5RveBEFgDgsvJFUvZIbMbUZT3T00M/1wdzwWXk4GIh0KaAw==", "cpu": [ "x64" ], @@ -1134,9 +1134,9 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.7.tgz", - "integrity": "sha512-CQUtgH1tIN6e5wiYSJqzSwJumHYolNtaj1dwZGCnZXm2PZU1jOJof9TsyiP3bXNDb+VOR7oo7ZvY01If0W3iFQ==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.8.tgz", + "integrity": "sha512-YGLkqU91r1276uwSjiUD/xaVikdxgV1QpsicT0bIA1TaieM6E5ibMZeSyjQ/izBn4tKQthUSsVZacmoJfa3pDA==", "cpu": [ "x64" ], @@ -1151,9 +1151,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.7.tgz", - "integrity": "sha512-aJAE8eCNyRpcfx2JJAtsPtISnELJ0H4xVVSwnxm13bzI8RwbXMyVtxy2r5DV1xT3WiSP+7LxORcApWw0LM8HiA==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.8.tgz", + "integrity": "sha512-H4IoCHvL1fXKDrTALeTKMiE7GGWFAraDwBYFquE/L/5r1927Te0mYIGseXi4F+lrrwhSWbSGt5qPFswNoBaCxg==", "cpu": [ "arm64" ], @@ -1168,9 +1168,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.7.tgz", - "integrity": "sha512-pulzUshqv9Ed//MiE8MOUeeEkbkSHVDVY5Cz5wVAnH1DUqliCQG3j6s1POaITTFqFfo7AVIx2sWdKpx/GS+Nqw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.8.tgz", + "integrity": "sha512-RguzimPoZWtBapfKhKjcWXBVI91tiSprqdBYu7tWhgN8pKRZhw24rFeNZTNf6UiBfjCYCi9eFQs/JzJZIhuK4w==", "cpu": [ "x64" ], diff --git a/frontend/package.json b/frontend/package.json index 2de787e18..281e60206 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -39,7 +39,7 @@ "vaul": "^1.1.2" }, "devDependencies": { - "@biomejs/biome": "^2.3.7", + "@biomejs/biome": "^2.3.8", "@browser-logos/chrome": "^2.0.0", "@browser-logos/firefox": "^3.0.10", "@browser-logos/safari": "^2.1.0", From eb2284ba7c134836068d60013ea84b2dbc664fbd Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 3 Dec 2025 13:42:05 +0100 Subject: [PATCH 73/86] Better lock and handle missing and modified migrations This rewrites the database migration code to: - avoid deadlocks when running multiple migration processes at the same time with a `CREATE INDEX CONCURRENTLY` statement - allow us to remove some migrations from the code base and mark them as intentionally removed - allow us to modify some migrations and declare alternate checksums for previous versions of the migration --- Cargo.lock | 2 + crates/cli/src/commands/config.rs | 7 +- crates/cli/src/commands/database.rs | 7 +- crates/cli/src/commands/server.rs | 23 +- crates/cli/src/commands/syn2mas.rs | 7 +- ...6a38f3b244c2f46444c0ab345de7feff54aba.json | 20 ++ ...c0f594332be5829d5d7c6b66183ac25b3d166.json | 20 ++ crates/storage-pg/Cargo.toml | 2 + crates/storage-pg/src/lib.rs | 298 +++++++++++++++++- 9 files changed, 346 insertions(+), 40 deletions(-) create mode 100644 crates/storage-pg/.sqlx/query-2f66991d7b9ba58f011d9aef0eb6a38f3b244c2f46444c0ab345de7feff54aba.json create mode 100644 crates/storage-pg/.sqlx/query-fbf926f630df5d588df4f1c9c0dc0f594332be5829d5d7c6b66183ac25b3d166.json diff --git a/Cargo.lock b/Cargo.lock index c1e9791eb..fd874b2c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3657,6 +3657,7 @@ version = "1.7.0" dependencies = [ "async-trait", "chrono", + "crc", "futures-util", "mas-data-model", "mas-iana", @@ -3673,6 +3674,7 @@ dependencies = [ "sha2", "sqlx", "thiserror 2.0.17", + "tokio", "tracing", "ulid", "url", diff --git a/crates/cli/src/commands/config.rs b/crates/cli/src/commands/config.rs index 034f84b4a..ae4d329ff 100644 --- a/crates/cli/src/commands/config.rs +++ b/crates/cli/src/commands/config.rs @@ -12,10 +12,9 @@ use clap::Parser; use figment::Figment; use mas_config::{ConfigurationSection, RootConfig, SyncConfig}; use mas_data_model::{Clock as _, SystemClock}; -use mas_storage_pg::MIGRATOR; use rand::SeedableRng; use tokio::io::AsyncWriteExt; -use tracing::{Instrument, info, info_span}; +use tracing::{info, info_span}; use crate::util::database_connection_from_config; @@ -129,9 +128,7 @@ impl Options { // Grab a connection to the database let mut conn = database_connection_from_config(&config.database).await?; - MIGRATOR - .run(&mut conn) - .instrument(info_span!("db.migrate")) + mas_storage_pg::migrate(&mut conn) .await .context("could not run migrations")?; diff --git a/crates/cli/src/commands/database.rs b/crates/cli/src/commands/database.rs index 519536fff..7acc6830e 100644 --- a/crates/cli/src/commands/database.rs +++ b/crates/cli/src/commands/database.rs @@ -10,8 +10,7 @@ use anyhow::Context; use clap::Parser; use figment::Figment; use mas_config::{ConfigurationSectionExt, DatabaseConfig}; -use mas_storage_pg::MIGRATOR; -use tracing::{Instrument, info_span}; +use tracing::info_span; use crate::util::database_connection_from_config; @@ -35,9 +34,7 @@ impl Options { let mut conn = database_connection_from_config(&config).await?; // Run pending migrations - MIGRATOR - .run(&mut conn) - .instrument(info_span!("db.migrate")) + mas_storage_pg::migrate(&mut conn) .await .context("could not run migrations")?; diff --git a/crates/cli/src/commands/server.rs b/crates/cli/src/commands/server.rs index d64cd26a3..b72d48111 100644 --- a/crates/cli/src/commands/server.rs +++ b/crates/cli/src/commands/server.rs @@ -4,7 +4,7 @@ // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial // Please see LICENSE files in the repository root for full details. -use std::{collections::BTreeSet, process::ExitCode, sync::Arc, time::Duration}; +use std::{process::ExitCode, sync::Arc, time::Duration}; use anyhow::Context; use clap::Parser; @@ -18,9 +18,8 @@ use mas_data_model::SystemClock; use mas_handlers::{ActivityTracker, CookieManager, Limiter, MetadataCache}; use mas_listener::server::Server; use mas_router::UrlBuilder; -use mas_storage_pg::{MIGRATOR, PgRepositoryFactory}; -use sqlx::migrate::Migrate; -use tracing::{Instrument, info, info_span, warn}; +use mas_storage_pg::PgRepositoryFactory; +use tracing::{info, info_span, warn}; use crate::{ app_state::AppState, @@ -73,24 +72,20 @@ impl Options { let pool = database_pool_from_config(&config.database).await?; if self.no_migrate { - // Check that we applied all the migrations let mut conn = pool.acquire().await?; - let applied = conn.list_applied_migrations().await?; - let applied: BTreeSet<_> = applied.into_iter().map(|m| m.version).collect(); - let has_missing_migrations = MIGRATOR.iter().any(|m| !applied.contains(&m.version)); - if has_missing_migrations { + let pending_migrations = mas_storage_pg::pending_migrations(&mut conn).await?; + if !pending_migrations.is_empty() { // Refuse to start if there are pending migrations return Err(anyhow::anyhow!( - "The server is running with `--no-migrate` but there are pending. Please run them first with `mas-cli database migrate`, or omit the `--no-migrate` flag to apply them automatically on startup." + "The server is running with `--no-migrate` but there are pending migrations. Please run them first with `mas-cli database migrate`, or omit the `--no-migrate` flag to apply them automatically on startup." )); } } else { info!("Running pending database migrations"); - MIGRATOR - .run(&pool) - .instrument(info_span!("db.migrate")) + let mut conn = pool.acquire().await?; + mas_storage_pg::migrate(&mut conn) .await - .context("could not run database migrations")?; + .context("could not run migrations")?; } let encrypter = config.secrets.encrypter().await?; diff --git a/crates/cli/src/commands/syn2mas.rs b/crates/cli/src/commands/syn2mas.rs index c28935af5..dfa835b95 100644 --- a/crates/cli/src/commands/syn2mas.rs +++ b/crates/cli/src/commands/syn2mas.rs @@ -14,13 +14,12 @@ use mas_config::{ UpstreamOAuth2Config, }; use mas_data_model::SystemClock; -use mas_storage_pg::MIGRATOR; use rand::thread_rng; use sqlx::{Connection, Either, PgConnection, postgres::PgConnectOptions, types::Uuid}; use syn2mas::{ LockedMasDatabase, MasWriter, Progress, ProgressStage, SynapseReader, synapse_config, }; -use tracing::{Instrument, error, info, info_span}; +use tracing::{Instrument, error, info}; use crate::util::{DatabaseConnectOptions, database_connection_from_config_with_options}; @@ -122,9 +121,7 @@ impl Options { ) .await?; - MIGRATOR - .run(&mut mas_connection) - .instrument(info_span!("db.migrate")) + mas_storage_pg::migrate(&mut mas_connection) .await .context("could not run migrations")?; diff --git a/crates/storage-pg/.sqlx/query-2f66991d7b9ba58f011d9aef0eb6a38f3b244c2f46444c0ab345de7feff54aba.json b/crates/storage-pg/.sqlx/query-2f66991d7b9ba58f011d9aef0eb6a38f3b244c2f46444c0ab345de7feff54aba.json new file mode 100644 index 000000000..7fb8be867 --- /dev/null +++ b/crates/storage-pg/.sqlx/query-2f66991d7b9ba58f011d9aef0eb6a38f3b244c2f46444c0ab345de7feff54aba.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT current_database() as \"current_database!\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "current_database!", + "type_info": "Name" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "2f66991d7b9ba58f011d9aef0eb6a38f3b244c2f46444c0ab345de7feff54aba" +} diff --git a/crates/storage-pg/.sqlx/query-fbf926f630df5d588df4f1c9c0dc0f594332be5829d5d7c6b66183ac25b3d166.json b/crates/storage-pg/.sqlx/query-fbf926f630df5d588df4f1c9c0dc0f594332be5829d5d7c6b66183ac25b3d166.json new file mode 100644 index 000000000..d41b1dccd --- /dev/null +++ b/crates/storage-pg/.sqlx/query-fbf926f630df5d588df4f1c9c0dc0f594332be5829d5d7c6b66183ac25b3d166.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT EXISTS (\n SELECT 1\n FROM information_schema.tables\n WHERE table_name = '_sqlx_migrations'\n ) AS \"exists!\"\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists!", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "fbf926f630df5d588df4f1c9c0dc0f594332be5829d5d7c6b66183ac25b3d166" +} diff --git a/crates/storage-pg/Cargo.toml b/crates/storage-pg/Cargo.toml index 8710ead70..c058c3c14 100644 --- a/crates/storage-pg/Cargo.toml +++ b/crates/storage-pg/Cargo.toml @@ -19,6 +19,7 @@ workspace = true [dependencies] async-trait.workspace = true chrono.workspace = true +crc.workspace = true futures-util.workspace = true opentelemetry-semantic-conventions.workspace = true opentelemetry.workspace = true @@ -31,6 +32,7 @@ sha2.workspace = true sqlx.workspace = true thiserror.workspace = true tracing.workspace = true +tokio.workspace = true ulid.workspace = true url.workspace = true uuid.workspace = true diff --git a/crates/storage-pg/src/lib.rs b/crates/storage-pg/src/lib.rs index 207235667..2aada4975 100644 --- a/crates/storage-pg/src/lib.rs +++ b/crates/storage-pg/src/lib.rs @@ -160,7 +160,15 @@ #![deny(clippy::future_not_send, missing_docs)] #![allow(clippy::module_name_repetitions, clippy::blocks_in_conditions)] -use sqlx::migrate::Migrator; +use std::collections::{BTreeMap, BTreeSet, HashSet}; + +use ::tracing::{Instrument, debug, info, info_span, warn}; +use opentelemetry_semantic_conventions::trace::DB_QUERY_TEXT; +use sqlx::{ + Either, PgConnection, + migrate::{AppliedMigration, Migrate, MigrateError, Migration, Migrator}, + postgres::{PgAdvisoryLock, PgAdvisoryLockKey}, +}; pub mod app_session; pub mod compat; @@ -186,14 +194,282 @@ pub use self::{ tracing::ExecuteExt, }; -/// Embedded migrations, allowing them to run on startup -pub static MIGRATOR: Migrator = { - // XXX: The macro does not let us ignore missing migrations, so we have to do it - // like this. See https://github.com/launchbadge/sqlx/issues/1788 - let mut m = sqlx::migrate!(); +/// Embedded migrations in the binary +pub static MIGRATOR: Migrator = sqlx::migrate!(); - // We manually removed some migrations because they made us depend on the - // `pgcrypto` extension. See: https://github.com/matrix-org/matrix-authentication-service/issues/1557 - m.ignore_missing = true; - m -}; +fn available_migrations() -> BTreeMap { + MIGRATOR.iter().map(|m| (m.version, m)).collect() +} + +/// This is the list of migrations we've removed from the migration history but +/// might have been applied in the past +#[allow(clippy::inconsistent_digit_grouping)] +const ALLOWED_MISSING_MIGRATIONS: &[i64] = &[ + // https://github.com/matrix-org/matrix-authentication-service/pull/1585 + 20220709_210445, + 20230330_210841, + 20230408_110421, +]; + +fn allowed_missing_migrations() -> BTreeSet { + ALLOWED_MISSING_MIGRATIONS.iter().copied().collect() +} + +/// This is a list of possible additional checksums from previous versions of +/// migrations. The checksum we store in the database is 48 bytes long. We're +/// not really concerned with partial hash collisions, and to avoid this file to +/// be completely unreadable, we only store the upper 16 bytes of that hash. +#[allow(clippy::inconsistent_digit_grouping)] +const ALLOWED_ALTERNATE_CHECKSUMS: &[(i64, u128)] = &[ + // https://github.com/element-hq/matrix-authentication-service/pull/5300 + (20250410_000000, 0x8811_c3ef_dbee_8c00_5b49_25da_5d55_9c3f), + (20250410_000001, 0x7990_37b3_2193_8a5d_c72f_bccd_95fd_82e5), + (20250410_000002, 0xf2b8_f120_deae_27e7_60d0_79a3_0b77_eea3), + (20250410_000003, 0x06be_fc2b_cedc_acf4_b981_02c7_b40c_c469), + (20250410_000004, 0x0a90_9c6a_dba7_545c_10d9_60eb_6d30_2f50), + (20250410_000006, 0xcc7f_5152_6497_5729_d94b_be0d_9c95_8316), + (20250410_000007, 0x12e7_cfab_a017_a5a5_4f2c_18fa_541c_ce62), + (20250410_000008, 0x171d_62e5_ee1a_f0d9_3639_6c5a_277c_54cd), + (20250410_000009, 0xb1a0_93c7_6645_92ad_df45_b395_57bb_a281), + (20250410_000010, 0x8089_86ac_7cff_8d86_2850_d287_cdb1_2b57), + (20250410_000011, 0x8d9d_3fae_02c9_3d3f_81e4_6242_2b39_b5b8), + (20250410_000012, 0x9805_1372_41aa_d5b0_ebe1_ba9d_28c7_faf6), + (20250410_000013, 0x7291_9a97_e4d1_0d45_1791_6e8c_3f2d_e34d), + (20250410_000014, 0x811d_f965_8127_e168_4aa2_f177_a4e6_f077), + (20250410_000015, 0xa639_0780_aab7_d60d_5fcb_771d_13ed_73ee), + (20250410_000016, 0x22b6_e909_6de4_39e3_b2b9_c684_7417_fe07), + (20250410_000017, 0x9dfe_b6d3_89e4_e509_651b_2793_8d8d_cd32), + (20250410_000018, 0x638f_bdbc_2276_5094_020b_cec1_ab95_c07f), + (20250410_000019, 0xa283_84bc_5fd5_7cbd_b5fb_b5fe_0255_6845), + (20250410_000020, 0x17d1_54b1_7c6e_fc48_61dd_da3d_f8a5_9546), + (20250410_000022, 0xbc36_af82_994a_6f93_8aca_a46b_fc3c_ffde), + (20250410_000023, 0x54ec_3b07_ac79_443b_9e18_a2b3_2d17_5ab9), + (20250410_000024, 0x8ab4_4f80_00b6_58b2_d757_c40f_bc72_3d87), + (20250410_000025, 0x5dc4_2ff3_3042_2f45_046d_10af_ab3a_b583), + (20250410_000026, 0x5263_c547_0b64_6425_5729_48b2_ce84_7cad), + (20250410_000027, 0x0aad_cb50_1d6a_7794_9017_d24d_55e7_1b9d), + (20250410_000028, 0x8fc1_92f8_68df_ca4e_3e2b_cddf_bc12_cffe), + (20250410_000029, 0x416c_9446_b6a3_1b49_2940_a8ac_c1c2_665a), + (20250410_000030, 0x83a5_e51e_25a6_77fb_2b79_6ea5_db1e_364f), + (20250410_000031, 0xfa18_a707_9438_dbc7_2cde_b5f1_ee21_5c7e), + (20250410_000032, 0xd669_662e_8930_838a_b142_c3fa_7b39_d2a0), + (20250410_000033, 0x4019_1053_cabc_191c_c02e_9aa9_407c_0de5), + (20250410_000034, 0xdd59_e595_24e6_4dad_c5f7_fef2_90b8_df57), + (20250410_000035, 0x09b4_ea53_2da4_9c39_eb10_db33_6a6d_608b), + (20250410_000036, 0x3ca5_9c78_8480_e342_d729_907c_d293_2049), + (20250410_000037, 0xc857_2a10_450b_0612_822c_2b86_535a_ea7d), + (20250410_000038, 0x1642_39da_9c3b_d9fd_b1e1_72b1_db78_b978), + (20250410_000039, 0xdd70_b211_6016_bb84_0d84_f04e_eb8a_59d9), + (20250410_000040, 0xe435_ead6_c363_a0b6_e048_dd85_0ecb_9499), + (20250410_000041, 0xe9f3_122f_70d4_9839_c818_4b18_0192_ae26), + (20250410_000043, 0xec5e_1400_483d_c4bf_6014_aba4_ffc3_6236), + (20250410_000044, 0x4750_5eba_4095_6664_78d0_27f9_64bf_64f4), + (20250410_000045, 0x9a53_bd70_4cad_2bf1_61d4_f143_0c82_681d), + (20250410_121612, 0x25f0_9d20_a897_df18_162d_1c47_b68e_81bd), + (20250602_212101, 0xd1a8_782c_b3f0_5045_3f46_49a0_bab0_822b), + (20250708_155857, 0xb78e_6957_a588_c16a_d292_a0c7_cae9_f290), + (20250915_092635, 0x6854_d58b_99d7_3ac5_82f8_25e5_b1c3_cc0b), + (20251127_145951, 0x3bcd_d92e_8391_2a2c_8a18_1d76_354f_96c6), +]; + +fn alternate_checksums_map() -> BTreeMap> { + let mut map = BTreeMap::new(); + for (version, checksum) in ALLOWED_ALTERNATE_CHECKSUMS { + map.entry(*version) + .or_insert_with(HashSet::new) + .insert(*checksum); + } + map +} + +/// Load the list of applied migrations into a map. +/// +/// It's important to use a [`BTreeMap`] so that the migrations are naturally +/// ordered by version. +async fn applied_migrations_map( + conn: &mut PgConnection, +) -> Result, MigrateError> { + let applied_migrations = conn + .list_applied_migrations() + .await? + .into_iter() + .map(|m| (m.version, m)) + .collect(); + + Ok(applied_migrations) +} + +/// Checks if the migration table exists +async fn migration_table_exists(conn: &mut PgConnection) -> Result { + sqlx::query_scalar!( + r#" + SELECT EXISTS ( + SELECT 1 + FROM information_schema.tables + WHERE table_name = '_sqlx_migrations' + ) AS "exists!" + "#, + ) + .fetch_one(conn) + .await +} + +/// Run the migrations on the given connection +/// +/// This function acquires an advisory lock on the database to ensure that only +/// one migrator is running at a time. +/// +/// # Errors +/// +/// This function returns an error if the migration fails. +#[::tracing::instrument(name = "db.migrate", skip_all, err)] +pub async fn migrate(conn: &mut PgConnection) -> Result<(), MigrateError> { + // Get the database name and use it to derivate an advisory lock key. This + // is the same lock key used by SQLx default migrator, so that it works even + // with older versions of MAS, and when running through `cargo sqlx migrate run` + let database_name = sqlx::query_scalar!(r#"SELECT current_database() as "current_database!""#) + .fetch_one(&mut *conn) + .await + .map_err(MigrateError::from)?; + + let lock = + PgAdvisoryLock::with_key(PgAdvisoryLockKey::BigInt(generate_lock_id(&database_name))); + + // Try to acquire the migration lock in a loop. + // + // The reason we do that with a `try_acquire` is because in Postgres, `CREATE + // INDEX CONCURRENTLY` will *not* complete whilst an advisory lock is being + // acquired on another connection. This then means that if we run two + // migration process at the same time, one of them will go through and block + // on concurrent index creations, because the other will get stuck trying to + // acquire this lock. + // + // To avoid this, we use `try_acquire`/`pg_advisory_lock_try` in a loop, which + // will fail immediately if the lock is held by another connection, allowing + // potential 'CREATE INDEX CONCURRENTLY' statements to complete. + let mut backoff = std::time::Duration::from_millis(250); + let mut conn = conn; + let mut locked_connection = loop { + match lock.try_acquire(conn).await? { + Either::Left(guard) => break guard, + Either::Right(conn_) => { + warn!( + "Another process is already running migrations on the database, waiting {duration}s and trying again…", + duration = backoff.as_secs_f32() + ); + tokio::time::sleep(backoff).await; + backoff = std::cmp::min(backoff * 2, std::time::Duration::from_secs(5)); + conn = conn_; + } + } + }; + + // Creates the migration table if missing + // We check if the table exists before calling `ensure_migrations_table` to + // avoid the pesky 'relation "_sqlx_migrations" already exists, skipping' notice + if !migration_table_exists(locked_connection.as_mut()).await? { + locked_connection.as_mut().ensure_migrations_table().await?; + } + + for migration in pending_migrations(locked_connection.as_mut()).await? { + info!( + "Applying migration {version}: {description}", + version = migration.version, + description = migration.description + ); + locked_connection + .as_mut() + .apply(migration) + .instrument(info_span!( + "db.migrate.run_migration", + db.migration.version = migration.version, + db.migration.description = &*migration.description, + { DB_QUERY_TEXT } = &*migration.sql, + )) + .await?; + } + + locked_connection.release_now().await?; + + Ok(()) +} + +/// Get the list of pending migrations +/// +/// # Errors +/// +/// This function returns an error if there is a problem checking the applied +/// migrations +pub async fn pending_migrations( + conn: &mut PgConnection, +) -> Result, MigrateError> { + // Load the maps of available migrations, applied migrations, migrations that + // are allowed to be missing, alternate checksums for migrations that changed + let available_migrations = available_migrations(); + let allowed_missing = allowed_missing_migrations(); + let alternate_checksums = alternate_checksums_map(); + let applied_migrations = if migration_table_exists(&mut *conn).await? { + applied_migrations_map(&mut *conn).await? + } else { + BTreeMap::new() + }; + + // Check that all applied migrations are still valid + for applied_migration in applied_migrations.values() { + // Check that we know about the applied migration + if let Some(migration) = available_migrations.get(&applied_migration.version) { + // Check the migration checksum + if applied_migration.checksum != migration.checksum { + // The checksum we have in the database doesn't match the one we + // have embedded. This might be because a migration was + // intentionally changed, so we check the alternate checksums + if let Some(alternates) = alternate_checksums.get(&applied_migration.version) { + // This converts the first 16 bytes of the checksum into a u128 + let Some(applied_checksum_prefix) = applied_migration + .checksum + .get(..16) + .and_then(|bytes| bytes.try_into().ok()) + .map(u128::from_be_bytes) + else { + return Err(MigrateError::VersionMismatch(applied_migration.version)); + }; + + if !alternates.contains(&applied_checksum_prefix) { + warn!( + "The database has a migration applied ({version}) which has known alternative checksums {alternates:x?}, but none of them matched {applied_checksum_prefix:x}", + version = applied_migration.version, + ); + return Err(MigrateError::VersionMismatch(applied_migration.version)); + } + } else { + return Err(MigrateError::VersionMismatch(applied_migration.version)); + } + } + } else if allowed_missing.contains(&applied_migration.version) { + // The migration is missing, but allowed to be missing + debug!( + "The database has a migration applied ({version}) that doesn't exist anymore, but it was intentionally removed", + version = applied_migration.version + ); + } else { + // The migration is missing, and not allowed to be missing + return Err(MigrateError::VersionMissing(applied_migration.version)); + } + } + + Ok(available_migrations + .values() + .copied() + .filter(|migration| { + !migration.migration_type.is_down_migration() + && !applied_migrations.contains_key(&migration.version) + }) + .collect()) +} + +// Copied from the sqlx source code, so that we generate the same lock ID +fn generate_lock_id(database_name: &str) -> i64 { + const CRC_IEEE: crc::Crc = crc::Crc::::new(&crc::CRC_32_ISO_HDLC); + // 0x3d32ad9e chosen by fair dice roll + 0x3d32_ad9e * i64::from(CRC_IEEE.checksum(database_name.as_bytes())) +} From 368cd4541dcdd92c52b8ee7d3cfede8cc20cfbdd Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Thu, 4 Dec 2025 15:13:42 +0100 Subject: [PATCH 74/86] Don't error out if a migration is missing This allows us to roll back to older versions of MAS. --- crates/storage-pg/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/storage-pg/src/lib.rs b/crates/storage-pg/src/lib.rs index 2aada4975..fa3a885ab 100644 --- a/crates/storage-pg/src/lib.rs +++ b/crates/storage-pg/src/lib.rs @@ -452,8 +452,11 @@ pub async fn pending_migrations( version = applied_migration.version ); } else { - // The migration is missing, and not allowed to be missing - return Err(MigrateError::VersionMissing(applied_migration.version)); + // The migration is missing, warn about it + warn!( + "The database has a migration applied ({version}) that doesn't exist anymore! This should not happen, unless rolling back to an older version of MAS.", + version = applied_migration.version + ); } } From b99722f42a20eb92cffd5f1adacbe55087297767 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Thu, 4 Dec 2025 15:14:14 +0100 Subject: [PATCH 75/86] Better error message when checksum in database is invalid --- crates/storage-pg/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/storage-pg/src/lib.rs b/crates/storage-pg/src/lib.rs index fa3a885ab..b133e83a4 100644 --- a/crates/storage-pg/src/lib.rs +++ b/crates/storage-pg/src/lib.rs @@ -431,7 +431,12 @@ pub async fn pending_migrations( .and_then(|bytes| bytes.try_into().ok()) .map(u128::from_be_bytes) else { - return Err(MigrateError::VersionMismatch(applied_migration.version)); + return Err(MigrateError::ExecuteMigration( + sqlx::Error::InvalidArgument( + "checksum stored in database is invalid".to_owned(), + ), + applied_migration.version, + )); }; if !alternates.contains(&applied_checksum_prefix) { From 6c5bba19f9a2c6e21f47a306822ce60df900c37d Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Thu, 4 Dec 2025 15:14:33 +0100 Subject: [PATCH 76/86] Developer documentation for modifying or removing database migrations --- docs/development/database.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/development/database.md b/docs/development/database.md index 5ffe8a5a8..91bd7f9fa 100644 --- a/docs/development/database.md +++ b/docs/development/database.md @@ -40,7 +40,7 @@ cargo sqlx prepare ## Migrations -Migration files live in the `migrations` folder in the `mas-core` crate. +Migration files live in the `migrations` folder in the `mas-storage-pg` crate. ```sh cd crates/storage-pg/ # Again, in the mas-storage-pg crate folder @@ -50,3 +50,29 @@ cargo sqlx migrate add [description] # Add new migration files ``` Note that migrations are embedded in the final binary and can be run from the service CLI tool. + +### Removing migrations + +For various reason, we may want to delete migrations. +In case we do, we *must* declare that migration version as it is fine to be missing. +This is because on startup, MAS will validate that all the applied migrations are known, and warn if some are missing. + +To do so, get the migration version and add it to the `ALLOWED_MISSING_MIGRATIONS` array in the `mas-storage-pg` crate. + +### Modifying existing migrations + +We may want to modify existing migrations to fix mistakes. +In case we do, we *must* save the hash of the original migration file so that MAS can validate it on startup. + +To do so, extract the first 16 bytes of the existing applied migration and append it to the `ALLOWED_ALTERNATE_CHECKSUMS` array in the `mas-storage-pg` crate. + +```sql +SELECT version, ENCODE(SUBSTRING(checksum FOR 16), 'hex') AS short_checksum +FROM _sqlx_migrations +WHERE version = 20250410000002; +``` +``` + version | short_checksum +----------------+---------------------------------- + 20250410000002 | f2b8f120deae27e760d079a30b77eea3 +``` From cca2015920f20f7a8f65db95e7024990c766f265 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Thu, 4 Dec 2025 16:32:13 +0100 Subject: [PATCH 77/86] Fix typos Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/storage-pg/src/lib.rs | 2 +- docs/development/database.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/storage-pg/src/lib.rs b/crates/storage-pg/src/lib.rs index b133e83a4..ea2029475 100644 --- a/crates/storage-pg/src/lib.rs +++ b/crates/storage-pg/src/lib.rs @@ -324,7 +324,7 @@ async fn migration_table_exists(conn: &mut PgConnection) -> Result Result<(), MigrateError> { - // Get the database name and use it to derivate an advisory lock key. This + // Get the database name and use it to derive an advisory lock key. This // is the same lock key used by SQLx default migrator, so that it works even // with older versions of MAS, and when running through `cargo sqlx migrate run` let database_name = sqlx::query_scalar!(r#"SELECT current_database() as "current_database!""#) diff --git a/docs/development/database.md b/docs/development/database.md index 91bd7f9fa..43c2b33fe 100644 --- a/docs/development/database.md +++ b/docs/development/database.md @@ -53,7 +53,7 @@ Note that migrations are embedded in the final binary and can be run from the se ### Removing migrations -For various reason, we may want to delete migrations. +For various reasons, we may want to delete migrations. In case we do, we *must* declare that migration version as it is fine to be missing. This is because on startup, MAS will validate that all the applied migrations are known, and warn if some are missing. From d6b701f93de621fcc7c62b9cab3226a2d7b44594 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 18:18:41 +0000 Subject: [PATCH 78/86] Translations updates --- frontend/.storybook/locales.ts | 66 +++++++++++++++++----------------- frontend/locales/et.json | 4 +-- frontend/locales/uk.json | 4 +-- translations/et.json | 14 ++++++-- translations/fr.json | 2 +- translations/uk.json | 14 ++++++-- 6 files changed, 60 insertions(+), 44 deletions(-) diff --git a/frontend/.storybook/locales.ts b/frontend/.storybook/locales.ts index 9a545003e..5b1fd2477 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", + projectUrl: "https://localazy.com/p/matrix-authentication-service!v1.8", baseLocale: "en", languages: [ { @@ -181,22 +181,22 @@ const localazyMetadata: LocalazyMetadata = { file: "frontend.json", path: "", cdnFiles: { - "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", - "ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json", - "sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json", - "uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json", - "zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json" + "cs": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/cs/frontend.json", + "da": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/da/frontend.json", + "de": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/de/frontend.json", + "en": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/en/frontend.json", + "et": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/et/frontend.json", + "fi": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fi/frontend.json", + "fr": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fr/frontend.json", + "hu": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/hu/frontend.json", + "nb_NO": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nb-NO/frontend.json", + "nl": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nl/frontend.json", + "pl": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pl/frontend.json", + "pt": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt/frontend.json", + "ru": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json", + "sv": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json", + "uk": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json", + "zh#Hans": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json" } }, { @@ -204,22 +204,22 @@ const localazyMetadata: LocalazyMetadata = { file: "file.json", path: "", cdnFiles: { - "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", - "ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json", - "sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json", - "uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json", - "zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json" + "cs": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/cs/file.json", + "da": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/da/file.json", + "de": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/de/file.json", + "en": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/en/file.json", + "et": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/et/file.json", + "fi": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fi/file.json", + "fr": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fr/file.json", + "hu": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/hu/file.json", + "nb_NO": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nb-NO/file.json", + "nl": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nl/file.json", + "pl": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pl/file.json", + "pt": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt/file.json", + "ru": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json", + "sv": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json", + "uk": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json", + "zh#Hans": "https://delivery.localazy.com/_a6714272761432419575f0da7e87/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json" } } ] diff --git a/frontend/locales/et.json b/frontend/locales/et.json index 086c6ba27..e9f90b09a 100644 --- a/frontend/locales/et.json +++ b/frontend/locales/et.json @@ -391,9 +391,9 @@ "scope": { "edit_profile": "Muuta sinu kasutajaprofiili ning kontaktandmeid", "manage_sessions": "Hallata sinu seadmeid ja sessioone", - "mas_admin": "Hallata iga kasutajat teenuses matrix-authentication-service", + "mas_admin": "Hallata kasutajaid (urn:mas:admin)", "send_messages": "Saata sõnumeid sinu nimel", - "synapse_admin": "Hallata seda Synapse koduserverit", + "synapse_admin": "Hallata seda Synapse koduserverit (urn:synapse:admin:*)", "view_messages": "Vaadata sinu sõnumeid ja andmeid", "view_profile": "Vaadata sinu profiili teavet ja kontaktadmeid" } diff --git a/frontend/locales/uk.json b/frontend/locales/uk.json index e15d27af9..41c945312 100644 --- a/frontend/locales/uk.json +++ b/frontend/locales/uk.json @@ -394,9 +394,9 @@ "scope": { "edit_profile": "Редагування профілю та контактних даних", "manage_sessions": "Керування пристроями та сеансами", - "mas_admin": "Адміністрування будь-якого користувача на matrix-authentication-service", + "mas_admin": "Керування користувачами (urn:mas:admin)", "send_messages": "Надсилати нові повідомлення від вашого імені", - "synapse_admin": "Адміністрування домашнього сервера Synapse", + "synapse_admin": "Адмініструвати сервер (urn:synapse:admin:*)", "view_messages": "Перегляд наявних повідомлень і даних", "view_profile": "Перегляд інформації профілю та контактних даних" } diff --git a/translations/et.json b/translations/et.json index 50f3550e7..66f957dac 100644 --- a/translations/et.json +++ b/translations/et.json @@ -74,9 +74,13 @@ }, "consent": { "client_wants_access": "%(client_name)s aadressil %(redirect_uri)s soovib ligipääsu sinu kasutajakontole.", + "continue_to": "Kas jätkad kliendis %(client_name)s?", "heading": "Kas lubad ligipääsu sinu kasutajakontole?", "make_sure_you_trust": "Palun kontrolli, et %(client_name)s on sinu jaoks usaldusväärne teenus.", + "scope_list_preface": "Jätkates lubad sa %(client_name)s kliendil:", "this_will_allow": "Sellega %(client_name)s saab õigused:", + "this_will_setup": "Sellega seadistad %(client_name)s (%(client_uri)s) kliendi kasutama sinu %(server_name)s kontot.", + "use_another_account": "Kasuta teist kontot", "you_may_be_sharing": "Sa tõenäoliselt jagad privaatset teavet selle veebisaidi või rakendusega." }, "device_card": { @@ -98,7 +102,8 @@ "granted": { "description": "Sa lubasid seadmele %(client_name)s ligipääsu. Sa võid nüüd selle akna sulgeda.", "heading": "Ligipääs on lubatud" - } + }, + "this_will_setup": "Üks teine seade tahab seadistada %(client_name)s (%(client_uri)s) klienti kasutama sinu %(server_name)s kontot. Palun kontrolli, et see on õige ja sinule vajalik seade." }, "device_display_name": { "client_on_device": "%(client_name)s seadmes %(device_name)s", @@ -145,6 +150,9 @@ "username_too_long": "Kasutajanimi on liiga pikk", "username_too_short": "Kasutajanimi on liiga lühike" }, + "legacy_consent": { + "this_will_setup": "Sellega seadistad %(client_name)s kliendi kasutama oma %(server_name)s kontot." + }, "login": { "call_to_register": "Sul veel pole kasutajakontot?", "continue_with_provider": "Jätka teenusepakkujaga %(provider)s", @@ -226,9 +234,9 @@ "scope": { "edit_profile": "Muuta sinu kasutajaprofiili ning kontaktandmeid", "manage_sessions": "Hallata sinu seadmeid ja sessioone", - "mas_admin": "Hallata iga kasutajat teenuses matrix-authentication-service", + "mas_admin": "Hallata kasutajaid (urn:mas:admin)", "send_messages": "Saata sõnumeid sinu nimel", - "synapse_admin": "Hallata seda Synapse koduserverit", + "synapse_admin": "Hallata seda Synapse koduserverit (urn:synapse:admin:*)", "view_messages": "Vaadata sinu sõnumeid ja andmeid", "view_profile": "Vaadata sinu profiili teavet ja kontaktadmeid" }, diff --git a/translations/fr.json b/translations/fr.json index 9678dcc80..e084bb929 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -103,7 +103,7 @@ "description": "Vous avez accordé l'accès à %(client_name)s. Vous pouvez fermer cette fenêtre.", "heading": "Accès accordé" }, - "this_will_setup": "Un autre appareil souhaite connecter %(client_name)s (%(client_uri)s) avec votre compte %(server_name)s. Assurez-vous de reconnaître cet appareil." + "this_will_setup": "Un autre appareil souhaite connecter %(client_name)s (%(client_uri)s) avec votre compte %(server_name)s. Assurez-vous de reconnaître cet appareil." }, "device_display_name": { "client_on_device": "%(client_name)s sur %(device_name)s", diff --git a/translations/uk.json b/translations/uk.json index 0aab7bf97..941d1ee27 100644 --- a/translations/uk.json +++ b/translations/uk.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": "Це налаштує %(client_name)s (%(client_uri)s) з вашим обліковим записом %(server_name)s.", + "use_another_account": "Використати інший обліковий запис", "you_may_be_sharing": "Можливо, ви ділитеся конфіденційною інформацією з цим сайтом або застосунком." }, "device_card": { @@ -98,7 +102,8 @@ "granted": { "description": "Ви надали доступ до %(client_name)s. Ви можете закрити це вікно.", "heading": "Доступ надано" - } + }, + "this_will_setup": "Інший пристрій хоче налаштувати %(client_name)s (%(client_uri)s) з вашим обліковим записом %(server_name)s. Переконайтеся, що ви розпізнаєте цей пристрій." }, "device_display_name": { "client_on_device": "%(client_name)s на %(device_name)s", @@ -145,6 +150,9 @@ "username_too_long": "Ім'я користувача задовге", "username_too_short": "Ім'я користувача закоротке" }, + "legacy_consent": { + "this_will_setup": "Це налаштує %(client_name)s з вашим обліковим записом %(server_name)s." + }, "login": { "call_to_register": "У вас ще немає облікового запису?", "continue_with_provider": "Продовжити з %(provider)s", @@ -226,9 +234,9 @@ "scope": { "edit_profile": "Редагування профілю та контактних даних", "manage_sessions": "Керування пристроями та сеансами", - "mas_admin": "Адміністрування будь-якого користувача на matrix-authentication-service", + "mas_admin": "Керування користувачами (urn:mas:admin)", "send_messages": "Надсилати нові повідомлення від вашого імені", - "synapse_admin": "Адміністрування домашнього сервера Synapse", + "synapse_admin": "Адмініструвати сервер (urn:synapse:admin:*)", "view_messages": "Перегляд наявних повідомлень і даних", "view_profile": "Перегляд інформації профілю та контактних даних" }, From 44ca219aaa54f0d40cdc039a599fba995a7c2fb7 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Tue, 9 Dec 2025 12:14:19 -0700 Subject: [PATCH 79/86] Ignore cargo-deny unmaintained package for now --- deny.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deny.toml b/deny.toml index 1671119ca..8fbbe5220 100644 --- a/deny.toml +++ b/deny.toml @@ -19,6 +19,9 @@ ignore = [ # RSA key extraction "Marvin Attack". This is only relevant when using # PKCS#1 v1.5 encryption, which we don't "RUSTSEC-2023-0071", + # This is a newly unmaintained package that we can allow temporarily. + # Remove ASAP once https://github.com/element-hq/matrix-authentication-service/issues/5337 is fixed. + "RUSTSEC-2025-0134", ] [licenses] From 3f63fbc1ebf8f7389b9841d8d28087f95eb37122 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 19:25:29 +0000 Subject: [PATCH 80/86] 1.8.0 --- Cargo.lock | 56 +++++++++++++++++++++++++------------------------- Cargo.toml | 60 +++++++++++++++++++++++++++--------------------------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6e9d36327..421938132 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3097,7 +3097,7 @@ dependencies = [ [[package]] name = "mas-axum-utils" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "anyhow", "axum", @@ -3131,7 +3131,7 @@ dependencies = [ [[package]] name = "mas-cli" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "anyhow", "axum", @@ -3204,7 +3204,7 @@ dependencies = [ [[package]] name = "mas-config" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "anyhow", "camino", @@ -3236,7 +3236,7 @@ dependencies = [ [[package]] name = "mas-context" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "console", "opentelemetry", @@ -3252,7 +3252,7 @@ dependencies = [ [[package]] name = "mas-data-model" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "base64ct", "chrono", @@ -3275,7 +3275,7 @@ dependencies = [ [[package]] name = "mas-email" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "async-trait", "lettre", @@ -3286,7 +3286,7 @@ dependencies = [ [[package]] name = "mas-handlers" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "aide", "anyhow", @@ -3366,7 +3366,7 @@ dependencies = [ [[package]] name = "mas-http" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "futures-util", "headers", @@ -3387,7 +3387,7 @@ dependencies = [ [[package]] name = "mas-i18n" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "camino", "icu_calendar", @@ -3409,7 +3409,7 @@ dependencies = [ [[package]] name = "mas-i18n-scan" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "camino", "clap", @@ -3423,7 +3423,7 @@ dependencies = [ [[package]] name = "mas-iana" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "schemars 0.9.0", "serde", @@ -3431,7 +3431,7 @@ dependencies = [ [[package]] name = "mas-iana-codegen" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "anyhow", "async-trait", @@ -3448,7 +3448,7 @@ dependencies = [ [[package]] name = "mas-jose" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "base64ct", "chrono", @@ -3478,7 +3478,7 @@ dependencies = [ [[package]] name = "mas-keystore" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "aead", "base64ct", @@ -3506,7 +3506,7 @@ dependencies = [ [[package]] name = "mas-listener" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "anyhow", "bytes", @@ -3531,7 +3531,7 @@ dependencies = [ [[package]] name = "mas-matrix" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "anyhow", "async-trait", @@ -3541,7 +3541,7 @@ dependencies = [ [[package]] name = "mas-matrix-synapse" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "anyhow", "async-trait", @@ -3558,7 +3558,7 @@ dependencies = [ [[package]] name = "mas-oidc-client" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "assert_matches", "async-trait", @@ -3594,7 +3594,7 @@ dependencies = [ [[package]] name = "mas-policy" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "anyhow", "arc-swap", @@ -3611,7 +3611,7 @@ dependencies = [ [[package]] name = "mas-router" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "axum", "serde", @@ -3622,7 +3622,7 @@ dependencies = [ [[package]] name = "mas-spa" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "camino", "serde", @@ -3631,7 +3631,7 @@ dependencies = [ [[package]] name = "mas-storage" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "async-trait", "chrono", @@ -3653,7 +3653,7 @@ dependencies = [ [[package]] name = "mas-storage-pg" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "async-trait", "chrono", @@ -3681,7 +3681,7 @@ dependencies = [ [[package]] name = "mas-tasks" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "anyhow", "async-trait", @@ -3713,7 +3713,7 @@ dependencies = [ [[package]] name = "mas-templates" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "anyhow", "arc-swap", @@ -3745,7 +3745,7 @@ dependencies = [ [[package]] name = "mas-tower" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "http", "opentelemetry", @@ -4015,7 +4015,7 @@ dependencies = [ [[package]] name = "oauth2-types" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "assert_matches", "base64ct", @@ -6086,7 +6086,7 @@ dependencies = [ [[package]] name = "syn2mas" -version = "1.8.0-rc.0" +version = "1.8.0" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index b49f4546e..7b1aba0d4 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.8.0-rc.0" +package.version = "1.8.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.8.0-rc.0" } -mas-cli = { path = "./crates/cli/", version = "=1.8.0-rc.0" } -mas-config = { path = "./crates/config/", version = "=1.8.0-rc.0" } -mas-context = { path = "./crates/context/", version = "=1.8.0-rc.0" } -mas-data-model = { path = "./crates/data-model/", version = "=1.8.0-rc.0" } -mas-email = { path = "./crates/email/", version = "=1.8.0-rc.0" } -mas-graphql = { path = "./crates/graphql/", version = "=1.8.0-rc.0" } -mas-handlers = { path = "./crates/handlers/", version = "=1.8.0-rc.0" } -mas-http = { path = "./crates/http/", version = "=1.8.0-rc.0" } -mas-i18n = { path = "./crates/i18n/", version = "=1.8.0-rc.0" } -mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.8.0-rc.0" } -mas-iana = { path = "./crates/iana/", version = "=1.8.0-rc.0" } -mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.8.0-rc.0" } -mas-jose = { path = "./crates/jose/", version = "=1.8.0-rc.0" } -mas-keystore = { path = "./crates/keystore/", version = "=1.8.0-rc.0" } -mas-listener = { path = "./crates/listener/", version = "=1.8.0-rc.0" } -mas-matrix = { path = "./crates/matrix/", version = "=1.8.0-rc.0" } -mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.8.0-rc.0" } -mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.8.0-rc.0" } -mas-policy = { path = "./crates/policy/", version = "=1.8.0-rc.0" } -mas-router = { path = "./crates/router/", version = "=1.8.0-rc.0" } -mas-spa = { path = "./crates/spa/", version = "=1.8.0-rc.0" } -mas-storage = { path = "./crates/storage/", version = "=1.8.0-rc.0" } -mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.8.0-rc.0" } -mas-tasks = { path = "./crates/tasks/", version = "=1.8.0-rc.0" } -mas-templates = { path = "./crates/templates/", version = "=1.8.0-rc.0" } -mas-tower = { path = "./crates/tower/", version = "=1.8.0-rc.0" } -oauth2-types = { path = "./crates/oauth2-types/", version = "=1.8.0-rc.0" } -syn2mas = { path = "./crates/syn2mas", version = "=1.8.0-rc.0" } +mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.8.0" } +mas-cli = { path = "./crates/cli/", version = "=1.8.0" } +mas-config = { path = "./crates/config/", version = "=1.8.0" } +mas-context = { path = "./crates/context/", version = "=1.8.0" } +mas-data-model = { path = "./crates/data-model/", version = "=1.8.0" } +mas-email = { path = "./crates/email/", version = "=1.8.0" } +mas-graphql = { path = "./crates/graphql/", version = "=1.8.0" } +mas-handlers = { path = "./crates/handlers/", version = "=1.8.0" } +mas-http = { path = "./crates/http/", version = "=1.8.0" } +mas-i18n = { path = "./crates/i18n/", version = "=1.8.0" } +mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.8.0" } +mas-iana = { path = "./crates/iana/", version = "=1.8.0" } +mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.8.0" } +mas-jose = { path = "./crates/jose/", version = "=1.8.0" } +mas-keystore = { path = "./crates/keystore/", version = "=1.8.0" } +mas-listener = { path = "./crates/listener/", version = "=1.8.0" } +mas-matrix = { path = "./crates/matrix/", version = "=1.8.0" } +mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.8.0" } +mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.8.0" } +mas-policy = { path = "./crates/policy/", version = "=1.8.0" } +mas-router = { path = "./crates/router/", version = "=1.8.0" } +mas-spa = { path = "./crates/spa/", version = "=1.8.0" } +mas-storage = { path = "./crates/storage/", version = "=1.8.0" } +mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.8.0" } +mas-tasks = { path = "./crates/tasks/", version = "=1.8.0" } +mas-templates = { path = "./crates/templates/", version = "=1.8.0" } +mas-tower = { path = "./crates/tower/", version = "=1.8.0" } +oauth2-types = { path = "./crates/oauth2-types/", version = "=1.8.0" } +syn2mas = { path = "./crates/syn2mas", version = "=1.8.0" } # OpenAPI schema generation and validation [workspace.dependencies.aide] From a21383a0d81312ce417cf8a00e4d56be61e12a8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 13:12:00 +0000 Subject: [PATCH 81/86] build(deps): bump peter-evans/create-pull-request from 7.0.9 to 8.0.0 Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.9 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/v7.0.9...v8.0.0) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/translations-download.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/translations-download.yaml b/.github/workflows/translations-download.yaml index 5dbf2f79a..b248ed19b 100644 --- a/.github/workflows/translations-download.yaml +++ b/.github/workflows/translations-download.yaml @@ -42,7 +42,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@v7.0.9 + uses: peter-evans/create-pull-request@v8.0.0 with: sign-commits: true token: ${{ secrets.BOT_GITHUB_TOKEN }} From 0ada502b76721476bae8102cedf47f34a5857831 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 13:12:26 +0000 Subject: [PATCH 82/86] build(deps-dev): bump the vite group in /frontend with 2 updates Bumps the vite group in /frontend with 2 updates: [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite). Updates `@vitejs/plugin-react` from 5.1.1 to 5.1.2 - [Release notes](https://github.com/vitejs/vite-plugin-react/releases) - [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@5.1.2/packages/plugin-react) Updates `vite` from 7.2.6 to 7.2.7 - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v7.2.7/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v7.2.7/packages/vite) --- updated-dependencies: - dependency-name: "@vitejs/plugin-react" dependency-version: 5.1.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: vite - dependency-name: vite dependency-version: 7.2.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: vite ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 24 ++++++++++++------------ frontend/package.json | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 34b9a3f9f..199ba624b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -48,7 +48,7 @@ "@types/react": "19.2.7", "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", - "@vitejs/plugin-react": "^5.1.1", + "@vitejs/plugin-react": "^5.1.2", "@vitest/coverage-v8": "^4.0.15", "autoprefixer": "^10.4.21", "browserslist-to-esbuild": "^2.1.1", @@ -66,7 +66,7 @@ "tailwindcss": "^3.4.18", "tinyglobby": "^0.2.15", "typescript": "^5.9.3", - "vite": "7.2.6", + "vite": "7.2.7", "vite-plugin-compression": "^0.5.1", "vite-plugin-graphql-codegen": "^3.7.0", "vite-plugin-manifest-sri": "^0.2.0", @@ -5021,9 +5021,9 @@ "license": "MIT" }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", - "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", "dev": true, "license": "MIT" }, @@ -6413,16 +6413,16 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz", - "integrity": "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", "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.47", + "@rolldown/pluginutils": "1.0.0-beta.53", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, @@ -12856,9 +12856,9 @@ } }, "node_modules/vite": { - "version": "7.2.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz", - "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==", + "version": "7.2.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz", + "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", "dev": true, "license": "MIT", "peer": true, diff --git a/frontend/package.json b/frontend/package.json index dbaa5d798..f50583f97 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -58,7 +58,7 @@ "@types/react": "19.2.7", "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", - "@vitejs/plugin-react": "^5.1.1", + "@vitejs/plugin-react": "^5.1.2", "@vitest/coverage-v8": "^4.0.15", "autoprefixer": "^10.4.21", "browserslist-to-esbuild": "^2.1.1", @@ -76,7 +76,7 @@ "tailwindcss": "^3.4.18", "tinyglobby": "^0.2.15", "typescript": "^5.9.3", - "vite": "7.2.6", + "vite": "7.2.7", "vite-plugin-compression": "^0.5.1", "vite-plugin-graphql-codegen": "^3.7.0", "vite-plugin-manifest-sri": "^0.2.0", From d466f601db5bd2c8d29b97bf4a17a8bf41549fba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 13:12:36 +0000 Subject: [PATCH 83/86] build(deps): bump the i18next group in /frontend with 3 updates Bumps the i18next group in /frontend with 3 updates: [i18next](https://github.com/i18next/i18next), [react-i18next](https://github.com/i18next/react-i18next) and [i18next-cli](https://github.com/i18next/i18next-cli). Updates `i18next` from 25.7.1 to 25.7.2 - [Release notes](https://github.com/i18next/i18next/releases) - [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md) - [Commits](https://github.com/i18next/i18next/compare/v25.7.1...v25.7.2) Updates `react-i18next` from 16.3.5 to 16.4.0 - [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md) - [Commits](https://github.com/i18next/react-i18next/compare/v16.3.5...v16.4.0) Updates `i18next-cli` from 1.29.4 to 1.30.4 - [Changelog](https://github.com/i18next/i18next-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/i18next/i18next-cli/compare/v1.29.4...v1.30.4) --- updated-dependencies: - dependency-name: i18next dependency-version: 25.7.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: i18next - dependency-name: react-i18next dependency-version: 16.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: i18next - dependency-name: i18next-cli dependency-version: 1.30.4 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: i18next ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 206 +++++++++++++++++-------------------- frontend/package.json | 6 +- 2 files changed, 97 insertions(+), 115 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 34b9a3f9f..baf029013 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -20,10 +20,10 @@ "@zxcvbn-ts/language-common": "^3.0.4", "classnames": "^2.5.1", "date-fns": "^4.1.0", - "i18next": "^25.7.1", + "i18next": "^25.7.2", "react": "^19.2.1", "react-dom": "^19.2.1", - "react-i18next": "^16.3.5", + "react-i18next": "^16.4.1", "swagger-ui-dist": "^5.29.5", "valibot": "^1.2.0", "vaul": "^1.1.2" @@ -54,7 +54,7 @@ "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", "happy-dom": "^20.0.4", - "i18next-cli": "^1.29.4", + "i18next-cli": "^1.30.5", "knip": "^5.66.4", "msw": "^2.11.6", "msw-storybook-addon": "^2.0.6", @@ -1210,9 +1210,9 @@ "license": "MIT" }, "node_modules/@croct/json5-parser": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@croct/json5-parser/-/json5-parser-0.2.1.tgz", - "integrity": "sha512-YB21uimsK6lZOUr8z+W0gsLI3TlUz4e1iWtN4qxiAsl7yCBgeg7Xfc3Y8kRJzWnZL/eOU5WaMgD09KOyuqenuQ==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@croct/json5-parser/-/json5-parser-0.2.2.tgz", + "integrity": "sha512-0NJMLrbeLbQ0eCVj3UoH/kG2QckUgOASfwmfDTjyW1xAYPyTNJXcWVT/dssJdTJd0pRchW+qF0VFWQHcxs1OVw==", "dev": true, "license": "MIT", "dependencies": { @@ -5546,9 +5546,9 @@ } }, "node_modules/@swc/core": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.2.tgz", - "integrity": "sha512-OQm+yJdXxvSjqGeaWhP6Ia264ogifwAO7Q12uTDVYj/Ks4jBTI4JknlcjDRAXtRhqbWsfbZyK/5RtuIPyptk3w==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.3.tgz", + "integrity": "sha512-Qd8eBPkUFL4eAONgGjycZXj1jFCBW8Fd+xF0PzdTlBCWQIV1xnUT7B93wUANtW3KGjl3TRcOyxwSx/u/jyKw/Q==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -5564,16 +5564,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.2", - "@swc/core-darwin-x64": "1.15.2", - "@swc/core-linux-arm-gnueabihf": "1.15.2", - "@swc/core-linux-arm64-gnu": "1.15.2", - "@swc/core-linux-arm64-musl": "1.15.2", - "@swc/core-linux-x64-gnu": "1.15.2", - "@swc/core-linux-x64-musl": "1.15.2", - "@swc/core-win32-arm64-msvc": "1.15.2", - "@swc/core-win32-ia32-msvc": "1.15.2", - "@swc/core-win32-x64-msvc": "1.15.2" + "@swc/core-darwin-arm64": "1.15.3", + "@swc/core-darwin-x64": "1.15.3", + "@swc/core-linux-arm-gnueabihf": "1.15.3", + "@swc/core-linux-arm64-gnu": "1.15.3", + "@swc/core-linux-arm64-musl": "1.15.3", + "@swc/core-linux-x64-gnu": "1.15.3", + "@swc/core-linux-x64-musl": "1.15.3", + "@swc/core-win32-arm64-msvc": "1.15.3", + "@swc/core-win32-ia32-msvc": "1.15.3", + "@swc/core-win32-x64-msvc": "1.15.3" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -5585,9 +5585,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.2.tgz", - "integrity": "sha512-Ghyz4RJv4zyXzrUC1B2MLQBbppIB5c4jMZJybX2ebdEQAvryEKp3gq1kBksCNsatKGmEgXul88SETU19sMWcrw==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.3.tgz", + "integrity": "sha512-AXfeQn0CvcQ4cndlIshETx6jrAM45oeUrK8YeEY6oUZU/qzz0Id0CyvlEywxkWVC81Ajpd8TQQ1fW5yx6zQWkQ==", "cpu": [ "arm64" ], @@ -5602,9 +5602,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.2.tgz", - "integrity": "sha512-7n/PGJOcL2QoptzL42L5xFFfXY5rFxLHnuz1foU+4ruUTG8x2IebGhtwVTpaDN8ShEv2UZObBlT1rrXTba15Zw==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.3.tgz", + "integrity": "sha512-p68OeCz1ui+MZYG4wmfJGvcsAcFYb6Sl25H9TxWl+GkBgmNimIiRdnypK9nBGlqMZAcxngNPtnG3kEMNnvoJ2A==", "cpu": [ "x64" ], @@ -5619,9 +5619,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.2.tgz", - "integrity": "sha512-ZUQVCfRJ9wimuxkStRSlLwqX4TEDmv6/J+E6FicGkQ6ssLMWoKDy0cAo93HiWt/TWEee5vFhFaSQYzCuBEGO6A==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.3.tgz", + "integrity": "sha512-Nuj5iF4JteFgwrai97mUX+xUOl+rQRHqTvnvHMATL/l9xE6/TJfPBpd3hk/PVpClMXG3Uvk1MxUFOEzM1JrMYg==", "cpu": [ "arm" ], @@ -5636,9 +5636,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.2.tgz", - "integrity": "sha512-GZh3pYBmfnpQ+JIg+TqLuz+pM+Mjsk5VOzi8nwKn/m+GvQBsxD5ectRtxuWUxMGNG8h0lMy4SnHRqdK3/iJl7A==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.3.tgz", + "integrity": "sha512-2Nc/s8jE6mW2EjXWxO/lyQuLKShcmTrym2LRf5Ayp3ICEMX6HwFqB1EzDhwoMa2DcUgmnZIalesq2lG3krrUNw==", "cpu": [ "arm64" ], @@ -5653,9 +5653,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.2.tgz", - "integrity": "sha512-5av6VYZZeneiYIodwzGMlnyVakpuYZryGzFIbgu1XP8wVylZxduEzup4eP8atiMDFmIm+s4wn8GySJmYqeJC0A==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.3.tgz", + "integrity": "sha512-j4SJniZ/qaZ5g8op+p1G9K1z22s/EYGg1UXIb3+Cg4nsxEpF5uSIGEE4mHUfA70L0BR9wKT2QF/zv3vkhfpX4g==", "cpu": [ "arm64" ], @@ -5670,9 +5670,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.2.tgz", - "integrity": "sha512-1nO/UfdCLuT/uE/7oB3EZgTeZDCIa6nL72cFEpdegnqpJVNDI6Qb8U4g/4lfVPkmHq2lvxQ0L+n+JdgaZLhrRA==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.3.tgz", + "integrity": "sha512-aKttAZnz8YB1VJwPQZtyU8Uk0BfMP63iDMkvjhJzRZVgySmqt/apWSdnoIcZlUoGheBrcqbMC17GGUmur7OT5A==", "cpu": [ "x64" ], @@ -5687,9 +5687,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.2.tgz", - "integrity": "sha512-Ksfrb0Tx310kr+TLiUOvB/I80lyZ3lSOp6cM18zmNRT/92NB4mW8oX2Jo7K4eVEI2JWyaQUAFubDSha2Q+439A==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.3.tgz", + "integrity": "sha512-oe8FctPu1gnUsdtGJRO2rvOUIkkIIaHqsO9xxN0bTR7dFTlPTGi2Fhk1tnvXeyAvCPxLIcwD8phzKg6wLv9yug==", "cpu": [ "x64" ], @@ -5704,9 +5704,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.2.tgz", - "integrity": "sha512-IzUb5RlMUY0r1A9IuJrQ7Tbts1wWb73/zXVXT8VhewbHGoNlBKE0qUhKMED6Tv4wDF+pmbtUJmKXDthytAvLmg==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.3.tgz", + "integrity": "sha512-L9AjzP2ZQ/Xh58e0lTRMLvEDrcJpR7GwZqAtIeNLcTK7JVE+QineSyHp0kLkO1rttCHyCy0U74kDTj0dRz6raA==", "cpu": [ "arm64" ], @@ -5721,9 +5721,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.2.tgz", - "integrity": "sha512-kCATEzuY2LP9AlbU2uScjcVhgnCAkRdu62vbce17Ro5kxEHxYWcugkveyBRS3AqZGtwAKYbMAuNloer9LS/hpw==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.3.tgz", + "integrity": "sha512-B8UtogMzErUPDWUoKONSVBdsgKYd58rRyv2sHJWKOIMCHfZ22FVXICR4O/VwIYtlnZ7ahERcjayBHDlBZpR0aw==", "cpu": [ "ia32" ], @@ -5738,9 +5738,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.2.tgz", - "integrity": "sha512-iJaHeYCF4jTn7OEKSa3KRiuVFIVYts8jYjNmCdyz1u5g8HRyTDISD76r8+ljEOgm36oviRQvcXaw6LFp1m0yyA==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.3.tgz", + "integrity": "sha512-SpZKMR9QBTecHeqpzJdYEfgw30Oo8b/Xl6rjSzBt1g0ZsXyy60KLXrp6IagQyfTYqNYE/caDvwtF2FPn7pomog==", "cpu": [ "x64" ], @@ -8070,9 +8070,9 @@ "license": "MIT" }, "node_modules/execa": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", - "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", "dev": true, "license": "MIT", "dependencies": { @@ -8705,9 +8705,9 @@ } }, "node_modules/i18next": { - "version": "25.7.1", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.1.tgz", - "integrity": "sha512-XbTnkh1yCZWSAZGnA9xcQfHcYNgZs2cNxm+c6v1Ma9UAUGCeJPplRe1ILia6xnDvXBjk0uXU+Z8FYWhA19SKFw==", + "version": "25.7.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.2.tgz", + "integrity": "sha512-58b4kmLpLv1buWUEwegMDUqZVR5J+rT+WTRFaBGL7lxDuJQQ0NrJFrq+eT2N94aYVR1k1Sr13QITNOL88tZCuw==", "funding": [ { "type": "individual", @@ -8737,20 +8737,20 @@ } }, "node_modules/i18next-cli": { - "version": "1.29.4", - "resolved": "https://registry.npmjs.org/i18next-cli/-/i18next-cli-1.29.4.tgz", - "integrity": "sha512-EqnCI+OP7ljAt2XFWVE0Rf189Ci0qIIMCc+Q8oV7HSPQSkZFwohtIFrqMdTGOJ5O/znW3y83mGxgPuP25NP4wg==", + "version": "1.30.5", + "resolved": "https://registry.npmjs.org/i18next-cli/-/i18next-cli-1.30.5.tgz", + "integrity": "sha512-1Ls80XXcwlsNhsFsWYMZV9ik1fPMdCny+hF8w64UkLLUdwxuUc/F8Vi7xbMv9oy3ODd6YBwNQwbKdH+5e6F9vA==", "dev": true, "license": "MIT", "dependencies": { - "@croct/json5-parser": "0.2.1", - "@swc/core": "1.15.2", + "@croct/json5-parser": "0.2.2", + "@swc/core": "1.15.3", "chalk": "5.6.2", "chokidar": "4.0.3", "commander": "14.0.2", - "execa": "9.6.0", - "glob": "12.0.0", - "i18next-resources-for-ts": "1.8.0", + "execa": "9.6.1", + "glob": "13.0.0", + "i18next-resources-for-ts": "1.9.0", "inquirer": "12.10.0", "jiti": "2.6.1", "jsonc-parser": "3.3.1", @@ -8805,38 +8805,16 @@ } }, "node_modules/i18next-cli/node_modules/glob": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-12.0.0.tgz", - "integrity": "sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw==", + "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": { - "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" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/i18next-cli/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" }, @@ -8855,11 +8833,11 @@ } }, "node_modules/i18next-cli/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } @@ -8912,44 +8890,45 @@ } }, "node_modules/i18next-resources-for-ts": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/i18next-resources-for-ts/-/i18next-resources-for-ts-1.8.0.tgz", - "integrity": "sha512-I/qMaoARO2WBPjFYumh0Ceu1Bj9GckdxMAFFcAz9EtAFNPGybbeZNKluVPIN6iqMQatuJQ4XHagvx/C/gjqBCQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/i18next-resources-for-ts/-/i18next-resources-for-ts-1.9.0.tgz", + "integrity": "sha512-P5kZmxCVKVdiJU0z6Nf+3qW3sdN4TlxDRsuyiuLT5SdM1Yy8yqUgR6JlNZPAd1VWMaXp8aiauK8EuOi3XuKhcw==", "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.4", - "chokidar": "^4.0.3", - "yaml": "^2.8.1" + "@swc/core": "^1.15.3", + "chokidar": "^5.0.0", + "yaml": "^2.8.2" }, "bin": { "i18next-resources-for-ts": "bin/i18next-resources-for-ts.js" } }, "node_modules/i18next-resources-for-ts/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "readdirp": "^5.0.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/i18next-resources-for-ts/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14.18.0" + "node": ">= 20.19.0" }, "funding": { "type": "individual", @@ -11135,9 +11114,9 @@ } }, "node_modules/react-i18next": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.3.5.tgz", - "integrity": "sha512-F7Kglc+T0aE6W2rO5eCAFBEuWRpNb5IFmXOYEgztjZEuiuSLTe/xBIEG6Q3S0fbl8GXMNo+Q7gF8bpokFNWJww==", + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.4.1.tgz", + "integrity": "sha512-GzsYomxb1/uE7nlJm0e1qQ8f+W9I3Xirh9VoycZIahk6C8Pmv/9Fd0ek6zjf1FSgtGLElDGqwi/4FOHEGUbsEQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.27.6", @@ -13357,9 +13336,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "dev": true, "license": "ISC", "bin": { @@ -13367,6 +13346,9 @@ }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { diff --git a/frontend/package.json b/frontend/package.json index dbaa5d798..67c1d854f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,10 +30,10 @@ "@zxcvbn-ts/language-common": "^3.0.4", "classnames": "^2.5.1", "date-fns": "^4.1.0", - "i18next": "^25.7.1", + "i18next": "^25.7.2", "react": "^19.2.1", "react-dom": "^19.2.1", - "react-i18next": "^16.3.5", + "react-i18next": "^16.4.1", "swagger-ui-dist": "^5.29.5", "valibot": "^1.2.0", "vaul": "^1.1.2" @@ -64,7 +64,7 @@ "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", "happy-dom": "^20.0.4", - "i18next-cli": "^1.29.4", + "i18next-cli": "^1.30.5", "knip": "^5.66.4", "msw": "^2.11.6", "msw-storybook-addon": "^2.0.6", From d0315ed16c57f42c0d95fe83a7b10d0494ba0fab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:49:11 +0000 Subject: [PATCH 84/86] build(deps-dev): bump autoprefixer from 10.4.21 to 10.4.22 in /frontend Bumps [autoprefixer](https://github.com/postcss/autoprefixer) from 10.4.21 to 10.4.22. - [Release notes](https://github.com/postcss/autoprefixer/releases) - [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/autoprefixer/compare/10.4.21...10.4.22) --- updated-dependencies: - dependency-name: autoprefixer dependency-version: 10.4.22 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 68 +++++++++++++++++++------------------- frontend/package.json | 2 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f5c734f0c..121d698ad 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -50,7 +50,7 @@ "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.2", "@vitest/coverage-v8": "^4.0.15", - "autoprefixer": "^10.4.21", + "autoprefixer": "^10.4.22", "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", "happy-dom": "^20.0.4", @@ -6989,9 +6989,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", + "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", "dev": true, "funding": [ { @@ -7009,9 +7009,9 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", + "browserslist": "^4.27.0", + "caniuse-lite": "^1.0.30001754", + "fraction.js": "^5.3.4", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -7093,9 +7093,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.2.tgz", - "integrity": "sha512-NvcIedLxrs9llVpX7wI+Jz4Hn9vJQkCPKrTaHIE0sW/Rj1iq6Fzby4NbyTZjQJNoypBXNaG7tEHkTgONZpwgxQ==", + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.6.tgz", + "integrity": "sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7139,9 +7139,9 @@ } }, "node_modules/browserslist": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.0.tgz", - "integrity": "sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -7160,11 +7160,11 @@ "license": "MIT", "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.2", - "caniuse-lite": "^1.0.30001741", - "electron-to-chromium": "^1.5.218", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -7244,9 +7244,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001741", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", - "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", + "version": "1.0.30001760", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", + "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", "dev": true, "funding": [ { @@ -7915,9 +7915,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.218", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.218.tgz", - "integrity": "sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==", + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", "dev": true, "license": "ISC" }, @@ -8316,16 +8316,16 @@ } }, "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "dev": true, "license": "MIT", "engines": { "node": "*" }, "funding": { - "type": "patreon", + "type": "github", "url": "https://github.com/sponsors/rawify" } }, @@ -10228,9 +10228,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", - "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -12691,9 +12691,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", "dev": true, "funding": [ { diff --git a/frontend/package.json b/frontend/package.json index 78de0661e..78a21c4fa 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -60,7 +60,7 @@ "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.2", "@vitest/coverage-v8": "^4.0.15", - "autoprefixer": "^10.4.21", + "autoprefixer": "^10.4.22", "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.11.0", "happy-dom": "^20.0.4", From 299b4f5ebe42c9708624b2d1bade31d754e21192 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:50:24 +0000 Subject: [PATCH 85/86] build(deps): bump actions/setup-node from 6.0.0 to 6.1.0 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.0.0 to 6.1.0. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v6.0.0...v6.1.0) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 6 +++--- .github/workflows/docs.yaml | 2 +- .github/workflows/release-branch.yaml | 2 +- .github/workflows/translations-download.yaml | 2 +- .github/workflows/translations-upload.yaml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b4cbdfff8..bf7ac011a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -64,7 +64,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.0.0 + uses: actions/setup-node@v6.1.0 with: node-version: 24 @@ -88,7 +88,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.0.0 + uses: actions/setup-node@v6.1.0 with: node-version: 24 @@ -112,7 +112,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.0.0 + uses: actions/setup-node@v6.1.0 with: node-version: 24 diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 1fa2f56a7..3053b56fb 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.0.0 + uses: actions/setup-node@v6.1.0 with: node-version: 24 diff --git a/.github/workflows/release-branch.yaml b/.github/workflows/release-branch.yaml index b78c9363f..e058a62be 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.0.0 + uses: actions/setup-node@v6.1.0 with: node-version: 24 diff --git a/.github/workflows/translations-download.yaml b/.github/workflows/translations-download.yaml index b248ed19b..70419fce0 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.0.0 + uses: actions/setup-node@v6.1.0 with: node-version: 24 diff --git a/.github/workflows/translations-upload.yaml b/.github/workflows/translations-upload.yaml index b09a7d63c..ba723278e 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.0.0 + uses: actions/setup-node@v6.1.0 with: node-version: 24 From 089bd56c66c1e67c3c51fa6986913a7aa4028ac8 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 10 Dec 2025 18:41:36 +0100 Subject: [PATCH 86/86] Minor reword in the documentation Co-authored-by: Olivier 'reivilibre' --- docs/development/database.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/development/database.md b/docs/development/database.md index 43c2b33fe..2deafce2b 100644 --- a/docs/development/database.md +++ b/docs/development/database.md @@ -54,7 +54,7 @@ Note that migrations are embedded in the final binary and can be run from the se ### Removing migrations For various reasons, we may want to delete migrations. -In case we do, we *must* declare that migration version as it is fine to be missing. +In case we do, we *must* declare that migration version as allowed to be missing. This is because on startup, MAS will validate that all the applied migrations are known, and warn if some are missing. To do so, get the migration version and add it to the `ALLOWED_MISSING_MIGRATIONS` array in the `mas-storage-pg` crate.