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/27] 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/27] 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/27] 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/27] 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/27] 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 069b57758b9d4153b6cb9b005778a958be7bf71a Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 25 Nov 2025 18:20:14 +0000 Subject: [PATCH 06/27] 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 07/27] 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 08/27] 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 09/27] 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 10/27] 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 11/27] 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 12/27] 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 13/27] 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 14/27] 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 15/27] 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 16/27] 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 5c7ff7b8dcee31b7c30ca6724295e8c1a9ca99ce Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Thu, 27 Nov 2025 15:13:16 +0000 Subject: [PATCH 17/27] 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 18/27] 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 e065f830e9dcaecc646bb0b1bae2974f2c8d49c0 Mon Sep 17 00:00:00 2001 From: Ben Banfield-Zanin Date: Fri, 28 Nov 2025 15:18:53 +0000 Subject: [PATCH 19/27] Add 'IF NOT EXISTS' to all 'CREATE INDEX CONCURRENTLY' statements to avoid deadlocks --- .../20250410000000_idx_compat_access_tokens_session_fk.sql | 2 +- .../20250410000001_idx_compat_refresh_tokens_session_fk.sql | 2 +- ...250410000002_idx_compat_refresh_tokens_access_token_fk.sql | 2 +- .../migrations/20250410000003_idx_compat_sessions_user_fk.sql | 2 +- .../20250410000004_idx_compat_sessions_user_session_fk.sql | 2 +- .../20250410000006_idx_compat_sso_logins_session_fk.sql | 2 +- .../20250410000007_idx_oauth2_access_tokens_session_fk.sql | 2 +- ...50410000008_idx_oauth2_authorization_grants_session_fk.sql | 2 +- ...250410000009_idx_oauth2_authorization_grants_client_fk.sql | 2 +- .../20250410000010_idx_oauth2_consents_client_fk.sql | 2 +- .../migrations/20250410000011_idx_oauth2_consents_user_fk.sql | 2 +- ...20250410000012_idx_oauth2_device_code_grants_client_fk.sql | 2 +- ...0250410000013_idx_oauth2_device_code_grants_session_fk.sql | 2 +- ...10000014_idx_oauth2_device_code_grants_user_session_fk.sql | 2 +- .../20250410000015_idx_oauth2_refresh_tokens_session_fk.sql | 2 +- ...250410000016_idx_oauth2_refresh_tokens_access_token_fk.sql | 2 +- ...000017_idx_oauth2_refresh_tokens_next_refresh_token_fk.sql | 2 +- .../20250410000018_idx_oauth2_sessions_user_session_fk.sql | 2 +- .../20250410000019_idx_oauth2_sessions_client_fk.sql | 2 +- .../migrations/20250410000020_idx_oauth2_sessions_user_fk.sql | 2 +- .../20250410000022_idx_queue_jobs_started_by_fk.sql | 2 +- .../20250410000023_idx_queue_jobs_next_attempt_fk.sql | 2 +- .../20250410000024_idx_queue_jobs_schedule_name_fk.sql | 2 +- ..._idx_upstream_oauth_authorization_sessions_provider_fk.sql | 2 +- ...0026_idx_upstream_oauth_authorization_sessions_link_fk.sql | 2 +- .../20250410000027_idx_upstream_oauth_links_provider_fk.sql | 2 +- .../20250410000028_idx_upstream_oauth_links_user_fk.sql | 2 +- ..._idx_user_email_authentication_codes_authentication_fk.sql | 2 +- ...0000030_idx_user_email_authentications_user_session_fk.sql | 2 +- ...31_idx_user_email_authentications_user_registration_fk.sql | 2 +- .../migrations/20250410000032_idx_user_emails_user_fk.sql | 2 +- .../migrations/20250410000033_idx_user_emails_email_idx.sql | 2 +- .../migrations/20250410000034_idx_user_passwords_user_fk.sql | 2 +- .../20250410000035_idx_user_recovery_tickets_session_fk.sql | 2 +- ...20250410000036_idx_user_recovery_tickets_user_email_fk.sql | 2 +- ...0000037_idx_user_registrations_email_authentication_fk.sql | 2 +- ...00038_idx_user_session_authentications_user_session_fk.sql | 2 +- ...0039_idx_user_session_authentications_user_password_fk.sql | 2 +- ...user_session_authentications_upstream_oauth_session_fk.sql | 2 +- .../migrations/20250410000041_idx_user_sessions_user_fk.sql | 2 +- .../migrations/20250410000043_idx_user_terms_user_fk.sql | 2 +- .../migrations/20250410000044_idx_users_primary_email_fk.sql | 2 +- .../20250410000045_idx_user_recovery_tickets_ticket_idx.sql | 2 +- .../migrations/20250410121612_users_lower_username_idx.sql | 2 +- .../migrations/20250602212101_idx_user_registration_token.sql | 4 ++-- .../migrations/20250708155857_idx_user_emails_lower_email.sql | 2 +- .../migrations/20250915092635_users_username_trgm_idx.sql | 2 +- ...127145951_user_registration_upstream_oauth_session_idx.sql | 2 +- 48 files changed, 49 insertions(+), 49 deletions(-) diff --git a/crates/storage-pg/migrations/20250410000000_idx_compat_access_tokens_session_fk.sql b/crates/storage-pg/migrations/20250410000000_idx_compat_access_tokens_session_fk.sql index 777118233..880488b54 100644 --- a/crates/storage-pg/migrations/20250410000000_idx_compat_access_tokens_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000000_idx_compat_access_tokens_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS compat_access_tokens_session_fk ON compat_access_tokens (compat_session_id); diff --git a/crates/storage-pg/migrations/20250410000001_idx_compat_refresh_tokens_session_fk.sql b/crates/storage-pg/migrations/20250410000001_idx_compat_refresh_tokens_session_fk.sql index d0e99ad9b..806a9f1e1 100644 --- a/crates/storage-pg/migrations/20250410000001_idx_compat_refresh_tokens_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000001_idx_compat_refresh_tokens_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS compat_refresh_tokens_session_fk ON compat_refresh_tokens (compat_session_id); diff --git a/crates/storage-pg/migrations/20250410000002_idx_compat_refresh_tokens_access_token_fk.sql b/crates/storage-pg/migrations/20250410000002_idx_compat_refresh_tokens_access_token_fk.sql index 133b00072..399f3731f 100644 --- a/crates/storage-pg/migrations/20250410000002_idx_compat_refresh_tokens_access_token_fk.sql +++ b/crates/storage-pg/migrations/20250410000002_idx_compat_refresh_tokens_access_token_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS compat_refresh_tokens_access_token_fk ON compat_refresh_tokens (compat_access_token_id); diff --git a/crates/storage-pg/migrations/20250410000003_idx_compat_sessions_user_fk.sql b/crates/storage-pg/migrations/20250410000003_idx_compat_sessions_user_fk.sql index 3234fc8ca..1b038c53f 100644 --- a/crates/storage-pg/migrations/20250410000003_idx_compat_sessions_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000003_idx_compat_sessions_user_fk.sql @@ -7,7 +7,7 @@ -- Including the `last_active_at` column lets us effeciently filter in-memory -- for those sessions without fetching the rows, and without including it in the -- index btree -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS compat_sessions_user_fk ON compat_sessions (user_id) INCLUDE (last_active_at); diff --git a/crates/storage-pg/migrations/20250410000004_idx_compat_sessions_user_session_fk.sql b/crates/storage-pg/migrations/20250410000004_idx_compat_sessions_user_session_fk.sql index 4b35f9960..52633ad0b 100644 --- a/crates/storage-pg/migrations/20250410000004_idx_compat_sessions_user_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000004_idx_compat_sessions_user_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS compat_sessions_user_session_fk ON compat_sessions (user_session_id); diff --git a/crates/storage-pg/migrations/20250410000006_idx_compat_sso_logins_session_fk.sql b/crates/storage-pg/migrations/20250410000006_idx_compat_sso_logins_session_fk.sql index e88224e66..da209e586 100644 --- a/crates/storage-pg/migrations/20250410000006_idx_compat_sso_logins_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000006_idx_compat_sso_logins_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS compat_sso_logins_session_fk ON compat_sso_logins (compat_session_id); diff --git a/crates/storage-pg/migrations/20250410000007_idx_oauth2_access_tokens_session_fk.sql b/crates/storage-pg/migrations/20250410000007_idx_oauth2_access_tokens_session_fk.sql index 7495af2a3..fd44fb6c4 100644 --- a/crates/storage-pg/migrations/20250410000007_idx_oauth2_access_tokens_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000007_idx_oauth2_access_tokens_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS oauth2_access_tokens_session_fk ON oauth2_access_tokens (oauth2_session_id); diff --git a/crates/storage-pg/migrations/20250410000008_idx_oauth2_authorization_grants_session_fk.sql b/crates/storage-pg/migrations/20250410000008_idx_oauth2_authorization_grants_session_fk.sql index dac57bf91..4a32010f2 100644 --- a/crates/storage-pg/migrations/20250410000008_idx_oauth2_authorization_grants_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000008_idx_oauth2_authorization_grants_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS oauth2_authorization_grants_session_fk ON oauth2_authorization_grants (oauth2_session_id); diff --git a/crates/storage-pg/migrations/20250410000009_idx_oauth2_authorization_grants_client_fk.sql b/crates/storage-pg/migrations/20250410000009_idx_oauth2_authorization_grants_client_fk.sql index fd5d30344..945d751db 100644 --- a/crates/storage-pg/migrations/20250410000009_idx_oauth2_authorization_grants_client_fk.sql +++ b/crates/storage-pg/migrations/20250410000009_idx_oauth2_authorization_grants_client_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS oauth2_authorization_grants_client_fk ON oauth2_authorization_grants (oauth2_client_id); diff --git a/crates/storage-pg/migrations/20250410000010_idx_oauth2_consents_client_fk.sql b/crates/storage-pg/migrations/20250410000010_idx_oauth2_consents_client_fk.sql index ae8e0b5dd..b0928ef5e 100644 --- a/crates/storage-pg/migrations/20250410000010_idx_oauth2_consents_client_fk.sql +++ b/crates/storage-pg/migrations/20250410000010_idx_oauth2_consents_client_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS oauth2_consents_client_fk ON oauth2_consents (oauth2_client_id); diff --git a/crates/storage-pg/migrations/20250410000011_idx_oauth2_consents_user_fk.sql b/crates/storage-pg/migrations/20250410000011_idx_oauth2_consents_user_fk.sql index c9226b7d6..89751503b 100644 --- a/crates/storage-pg/migrations/20250410000011_idx_oauth2_consents_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000011_idx_oauth2_consents_user_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS oauth2_consents_user_fk ON oauth2_consents (user_id); diff --git a/crates/storage-pg/migrations/20250410000012_idx_oauth2_device_code_grants_client_fk.sql b/crates/storage-pg/migrations/20250410000012_idx_oauth2_device_code_grants_client_fk.sql index a9981fe4b..3f97117f7 100644 --- a/crates/storage-pg/migrations/20250410000012_idx_oauth2_device_code_grants_client_fk.sql +++ b/crates/storage-pg/migrations/20250410000012_idx_oauth2_device_code_grants_client_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS oauth2_device_code_grants_client_fk ON oauth2_device_code_grant (oauth2_client_id); diff --git a/crates/storage-pg/migrations/20250410000013_idx_oauth2_device_code_grants_session_fk.sql b/crates/storage-pg/migrations/20250410000013_idx_oauth2_device_code_grants_session_fk.sql index be8f685d1..7400dcd5d 100644 --- a/crates/storage-pg/migrations/20250410000013_idx_oauth2_device_code_grants_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000013_idx_oauth2_device_code_grants_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS oauth2_device_code_grants_session_fk ON oauth2_device_code_grant (oauth2_session_id); diff --git a/crates/storage-pg/migrations/20250410000014_idx_oauth2_device_code_grants_user_session_fk.sql b/crates/storage-pg/migrations/20250410000014_idx_oauth2_device_code_grants_user_session_fk.sql index f3f6613d7..8b07a4366 100644 --- a/crates/storage-pg/migrations/20250410000014_idx_oauth2_device_code_grants_user_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000014_idx_oauth2_device_code_grants_user_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS oauth2_device_code_grants_user_session_fk ON oauth2_device_code_grant (user_session_id); diff --git a/crates/storage-pg/migrations/20250410000015_idx_oauth2_refresh_tokens_session_fk.sql b/crates/storage-pg/migrations/20250410000015_idx_oauth2_refresh_tokens_session_fk.sql index 897d247c8..7da896268 100644 --- a/crates/storage-pg/migrations/20250410000015_idx_oauth2_refresh_tokens_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000015_idx_oauth2_refresh_tokens_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS oauth2_refresh_tokens_session_fk ON oauth2_refresh_tokens (oauth2_session_id); diff --git a/crates/storage-pg/migrations/20250410000016_idx_oauth2_refresh_tokens_access_token_fk.sql b/crates/storage-pg/migrations/20250410000016_idx_oauth2_refresh_tokens_access_token_fk.sql index 5d391e6a5..f6059d223 100644 --- a/crates/storage-pg/migrations/20250410000016_idx_oauth2_refresh_tokens_access_token_fk.sql +++ b/crates/storage-pg/migrations/20250410000016_idx_oauth2_refresh_tokens_access_token_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS oauth2_refresh_tokens_access_token_fk ON oauth2_refresh_tokens (oauth2_access_token_id); diff --git a/crates/storage-pg/migrations/20250410000017_idx_oauth2_refresh_tokens_next_refresh_token_fk.sql b/crates/storage-pg/migrations/20250410000017_idx_oauth2_refresh_tokens_next_refresh_token_fk.sql index f593f1d28..40fd117cc 100644 --- a/crates/storage-pg/migrations/20250410000017_idx_oauth2_refresh_tokens_next_refresh_token_fk.sql +++ b/crates/storage-pg/migrations/20250410000017_idx_oauth2_refresh_tokens_next_refresh_token_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS oauth2_refresh_tokens_next_refresh_token_fk ON oauth2_refresh_tokens (next_oauth2_refresh_token_id); diff --git a/crates/storage-pg/migrations/20250410000018_idx_oauth2_sessions_user_session_fk.sql b/crates/storage-pg/migrations/20250410000018_idx_oauth2_sessions_user_session_fk.sql index 44d6d0e3c..b2639e714 100644 --- a/crates/storage-pg/migrations/20250410000018_idx_oauth2_sessions_user_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000018_idx_oauth2_sessions_user_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS oauth2_sessions_user_session_fk ON oauth2_sessions (user_session_id); diff --git a/crates/storage-pg/migrations/20250410000019_idx_oauth2_sessions_client_fk.sql b/crates/storage-pg/migrations/20250410000019_idx_oauth2_sessions_client_fk.sql index ad868a310..341d2dea1 100644 --- a/crates/storage-pg/migrations/20250410000019_idx_oauth2_sessions_client_fk.sql +++ b/crates/storage-pg/migrations/20250410000019_idx_oauth2_sessions_client_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS oauth2_sessions_client_fk ON oauth2_sessions (oauth2_client_id); diff --git a/crates/storage-pg/migrations/20250410000020_idx_oauth2_sessions_user_fk.sql b/crates/storage-pg/migrations/20250410000020_idx_oauth2_sessions_user_fk.sql index b47bb00a8..d7b5f52af 100644 --- a/crates/storage-pg/migrations/20250410000020_idx_oauth2_sessions_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000020_idx_oauth2_sessions_user_fk.sql @@ -7,7 +7,7 @@ -- Including the `last_active_at` column lets us effeciently filter in-memory -- for those sessions without fetching the rows, and without including it in the -- index btree -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS oauth2_sessions_user_fk ON oauth2_sessions (user_id) INCLUDE (last_active_at); diff --git a/crates/storage-pg/migrations/20250410000022_idx_queue_jobs_started_by_fk.sql b/crates/storage-pg/migrations/20250410000022_idx_queue_jobs_started_by_fk.sql index 38bb79ede..272785332 100644 --- a/crates/storage-pg/migrations/20250410000022_idx_queue_jobs_started_by_fk.sql +++ b/crates/storage-pg/migrations/20250410000022_idx_queue_jobs_started_by_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS queue_jobs_started_by_fk ON queue_jobs (started_by); diff --git a/crates/storage-pg/migrations/20250410000023_idx_queue_jobs_next_attempt_fk.sql b/crates/storage-pg/migrations/20250410000023_idx_queue_jobs_next_attempt_fk.sql index ea611f76e..9824557e1 100644 --- a/crates/storage-pg/migrations/20250410000023_idx_queue_jobs_next_attempt_fk.sql +++ b/crates/storage-pg/migrations/20250410000023_idx_queue_jobs_next_attempt_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS queue_jobs_next_attempt_fk ON queue_jobs (next_attempt_id); diff --git a/crates/storage-pg/migrations/20250410000024_idx_queue_jobs_schedule_name_fk.sql b/crates/storage-pg/migrations/20250410000024_idx_queue_jobs_schedule_name_fk.sql index 02b2bfaea..4c70fea50 100644 --- a/crates/storage-pg/migrations/20250410000024_idx_queue_jobs_schedule_name_fk.sql +++ b/crates/storage-pg/migrations/20250410000024_idx_queue_jobs_schedule_name_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS queue_jobs_schedule_name_fk ON queue_jobs (schedule_name); diff --git a/crates/storage-pg/migrations/20250410000025_idx_upstream_oauth_authorization_sessions_provider_fk.sql b/crates/storage-pg/migrations/20250410000025_idx_upstream_oauth_authorization_sessions_provider_fk.sql index f5e388613..b86b1e0ee 100644 --- a/crates/storage-pg/migrations/20250410000025_idx_upstream_oauth_authorization_sessions_provider_fk.sql +++ b/crates/storage-pg/migrations/20250410000025_idx_upstream_oauth_authorization_sessions_provider_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS upstream_oauth_authorization_sessions_provider_fk ON upstream_oauth_authorization_sessions (upstream_oauth_provider_id); diff --git a/crates/storage-pg/migrations/20250410000026_idx_upstream_oauth_authorization_sessions_link_fk.sql b/crates/storage-pg/migrations/20250410000026_idx_upstream_oauth_authorization_sessions_link_fk.sql index aa08e7fa1..1b296090a 100644 --- a/crates/storage-pg/migrations/20250410000026_idx_upstream_oauth_authorization_sessions_link_fk.sql +++ b/crates/storage-pg/migrations/20250410000026_idx_upstream_oauth_authorization_sessions_link_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS upstream_oauth_authorization_sessions_link_fk ON upstream_oauth_authorization_sessions (upstream_oauth_link_id); diff --git a/crates/storage-pg/migrations/20250410000027_idx_upstream_oauth_links_provider_fk.sql b/crates/storage-pg/migrations/20250410000027_idx_upstream_oauth_links_provider_fk.sql index 9f6a9301f..55dd22612 100644 --- a/crates/storage-pg/migrations/20250410000027_idx_upstream_oauth_links_provider_fk.sql +++ b/crates/storage-pg/migrations/20250410000027_idx_upstream_oauth_links_provider_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS upstream_oauth_links_provider_fk ON upstream_oauth_links (upstream_oauth_provider_id); diff --git a/crates/storage-pg/migrations/20250410000028_idx_upstream_oauth_links_user_fk.sql b/crates/storage-pg/migrations/20250410000028_idx_upstream_oauth_links_user_fk.sql index af7791f18..21ba23e40 100644 --- a/crates/storage-pg/migrations/20250410000028_idx_upstream_oauth_links_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000028_idx_upstream_oauth_links_user_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS upstream_oauth_links_user_fk ON upstream_oauth_links (user_id); diff --git a/crates/storage-pg/migrations/20250410000029_idx_user_email_authentication_codes_authentication_fk.sql b/crates/storage-pg/migrations/20250410000029_idx_user_email_authentication_codes_authentication_fk.sql index 889fac2ab..591409632 100644 --- a/crates/storage-pg/migrations/20250410000029_idx_user_email_authentication_codes_authentication_fk.sql +++ b/crates/storage-pg/migrations/20250410000029_idx_user_email_authentication_codes_authentication_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_email_authentication_codes_authentication_fk ON user_email_authentication_codes (user_email_authentication_id); diff --git a/crates/storage-pg/migrations/20250410000030_idx_user_email_authentications_user_session_fk.sql b/crates/storage-pg/migrations/20250410000030_idx_user_email_authentications_user_session_fk.sql index 3a6284f67..bd1558b7d 100644 --- a/crates/storage-pg/migrations/20250410000030_idx_user_email_authentications_user_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000030_idx_user_email_authentications_user_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_email_authentications_user_session_fk ON user_email_authentications (user_session_id); diff --git a/crates/storage-pg/migrations/20250410000031_idx_user_email_authentications_user_registration_fk.sql b/crates/storage-pg/migrations/20250410000031_idx_user_email_authentications_user_registration_fk.sql index 9ad343ac9..7f233f7c3 100644 --- a/crates/storage-pg/migrations/20250410000031_idx_user_email_authentications_user_registration_fk.sql +++ b/crates/storage-pg/migrations/20250410000031_idx_user_email_authentications_user_registration_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_email_authentications_user_registration_fk ON user_email_authentications (user_registration_id); diff --git a/crates/storage-pg/migrations/20250410000032_idx_user_emails_user_fk.sql b/crates/storage-pg/migrations/20250410000032_idx_user_emails_user_fk.sql index f66117d43..c4834c714 100644 --- a/crates/storage-pg/migrations/20250410000032_idx_user_emails_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000032_idx_user_emails_user_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_emails_user_fk ON user_emails (user_id); diff --git a/crates/storage-pg/migrations/20250410000033_idx_user_emails_email_idx.sql b/crates/storage-pg/migrations/20250410000033_idx_user_emails_email_idx.sql index e75e1e028..b0f2ec4ac 100644 --- a/crates/storage-pg/migrations/20250410000033_idx_user_emails_email_idx.sql +++ b/crates/storage-pg/migrations/20250410000033_idx_user_emails_email_idx.sql @@ -5,6 +5,6 @@ -- Please see LICENSE in the repository root for full details. -- This isn't a foreign key, but we really need that to be indexed -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_emails_email_idx ON user_emails (email); diff --git a/crates/storage-pg/migrations/20250410000034_idx_user_passwords_user_fk.sql b/crates/storage-pg/migrations/20250410000034_idx_user_passwords_user_fk.sql index cfc1a9df7..334fb878a 100644 --- a/crates/storage-pg/migrations/20250410000034_idx_user_passwords_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000034_idx_user_passwords_user_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_passwords_user_fk ON user_passwords (user_id); diff --git a/crates/storage-pg/migrations/20250410000035_idx_user_recovery_tickets_session_fk.sql b/crates/storage-pg/migrations/20250410000035_idx_user_recovery_tickets_session_fk.sql index 22537f6a1..6cda37194 100644 --- a/crates/storage-pg/migrations/20250410000035_idx_user_recovery_tickets_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000035_idx_user_recovery_tickets_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_recovery_tickets_session_fk ON user_recovery_tickets (user_recovery_session_id); diff --git a/crates/storage-pg/migrations/20250410000036_idx_user_recovery_tickets_user_email_fk.sql b/crates/storage-pg/migrations/20250410000036_idx_user_recovery_tickets_user_email_fk.sql index 81bee0605..e6561ade5 100644 --- a/crates/storage-pg/migrations/20250410000036_idx_user_recovery_tickets_user_email_fk.sql +++ b/crates/storage-pg/migrations/20250410000036_idx_user_recovery_tickets_user_email_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_recovery_tickets_user_email_fk ON user_recovery_tickets (user_email_id); diff --git a/crates/storage-pg/migrations/20250410000037_idx_user_registrations_email_authentication_fk.sql b/crates/storage-pg/migrations/20250410000037_idx_user_registrations_email_authentication_fk.sql index 3385114d1..95a9ec128 100644 --- a/crates/storage-pg/migrations/20250410000037_idx_user_registrations_email_authentication_fk.sql +++ b/crates/storage-pg/migrations/20250410000037_idx_user_registrations_email_authentication_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_registrations_email_authentication_fk ON user_registrations (email_authentication_id); diff --git a/crates/storage-pg/migrations/20250410000038_idx_user_session_authentications_user_session_fk.sql b/crates/storage-pg/migrations/20250410000038_idx_user_session_authentications_user_session_fk.sql index e71abc0a6..ee78dbd73 100644 --- a/crates/storage-pg/migrations/20250410000038_idx_user_session_authentications_user_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000038_idx_user_session_authentications_user_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_session_authentications_user_session_fk ON user_session_authentications (user_session_id); diff --git a/crates/storage-pg/migrations/20250410000039_idx_user_session_authentications_user_password_fk.sql b/crates/storage-pg/migrations/20250410000039_idx_user_session_authentications_user_password_fk.sql index c6e262d0e..450a0672a 100644 --- a/crates/storage-pg/migrations/20250410000039_idx_user_session_authentications_user_password_fk.sql +++ b/crates/storage-pg/migrations/20250410000039_idx_user_session_authentications_user_password_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_session_authentications_user_password_fk ON user_session_authentications (user_password_id); diff --git a/crates/storage-pg/migrations/20250410000040_idx_user_session_authentications_upstream_oauth_session_fk.sql b/crates/storage-pg/migrations/20250410000040_idx_user_session_authentications_upstream_oauth_session_fk.sql index 98f5728ad..c021595d7 100644 --- a/crates/storage-pg/migrations/20250410000040_idx_user_session_authentications_upstream_oauth_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000040_idx_user_session_authentications_upstream_oauth_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_session_authentications_upstream_oauth_session_fk ON user_session_authentications (upstream_oauth_authorization_session_id); diff --git a/crates/storage-pg/migrations/20250410000041_idx_user_sessions_user_fk.sql b/crates/storage-pg/migrations/20250410000041_idx_user_sessions_user_fk.sql index e2b83345c..443216bd9 100644 --- a/crates/storage-pg/migrations/20250410000041_idx_user_sessions_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000041_idx_user_sessions_user_fk.sql @@ -7,7 +7,7 @@ -- Including the `last_active_at` column lets us effeciently filter in-memory -- for those sessions without fetching the rows, and without including it in the -- index btree -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_sessions_user_fk ON user_sessions (user_id) INCLUDE (last_active_at); diff --git a/crates/storage-pg/migrations/20250410000043_idx_user_terms_user_fk.sql b/crates/storage-pg/migrations/20250410000043_idx_user_terms_user_fk.sql index f95b63a72..f885b557d 100644 --- a/crates/storage-pg/migrations/20250410000043_idx_user_terms_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000043_idx_user_terms_user_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_terms_user_fk ON user_terms (user_id); diff --git a/crates/storage-pg/migrations/20250410000044_idx_users_primary_email_fk.sql b/crates/storage-pg/migrations/20250410000044_idx_users_primary_email_fk.sql index 57ae2d0d0..0a6318efa 100644 --- a/crates/storage-pg/migrations/20250410000044_idx_users_primary_email_fk.sql +++ b/crates/storage-pg/migrations/20250410000044_idx_users_primary_email_fk.sql @@ -6,6 +6,6 @@ -- We don't use this column anymore, but… it will still tank the performance on -- deletions of user_emails if we don't have it -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS users_primary_email_fk ON users (primary_user_email_id); diff --git a/crates/storage-pg/migrations/20250410000045_idx_user_recovery_tickets_ticket_idx.sql b/crates/storage-pg/migrations/20250410000045_idx_user_recovery_tickets_ticket_idx.sql index 3030078f9..648b42fa6 100644 --- a/crates/storage-pg/migrations/20250410000045_idx_user_recovery_tickets_ticket_idx.sql +++ b/crates/storage-pg/migrations/20250410000045_idx_user_recovery_tickets_ticket_idx.sql @@ -5,6 +5,6 @@ -- Please see LICENSE in the repository root for full details. -- This isn't a foreign key, but we really need that to be indexed -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_recovery_tickets_ticket_idx ON user_recovery_tickets (ticket); diff --git a/crates/storage-pg/migrations/20250410121612_users_lower_username_idx.sql b/crates/storage-pg/migrations/20250410121612_users_lower_username_idx.sql index 625e07e1b..0225f6a72 100644 --- a/crates/storage-pg/migrations/20250410121612_users_lower_username_idx.sql +++ b/crates/storage-pg/migrations/20250410121612_users_lower_username_idx.sql @@ -6,5 +6,5 @@ -- Create an index on the username column, lower-cased, so that we can lookup -- usernames in a case-insensitive manner. -CREATE INDEX CONCURRENTLY users_lower_username_idx +CREATE INDEX CONCURRENTLY IF NOT EXISTS users_lower_username_idx ON users (LOWER(username)); diff --git a/crates/storage-pg/migrations/20250602212101_idx_user_registration_token.sql b/crates/storage-pg/migrations/20250602212101_idx_user_registration_token.sql index a25d6358a..957b453bf 100644 --- a/crates/storage-pg/migrations/20250602212101_idx_user_registration_token.sql +++ b/crates/storage-pg/migrations/20250602212101_idx_user_registration_token.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_registrations_user_registration_token_id_fk - ON user_registrations (user_registration_token_id); \ No newline at end of file + ON user_registrations (user_registration_token_id); diff --git a/crates/storage-pg/migrations/20250708155857_idx_user_emails_lower_email.sql b/crates/storage-pg/migrations/20250708155857_idx_user_emails_lower_email.sql index 06b3dde6a..1b4f9afe7 100644 --- a/crates/storage-pg/migrations/20250708155857_idx_user_emails_lower_email.sql +++ b/crates/storage-pg/migrations/20250708155857_idx_user_emails_lower_email.sql @@ -6,6 +6,6 @@ -- When we're looking up an email address, we want to be able to do a case-insensitive -- lookup, so we index the email address lowercase and request it like that -CREATE INDEX CONCURRENTLY +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_emails_lower_email_idx ON user_emails (LOWER(email)); diff --git a/crates/storage-pg/migrations/20250915092635_users_username_trgm_idx.sql b/crates/storage-pg/migrations/20250915092635_users_username_trgm_idx.sql index 5f007d750..3e2cd4dca 100644 --- a/crates/storage-pg/migrations/20250915092635_users_username_trgm_idx.sql +++ b/crates/storage-pg/migrations/20250915092635_users_username_trgm_idx.sql @@ -6,5 +6,5 @@ -- This adds an index on the username field for ILIKE '%search%' operations, -- enabling fuzzy searches of usernames -CREATE INDEX CONCURRENTLY users_username_trgm_idx +CREATE INDEX CONCURRENTLY IF NOT EXISTS users_username_trgm_idx ON users USING gin(username gin_trgm_ops); diff --git a/crates/storage-pg/migrations/20251127145951_user_registration_upstream_oauth_session_idx.sql b/crates/storage-pg/migrations/20251127145951_user_registration_upstream_oauth_session_idx.sql index b9890ffad..665a2ff18 100644 --- a/crates/storage-pg/migrations/20251127145951_user_registration_upstream_oauth_session_idx.sql +++ b/crates/storage-pg/migrations/20251127145951_user_registration_upstream_oauth_session_idx.sql @@ -5,5 +5,5 @@ -- Please see LICENSE in the repository root for full details. -- Index on the new foreign key added by the previous migration -CREATE INDEX CONCURRENTLY user_registrations_upstream_oauth_session_id_idx +CREATE INDEX CONCURRENTLY IF NOT EXISTS user_registrations_upstream_oauth_session_id_idx ON user_registrations (upstream_oauth_authorization_session_id); From e484a810a7e1eae2ea4b4c7d3e4bc1d8420cbffd Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Mon, 1 Dec 2025 11:03:04 +0100 Subject: [PATCH 20/27] 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 21/27] 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 22/27] 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 23/27] 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 24/27] (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 21e45ce972bc44ab8f98edb8bd5deb8a7cacb7e0 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Mon, 1 Dec 2025 12:14:31 +0000 Subject: [PATCH 25/27] Revert "Add 'IF NOT EXISTS' to all 'CREATE INDEX CONCURRENTLY' statements to avoid deadlocks (#5297)" This reverts commit 396950806c08483f793272929b143da85edd3e75, reversing changes made to ef563f33c635e574666fb0cba0bbc85e7c3e7085. --- .../20250410000000_idx_compat_access_tokens_session_fk.sql | 2 +- .../20250410000001_idx_compat_refresh_tokens_session_fk.sql | 2 +- ...250410000002_idx_compat_refresh_tokens_access_token_fk.sql | 2 +- .../migrations/20250410000003_idx_compat_sessions_user_fk.sql | 2 +- .../20250410000004_idx_compat_sessions_user_session_fk.sql | 2 +- .../20250410000006_idx_compat_sso_logins_session_fk.sql | 2 +- .../20250410000007_idx_oauth2_access_tokens_session_fk.sql | 2 +- ...50410000008_idx_oauth2_authorization_grants_session_fk.sql | 2 +- ...250410000009_idx_oauth2_authorization_grants_client_fk.sql | 2 +- .../20250410000010_idx_oauth2_consents_client_fk.sql | 2 +- .../migrations/20250410000011_idx_oauth2_consents_user_fk.sql | 2 +- ...20250410000012_idx_oauth2_device_code_grants_client_fk.sql | 2 +- ...0250410000013_idx_oauth2_device_code_grants_session_fk.sql | 2 +- ...10000014_idx_oauth2_device_code_grants_user_session_fk.sql | 2 +- .../20250410000015_idx_oauth2_refresh_tokens_session_fk.sql | 2 +- ...250410000016_idx_oauth2_refresh_tokens_access_token_fk.sql | 2 +- ...000017_idx_oauth2_refresh_tokens_next_refresh_token_fk.sql | 2 +- .../20250410000018_idx_oauth2_sessions_user_session_fk.sql | 2 +- .../20250410000019_idx_oauth2_sessions_client_fk.sql | 2 +- .../migrations/20250410000020_idx_oauth2_sessions_user_fk.sql | 2 +- .../20250410000022_idx_queue_jobs_started_by_fk.sql | 2 +- .../20250410000023_idx_queue_jobs_next_attempt_fk.sql | 2 +- .../20250410000024_idx_queue_jobs_schedule_name_fk.sql | 2 +- ..._idx_upstream_oauth_authorization_sessions_provider_fk.sql | 2 +- ...0026_idx_upstream_oauth_authorization_sessions_link_fk.sql | 2 +- .../20250410000027_idx_upstream_oauth_links_provider_fk.sql | 2 +- .../20250410000028_idx_upstream_oauth_links_user_fk.sql | 2 +- ..._idx_user_email_authentication_codes_authentication_fk.sql | 2 +- ...0000030_idx_user_email_authentications_user_session_fk.sql | 2 +- ...31_idx_user_email_authentications_user_registration_fk.sql | 2 +- .../migrations/20250410000032_idx_user_emails_user_fk.sql | 2 +- .../migrations/20250410000033_idx_user_emails_email_idx.sql | 2 +- .../migrations/20250410000034_idx_user_passwords_user_fk.sql | 2 +- .../20250410000035_idx_user_recovery_tickets_session_fk.sql | 2 +- ...20250410000036_idx_user_recovery_tickets_user_email_fk.sql | 2 +- ...0000037_idx_user_registrations_email_authentication_fk.sql | 2 +- ...00038_idx_user_session_authentications_user_session_fk.sql | 2 +- ...0039_idx_user_session_authentications_user_password_fk.sql | 2 +- ...user_session_authentications_upstream_oauth_session_fk.sql | 2 +- .../migrations/20250410000041_idx_user_sessions_user_fk.sql | 2 +- .../migrations/20250410000043_idx_user_terms_user_fk.sql | 2 +- .../migrations/20250410000044_idx_users_primary_email_fk.sql | 2 +- .../20250410000045_idx_user_recovery_tickets_ticket_idx.sql | 2 +- .../migrations/20250410121612_users_lower_username_idx.sql | 2 +- .../migrations/20250602212101_idx_user_registration_token.sql | 4 ++-- .../migrations/20250708155857_idx_user_emails_lower_email.sql | 2 +- .../migrations/20250915092635_users_username_trgm_idx.sql | 2 +- ...127145951_user_registration_upstream_oauth_session_idx.sql | 2 +- 48 files changed, 49 insertions(+), 49 deletions(-) diff --git a/crates/storage-pg/migrations/20250410000000_idx_compat_access_tokens_session_fk.sql b/crates/storage-pg/migrations/20250410000000_idx_compat_access_tokens_session_fk.sql index 880488b54..777118233 100644 --- a/crates/storage-pg/migrations/20250410000000_idx_compat_access_tokens_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000000_idx_compat_access_tokens_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY compat_access_tokens_session_fk ON compat_access_tokens (compat_session_id); diff --git a/crates/storage-pg/migrations/20250410000001_idx_compat_refresh_tokens_session_fk.sql b/crates/storage-pg/migrations/20250410000001_idx_compat_refresh_tokens_session_fk.sql index 806a9f1e1..d0e99ad9b 100644 --- a/crates/storage-pg/migrations/20250410000001_idx_compat_refresh_tokens_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000001_idx_compat_refresh_tokens_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY compat_refresh_tokens_session_fk ON compat_refresh_tokens (compat_session_id); diff --git a/crates/storage-pg/migrations/20250410000002_idx_compat_refresh_tokens_access_token_fk.sql b/crates/storage-pg/migrations/20250410000002_idx_compat_refresh_tokens_access_token_fk.sql index 399f3731f..133b00072 100644 --- a/crates/storage-pg/migrations/20250410000002_idx_compat_refresh_tokens_access_token_fk.sql +++ b/crates/storage-pg/migrations/20250410000002_idx_compat_refresh_tokens_access_token_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY compat_refresh_tokens_access_token_fk ON compat_refresh_tokens (compat_access_token_id); diff --git a/crates/storage-pg/migrations/20250410000003_idx_compat_sessions_user_fk.sql b/crates/storage-pg/migrations/20250410000003_idx_compat_sessions_user_fk.sql index 1b038c53f..3234fc8ca 100644 --- a/crates/storage-pg/migrations/20250410000003_idx_compat_sessions_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000003_idx_compat_sessions_user_fk.sql @@ -7,7 +7,7 @@ -- Including the `last_active_at` column lets us effeciently filter in-memory -- for those sessions without fetching the rows, and without including it in the -- index btree -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY compat_sessions_user_fk ON compat_sessions (user_id) INCLUDE (last_active_at); diff --git a/crates/storage-pg/migrations/20250410000004_idx_compat_sessions_user_session_fk.sql b/crates/storage-pg/migrations/20250410000004_idx_compat_sessions_user_session_fk.sql index 52633ad0b..4b35f9960 100644 --- a/crates/storage-pg/migrations/20250410000004_idx_compat_sessions_user_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000004_idx_compat_sessions_user_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY compat_sessions_user_session_fk ON compat_sessions (user_session_id); diff --git a/crates/storage-pg/migrations/20250410000006_idx_compat_sso_logins_session_fk.sql b/crates/storage-pg/migrations/20250410000006_idx_compat_sso_logins_session_fk.sql index da209e586..e88224e66 100644 --- a/crates/storage-pg/migrations/20250410000006_idx_compat_sso_logins_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000006_idx_compat_sso_logins_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY compat_sso_logins_session_fk ON compat_sso_logins (compat_session_id); diff --git a/crates/storage-pg/migrations/20250410000007_idx_oauth2_access_tokens_session_fk.sql b/crates/storage-pg/migrations/20250410000007_idx_oauth2_access_tokens_session_fk.sql index fd44fb6c4..7495af2a3 100644 --- a/crates/storage-pg/migrations/20250410000007_idx_oauth2_access_tokens_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000007_idx_oauth2_access_tokens_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY oauth2_access_tokens_session_fk ON oauth2_access_tokens (oauth2_session_id); diff --git a/crates/storage-pg/migrations/20250410000008_idx_oauth2_authorization_grants_session_fk.sql b/crates/storage-pg/migrations/20250410000008_idx_oauth2_authorization_grants_session_fk.sql index 4a32010f2..dac57bf91 100644 --- a/crates/storage-pg/migrations/20250410000008_idx_oauth2_authorization_grants_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000008_idx_oauth2_authorization_grants_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY oauth2_authorization_grants_session_fk ON oauth2_authorization_grants (oauth2_session_id); diff --git a/crates/storage-pg/migrations/20250410000009_idx_oauth2_authorization_grants_client_fk.sql b/crates/storage-pg/migrations/20250410000009_idx_oauth2_authorization_grants_client_fk.sql index 945d751db..fd5d30344 100644 --- a/crates/storage-pg/migrations/20250410000009_idx_oauth2_authorization_grants_client_fk.sql +++ b/crates/storage-pg/migrations/20250410000009_idx_oauth2_authorization_grants_client_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY oauth2_authorization_grants_client_fk ON oauth2_authorization_grants (oauth2_client_id); diff --git a/crates/storage-pg/migrations/20250410000010_idx_oauth2_consents_client_fk.sql b/crates/storage-pg/migrations/20250410000010_idx_oauth2_consents_client_fk.sql index b0928ef5e..ae8e0b5dd 100644 --- a/crates/storage-pg/migrations/20250410000010_idx_oauth2_consents_client_fk.sql +++ b/crates/storage-pg/migrations/20250410000010_idx_oauth2_consents_client_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY oauth2_consents_client_fk ON oauth2_consents (oauth2_client_id); diff --git a/crates/storage-pg/migrations/20250410000011_idx_oauth2_consents_user_fk.sql b/crates/storage-pg/migrations/20250410000011_idx_oauth2_consents_user_fk.sql index 89751503b..c9226b7d6 100644 --- a/crates/storage-pg/migrations/20250410000011_idx_oauth2_consents_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000011_idx_oauth2_consents_user_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY oauth2_consents_user_fk ON oauth2_consents (user_id); diff --git a/crates/storage-pg/migrations/20250410000012_idx_oauth2_device_code_grants_client_fk.sql b/crates/storage-pg/migrations/20250410000012_idx_oauth2_device_code_grants_client_fk.sql index 3f97117f7..a9981fe4b 100644 --- a/crates/storage-pg/migrations/20250410000012_idx_oauth2_device_code_grants_client_fk.sql +++ b/crates/storage-pg/migrations/20250410000012_idx_oauth2_device_code_grants_client_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY oauth2_device_code_grants_client_fk ON oauth2_device_code_grant (oauth2_client_id); diff --git a/crates/storage-pg/migrations/20250410000013_idx_oauth2_device_code_grants_session_fk.sql b/crates/storage-pg/migrations/20250410000013_idx_oauth2_device_code_grants_session_fk.sql index 7400dcd5d..be8f685d1 100644 --- a/crates/storage-pg/migrations/20250410000013_idx_oauth2_device_code_grants_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000013_idx_oauth2_device_code_grants_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY oauth2_device_code_grants_session_fk ON oauth2_device_code_grant (oauth2_session_id); diff --git a/crates/storage-pg/migrations/20250410000014_idx_oauth2_device_code_grants_user_session_fk.sql b/crates/storage-pg/migrations/20250410000014_idx_oauth2_device_code_grants_user_session_fk.sql index 8b07a4366..f3f6613d7 100644 --- a/crates/storage-pg/migrations/20250410000014_idx_oauth2_device_code_grants_user_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000014_idx_oauth2_device_code_grants_user_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY oauth2_device_code_grants_user_session_fk ON oauth2_device_code_grant (user_session_id); diff --git a/crates/storage-pg/migrations/20250410000015_idx_oauth2_refresh_tokens_session_fk.sql b/crates/storage-pg/migrations/20250410000015_idx_oauth2_refresh_tokens_session_fk.sql index 7da896268..897d247c8 100644 --- a/crates/storage-pg/migrations/20250410000015_idx_oauth2_refresh_tokens_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000015_idx_oauth2_refresh_tokens_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY oauth2_refresh_tokens_session_fk ON oauth2_refresh_tokens (oauth2_session_id); diff --git a/crates/storage-pg/migrations/20250410000016_idx_oauth2_refresh_tokens_access_token_fk.sql b/crates/storage-pg/migrations/20250410000016_idx_oauth2_refresh_tokens_access_token_fk.sql index f6059d223..5d391e6a5 100644 --- a/crates/storage-pg/migrations/20250410000016_idx_oauth2_refresh_tokens_access_token_fk.sql +++ b/crates/storage-pg/migrations/20250410000016_idx_oauth2_refresh_tokens_access_token_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY oauth2_refresh_tokens_access_token_fk ON oauth2_refresh_tokens (oauth2_access_token_id); diff --git a/crates/storage-pg/migrations/20250410000017_idx_oauth2_refresh_tokens_next_refresh_token_fk.sql b/crates/storage-pg/migrations/20250410000017_idx_oauth2_refresh_tokens_next_refresh_token_fk.sql index 40fd117cc..f593f1d28 100644 --- a/crates/storage-pg/migrations/20250410000017_idx_oauth2_refresh_tokens_next_refresh_token_fk.sql +++ b/crates/storage-pg/migrations/20250410000017_idx_oauth2_refresh_tokens_next_refresh_token_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY oauth2_refresh_tokens_next_refresh_token_fk ON oauth2_refresh_tokens (next_oauth2_refresh_token_id); diff --git a/crates/storage-pg/migrations/20250410000018_idx_oauth2_sessions_user_session_fk.sql b/crates/storage-pg/migrations/20250410000018_idx_oauth2_sessions_user_session_fk.sql index b2639e714..44d6d0e3c 100644 --- a/crates/storage-pg/migrations/20250410000018_idx_oauth2_sessions_user_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000018_idx_oauth2_sessions_user_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY oauth2_sessions_user_session_fk ON oauth2_sessions (user_session_id); diff --git a/crates/storage-pg/migrations/20250410000019_idx_oauth2_sessions_client_fk.sql b/crates/storage-pg/migrations/20250410000019_idx_oauth2_sessions_client_fk.sql index 341d2dea1..ad868a310 100644 --- a/crates/storage-pg/migrations/20250410000019_idx_oauth2_sessions_client_fk.sql +++ b/crates/storage-pg/migrations/20250410000019_idx_oauth2_sessions_client_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY oauth2_sessions_client_fk ON oauth2_sessions (oauth2_client_id); diff --git a/crates/storage-pg/migrations/20250410000020_idx_oauth2_sessions_user_fk.sql b/crates/storage-pg/migrations/20250410000020_idx_oauth2_sessions_user_fk.sql index d7b5f52af..b47bb00a8 100644 --- a/crates/storage-pg/migrations/20250410000020_idx_oauth2_sessions_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000020_idx_oauth2_sessions_user_fk.sql @@ -7,7 +7,7 @@ -- Including the `last_active_at` column lets us effeciently filter in-memory -- for those sessions without fetching the rows, and without including it in the -- index btree -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY oauth2_sessions_user_fk ON oauth2_sessions (user_id) INCLUDE (last_active_at); diff --git a/crates/storage-pg/migrations/20250410000022_idx_queue_jobs_started_by_fk.sql b/crates/storage-pg/migrations/20250410000022_idx_queue_jobs_started_by_fk.sql index 272785332..38bb79ede 100644 --- a/crates/storage-pg/migrations/20250410000022_idx_queue_jobs_started_by_fk.sql +++ b/crates/storage-pg/migrations/20250410000022_idx_queue_jobs_started_by_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY queue_jobs_started_by_fk ON queue_jobs (started_by); diff --git a/crates/storage-pg/migrations/20250410000023_idx_queue_jobs_next_attempt_fk.sql b/crates/storage-pg/migrations/20250410000023_idx_queue_jobs_next_attempt_fk.sql index 9824557e1..ea611f76e 100644 --- a/crates/storage-pg/migrations/20250410000023_idx_queue_jobs_next_attempt_fk.sql +++ b/crates/storage-pg/migrations/20250410000023_idx_queue_jobs_next_attempt_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY queue_jobs_next_attempt_fk ON queue_jobs (next_attempt_id); diff --git a/crates/storage-pg/migrations/20250410000024_idx_queue_jobs_schedule_name_fk.sql b/crates/storage-pg/migrations/20250410000024_idx_queue_jobs_schedule_name_fk.sql index 4c70fea50..02b2bfaea 100644 --- a/crates/storage-pg/migrations/20250410000024_idx_queue_jobs_schedule_name_fk.sql +++ b/crates/storage-pg/migrations/20250410000024_idx_queue_jobs_schedule_name_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY queue_jobs_schedule_name_fk ON queue_jobs (schedule_name); diff --git a/crates/storage-pg/migrations/20250410000025_idx_upstream_oauth_authorization_sessions_provider_fk.sql b/crates/storage-pg/migrations/20250410000025_idx_upstream_oauth_authorization_sessions_provider_fk.sql index b86b1e0ee..f5e388613 100644 --- a/crates/storage-pg/migrations/20250410000025_idx_upstream_oauth_authorization_sessions_provider_fk.sql +++ b/crates/storage-pg/migrations/20250410000025_idx_upstream_oauth_authorization_sessions_provider_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY upstream_oauth_authorization_sessions_provider_fk ON upstream_oauth_authorization_sessions (upstream_oauth_provider_id); diff --git a/crates/storage-pg/migrations/20250410000026_idx_upstream_oauth_authorization_sessions_link_fk.sql b/crates/storage-pg/migrations/20250410000026_idx_upstream_oauth_authorization_sessions_link_fk.sql index 1b296090a..aa08e7fa1 100644 --- a/crates/storage-pg/migrations/20250410000026_idx_upstream_oauth_authorization_sessions_link_fk.sql +++ b/crates/storage-pg/migrations/20250410000026_idx_upstream_oauth_authorization_sessions_link_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY upstream_oauth_authorization_sessions_link_fk ON upstream_oauth_authorization_sessions (upstream_oauth_link_id); diff --git a/crates/storage-pg/migrations/20250410000027_idx_upstream_oauth_links_provider_fk.sql b/crates/storage-pg/migrations/20250410000027_idx_upstream_oauth_links_provider_fk.sql index 55dd22612..9f6a9301f 100644 --- a/crates/storage-pg/migrations/20250410000027_idx_upstream_oauth_links_provider_fk.sql +++ b/crates/storage-pg/migrations/20250410000027_idx_upstream_oauth_links_provider_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY upstream_oauth_links_provider_fk ON upstream_oauth_links (upstream_oauth_provider_id); diff --git a/crates/storage-pg/migrations/20250410000028_idx_upstream_oauth_links_user_fk.sql b/crates/storage-pg/migrations/20250410000028_idx_upstream_oauth_links_user_fk.sql index 21ba23e40..af7791f18 100644 --- a/crates/storage-pg/migrations/20250410000028_idx_upstream_oauth_links_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000028_idx_upstream_oauth_links_user_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY upstream_oauth_links_user_fk ON upstream_oauth_links (user_id); diff --git a/crates/storage-pg/migrations/20250410000029_idx_user_email_authentication_codes_authentication_fk.sql b/crates/storage-pg/migrations/20250410000029_idx_user_email_authentication_codes_authentication_fk.sql index 591409632..889fac2ab 100644 --- a/crates/storage-pg/migrations/20250410000029_idx_user_email_authentication_codes_authentication_fk.sql +++ b/crates/storage-pg/migrations/20250410000029_idx_user_email_authentication_codes_authentication_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_email_authentication_codes_authentication_fk ON user_email_authentication_codes (user_email_authentication_id); diff --git a/crates/storage-pg/migrations/20250410000030_idx_user_email_authentications_user_session_fk.sql b/crates/storage-pg/migrations/20250410000030_idx_user_email_authentications_user_session_fk.sql index bd1558b7d..3a6284f67 100644 --- a/crates/storage-pg/migrations/20250410000030_idx_user_email_authentications_user_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000030_idx_user_email_authentications_user_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_email_authentications_user_session_fk ON user_email_authentications (user_session_id); diff --git a/crates/storage-pg/migrations/20250410000031_idx_user_email_authentications_user_registration_fk.sql b/crates/storage-pg/migrations/20250410000031_idx_user_email_authentications_user_registration_fk.sql index 7f233f7c3..9ad343ac9 100644 --- a/crates/storage-pg/migrations/20250410000031_idx_user_email_authentications_user_registration_fk.sql +++ b/crates/storage-pg/migrations/20250410000031_idx_user_email_authentications_user_registration_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_email_authentications_user_registration_fk ON user_email_authentications (user_registration_id); diff --git a/crates/storage-pg/migrations/20250410000032_idx_user_emails_user_fk.sql b/crates/storage-pg/migrations/20250410000032_idx_user_emails_user_fk.sql index c4834c714..f66117d43 100644 --- a/crates/storage-pg/migrations/20250410000032_idx_user_emails_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000032_idx_user_emails_user_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_emails_user_fk ON user_emails (user_id); diff --git a/crates/storage-pg/migrations/20250410000033_idx_user_emails_email_idx.sql b/crates/storage-pg/migrations/20250410000033_idx_user_emails_email_idx.sql index b0f2ec4ac..e75e1e028 100644 --- a/crates/storage-pg/migrations/20250410000033_idx_user_emails_email_idx.sql +++ b/crates/storage-pg/migrations/20250410000033_idx_user_emails_email_idx.sql @@ -5,6 +5,6 @@ -- Please see LICENSE in the repository root for full details. -- This isn't a foreign key, but we really need that to be indexed -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_emails_email_idx ON user_emails (email); diff --git a/crates/storage-pg/migrations/20250410000034_idx_user_passwords_user_fk.sql b/crates/storage-pg/migrations/20250410000034_idx_user_passwords_user_fk.sql index 334fb878a..cfc1a9df7 100644 --- a/crates/storage-pg/migrations/20250410000034_idx_user_passwords_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000034_idx_user_passwords_user_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_passwords_user_fk ON user_passwords (user_id); diff --git a/crates/storage-pg/migrations/20250410000035_idx_user_recovery_tickets_session_fk.sql b/crates/storage-pg/migrations/20250410000035_idx_user_recovery_tickets_session_fk.sql index 6cda37194..22537f6a1 100644 --- a/crates/storage-pg/migrations/20250410000035_idx_user_recovery_tickets_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000035_idx_user_recovery_tickets_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_recovery_tickets_session_fk ON user_recovery_tickets (user_recovery_session_id); diff --git a/crates/storage-pg/migrations/20250410000036_idx_user_recovery_tickets_user_email_fk.sql b/crates/storage-pg/migrations/20250410000036_idx_user_recovery_tickets_user_email_fk.sql index e6561ade5..81bee0605 100644 --- a/crates/storage-pg/migrations/20250410000036_idx_user_recovery_tickets_user_email_fk.sql +++ b/crates/storage-pg/migrations/20250410000036_idx_user_recovery_tickets_user_email_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_recovery_tickets_user_email_fk ON user_recovery_tickets (user_email_id); diff --git a/crates/storage-pg/migrations/20250410000037_idx_user_registrations_email_authentication_fk.sql b/crates/storage-pg/migrations/20250410000037_idx_user_registrations_email_authentication_fk.sql index 95a9ec128..3385114d1 100644 --- a/crates/storage-pg/migrations/20250410000037_idx_user_registrations_email_authentication_fk.sql +++ b/crates/storage-pg/migrations/20250410000037_idx_user_registrations_email_authentication_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_registrations_email_authentication_fk ON user_registrations (email_authentication_id); diff --git a/crates/storage-pg/migrations/20250410000038_idx_user_session_authentications_user_session_fk.sql b/crates/storage-pg/migrations/20250410000038_idx_user_session_authentications_user_session_fk.sql index ee78dbd73..e71abc0a6 100644 --- a/crates/storage-pg/migrations/20250410000038_idx_user_session_authentications_user_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000038_idx_user_session_authentications_user_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_session_authentications_user_session_fk ON user_session_authentications (user_session_id); diff --git a/crates/storage-pg/migrations/20250410000039_idx_user_session_authentications_user_password_fk.sql b/crates/storage-pg/migrations/20250410000039_idx_user_session_authentications_user_password_fk.sql index 450a0672a..c6e262d0e 100644 --- a/crates/storage-pg/migrations/20250410000039_idx_user_session_authentications_user_password_fk.sql +++ b/crates/storage-pg/migrations/20250410000039_idx_user_session_authentications_user_password_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_session_authentications_user_password_fk ON user_session_authentications (user_password_id); diff --git a/crates/storage-pg/migrations/20250410000040_idx_user_session_authentications_upstream_oauth_session_fk.sql b/crates/storage-pg/migrations/20250410000040_idx_user_session_authentications_upstream_oauth_session_fk.sql index c021595d7..98f5728ad 100644 --- a/crates/storage-pg/migrations/20250410000040_idx_user_session_authentications_upstream_oauth_session_fk.sql +++ b/crates/storage-pg/migrations/20250410000040_idx_user_session_authentications_upstream_oauth_session_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_session_authentications_upstream_oauth_session_fk ON user_session_authentications (upstream_oauth_authorization_session_id); diff --git a/crates/storage-pg/migrations/20250410000041_idx_user_sessions_user_fk.sql b/crates/storage-pg/migrations/20250410000041_idx_user_sessions_user_fk.sql index 443216bd9..e2b83345c 100644 --- a/crates/storage-pg/migrations/20250410000041_idx_user_sessions_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000041_idx_user_sessions_user_fk.sql @@ -7,7 +7,7 @@ -- Including the `last_active_at` column lets us effeciently filter in-memory -- for those sessions without fetching the rows, and without including it in the -- index btree -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_sessions_user_fk ON user_sessions (user_id) INCLUDE (last_active_at); diff --git a/crates/storage-pg/migrations/20250410000043_idx_user_terms_user_fk.sql b/crates/storage-pg/migrations/20250410000043_idx_user_terms_user_fk.sql index f885b557d..f95b63a72 100644 --- a/crates/storage-pg/migrations/20250410000043_idx_user_terms_user_fk.sql +++ b/crates/storage-pg/migrations/20250410000043_idx_user_terms_user_fk.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_terms_user_fk ON user_terms (user_id); diff --git a/crates/storage-pg/migrations/20250410000044_idx_users_primary_email_fk.sql b/crates/storage-pg/migrations/20250410000044_idx_users_primary_email_fk.sql index 0a6318efa..57ae2d0d0 100644 --- a/crates/storage-pg/migrations/20250410000044_idx_users_primary_email_fk.sql +++ b/crates/storage-pg/migrations/20250410000044_idx_users_primary_email_fk.sql @@ -6,6 +6,6 @@ -- We don't use this column anymore, but… it will still tank the performance on -- deletions of user_emails if we don't have it -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY users_primary_email_fk ON users (primary_user_email_id); diff --git a/crates/storage-pg/migrations/20250410000045_idx_user_recovery_tickets_ticket_idx.sql b/crates/storage-pg/migrations/20250410000045_idx_user_recovery_tickets_ticket_idx.sql index 648b42fa6..3030078f9 100644 --- a/crates/storage-pg/migrations/20250410000045_idx_user_recovery_tickets_ticket_idx.sql +++ b/crates/storage-pg/migrations/20250410000045_idx_user_recovery_tickets_ticket_idx.sql @@ -5,6 +5,6 @@ -- Please see LICENSE in the repository root for full details. -- This isn't a foreign key, but we really need that to be indexed -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_recovery_tickets_ticket_idx ON user_recovery_tickets (ticket); diff --git a/crates/storage-pg/migrations/20250410121612_users_lower_username_idx.sql b/crates/storage-pg/migrations/20250410121612_users_lower_username_idx.sql index 0225f6a72..625e07e1b 100644 --- a/crates/storage-pg/migrations/20250410121612_users_lower_username_idx.sql +++ b/crates/storage-pg/migrations/20250410121612_users_lower_username_idx.sql @@ -6,5 +6,5 @@ -- Create an index on the username column, lower-cased, so that we can lookup -- usernames in a case-insensitive manner. -CREATE INDEX CONCURRENTLY IF NOT EXISTS users_lower_username_idx +CREATE INDEX CONCURRENTLY users_lower_username_idx ON users (LOWER(username)); diff --git a/crates/storage-pg/migrations/20250602212101_idx_user_registration_token.sql b/crates/storage-pg/migrations/20250602212101_idx_user_registration_token.sql index 957b453bf..a25d6358a 100644 --- a/crates/storage-pg/migrations/20250602212101_idx_user_registration_token.sql +++ b/crates/storage-pg/migrations/20250602212101_idx_user_registration_token.sql @@ -4,6 +4,6 @@ -- SPDX-License-Identifier: AGPL-3.0-only -- Please see LICENSE in the repository root for full details. -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_registrations_user_registration_token_id_fk - ON user_registrations (user_registration_token_id); + ON user_registrations (user_registration_token_id); \ No newline at end of file diff --git a/crates/storage-pg/migrations/20250708155857_idx_user_emails_lower_email.sql b/crates/storage-pg/migrations/20250708155857_idx_user_emails_lower_email.sql index 1b4f9afe7..06b3dde6a 100644 --- a/crates/storage-pg/migrations/20250708155857_idx_user_emails_lower_email.sql +++ b/crates/storage-pg/migrations/20250708155857_idx_user_emails_lower_email.sql @@ -6,6 +6,6 @@ -- When we're looking up an email address, we want to be able to do a case-insensitive -- lookup, so we index the email address lowercase and request it like that -CREATE INDEX CONCURRENTLY IF NOT EXISTS +CREATE INDEX CONCURRENTLY user_emails_lower_email_idx ON user_emails (LOWER(email)); diff --git a/crates/storage-pg/migrations/20250915092635_users_username_trgm_idx.sql b/crates/storage-pg/migrations/20250915092635_users_username_trgm_idx.sql index 3e2cd4dca..5f007d750 100644 --- a/crates/storage-pg/migrations/20250915092635_users_username_trgm_idx.sql +++ b/crates/storage-pg/migrations/20250915092635_users_username_trgm_idx.sql @@ -6,5 +6,5 @@ -- This adds an index on the username field for ILIKE '%search%' operations, -- enabling fuzzy searches of usernames -CREATE INDEX CONCURRENTLY IF NOT EXISTS users_username_trgm_idx +CREATE INDEX CONCURRENTLY users_username_trgm_idx ON users USING gin(username gin_trgm_ops); diff --git a/crates/storage-pg/migrations/20251127145951_user_registration_upstream_oauth_session_idx.sql b/crates/storage-pg/migrations/20251127145951_user_registration_upstream_oauth_session_idx.sql index 665a2ff18..b9890ffad 100644 --- a/crates/storage-pg/migrations/20251127145951_user_registration_upstream_oauth_session_idx.sql +++ b/crates/storage-pg/migrations/20251127145951_user_registration_upstream_oauth_session_idx.sql @@ -5,5 +5,5 @@ -- Please see LICENSE in the repository root for full details. -- Index on the new foreign key added by the previous migration -CREATE INDEX CONCURRENTLY IF NOT EXISTS user_registrations_upstream_oauth_session_id_idx +CREATE INDEX CONCURRENTLY user_registrations_upstream_oauth_session_id_idx ON user_registrations (upstream_oauth_authorization_session_id); 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 26/27] 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 6c066ca972b4e9259ef770adf098b6f6c80ac6fb Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 2 Dec 2025 14:53:55 +0000 Subject: [PATCH 27/27] 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": {