From 182e67fef2d537ddc37958af21956f447d43a427 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:54:35 +0000 Subject: [PATCH 01/43] build(deps): bump actions/setup-node from 6.1.0 to 6.2.0 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.1.0 to 6.2.0. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v6.1.0...v6.2.0) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 6 +++--- .github/workflows/docs.yaml | 2 +- .github/workflows/release-branch.yaml | 2 +- .github/workflows/translations-download.yaml | 2 +- .github/workflows/translations-upload.yaml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ff68ade15..5c0611acf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -64,7 +64,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.2.0 with: node-version: 24 @@ -88,7 +88,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.2.0 with: node-version: 24 @@ -112,7 +112,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.2.0 with: node-version: 24 diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 3053b56fb..6b0ec68b9 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -39,7 +39,7 @@ jobs: tool: mdbook - name: Install Node - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.2.0 with: node-version: 24 diff --git a/.github/workflows/release-branch.yaml b/.github/workflows/release-branch.yaml index e058a62be..0b46539be 100644 --- a/.github/workflows/release-branch.yaml +++ b/.github/workflows/release-branch.yaml @@ -64,7 +64,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.2.0 with: node-version: 24 diff --git a/.github/workflows/translations-download.yaml b/.github/workflows/translations-download.yaml index 70419fce0..c29333e0a 100644 --- a/.github/workflows/translations-download.yaml +++ b/.github/workflows/translations-download.yaml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.2.0 with: node-version: 24 diff --git a/.github/workflows/translations-upload.yaml b/.github/workflows/translations-upload.yaml index ba723278e..453a0d46d 100644 --- a/.github/workflows/translations-upload.yaml +++ b/.github/workflows/translations-upload.yaml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.2.0 with: node-version: 24 From 4cdf275c73a5c1d8f3ae57440652965b2dbe1754 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:54:16 +0000 Subject: [PATCH 02/43] build(deps): bump peter-evans/create-pull-request from 8.0.0 to 8.1.0 Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 8.0.0 to 8.1.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/v8.0.0...v8.1.0) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... 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 70419fce0..640f602a7 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@v8.0.0 + uses: peter-evans/create-pull-request@v8.1.0 with: sign-commits: true token: ${{ secrets.BOT_GITHUB_TOKEN }} From 868d6cdc21c40c7b2e4ce1dbed27996bff25ffc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 09:56:39 +0000 Subject: [PATCH 03/43] build(deps): bump lodash from 4.17.21 to 4.17.23 in /frontend Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.17.23 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 837f12940..1fcfd0e1f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9968,9 +9968,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, From 028db8808d73c80ee444bbf5ab13b0dce128beaa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:54:26 +0000 Subject: [PATCH 04/43] build(deps): bump docker/login-action from 3.6.0 to 3.7.0 Bumps [docker/login-action](https://github.com/docker/login-action) from 3.6.0 to 3.7.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v3.6.0...v3.7.0) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 3.7.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 fbbf4fb2d..0eb691e95 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -268,7 +268,7 @@ jobs: mirrors = ["mirror.gcr.io"] - name: Login to GitHub Container Registry - uses: docker/login-action@v3.6.0 + uses: docker/login-action@v3.7.0 with: registry: ghcr.io username: ${{ github.repository_owner }} From a8b6cc747963720a3e0daee2e9c2fb6ece7543c3 Mon Sep 17 00:00:00 2001 From: Devon Hudson Date: Tue, 3 Feb 2026 10:23:51 -0700 Subject: [PATCH 05/43] Update bytes version --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba314ee8c..bc2a6d1d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -805,9 +805,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] From 07068143ef1266b75d28961a6e557c71c8e0f611 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:03:02 +0000 Subject: [PATCH 06/43] Translations updates --- frontend/.storybook/locales.ts | 66 +++++++++++++++++----------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/frontend/.storybook/locales.ts b/frontend/.storybook/locales.ts index 9b0e3684d..9a545003e 100644 --- a/frontend/.storybook/locales.ts +++ b/frontend/.storybook/locales.ts @@ -27,7 +27,7 @@ export type LocalazyMetadata = { }; const localazyMetadata: LocalazyMetadata = { - projectUrl: "https://localazy.com/p/matrix-authentication-service!v1.10", + projectUrl: "https://localazy.com/p/matrix-authentication-service", baseLocale: "en", languages: [ { @@ -181,22 +181,22 @@ const localazyMetadata: LocalazyMetadata = { file: "frontend.json", path: "", cdnFiles: { - "cs": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/cs/frontend.json", - "da": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/da/frontend.json", - "de": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/de/frontend.json", - "en": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/en/frontend.json", - "et": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/et/frontend.json", - "fi": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fi/frontend.json", - "fr": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fr/frontend.json", - "hu": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/hu/frontend.json", - "nb_NO": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nb-NO/frontend.json", - "nl": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nl/frontend.json", - "pl": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pl/frontend.json", - "pt": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt/frontend.json", - "ru": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json", - "sv": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json", - "uk": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json", - "zh#Hans": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json" + "cs": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/cs/frontend.json", + "da": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/da/frontend.json", + "de": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/de/frontend.json", + "en": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/en/frontend.json", + "et": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/et/frontend.json", + "fi": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fi/frontend.json", + "fr": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fr/frontend.json", + "hu": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/hu/frontend.json", + "nb_NO": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nb-NO/frontend.json", + "nl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nl/frontend.json", + "pl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pl/frontend.json", + "pt": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt/frontend.json", + "ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json", + "sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json", + "uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json", + "zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json" } }, { @@ -204,22 +204,22 @@ const localazyMetadata: LocalazyMetadata = { file: "file.json", path: "", cdnFiles: { - "cs": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/cs/file.json", - "da": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/da/file.json", - "de": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/de/file.json", - "en": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/en/file.json", - "et": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/et/file.json", - "fi": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fi/file.json", - "fr": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fr/file.json", - "hu": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/hu/file.json", - "nb_NO": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nb-NO/file.json", - "nl": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nl/file.json", - "pl": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pl/file.json", - "pt": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt/file.json", - "ru": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json", - "sv": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json", - "uk": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json", - "zh#Hans": "https://delivery.localazy.com/_a6657523900524156441ad8bd4f1/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json" + "cs": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/cs/file.json", + "da": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/da/file.json", + "de": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/de/file.json", + "en": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/en/file.json", + "et": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/et/file.json", + "fi": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fi/file.json", + "fr": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fr/file.json", + "hu": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/hu/file.json", + "nb_NO": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nb-NO/file.json", + "nl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nl/file.json", + "pl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pl/file.json", + "pt": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt/file.json", + "ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json", + "sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json", + "uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json", + "zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json" } } ] From f92d3eb7fff72dc6d6d0a9fece93a3904ce4459f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:15:50 +0000 Subject: [PATCH 07/43] 1.11.0-rc.0 --- Cargo.lock | 56 +++++++++++++++++++++++++------------------------- Cargo.toml | 60 +++++++++++++++++++++++++++--------------------------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc2a6d1d5..9d54d9e89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3097,7 +3097,7 @@ dependencies = [ [[package]] name = "mas-axum-utils" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "anyhow", "axum", @@ -3131,7 +3131,7 @@ dependencies = [ [[package]] name = "mas-cli" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "anyhow", "axum", @@ -3206,7 +3206,7 @@ dependencies = [ [[package]] name = "mas-config" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "anyhow", "camino", @@ -3237,7 +3237,7 @@ dependencies = [ [[package]] name = "mas-context" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "console", "opentelemetry", @@ -3253,7 +3253,7 @@ dependencies = [ [[package]] name = "mas-data-model" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "base64ct", "chrono", @@ -3276,7 +3276,7 @@ dependencies = [ [[package]] name = "mas-email" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "async-trait", "lettre", @@ -3287,7 +3287,7 @@ dependencies = [ [[package]] name = "mas-handlers" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "aide", "anyhow", @@ -3367,7 +3367,7 @@ dependencies = [ [[package]] name = "mas-http" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "futures-util", "headers", @@ -3388,7 +3388,7 @@ dependencies = [ [[package]] name = "mas-i18n" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "camino", "icu_calendar", @@ -3410,7 +3410,7 @@ dependencies = [ [[package]] name = "mas-i18n-scan" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "camino", "clap", @@ -3424,7 +3424,7 @@ dependencies = [ [[package]] name = "mas-iana" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "schemars 0.9.0", "serde", @@ -3432,7 +3432,7 @@ dependencies = [ [[package]] name = "mas-iana-codegen" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "anyhow", "async-trait", @@ -3449,7 +3449,7 @@ dependencies = [ [[package]] name = "mas-jose" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "base64ct", "chrono", @@ -3479,7 +3479,7 @@ dependencies = [ [[package]] name = "mas-keystore" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "aead", "base64ct", @@ -3507,7 +3507,7 @@ dependencies = [ [[package]] name = "mas-listener" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "anyhow", "bytes", @@ -3531,7 +3531,7 @@ dependencies = [ [[package]] name = "mas-matrix" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "anyhow", "async-trait", @@ -3541,7 +3541,7 @@ dependencies = [ [[package]] name = "mas-matrix-synapse" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "anyhow", "async-trait", @@ -3558,7 +3558,7 @@ dependencies = [ [[package]] name = "mas-oidc-client" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "assert_matches", "async-trait", @@ -3594,7 +3594,7 @@ dependencies = [ [[package]] name = "mas-policy" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "anyhow", "arc-swap", @@ -3611,7 +3611,7 @@ dependencies = [ [[package]] name = "mas-router" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "axum", "serde", @@ -3622,7 +3622,7 @@ dependencies = [ [[package]] name = "mas-spa" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "camino", "serde", @@ -3631,7 +3631,7 @@ dependencies = [ [[package]] name = "mas-storage" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "async-trait", "chrono", @@ -3653,7 +3653,7 @@ dependencies = [ [[package]] name = "mas-storage-pg" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "async-trait", "chrono", @@ -3683,7 +3683,7 @@ dependencies = [ [[package]] name = "mas-tasks" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "anyhow", "async-trait", @@ -3715,7 +3715,7 @@ dependencies = [ [[package]] name = "mas-templates" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "anyhow", "arc-swap", @@ -3747,7 +3747,7 @@ dependencies = [ [[package]] name = "mas-tower" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "http", "opentelemetry", @@ -4016,7 +4016,7 @@ dependencies = [ [[package]] name = "oauth2-types" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "assert_matches", "base64ct", @@ -6120,7 +6120,7 @@ dependencies = [ [[package]] name = "syn2mas" -version = "1.10.0" +version = "1.11.0-rc.0" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index 274b9510b..0f98906bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = ["crates/*"] resolver = "2" # Updated in the CI with a `sed` command -package.version = "1.10.0" +package.version = "1.11.0-rc.0" package.license = "AGPL-3.0-only OR LicenseRef-Element-Commercial" package.authors = ["Element Backend Team"] package.edition = "2024" @@ -34,35 +34,35 @@ broken_intra_doc_links = "deny" [workspace.dependencies] # Workspace crates -mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.10.0" } -mas-cli = { path = "./crates/cli/", version = "=1.10.0" } -mas-config = { path = "./crates/config/", version = "=1.10.0" } -mas-context = { path = "./crates/context/", version = "=1.10.0" } -mas-data-model = { path = "./crates/data-model/", version = "=1.10.0" } -mas-email = { path = "./crates/email/", version = "=1.10.0" } -mas-graphql = { path = "./crates/graphql/", version = "=1.10.0" } -mas-handlers = { path = "./crates/handlers/", version = "=1.10.0" } -mas-http = { path = "./crates/http/", version = "=1.10.0" } -mas-i18n = { path = "./crates/i18n/", version = "=1.10.0" } -mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.10.0" } -mas-iana = { path = "./crates/iana/", version = "=1.10.0" } -mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.10.0" } -mas-jose = { path = "./crates/jose/", version = "=1.10.0" } -mas-keystore = { path = "./crates/keystore/", version = "=1.10.0" } -mas-listener = { path = "./crates/listener/", version = "=1.10.0" } -mas-matrix = { path = "./crates/matrix/", version = "=1.10.0" } -mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.10.0" } -mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.10.0" } -mas-policy = { path = "./crates/policy/", version = "=1.10.0" } -mas-router = { path = "./crates/router/", version = "=1.10.0" } -mas-spa = { path = "./crates/spa/", version = "=1.10.0" } -mas-storage = { path = "./crates/storage/", version = "=1.10.0" } -mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.10.0" } -mas-tasks = { path = "./crates/tasks/", version = "=1.10.0" } -mas-templates = { path = "./crates/templates/", version = "=1.10.0" } -mas-tower = { path = "./crates/tower/", version = "=1.10.0" } -oauth2-types = { path = "./crates/oauth2-types/", version = "=1.10.0" } -syn2mas = { path = "./crates/syn2mas", version = "=1.10.0" } +mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.11.0-rc.0" } +mas-cli = { path = "./crates/cli/", version = "=1.11.0-rc.0" } +mas-config = { path = "./crates/config/", version = "=1.11.0-rc.0" } +mas-context = { path = "./crates/context/", version = "=1.11.0-rc.0" } +mas-data-model = { path = "./crates/data-model/", version = "=1.11.0-rc.0" } +mas-email = { path = "./crates/email/", version = "=1.11.0-rc.0" } +mas-graphql = { path = "./crates/graphql/", version = "=1.11.0-rc.0" } +mas-handlers = { path = "./crates/handlers/", version = "=1.11.0-rc.0" } +mas-http = { path = "./crates/http/", version = "=1.11.0-rc.0" } +mas-i18n = { path = "./crates/i18n/", version = "=1.11.0-rc.0" } +mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.11.0-rc.0" } +mas-iana = { path = "./crates/iana/", version = "=1.11.0-rc.0" } +mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.11.0-rc.0" } +mas-jose = { path = "./crates/jose/", version = "=1.11.0-rc.0" } +mas-keystore = { path = "./crates/keystore/", version = "=1.11.0-rc.0" } +mas-listener = { path = "./crates/listener/", version = "=1.11.0-rc.0" } +mas-matrix = { path = "./crates/matrix/", version = "=1.11.0-rc.0" } +mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.11.0-rc.0" } +mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.11.0-rc.0" } +mas-policy = { path = "./crates/policy/", version = "=1.11.0-rc.0" } +mas-router = { path = "./crates/router/", version = "=1.11.0-rc.0" } +mas-spa = { path = "./crates/spa/", version = "=1.11.0-rc.0" } +mas-storage = { path = "./crates/storage/", version = "=1.11.0-rc.0" } +mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.11.0-rc.0" } +mas-tasks = { path = "./crates/tasks/", version = "=1.11.0-rc.0" } +mas-templates = { path = "./crates/templates/", version = "=1.11.0-rc.0" } +mas-tower = { path = "./crates/tower/", version = "=1.11.0-rc.0" } +oauth2-types = { path = "./crates/oauth2-types/", version = "=1.11.0-rc.0" } +syn2mas = { path = "./crates/syn2mas", version = "=1.11.0-rc.0" } # OpenAPI schema generation and validation [workspace.dependencies.aide] From d3d6c3dcd90eb88d39ee0c0d72d661fba0304252 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 30 Jan 2026 06:28:05 +0100 Subject: [PATCH 08/43] Split the cleanup jobs into multiple files --- crates/tasks/src/cleanup/misc.rs | 88 +++ crates/tasks/src/cleanup/mod.rs | 24 + crates/tasks/src/cleanup/oauth.rs | 216 +++++++ crates/tasks/src/cleanup/sessions.rs | 290 +++++++++ crates/tasks/src/cleanup/tokens.rs | 214 +++++++ crates/tasks/src/cleanup/user.rs | 181 ++++++ crates/tasks/src/database.rs | 916 --------------------------- crates/tasks/src/lib.rs | 125 ++-- docs/development/cleanup-jobs.md | 18 +- 9 files changed, 1093 insertions(+), 979 deletions(-) create mode 100644 crates/tasks/src/cleanup/misc.rs create mode 100644 crates/tasks/src/cleanup/mod.rs create mode 100644 crates/tasks/src/cleanup/oauth.rs create mode 100644 crates/tasks/src/cleanup/sessions.rs create mode 100644 crates/tasks/src/cleanup/tokens.rs create mode 100644 crates/tasks/src/cleanup/user.rs delete mode 100644 crates/tasks/src/database.rs diff --git a/crates/tasks/src/cleanup/misc.rs b/crates/tasks/src/cleanup/misc.rs new file mode 100644 index 000000000..52fd62e5c --- /dev/null +++ b/crates/tasks/src/cleanup/misc.rs @@ -0,0 +1,88 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +//! Miscellaneous cleanup tasks + +use std::time::Duration; + +use async_trait::async_trait; +use mas_storage::queue::{CleanupQueueJobsJob, PruneStalePolicyDataJob}; +use tracing::{debug, info}; +use ulid::Ulid; + +use super::BATCH_SIZE; +use crate::{ + State, + new_queue::{JobContext, JobError, RunnableJob}, +}; + +#[async_trait] +impl RunnableJob for CleanupQueueJobsJob { + #[tracing::instrument(name = "job.cleanup_queue_jobs", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove completed and failed queue jobs after 30 days. + // Keep them for debugging purposes. + let until = state.clock.now() - chrono::Duration::days(30); + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + let (count, cursor) = repo + .queue_job() + .cleanup(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no queue jobs to clean up"); + } else { + info!(count = total, "cleaned up queue jobs"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for PruneStalePolicyDataJob { + #[tracing::instrument(name = "job.prune_stale_policy_data", skip_all)] + async fn run(&self, state: &State, _context: JobContext) -> Result<(), JobError> { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // Keep the last 10 policy data + let count = repo + .policy_data() + .prune(10) + .await + .map_err(JobError::retry)?; + + repo.save().await.map_err(JobError::retry)?; + + if count == 0 { + debug!("no stale policy data to prune"); + } else { + info!(count, "pruned stale policy data"); + } + + Ok(()) + } +} diff --git a/crates/tasks/src/cleanup/mod.rs b/crates/tasks/src/cleanup/mod.rs new file mode 100644 index 000000000..02ace1ded --- /dev/null +++ b/crates/tasks/src/cleanup/mod.rs @@ -0,0 +1,24 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +//! Database cleanup tasks +//! +//! This module contains tasks for cleaning up old data from the database. +//! Tasks are grouped by domain: +//! +//! - [`tokens`]: OAuth token cleanup (access and refresh tokens) +//! - [`sessions`]: Session cleanup (compat, `OAuth2`, user sessions and their +//! IPs) +//! - [`oauth`]: OAuth grants and upstream OAuth cleanup +//! - [`user`]: User-related cleanup (registrations, recovery, email auth) +//! - [`misc`]: Miscellaneous cleanup (queue jobs, policy data) + +mod misc; +mod oauth; +mod sessions; +mod tokens; +mod user; + +pub(crate) const BATCH_SIZE: usize = 1000; diff --git a/crates/tasks/src/cleanup/oauth.rs b/crates/tasks/src/cleanup/oauth.rs new file mode 100644 index 000000000..2a201d4df --- /dev/null +++ b/crates/tasks/src/cleanup/oauth.rs @@ -0,0 +1,216 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +//! OAuth grants and upstream OAuth cleanup tasks + +use std::time::Duration; + +use async_trait::async_trait; +use mas_storage::queue::{ + CleanupOAuthAuthorizationGrantsJob, CleanupOAuthDeviceCodeGrantsJob, + CleanupUpstreamOAuthLinksJob, CleanupUpstreamOAuthSessionsJob, +}; +use tracing::{debug, info}; +use ulid::Ulid; + +use super::BATCH_SIZE; +use crate::{ + State, + new_queue::{JobContext, JobError, RunnableJob}, +}; + +#[async_trait] +impl RunnableJob for CleanupOAuthAuthorizationGrantsJob { + #[tracing::instrument(name = "job.cleanup_oauth_authorization_grants", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove authorization grants after 7 days. They are in practice only + // valid for a short time, but keeping them around helps investigate abuse + // patterns. + let until = state.clock.now() - chrono::Duration::days(7); + // We use the fact that ULIDs include the creation time in their first 48 bits + // as a cursor + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + // This returns the number of deleted grants, and the greatest ULID processed + let (count, cursor) = repo + .oauth2_authorization_grant() + .cleanup(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no authorization grants to clean up"); + } else { + info!(count = total, "cleaned up authorization grants"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupOAuthDeviceCodeGrantsJob { + #[tracing::instrument(name = "job.cleanup_oauth_device_code_grants", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove device code grants after 7 days. They are in practice only + // valid for a short time, but keeping them around helps investigate abuse + // patterns. + let until = state.clock.now() - chrono::Duration::days(7); + // We use the fact that ULIDs include the creation time in their first 48 bits + // as a cursor + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + // This returns the number of deleted grants, and the greatest ULID processed + let (count, cursor) = repo + .oauth2_device_code_grant() + .cleanup(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no device code grants to clean up"); + } else { + info!(count = total, "cleaned up device code grants"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupUpstreamOAuthSessionsJob { + #[tracing::instrument(name = "job.cleanup_upstream_oauth_sessions", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove pending upstream OAuth authorization sessions after 7 days. + let until = state.clock.now() - chrono::Duration::days(7); + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + let (count, cursor) = repo + .upstream_oauth_session() + .cleanup_orphaned(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no pending upstream OAuth sessions to clean up"); + } else { + info!(count = total, "cleaned up pending upstream OAuth sessions"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupUpstreamOAuthLinksJob { + #[tracing::instrument(name = "job.cleanup_upstream_oauth_links", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove orphaned upstream OAuth links after 7 days. + let until = state.clock.now() - chrono::Duration::days(7); + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + let (count, cursor) = repo + .upstream_oauth_link() + .cleanup_orphaned(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no orphaned upstream OAuth links to clean up"); + } else { + info!(count = total, "cleaned up orphaned upstream OAuth links"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} diff --git a/crates/tasks/src/cleanup/sessions.rs b/crates/tasks/src/cleanup/sessions.rs new file mode 100644 index 000000000..0a11a6b99 --- /dev/null +++ b/crates/tasks/src/cleanup/sessions.rs @@ -0,0 +1,290 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +//! Session cleanup tasks + +use std::time::Duration; + +use async_trait::async_trait; +use mas_storage::queue::{ + CleanupFinishedCompatSessionsJob, CleanupFinishedOAuth2SessionsJob, + CleanupFinishedUserSessionsJob, CleanupInactiveCompatSessionIpsJob, + CleanupInactiveOAuth2SessionIpsJob, CleanupInactiveUserSessionIpsJob, +}; +use tracing::{debug, info}; + +use super::BATCH_SIZE; +use crate::{ + State, + new_queue::{JobContext, JobError, RunnableJob}, +}; + +#[async_trait] +impl RunnableJob for CleanupFinishedCompatSessionsJob { + #[tracing::instrument(name = "job.cleanup_finished_compat_sessions", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Cleanup compat sessions that were finished more than 30 days ago + let until = state.clock.now() - chrono::Duration::days(30); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // This returns the number of deleted sessions, and the last finished_at + // timestamp + let (count, last_finished_at) = repo + .compat_session() + .cleanup_finished(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_finished_at; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no finished compat sessions to clean up"); + } else { + info!(count = total, "cleaned up finished compat sessions"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupFinishedOAuth2SessionsJob { + #[tracing::instrument(name = "job.cleanup_finished_oauth2_sessions", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Cleanup OAuth2 sessions that were finished more than 30 days ago + let until = state.clock.now() - chrono::Duration::days(30); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // This returns the number of deleted sessions, and the last finished_at + // timestamp + let (count, last_finished_at) = repo + .oauth2_session() + .cleanup_finished(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_finished_at; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no finished OAuth2 sessions to clean up"); + } else { + info!(count = total, "cleaned up finished OAuth2 sessions"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupFinishedUserSessionsJob { + #[tracing::instrument(name = "job.cleanup_finished_user_sessions", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Cleanup user/browser sessions that were finished more than 30 days ago + let until = state.clock.now() - chrono::Duration::days(30); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // This returns the number of deleted sessions, and the last finished_at + // timestamp. Only deletes sessions that have no child sessions + // (compat_sessions or oauth2_sessions). + let (count, last_finished_at) = repo + .browser_session() + .cleanup_finished(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_finished_at; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no finished user sessions to clean up"); + } else { + info!(count = total, "cleaned up finished user sessions"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupInactiveOAuth2SessionIpsJob { + #[tracing::instrument(name = "job.cleanup_inactive_oauth2_session_ips", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Clear IPs from sessions inactive for 30+ days + let threshold = state.clock.now() - chrono::Duration::days(30); + let mut total = 0; + + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + let (count, last_active_at) = repo + .oauth2_session() + .cleanup_inactive_ips(since, threshold, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_active_at; + total += count; + + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no OAuth2 session IPs to clean up"); + } else { + info!(count = total, "cleaned up inactive OAuth2 session IPs"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupInactiveCompatSessionIpsJob { + #[tracing::instrument(name = "job.cleanup_inactive_compat_session_ips", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Clear IPs from sessions inactive for 30+ days + let threshold = state.clock.now() - chrono::Duration::days(30); + let mut total = 0; + + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + let (count, last_active_at) = repo + .compat_session() + .cleanup_inactive_ips(since, threshold, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_active_at; + total += count; + + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no compat session IPs to clean up"); + } else { + info!(count = total, "cleaned up inactive compat session IPs"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupInactiveUserSessionIpsJob { + #[tracing::instrument(name = "job.cleanup_inactive_user_session_ips", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Clear IPs from sessions inactive for 30+ days + let threshold = state.clock.now() - chrono::Duration::days(30); + let mut total = 0; + + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + let (count, last_active_at) = repo + .browser_session() + .cleanup_inactive_ips(since, threshold, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_active_at; + total += count; + + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no user session IPs to clean up"); + } else { + info!(count = total, "cleaned up inactive user session IPs"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + Some(Duration::from_secs(10 * 60)) + } +} diff --git a/crates/tasks/src/cleanup/tokens.rs b/crates/tasks/src/cleanup/tokens.rs new file mode 100644 index 000000000..dd91de2b9 --- /dev/null +++ b/crates/tasks/src/cleanup/tokens.rs @@ -0,0 +1,214 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +//! OAuth token cleanup tasks + +use std::time::Duration; + +use async_trait::async_trait; +use mas_storage::queue::{ + CleanupConsumedOAuthRefreshTokensJob, CleanupExpiredOAuthAccessTokensJob, + CleanupRevokedOAuthAccessTokensJob, CleanupRevokedOAuthRefreshTokensJob, +}; +use tracing::{debug, info}; + +use super::BATCH_SIZE; +use crate::{ + State, + new_queue::{JobContext, JobError, RunnableJob}, +}; + +#[async_trait] +impl RunnableJob for CleanupRevokedOAuthAccessTokensJob { + #[tracing::instrument(name = "job.cleanup_revoked_oauth_access_tokens", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Cleanup tokens that were revoked more than an hour ago + let until = state.clock.now() - chrono::Duration::hours(1); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // This returns the number of deleted tokens, and the last revoked_at timestamp + let (count, last_revoked_at) = repo + .oauth2_access_token() + .cleanup_revoked(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_revoked_at; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no token to clean up"); + } else { + info!(count = total, "cleaned up revoked tokens"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupExpiredOAuthAccessTokensJob { + #[tracing::instrument(name = "job.cleanup_expired_oauth_access_tokens", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Cleanup tokens that expired more than a month ago + // It is important to keep them around for a bit because of refresh + // token idempotency. When we see a refresh token twice, we allow + // reusing it *only* if both the next refresh token and the next access + // tokens were not used. By keeping expired access tokens around for a + // month, we cannot make the *correct* decision, we will assume that the + // token wasn't used. Refer to the token refresh logic for details. + let until = state.clock.now() - chrono::Duration::days(30); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // This returns the number of deleted tokens, and the last expires_at timestamp + let (count, last_expires_at) = repo + .oauth2_access_token() + .cleanup_expired(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_expires_at; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no token to clean up"); + } else { + info!(count = total, "cleaned up expired tokens"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + Some(Duration::from_secs(60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupRevokedOAuthRefreshTokensJob { + #[tracing::instrument(name = "job.cleanup_revoked_oauth_refresh_tokens", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Cleanup tokens that were revoked more than an hour ago + let until = state.clock.now() - chrono::Duration::hours(1); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // This returns the number of deleted tokens, and the last revoked_at timestamp + let (count, last_revoked_at) = repo + .oauth2_refresh_token() + .cleanup_revoked(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_revoked_at; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no token to clean up"); + } else { + info!(count = total, "cleaned up revoked tokens"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupConsumedOAuthRefreshTokensJob { + #[tracing::instrument(name = "job.cleanup_consumed_oauth_refresh_tokens", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Cleanup tokens that were consumed more than an hour ago + let until = state.clock.now() - chrono::Duration::hours(1); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + + // This returns the number of deleted tokens, and the last consumed_at timestamp + let (count, last_consumed_at) = repo + .oauth2_refresh_token() + .cleanup_consumed(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + + since = last_consumed_at; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no token to clean up"); + } else { + info!(count = total, "cleaned up consumed tokens"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} diff --git a/crates/tasks/src/cleanup/user.rs b/crates/tasks/src/cleanup/user.rs new file mode 100644 index 000000000..d682c1b51 --- /dev/null +++ b/crates/tasks/src/cleanup/user.rs @@ -0,0 +1,181 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +//! User-related cleanup tasks + +use std::time::Duration; + +use async_trait::async_trait; +use mas_storage::queue::{ + CleanupUserEmailAuthenticationsJob, CleanupUserRecoverySessionsJob, CleanupUserRegistrationsJob, +}; +use tracing::{debug, info}; +use ulid::Ulid; + +use super::BATCH_SIZE; +use crate::{ + State, + new_queue::{JobContext, JobError, RunnableJob}, +}; + +#[async_trait] +impl RunnableJob for CleanupUserRegistrationsJob { + #[tracing::instrument(name = "job.cleanup_user_registrations", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove user registrations after 30 days. They are in practice only + // valid for 1h, but keeping them around helps investigate abuse patterns. + let until = state.clock.now() - chrono::Duration::days(30); + // We use the fact that ULIDs include the creation time in their first 48 bits + // as a cursor + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + // This returns the number of deleted registrations, and the greatest ULID + // processed + let (count, cursor) = repo + .user_registration() + .cleanup(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no user registrations to clean up"); + } else { + info!(count = total, "cleaned up user registrations"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupUserRecoverySessionsJob { + #[tracing::instrument(name = "job.cleanup_user_recovery_sessions", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove recovery sessions after 7 days. They are in practice only + // valid for a short time (tickets expire after 10 minutes), but keeping + // them around helps investigate abuse patterns. + let until = state.clock.now() - chrono::Duration::days(7); + // We use the fact that ULIDs include the creation time in their first 48 bits + // as a cursor + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + // This returns the number of deleted sessions, and the greatest ULID processed + let (count, cursor) = repo + .user_recovery() + .cleanup(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no user recovery sessions to clean up"); + } else { + info!(count = total, "cleaned up user recovery sessions"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} + +#[async_trait] +impl RunnableJob for CleanupUserEmailAuthenticationsJob { + #[tracing::instrument(name = "job.cleanup_user_email_authentications", skip_all)] + async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { + // Remove email authentications after 7 days. They are in practice only + // valid for a short time (codes expire after 10 minutes), but keeping + // them around helps investigate abuse patterns. + let until = state.clock.now() - chrono::Duration::days(7); + // We use the fact that ULIDs include the creation time in their first 48 bits + // as a cursor + let until = Ulid::from_parts( + u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), + u128::MAX, + ); + let mut total = 0; + + // Run until we get cancelled. We don't schedule a retry if we get cancelled, as + // this is a scheduled job and it will end up being rescheduled later anyway. + let mut since = None; + while !context.cancellation_token.is_cancelled() { + let mut repo = state.repository().await.map_err(JobError::retry)?; + // This returns the number of deleted authentications, and the greatest ULID + // processed + let (count, cursor) = repo + .user_email() + .cleanup_authentications(since, until, BATCH_SIZE) + .await + .map_err(JobError::retry)?; + repo.save().await.map_err(JobError::retry)?; + since = cursor; + total += count; + + // Check how many we deleted. If we deleted exactly BATCH_SIZE, + // there might be more to delete + if count != BATCH_SIZE { + break; + } + } + + if total == 0 { + debug!("no user email authentications to clean up"); + } else { + info!(count = total, "cleaned up user email authentications"); + } + + Ok(()) + } + + fn timeout(&self) -> Option { + // This job runs every hour, so having it running it for 10 minutes is fine + Some(Duration::from_secs(10 * 60)) + } +} diff --git a/crates/tasks/src/database.rs b/crates/tasks/src/database.rs deleted file mode 100644 index c3fb46f78..000000000 --- a/crates/tasks/src/database.rs +++ /dev/null @@ -1,916 +0,0 @@ -// Copyright 2025, 2026 Element Creations Ltd. -// Copyright 2024, 2025 New Vector Ltd. -// Copyright 2023, 2024 The Matrix.org Foundation C.I.C. -// -// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial -// Please see LICENSE files in the repository root for full details. - -//! Database-related tasks - -use std::time::Duration; - -use async_trait::async_trait; -use mas_storage::queue::{ - CleanupConsumedOAuthRefreshTokensJob, CleanupExpiredOAuthAccessTokensJob, - CleanupFinishedCompatSessionsJob, CleanupFinishedOAuth2SessionsJob, - CleanupFinishedUserSessionsJob, CleanupInactiveCompatSessionIpsJob, - CleanupInactiveOAuth2SessionIpsJob, CleanupInactiveUserSessionIpsJob, - CleanupOAuthAuthorizationGrantsJob, CleanupOAuthDeviceCodeGrantsJob, CleanupQueueJobsJob, - CleanupRevokedOAuthAccessTokensJob, CleanupRevokedOAuthRefreshTokensJob, - CleanupUpstreamOAuthLinksJob, CleanupUpstreamOAuthSessionsJob, - CleanupUserEmailAuthenticationsJob, CleanupUserRecoverySessionsJob, - CleanupUserRegistrationsJob, PruneStalePolicyDataJob, -}; -use tracing::{debug, info}; -use ulid::Ulid; - -use crate::{ - State, - new_queue::{JobContext, JobError, RunnableJob}, -}; - -const BATCH_SIZE: usize = 1000; - -#[async_trait] -impl RunnableJob for CleanupRevokedOAuthAccessTokensJob { - #[tracing::instrument(name = "job.cleanup_revoked_oauth_access_tokens", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Cleanup tokens that were revoked more than an hour ago - let until = state.clock.now() - chrono::Duration::hours(1); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // This returns the number of deleted tokens, and the last revoked_at timestamp - let (count, last_revoked_at) = repo - .oauth2_access_token() - .cleanup_revoked(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_revoked_at; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no token to clean up"); - } else { - info!(count = total, "cleaned up revoked tokens"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupExpiredOAuthAccessTokensJob { - #[tracing::instrument(name = "job.cleanup_expired_oauth_access_tokens", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Cleanup tokens that expired more than a month ago - // It is important to keep them around for a bit because of refresh - // token idempotency. When we see a refresh token twice, we allow - // reusing it *only* if both the next refresh token and the next access - // tokens were not used. By keeping expired access tokens around for a - // month, we cannot make the *correct* decision, we will assume that the - // token wasn't used. Refer to the token refresh logic for details. - let until = state.clock.now() - chrono::Duration::days(30); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // This returns the number of deleted tokens, and the last expires_at timestamp - let (count, last_expires_at) = repo - .oauth2_access_token() - .cleanup_expired(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_expires_at; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no token to clean up"); - } else { - info!(count = total, "cleaned up expired tokens"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - Some(Duration::from_secs(60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupRevokedOAuthRefreshTokensJob { - #[tracing::instrument(name = "job.cleanup_revoked_oauth_refresh_tokens", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Cleanup tokens that were revoked more than an hour ago - let until = state.clock.now() - chrono::Duration::hours(1); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // This returns the number of deleted tokens, and the last revoked_at timestamp - let (count, last_revoked_at) = repo - .oauth2_refresh_token() - .cleanup_revoked(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_revoked_at; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no token to clean up"); - } else { - info!(count = total, "cleaned up revoked tokens"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupConsumedOAuthRefreshTokensJob { - #[tracing::instrument(name = "job.cleanup_consumed_oauth_refresh_tokens", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Cleanup tokens that were consumed more than an hour ago - let until = state.clock.now() - chrono::Duration::hours(1); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // This returns the number of deleted tokens, and the last consumed_at timestamp - let (count, last_consumed_at) = repo - .oauth2_refresh_token() - .cleanup_consumed(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_consumed_at; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no token to clean up"); - } else { - info!(count = total, "cleaned up consumed tokens"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupUserRecoverySessionsJob { - #[tracing::instrument(name = "job.cleanup_user_recovery_sessions", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove recovery sessions after 7 days. They are in practice only - // valid for a short time (tickets expire after 10 minutes), but keeping - // them around helps investigate abuse patterns. - let until = state.clock.now() - chrono::Duration::days(7); - // We use the fact that ULIDs include the creation time in their first 48 bits - // as a cursor - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - // This returns the number of deleted sessions, and the greatest ULID processed - let (count, cursor) = repo - .user_recovery() - .cleanup(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no user recovery sessions to clean up"); - } else { - info!(count = total, "cleaned up user recovery sessions"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupUserEmailAuthenticationsJob { - #[tracing::instrument(name = "job.cleanup_user_email_authentications", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove email authentications after 7 days. They are in practice only - // valid for a short time (codes expire after 10 minutes), but keeping - // them around helps investigate abuse patterns. - let until = state.clock.now() - chrono::Duration::days(7); - // We use the fact that ULIDs include the creation time in their first 48 bits - // as a cursor - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - // This returns the number of deleted authentications, and the greatest ULID - // processed - let (count, cursor) = repo - .user_email() - .cleanup_authentications(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no user email authentications to clean up"); - } else { - info!(count = total, "cleaned up user email authentications"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupUpstreamOAuthSessionsJob { - #[tracing::instrument(name = "job.cleanup_upstream_oauth_sessions", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove pending upstream OAuth authorization sessions after 7 days. - let until = state.clock.now() - chrono::Duration::days(7); - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - let (count, cursor) = repo - .upstream_oauth_session() - .cleanup_orphaned(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no pending upstream OAuth sessions to clean up"); - } else { - info!(count = total, "cleaned up pending upstream OAuth sessions"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupUpstreamOAuthLinksJob { - #[tracing::instrument(name = "job.cleanup_upstream_oauth_links", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove orphaned upstream OAuth links after 7 days. - let until = state.clock.now() - chrono::Duration::days(7); - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - let (count, cursor) = repo - .upstream_oauth_link() - .cleanup_orphaned(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no orphaned upstream OAuth links to clean up"); - } else { - info!(count = total, "cleaned up orphaned upstream OAuth links"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupQueueJobsJob { - #[tracing::instrument(name = "job.cleanup_queue_jobs", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove completed and failed queue jobs after 30 days. - // Keep them for debugging purposes. - let until = state.clock.now() - chrono::Duration::days(30); - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - let (count, cursor) = repo - .queue_job() - .cleanup(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no queue jobs to clean up"); - } else { - info!(count = total, "cleaned up queue jobs"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupUserRegistrationsJob { - #[tracing::instrument(name = "job.cleanup_user_registrations", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove user registrations after 30 days. They are in practice only - // valid for 1h, but keeping them around helps investigate abuse patterns. - let until = state.clock.now() - chrono::Duration::days(30); - // We use the fact that ULIDs include the creation time in their first 48 bits - // as a cursor - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - // This returns the number of deleted registrations, and the greatest ULID - // processed - let (count, cursor) = repo - .user_registration() - .cleanup(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no user registrations to clean up"); - } else { - info!(count = total, "cleaned up user registrations"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupFinishedCompatSessionsJob { - #[tracing::instrument(name = "job.cleanup_finished_compat_sessions", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Cleanup compat sessions that were finished more than 30 days ago - let until = state.clock.now() - chrono::Duration::days(30); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // This returns the number of deleted sessions, and the last finished_at - // timestamp - let (count, last_finished_at) = repo - .compat_session() - .cleanup_finished(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_finished_at; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no finished compat sessions to clean up"); - } else { - info!(count = total, "cleaned up finished compat sessions"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupFinishedOAuth2SessionsJob { - #[tracing::instrument(name = "job.cleanup_finished_oauth2_sessions", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Cleanup OAuth2 sessions that were finished more than 30 days ago - let until = state.clock.now() - chrono::Duration::days(30); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // This returns the number of deleted sessions, and the last finished_at - // timestamp - let (count, last_finished_at) = repo - .oauth2_session() - .cleanup_finished(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_finished_at; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no finished OAuth2 sessions to clean up"); - } else { - info!(count = total, "cleaned up finished OAuth2 sessions"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupFinishedUserSessionsJob { - #[tracing::instrument(name = "job.cleanup_finished_user_sessions", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Cleanup user/browser sessions that were finished more than 30 days ago - let until = state.clock.now() - chrono::Duration::days(30); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // This returns the number of deleted sessions, and the last finished_at - // timestamp. Only deletes sessions that have no child sessions - // (compat_sessions or oauth2_sessions). - let (count, last_finished_at) = repo - .browser_session() - .cleanup_finished(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_finished_at; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no finished user sessions to clean up"); - } else { - info!(count = total, "cleaned up finished user sessions"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupOAuthAuthorizationGrantsJob { - #[tracing::instrument(name = "job.cleanup_oauth_authorization_grants", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove authorization grants after 7 days. They are in practice only - // valid for a short time, but keeping them around helps investigate abuse - // patterns. - let until = state.clock.now() - chrono::Duration::days(7); - // We use the fact that ULIDs include the creation time in their first 48 bits - // as a cursor - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - // This returns the number of deleted grants, and the greatest ULID processed - let (count, cursor) = repo - .oauth2_authorization_grant() - .cleanup(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no authorization grants to clean up"); - } else { - info!(count = total, "cleaned up authorization grants"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupOAuthDeviceCodeGrantsJob { - #[tracing::instrument(name = "job.cleanup_oauth_device_code_grants", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Remove device code grants after 7 days. They are in practice only - // valid for a short time, but keeping them around helps investigate abuse - // patterns. - let until = state.clock.now() - chrono::Duration::days(7); - // We use the fact that ULIDs include the creation time in their first 48 bits - // as a cursor - let until = Ulid::from_parts( - u64::try_from(until.timestamp_millis()).unwrap_or(u64::MIN), - u128::MAX, - ); - let mut total = 0; - - // Run until we get cancelled. We don't schedule a retry if we get cancelled, as - // this is a scheduled job and it will end up being rescheduled later anyway. - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - // This returns the number of deleted grants, and the greatest ULID processed - let (count, cursor) = repo - .oauth2_device_code_grant() - .cleanup(since, until, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - since = cursor; - total += count; - - // Check how many we deleted. If we deleted exactly BATCH_SIZE, - // there might be more to delete - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no device code grants to clean up"); - } else { - info!(count = total, "cleaned up device code grants"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - // This job runs every hour, so having it running it for 10 minutes is fine - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for PruneStalePolicyDataJob { - #[tracing::instrument(name = "job.prune_stale_policy_data", skip_all)] - async fn run(&self, state: &State, _context: JobContext) -> Result<(), JobError> { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - // Keep the last 10 policy data - let count = repo - .policy_data() - .prune(10) - .await - .map_err(JobError::retry)?; - - repo.save().await.map_err(JobError::retry)?; - - if count == 0 { - debug!("no stale policy data to prune"); - } else { - info!(count, "pruned stale policy data"); - } - - Ok(()) - } -} - -#[async_trait] -impl RunnableJob for CleanupInactiveOAuth2SessionIpsJob { - #[tracing::instrument(name = "job.cleanup_inactive_oauth2_session_ips", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Clear IPs from sessions inactive for 30+ days - let threshold = state.clock.now() - chrono::Duration::days(30); - let mut total = 0; - - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - let (count, last_active_at) = repo - .oauth2_session() - .cleanup_inactive_ips(since, threshold, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_active_at; - total += count; - - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no OAuth2 session IPs to clean up"); - } else { - info!(count = total, "cleaned up inactive OAuth2 session IPs"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupInactiveCompatSessionIpsJob { - #[tracing::instrument(name = "job.cleanup_inactive_compat_session_ips", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Clear IPs from sessions inactive for 30+ days - let threshold = state.clock.now() - chrono::Duration::days(30); - let mut total = 0; - - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - let (count, last_active_at) = repo - .compat_session() - .cleanup_inactive_ips(since, threshold, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_active_at; - total += count; - - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no compat session IPs to clean up"); - } else { - info!(count = total, "cleaned up inactive compat session IPs"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - Some(Duration::from_secs(10 * 60)) - } -} - -#[async_trait] -impl RunnableJob for CleanupInactiveUserSessionIpsJob { - #[tracing::instrument(name = "job.cleanup_inactive_user_session_ips", skip_all)] - async fn run(&self, state: &State, context: JobContext) -> Result<(), JobError> { - // Clear IPs from sessions inactive for 30+ days - let threshold = state.clock.now() - chrono::Duration::days(30); - let mut total = 0; - - let mut since = None; - while !context.cancellation_token.is_cancelled() { - let mut repo = state.repository().await.map_err(JobError::retry)?; - - let (count, last_active_at) = repo - .browser_session() - .cleanup_inactive_ips(since, threshold, BATCH_SIZE) - .await - .map_err(JobError::retry)?; - repo.save().await.map_err(JobError::retry)?; - - since = last_active_at; - total += count; - - if count != BATCH_SIZE { - break; - } - } - - if total == 0 { - debug!("no user session IPs to clean up"); - } else { - info!(count = total, "cleaned up inactive user session IPs"); - } - - Ok(()) - } - - fn timeout(&self) -> Option { - Some(Duration::from_secs(10 * 60)) - } -} diff --git a/crates/tasks/src/lib.rs b/crates/tasks/src/lib.rs index ca339f96f..33748ae8e 100644 --- a/crates/tasks/src/lib.rs +++ b/crates/tasks/src/lib.rs @@ -21,7 +21,7 @@ use tokio_util::{sync::CancellationToken, task::TaskTracker}; pub use crate::new_queue::QueueWorker; -mod database; +mod cleanup; mod email; mod matrix; mod new_queue; @@ -162,125 +162,128 @@ pub async fn init( .register_handler::() .register_handler::() .register_deprecated_queue("cleanup-expired-tokens") + // Recurring jobs are spread across the hour at ~5 minute intervals + // to avoid clustering and distribute database load evenly. .add_schedule( "cleanup-revoked-oauth-access-tokens", - // Run this job every hour + // Run this job every hour at minute 0 "0 0 * * * *".parse()?, mas_storage::queue::CleanupRevokedOAuthAccessTokensJob, ) .add_schedule( "cleanup-revoked-oauth-refresh-tokens", - // Run this job every hour - "0 10 * * * *".parse()?, + // Run this job every hour at minute 5 + "0 5 * * * *".parse()?, mas_storage::queue::CleanupRevokedOAuthRefreshTokensJob, ) .add_schedule( "cleanup-consumed-oauth-refresh-tokens", - // Run this job every hour - "0 20 * * * *".parse()?, + // Run this job every hour at minute 5 (safe to parallelize with revoked) + "0 5 * * * *".parse()?, mas_storage::queue::CleanupConsumedOAuthRefreshTokensJob, ) - .add_schedule( - "cleanup-user-registrations", - // Run this job every hour - "0 30 * * * *".parse()?, - mas_storage::queue::CleanupUserRegistrationsJob, - ) .add_schedule( "cleanup-finished-compat-sessions", - // Run this job every hour - "0 40 * * * *".parse()?, + // Run this job every hour at minute 10 + "0 10 * * * *".parse()?, mas_storage::queue::CleanupFinishedCompatSessionsJob, ) .add_schedule( "cleanup-finished-oauth2-sessions", - // Run this job every hour - "0 42 * * * *".parse()?, + // Run this job every hour at minute 15 + "0 15 * * * *".parse()?, mas_storage::queue::CleanupFinishedOAuth2SessionsJob, ) .add_schedule( "cleanup-finished-user-sessions", - // Run this job every hour - "0 44 * * * *".parse()?, + // Run this job every hour at minute 20 + "0 20 * * * *".parse()?, mas_storage::queue::CleanupFinishedUserSessionsJob, ) + .add_schedule( + "cleanup-inactive-oauth2-session-ips", + // Run this job every hour at minute 25 + "0 25 * * * *".parse()?, + mas_storage::queue::CleanupInactiveOAuth2SessionIpsJob, + ) + .add_schedule( + "cleanup-inactive-compat-session-ips", + // Run this job every hour at minute 25 + "0 25 * * * *".parse()?, + mas_storage::queue::CleanupInactiveCompatSessionIpsJob, + ) + .add_schedule( + "cleanup-inactive-user-session-ips", + // Run this job every hour at minute 25 + "0 25 * * * *".parse()?, + mas_storage::queue::CleanupInactiveUserSessionIpsJob, + ) .add_schedule( "cleanup-oauth-authorization-grants", - // Run this job every hour - "0 50 * * * *".parse()?, + // Run this job every hour at minute 30 + "0 30 * * * *".parse()?, mas_storage::queue::CleanupOAuthAuthorizationGrantsJob, ) .add_schedule( "cleanup-oauth-device-code-grants", - // Run this job every hour - "0 55 * * * *".parse()?, + // Run this job every hour at minute 35 + "0 35 * * * *".parse()?, mas_storage::queue::CleanupOAuthDeviceCodeGrantsJob, ) - .add_schedule( - "cleanup-user-recovery-sessions", - // Run this job every hour - "0 56 * * * *".parse()?, - mas_storage::queue::CleanupUserRecoverySessionsJob, - ) - .add_schedule( - "cleanup-user-email-authentications", - // Run this job every hour - "0 57 * * * *".parse()?, - mas_storage::queue::CleanupUserEmailAuthenticationsJob, - ) .add_schedule( "cleanup-upstream-oauth-sessions", - // Run this job every hour - "0 58 * * * *".parse()?, + // Run this job every hour at minute 40 (independent, safe to parallelize) + "0 40 * * * *".parse()?, mas_storage::queue::CleanupUpstreamOAuthSessionsJob, ) .add_schedule( "cleanup-upstream-oauth-links", - // Run this job every hour - "0 59 * * * *".parse()?, + // Run this job every hour at minute 40 + "0 40 * * * *".parse()?, mas_storage::queue::CleanupUpstreamOAuthLinksJob, ) + // User cleanup jobs (minutes 45, 50) + .add_schedule( + "cleanup-user-registrations", + // Run this job every hour at minute 45 + "0 45 * * * *".parse()?, + mas_storage::queue::CleanupUserRegistrationsJob, + ) + .add_schedule( + "cleanup-user-recovery-sessions", + // Run this job every hour at minute 50 + "0 50 * * * *".parse()?, + mas_storage::queue::CleanupUserRecoverySessionsJob, + ) + .add_schedule( + "cleanup-user-email-authentications", + // Run this job every hour at minute 50 + "0 50 * * * *".parse()?, + mas_storage::queue::CleanupUserEmailAuthenticationsJob, + ) .add_schedule( "cleanup-queue-jobs", - // Run this job every hour - "0 45 * * * *".parse()?, + // Run this job every hour at minute 55 + "0 55 * * * *".parse()?, mas_storage::queue::CleanupQueueJobsJob, ) .add_schedule( "cleanup-expired-oauth-access-tokens", - // Run this job every 4 hours + // Run this job every 4 hours at minute 5 "0 5 */4 * * *".parse()?, mas_storage::queue::CleanupExpiredOAuthAccessTokensJob, ) .add_schedule( "expire-inactive-sessions", - // Run this job every 15 minutes + // Run this job every 15 minutes at second 30 "30 */15 * * * *".parse()?, mas_storage::queue::ExpireInactiveSessionsJob, ) .add_schedule( "prune-stale-policy-data", - // Run once a day + // Run once a day at 2:00 AM "0 0 2 * * *".parse()?, mas_storage::queue::PruneStalePolicyDataJob, - ) - .add_schedule( - "cleanup-inactive-oauth2-session-ips", - // Run this job every hour - "0 46 * * * *".parse()?, - mas_storage::queue::CleanupInactiveOAuth2SessionIpsJob, - ) - .add_schedule( - "cleanup-inactive-compat-session-ips", - // Run this job every hour - "0 47 * * * *".parse()?, - mas_storage::queue::CleanupInactiveCompatSessionIpsJob, - ) - .add_schedule( - "cleanup-inactive-user-session-ips", - // Run this job every hour - "0 48 * * * *".parse()?, - mas_storage::queue::CleanupInactiveUserSessionIpsJob, ); Ok(worker) diff --git a/docs/development/cleanup-jobs.md b/docs/development/cleanup-jobs.md index 639d0e7a3..f64cb7f21 100644 --- a/docs/development/cleanup-jobs.md +++ b/docs/development/cleanup-jobs.md @@ -11,9 +11,23 @@ Cleanup jobs are scheduled tasks that hard-delete old data from the database. Th 1. **Job struct** in `crates/storage/src/queue/tasks.rs` - Defines the job and queue name 2. **Storage trait** in `crates/storage/src/{domain}/` - Declares the cleanup method interface 3. **PostgreSQL implementation** in `crates/storage-pg/src/{domain}/` - Implements the actual cleanup logic -4. **Job runner** in `crates/tasks/src/database.rs` - Implements the `RunnableJob` trait with batching logic +4. **Job runner** in `crates/tasks/src/cleanup/` - Implements the `RunnableJob` trait with batching logic 5. **Registration** in `crates/tasks/src/lib.rs` - Registers the handler and schedules execution +### Module Structure + +The cleanup job implementations are organized into submodules by domain: + +``` +crates/tasks/src/cleanup/ +├── mod.rs # Re-exports, shared BATCH_SIZE constant +├── tokens.rs # OAuth token cleanup (access and refresh tokens) +├── sessions.rs # Session cleanup (compat, OAuth2, user sessions and their IPs) +├── oauth.rs # OAuth grants and upstream OAuth cleanup +├── user.rs # User-related cleanup (registrations, recovery, email auth) +└── misc.rs # Queue jobs, policy data cleanup +``` + ## All Cleanup Jobs | Job | Entity | Retention | Notes | @@ -183,7 +197,7 @@ The partial index (`WHERE timestamp_col IS NOT NULL`) makes queries more efficie ### 5. Implement RunnableJob -In `crates/tasks/src/database.rs`: +In the appropriate submodule under `crates/tasks/src/cleanup/` (e.g., `tokens.rs`, `sessions.rs`, `oauth.rs`, `user.rs`, or `misc.rs`): ```rust #[async_trait] From b8eeee0eca23e149bf050c2127090684c854c717 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 30 Jan 2026 13:58:42 +0100 Subject: [PATCH 09/43] Add a few tests for the cleanup jobs --- crates/handlers/src/cleanup_tests.rs | 774 +++++++++++++++++++++++++++ crates/handlers/src/lib.rs | 2 + 2 files changed, 776 insertions(+) create mode 100644 crates/handlers/src/cleanup_tests.rs diff --git a/crates/handlers/src/cleanup_tests.rs b/crates/handlers/src/cleanup_tests.rs new file mode 100644 index 000000000..7b1a74f53 --- /dev/null +++ b/crates/handlers/src/cleanup_tests.rs @@ -0,0 +1,774 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +//! Integration tests for session cleanup jobs. +//! +//! These tests verify that cleanup jobs correctly respect the session hierarchy +//! required for OIDC Backchannel Logout to function properly. +//! +//! Session hierarchy: +//! ```text +//! upstream_oauth_authorization_sessions (matched by sub/sid claims) +//! │ user_session_id +//! ▼ +//! user_sessions (browser sessions) +//! │ user_session_id FK +//! ┌────┴──────────────┐ +//! │ │ +//! ▼ ▼ +//! compat_sessions oauth2_sessions +//! ``` + +use chrono::Duration; +use hyper::{Request, StatusCode}; +use mas_data_model::{ + BrowserSession, Clock as _, CompatSession, Device, UpstreamOAuthAuthorizationSession, + UpstreamOAuthLink, UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports, + UpstreamOAuthProviderDiscoveryMode, UpstreamOAuthProviderOnBackchannelLogout, + UpstreamOAuthProviderPkceMode, UpstreamOAuthProviderTokenAuthMethod, User, +}; +use mas_iana::jose::JsonWebSignatureAlg; +use mas_jose::jwt::{JsonWebSignatureHeader, Jwt}; +use mas_storage::{ + RepositoryAccess, + queue::{DeactivateUserJob, QueueJobRepositoryExt}, + upstream_oauth2::UpstreamOAuthProviderParams, +}; +use oauth2_types::scope::{OPENID, Scope}; +use sqlx::PgPool; +use wiremock::{ + Mock, MockServer, ResponseTemplate, + matchers::{method, path}, +}; + +use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup}; + +/// Helper struct to hold all the entities created for testing the session +/// hierarchy. +struct TestSessionHierarchy { + user: User, + browser_session: BrowserSession, + compat_session: Option, + oauth2_session: Option, + upstream_session: Option, + #[expect(dead_code)] + upstream_link: Option, + provider: Option, + #[expect(dead_code)] + mock_server: MockServer, +} + +const UPSTREAM_OAUTH_ISSUER: &str = "https://idp.example.com"; +const UPSTREAM_OAUTH_CLIENT_ID: &str = "test-client"; +const UPSTREAM_OAUTH_SESSION_ID: &str = "upstream-oauth-session-id"; +const UPSTREAM_OAUTH_SUBJECT: &str = "upstream-oauth-sub"; + +/// Create the complete session hierarchy for testing. +/// +/// This creates: +/// - A user +/// - A browser session +/// - Optionally a compat session linked to the browser session +/// - Optionally an OAuth 2.0 session linked to the browser session +/// - Optionally an upstream OAuth session linked to the browser session +async fn create_session_hierarchy( + state: &TestState, + with_compat: bool, + with_oauth2: bool, + with_upstream: bool, +) -> TestSessionHierarchy { + let mut rng = state.rng(); + let mut repo = state.repository().await.unwrap(); + + // Start a mock server to answer to JWKS requests for the backchannel logout + // tests + let mock_server = MockServer::start().await; + + let jwks = state.key_store.public_jwks(); + + let mock_jwks = Mock::given(method("GET")) + .and(path("jwks.json")) + .respond_with(ResponseTemplate::new(200).set_body_json(jwks)); + mock_server.register(mock_jwks).await; + + // Create user + let user = repo + .user() + .add(&mut rng, &state.clock, "testuser".to_owned()) + .await + .unwrap(); + + // Create browser session + let browser_session = repo + .browser_session() + .add(&mut rng, &state.clock, &user, None) + .await + .unwrap(); + + // Create compat session if requested + let compat_session = if with_compat { + let device = Device::generate(&mut rng); + let session = repo + .compat_session() + .add( + &mut rng, + &state.clock, + &user, + device, + Some(&browser_session), + false, + None, + ) + .await + .unwrap(); + Some(session) + } else { + None + }; + + // Create OAuth2 session if requested + let oauth2_session = if with_oauth2 { + // First create an OAuth2 client + let client = repo + .oauth2_client() + .add( + &mut rng, + &state.clock, + vec!["https://example.com/callback".parse().unwrap()], + None, + None, + None, + vec![], + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ) + .await + .unwrap(); + + let session = repo + .oauth2_session() + .add_from_browser_session( + &mut rng, + &state.clock, + &client, + &browser_session, + Scope::from_iter([OPENID]), + ) + .await + .unwrap(); + Some(session) + } else { + None + }; + + // Create upstream OAuth session if requested + let (provider, upstream_link, upstream_session) = if with_upstream { + let params = UpstreamOAuthProviderParams { + issuer: Some(UPSTREAM_OAUTH_ISSUER.to_owned()), + human_name: Some("Test IdP".to_owned()), + brand_name: None, + scope: Scope::from_iter([OPENID]), + token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::ClientSecretBasic, + token_endpoint_signing_alg: None, + id_token_signed_response_alg: JsonWebSignatureAlg::Rs256, + fetch_userinfo: false, + userinfo_signed_response_alg: None, + client_id: UPSTREAM_OAUTH_CLIENT_ID.to_owned(), + encrypted_client_secret: None, + claims_imports: UpstreamOAuthProviderClaimsImports::default(), + authorization_endpoint_override: None, + token_endpoint_override: None, + userinfo_endpoint_override: None, + // Point to the mock server to have it use a JWKS we can use for signing + jwks_uri_override: Some(format!("{}/jwks.json", mock_server.uri()).parse().unwrap()), + discovery_mode: UpstreamOAuthProviderDiscoveryMode::Disabled, + pkce_mode: UpstreamOAuthProviderPkceMode::Auto, + response_mode: None, + additional_authorization_parameters: vec![], + forward_login_hint: false, + ui_order: 0, + on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::LogoutAll, + }; + + let provider = repo + .upstream_oauth_provider() + .add(&mut rng, &state.clock, params) + .await + .unwrap(); + + // Create a link + let link = repo + .upstream_oauth_link() + .add( + &mut rng, + &state.clock, + &provider, + UPSTREAM_OAUTH_SUBJECT.to_owned(), + Some("test@idp.example.com".to_owned()), + ) + .await + .unwrap(); + + // Associate link to user + repo.upstream_oauth_link() + .associate_to_user(&link, &user) + .await + .unwrap(); + + // Create an upstream session + let session = repo + .upstream_oauth_session() + .add( + &mut rng, + &state.clock, + &provider, + "state123".to_owned(), + Some("verifier123".to_owned()), + Some("nonce123".to_owned()), + ) + .await + .unwrap(); + + // Complete the session with the link and ID token claims (including sub and + // sid) + let id_token_claims = serde_json::json!({ + "sub": UPSTREAM_OAUTH_SUBJECT, + "sid": UPSTREAM_OAUTH_SESSION_ID, + }); + let session = repo + .upstream_oauth_session() + .complete_with_link( + &state.clock, + session, + &link, + Some("fake-id-token".to_owned()), + Some(id_token_claims), + None, + None, + ) + .await + .unwrap(); + + // Consume the session and link it to the browser session + let session = repo + .upstream_oauth_session() + .consume(&state.clock, session, &browser_session) + .await + .unwrap(); + + (Some(provider), Some(link), Some(session)) + } else { + (None, None, None) + }; + + repo.save().await.unwrap(); + + TestSessionHierarchy { + user, + browser_session, + compat_session, + oauth2_session, + upstream_session, + upstream_link, + provider, + mock_server, + } +} + +/// Test that sessions finished less than 30 days ago are NOT deleted. +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_cleanup_sessions_within_retention_preserved(pool: PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + // Create a full hierarchy with all session types + let hierarchy = create_session_hierarchy(&state, true, true, false).await; + + // Finish all sessions + let mut repo = state.repository().await.unwrap(); + let browser_session = repo + .browser_session() + .finish(&state.clock, hierarchy.browser_session) + .await + .unwrap(); + let compat_session = repo + .compat_session() + .finish(&state.clock, hierarchy.compat_session.unwrap()) + .await + .unwrap(); + let oauth2_session = repo + .oauth2_session() + .finish(&state.clock, hierarchy.oauth2_session.unwrap()) + .await + .unwrap(); + repo.save().await.unwrap(); + + // Wait one day and run the cleanup jobs a few times + state.clock.advance(Duration::try_days(1).unwrap()); + state.run_jobs_in_queue().await; + for _ in 0..5 { + state.clock.advance(Duration::try_hours(1).unwrap()); + state.run_jobs_in_queue().await; + } + + // Verify all sessions still exist + let mut repo = state.repository().await.unwrap(); + assert!( + repo.browser_session() + .lookup(browser_session.id) + .await + .unwrap() + .is_some(), + "Browser session should still exist" + ); + assert!( + repo.compat_session() + .lookup(compat_session.id) + .await + .unwrap() + .is_some(), + "Compat session should still exist" + ); + assert!( + repo.oauth2_session() + .lookup(oauth2_session.id) + .await + .unwrap() + .is_some(), + "OAuth2 session should still exist" + ); +} + +/// Test that deactivated users eventually get all their sessions cleaned up +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_cleanup_deactivated_users(pool: PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + let mut rng = state.rng(); + + // Create a hierarchy with all session types + let hierarchy = create_session_hierarchy(&state, true, true, true).await; + + // Deactivate the user + let mut repo = state.repository().await.unwrap(); + let user = repo + .user() + .lookup(hierarchy.user.id) + .await + .unwrap() + .unwrap(); + let user = repo.user().deactivate(&state.clock, user).await.unwrap(); + repo.queue_job() + .schedule_job(&mut rng, &state.clock, DeactivateUserJob::new(&user, false)) + .await + .unwrap(); + repo.save().await.unwrap(); + + state.run_jobs_in_queue().await; + + // Verify all sessions are finished + let mut repo = state.repository().await.unwrap(); + assert!( + repo.compat_session() + .lookup(hierarchy.compat_session.as_ref().unwrap().id) + .await + .unwrap() + .unwrap() + .is_finished(), + "Compat session should be finished" + ); + assert!( + repo.oauth2_session() + .lookup(hierarchy.oauth2_session.as_ref().unwrap().id) + .await + .unwrap() + .unwrap() + .is_finished(), + "OAuth2 session should be finished" + ); + assert!( + repo.browser_session() + .lookup(hierarchy.browser_session.id) + .await + .unwrap() + .unwrap() + .finished_at + .is_some(), + "Browser session should be there" + ); + assert!( + repo.upstream_oauth_session() + .lookup(hierarchy.upstream_session.as_ref().unwrap().id) + .await + .unwrap() + .is_some(), + "Upstream OAuth session should be there" + ); + + // Wait 31 days and run the cleanup jobs a few times + state.clock.advance(Duration::try_days(31).unwrap()); + state.run_jobs_in_queue().await; + for _ in 0..5 { + state.clock.advance(Duration::try_hours(1).unwrap()); + state.run_jobs_in_queue().await; + } + + // Verify all sessions are deleted + let mut repo = state.repository().await.unwrap(); + assert!( + repo.compat_session() + .lookup(hierarchy.compat_session.as_ref().unwrap().id) + .await + .unwrap() + .is_none(), + "Compat session should be deleted" + ); + assert!( + repo.oauth2_session() + .lookup(hierarchy.oauth2_session.as_ref().unwrap().id) + .await + .unwrap() + .is_none(), + "OAuth2 session should be deleted" + ); + assert!( + repo.browser_session() + .lookup(hierarchy.browser_session.id) + .await + .unwrap() + .is_none(), + "Browser session should be deleted" + ); + assert!( + repo.upstream_oauth_session() + .lookup(hierarchy.upstream_session.as_ref().unwrap().id) + .await + .unwrap() + .is_none(), + "Upstream OAuth session should be deleted" + ); +} + +/// Test that sessions finished more than 30 days ago ARE deleted. +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_cleanup_sessions_after_retention_deleted(pool: PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + // Create hierarchy with compat and oauth2 sessions + let hierarchy = create_session_hierarchy(&state, true, true, false).await; + + // Finish all sessions + let mut repo = state.repository().await.unwrap(); + let browser_session = repo + .browser_session() + .finish(&state.clock, hierarchy.browser_session) + .await + .unwrap(); + let compat_session = repo + .compat_session() + .finish(&state.clock, hierarchy.compat_session.unwrap()) + .await + .unwrap(); + let oauth2_session = repo + .oauth2_session() + .finish(&state.clock, hierarchy.oauth2_session.unwrap()) + .await + .unwrap(); + repo.save().await.unwrap(); + + // Wait 31 days and run the cleanup jobs a few times + state.clock.advance(Duration::try_days(31).unwrap()); + state.run_jobs_in_queue().await; + for _ in 0..5 { + state.clock.advance(Duration::try_hours(1).unwrap()); + state.run_jobs_in_queue().await; + } + + // Verify all sessions are deleted + let mut repo = state.repository().await.unwrap(); + assert!( + repo.compat_session() + .lookup(compat_session.id) + .await + .unwrap() + .is_none(), + "Compat session should be deleted" + ); + assert!( + repo.oauth2_session() + .lookup(oauth2_session.id) + .await + .unwrap() + .is_none(), + "OAuth2 session should be deleted" + ); + // Browser session should also be deleted since children are gone + assert!( + repo.browser_session() + .lookup(browser_session.id) + .await + .unwrap() + .is_none(), + "Browser session should be deleted after children are gone" + ); +} + +/// Test that user sessions with remaining child sessions are NOT deleted. +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_cleanup_user_session_blocked_by_child_sessions(pool: PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + // Create hierarchy with compat session only + let hierarchy = create_session_hierarchy(&state, true, false, false).await; + + // Finish only the browser session (not the compat session) + let mut repo = state.repository().await.unwrap(); + let browser_session = repo + .browser_session() + .finish(&state.clock, hierarchy.browser_session) + .await + .unwrap(); + repo.save().await.unwrap(); + + let compat_session_id = hierarchy.compat_session.as_ref().unwrap().id; + + // Wait 31 days and run the cleanup jobs a few times + state.clock.advance(Duration::try_days(31).unwrap()); + state.run_jobs_in_queue().await; + for _ in 0..5 { + state.clock.advance(Duration::try_hours(1).unwrap()); + state.run_jobs_in_queue().await; + } + + // Verify browser session still exists because compat session is still active + let mut repo = state.repository().await.unwrap(); + assert!( + repo.browser_session() + .lookup(browser_session.id) + .await + .unwrap() + .is_some(), + "Browser session should NOT be deleted because it has an active child session" + ); + assert!( + repo.compat_session() + .lookup(compat_session_id) + .await + .unwrap() + .is_some(), + "Compat session should still exist (not finished)" + ); +} + +/// Test that backchannel logout can find sessions before cleanup. +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_backchannel_logout_works_before_cleanup(pool: PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + // Create hierarchy with upstream session + let hierarchy = create_session_hierarchy(&state, true, true, true).await; + + let provider = hierarchy.provider.as_ref().unwrap(); + + // The edge case we're trying to make works, is that if the browser session + // is finished for 30 days but *not* the child sessions, that browser + // session and the upstream sessions stay there so that backchannel logout + // still works + let mut repo = state.repository().await.unwrap(); + let browser_session = repo + .browser_session() + .lookup(hierarchy.browser_session.id) + .await + .unwrap() + .unwrap(); + repo.browser_session() + .finish(&state.clock, browser_session) + .await + .unwrap(); + repo.save().await.unwrap(); + + // Now wait 31 days and run the cleanup jobs a few times + state.clock.advance(Duration::try_days(31).unwrap()); + state.run_jobs_in_queue().await; + for _ in 0..5 { + state.clock.advance(Duration::try_hours(1).unwrap()); + state.run_jobs_in_queue().await; + } + + // Now let's craft a backchannel logout request + let ts = state.clock.now().timestamp(); + let payload = serde_json::json!({ + "iss": UPSTREAM_OAUTH_ISSUER, + "aud": UPSTREAM_OAUTH_CLIENT_ID, + "sub": UPSTREAM_OAUTH_SUBJECT, + "sid": UPSTREAM_OAUTH_SESSION_ID, + "jti": "iswearthisisrandom", + "iat": ts, + "exp": ts + 300, + "events": { + "http://schemas.openid.net/event/backchannel-logout": {} + } + }); + + let key = state + .key_store + .signing_key_for_algorithm(&JsonWebSignatureAlg::Rs256) + .unwrap(); + let signer = key + .params() + .signing_key_for_alg(&JsonWebSignatureAlg::Rs256) + .unwrap(); + let signed = Jwt::sign( + JsonWebSignatureHeader::new(JsonWebSignatureAlg::Rs256), + payload, + &signer, + ) + .unwrap(); + + let request = Request::post(format!("/upstream/backchannel-logout/{}", provider.id)).form( + serde_json::json!({ + "logout_token": signed.as_str(), + }), + ); + + let response = state.request(request).await; + response.assert_status(StatusCode::OK); + + // The session should still exist, but are finished + let mut repo = state.repository().await.unwrap(); + assert!( + !repo + .browser_session() + .lookup(hierarchy.browser_session.id) + .await + .unwrap() + .unwrap() + .active(), + "Inactive browser session should not be cleaned up" + ); + assert!( + repo.compat_session() + .lookup(hierarchy.compat_session.as_ref().unwrap().id) + .await + .unwrap() + .unwrap() + .is_finished(), + "Active compat session should not be cleaned up" + ); + assert!( + repo.oauth2_session() + .lookup(hierarchy.oauth2_session.as_ref().unwrap().id) + .await + .unwrap() + .unwrap() + .is_finished(), + "Active OAuth2 session should not be cleaned up" + ); + + // Wait again, then the sessions should be completely deleted + state.clock.advance(Duration::try_days(31).unwrap()); + state.run_jobs_in_queue().await; + for _ in 0..5 { + state.clock.advance(Duration::try_hours(1).unwrap()); + state.run_jobs_in_queue().await; + } + + let mut repo = state.repository().await.unwrap(); + assert!( + repo.browser_session() + .lookup(hierarchy.browser_session.id) + .await + .unwrap() + .is_none(), + "Browser session should be deleted" + ); + assert!( + repo.compat_session() + .lookup(hierarchy.compat_session.as_ref().unwrap().id) + .await + .unwrap() + .is_none(), + "Compat session should be deleted" + ); + assert!( + repo.oauth2_session() + .lookup(hierarchy.oauth2_session.as_ref().unwrap().id) + .await + .unwrap() + .is_none(), + "OAuth2 session should be deleted" + ); + assert!( + repo.upstream_oauth_session() + .lookup(hierarchy.upstream_session.as_ref().unwrap().id) + .await + .unwrap() + .is_none(), + "Upstream OAuth session should be deleted" + ); +} + +/// Test that active sessions are not cleaned up even after retention period. +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_active_sessions_not_cleaned_up(pool: PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + // Create hierarchy without finishing any sessions + let hierarchy = create_session_hierarchy(&state, true, true, false).await; + + let browser_session_id = hierarchy.browser_session.id; + let compat_session_id = hierarchy.compat_session.as_ref().unwrap().id; + let oauth2_session_id = hierarchy.oauth2_session.as_ref().unwrap().id; + + // Wait 31 days and run the cleanup jobs a few times + state.clock.advance(Duration::try_days(31).unwrap()); + state.run_jobs_in_queue().await; + for _ in 0..5 { + state.clock.advance(Duration::try_hours(1).unwrap()); + state.run_jobs_in_queue().await; + } + + // All sessions should still exist because they're active + let mut repo = state.repository().await.unwrap(); + assert!( + repo.browser_session() + .lookup(browser_session_id) + .await + .unwrap() + .is_some(), + "Active browser session should not be cleaned up" + ); + assert!( + repo.compat_session() + .lookup(compat_session_id) + .await + .unwrap() + .is_some(), + "Active compat session should not be cleaned up" + ); + assert!( + repo.oauth2_session() + .lookup(oauth2_session_id) + .await + .unwrap() + .is_some(), + "Active OAuth2 session should not be cleaned up" + ); +} diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index ebd223e4a..0cb450f53 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -62,6 +62,8 @@ mod views; mod activity_tracker; mod captcha; +#[cfg(test)] +mod cleanup_tests; mod preferred_language; mod rate_limit; mod session; From 04bc6cf5db425cd04396dbd467d48756af8aa117 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:54:23 +0000 Subject: [PATCH 10/43] build(deps-dev): bump @graphql-codegen/cli Bumps the graphql-codegen group in /frontend with 1 update: [@graphql-codegen/cli](https://github.com/dotansimha/graphql-code-generator/tree/HEAD/packages/graphql-codegen-cli). Updates `@graphql-codegen/cli` from 6.1.0 to 6.1.1 - [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.1/packages/graphql-codegen-cli) --- updated-dependencies: - dependency-name: "@graphql-codegen/cli" dependency-version: 6.1.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: graphql-codegen ... 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 1fcfd0e1f..54a9ee449 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.1.0", + "@graphql-codegen/cli": "^6.1.1", "@graphql-codegen/client-preset": "^5.2.2", "@graphql-codegen/typescript-msw": "^3.0.1", "@storybook/addon-docs": "^10.1.11", @@ -1845,9 +1845,9 @@ "license": "0BSD" }, "node_modules/@graphql-codegen/cli": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-6.1.0.tgz", - "integrity": "sha512-7w3Zq5IFONVOBcyOiP01Nv9WRxGS/TEaBCAb/ALYA3xHq95dqKCpoGnxt/Ut9R18jiS+aMgT0gc8Tr8sHy44jA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-6.1.1.tgz", + "integrity": "sha512-Ni8UdZ6D/UTvLvDtPb6PzshI0lTqtLDnmv/2t1w2SYP92H0MMEdAzxB/ujDWwIXm2LzVPvvrGvzzCTMsyXa+mA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index b4a80273b..597a6e07b 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.1.0", + "@graphql-codegen/cli": "^6.1.1", "@graphql-codegen/client-preset": "^5.2.2", "@graphql-codegen/typescript-msw": "^3.0.1", "@storybook/addon-docs": "^10.1.11", From 0e3dc0396ffd69be818efc58fca465db3710c7c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:54:39 +0000 Subject: [PATCH 11/43] build(deps-dev): bump the vitest group in /frontend with 2 updates Bumps the vitest group in /frontend with 2 updates: [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) and [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest). Updates `@vitest/coverage-v8` from 4.0.16 to 4.0.17 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.17/packages/coverage-v8) Updates `vitest` from 4.0.16 to 4.0.17 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.17/packages/vitest) --- updated-dependencies: - dependency-name: "@vitest/coverage-v8" dependency-version: 4.0.17 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: vitest - dependency-name: vitest dependency-version: 4.0.17 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: vitest ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 128 ++++++++++++++++--------------------- frontend/package.json | 2 +- 2 files changed, 57 insertions(+), 73 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1fcfd0e1f..c2ab07f9e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -49,7 +49,7 @@ "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.2", - "@vitest/coverage-v8": "^4.0.16", + "@vitest/coverage-v8": "^4.0.18", "autoprefixer": "^10.4.23", "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.12.0", @@ -6109,18 +6109,17 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.16.tgz", - "integrity": "sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", + "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.16", - "ast-v8-to-istanbul": "^0.3.8", + "@vitest/utils": "4.0.18", + "ast-v8-to-istanbul": "^0.3.10", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.2.0", "magicast": "^0.5.1", "obug": "^2.1.1", @@ -6131,8 +6130,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.0.16", - "vitest": "4.0.16" + "@vitest/browser": "4.0.18", + "vitest": "4.0.18" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -6233,9 +6232,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz", - "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", "dev": true, "license": "MIT", "dependencies": { @@ -6246,13 +6245,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz", - "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.16", + "@vitest/utils": "4.0.18", "pathe": "^2.0.3" }, "funding": { @@ -6260,13 +6259,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz", - "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.16", + "@vitest/pretty-format": "4.0.18", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -6288,13 +6287,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz", - "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.16", + "@vitest/pretty-format": "4.0.18", "tinyrainbow": "^3.0.3" }, "funding": { @@ -6562,15 +6561,15 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.9.tgz", - "integrity": "sha512-dSC6tJeOJxbZrPzPbv5mMd6CMiQ1ugaVXXPRad2fXUSsy1kstFn9XQWemV9VW7Y7kpxgQ/4WMoZfwdH8XSU48w==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz", + "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", - "js-tokens": "^9.0.1" + "js-tokens": "^10.0.0" } }, "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": { @@ -6584,9 +6583,9 @@ } }, "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, "license": "MIT" }, @@ -9679,21 +9678,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -13214,19 +13198,19 @@ } }, "node_modules/vitest": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", - "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.0.16", - "@vitest/mocker": "4.0.16", - "@vitest/pretty-format": "4.0.16", - "@vitest/runner": "4.0.16", - "@vitest/snapshot": "4.0.16", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", @@ -13254,10 +13238,10 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.16", - "@vitest/browser-preview": "4.0.16", - "@vitest/browser-webdriverio": "4.0.16", - "@vitest/ui": "4.0.16", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, @@ -13292,16 +13276,16 @@ } }, "node_modules/vitest/node_modules/@vitest/expect": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", - "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" }, @@ -13310,13 +13294,13 @@ } }, "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", - "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.16", + "@vitest/spy": "4.0.18", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -13337,9 +13321,9 @@ } }, "node_modules/vitest/node_modules/@vitest/spy": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", - "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", "dev": true, "license": "MIT", "funding": { diff --git a/frontend/package.json b/frontend/package.json index b4a80273b..cf57cbcdb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -59,7 +59,7 @@ "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.2", - "@vitest/coverage-v8": "^4.0.16", + "@vitest/coverage-v8": "^4.0.18", "autoprefixer": "^10.4.23", "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.12.0", From 046e7132b690a75dd7cf3f6ffdb0bae65cad7ecd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:52:00 +0000 Subject: [PATCH 12/43] build(deps-dev): bump msw from 2.12.4 to 2.12.7 in /frontend Bumps [msw](https://github.com/mswjs/msw) from 2.12.4 to 2.12.7. - [Release notes](https://github.com/mswjs/msw/releases) - [Changelog](https://github.com/mswjs/msw/blob/main/CHANGELOG.md) - [Commits](https://github.com/mswjs/msw/compare/v2.12.4...v2.12.7) --- updated-dependencies: - dependency-name: msw dependency-version: 2.12.7 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 24 ++++++++++++------------ frontend/package.json | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f4a28eb86..9300adca9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -56,7 +56,7 @@ "happy-dom": "^20.0.11", "i18next-cli": "^1.36.1", "knip": "^5.81.0", - "msw": "^2.12.4", + "msw": "^2.12.8", "msw-storybook-addon": "^2.0.6", "postcss": "^8.5.6", "postcss-import": "^16.1.1", @@ -3476,9 +3476,9 @@ } }, "node_modules/@mswjs/interceptors": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.40.0.tgz", - "integrity": "sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.0.tgz", + "integrity": "sha512-edAo9bW53BLYeSK+UPRr2Iz1Fj9DeGMjytvVM0HXRoo750ElWUgPsZPAOTQa12EUiwgDErH2PsFNTLvk1jBxjQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10328,15 +10328,15 @@ "license": "MIT" }, "node_modules/msw": { - "version": "2.12.4", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.4.tgz", - "integrity": "sha512-rHNiVfTyKhzc0EjoXUBVGteNKBevdjOlVC6GlIRXpy+/3LHEIGRovnB5WPjcvmNODVQ1TNFnoa7wsGbd0V3epg==", + "version": "2.12.8", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.8.tgz", + "integrity": "sha512-KOriJUhjefCO+liF7Ie1KlSXcBAQEzuLhPZ4EKuEUSEmAR4YhuuzT9YuGxTipjqDrg6eWQ6oMoGVhvEnqukFGg==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "@inquirer/confirm": "^5.0.0", - "@mswjs/interceptors": "^0.40.0", + "@mswjs/interceptors": "^0.41.0", "@open-draft/deferred-promise": "^2.2.0", "@types/statuses": "^2.0.6", "cookie": "^1.0.2", @@ -10346,7 +10346,7 @@ "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", - "rettime": "^0.7.0", + "rettime": "^0.10.1", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.0", @@ -11709,9 +11709,9 @@ } }, "node_modules/rettime": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.7.0.tgz", - "integrity": "sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.10.1.tgz", + "integrity": "sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==", "dev": true, "license": "MIT" }, diff --git a/frontend/package.json b/frontend/package.json index 3575ff5b7..c458420f8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -66,7 +66,7 @@ "happy-dom": "^20.0.11", "i18next-cli": "^1.36.1", "knip": "^5.81.0", - "msw": "^2.12.4", + "msw": "^2.12.8", "msw-storybook-addon": "^2.0.6", "postcss": "^8.5.6", "postcss-import": "^16.1.1", From 30bb94735e324ffdb8811e7ad4ce3ab021741567 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 4 Feb 2026 16:58:53 +0100 Subject: [PATCH 13/43] Update msw --- .../.storybook/public/mockServiceWorker.js | 2 +- frontend/package-lock.json | 42 ++++++++++++++----- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/frontend/.storybook/public/mockServiceWorker.js b/frontend/.storybook/public/mockServiceWorker.js index 558540fa5..258b1b1e1 100644 --- a/frontend/.storybook/public/mockServiceWorker.js +++ b/frontend/.storybook/public/mockServiceWorker.js @@ -7,7 +7,7 @@ * - Please do NOT modify this file. */ -const PACKAGE_VERSION = '2.12.4' +const PACKAGE_VERSION = '2.12.8' const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9300adca9..2a8a91f41 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -147,6 +147,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", @@ -1807,6 +1808,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" } @@ -1816,6 +1818,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" } @@ -1850,6 +1853,7 @@ "integrity": "sha512-Ni8UdZ6D/UTvLvDtPb6PzshI0lTqtLDnmv/2t1w2SYP92H0MMEdAzxB/ujDWwIXm2LzVPvvrGvzzCTMsyXa+mA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/generator": "^7.18.13", "@babel/template": "^7.18.10", @@ -5485,6 +5489,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.16.tgz", "integrity": "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/query-core": "5.90.16" }, @@ -5519,6 +5524,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.150.0.tgz", "integrity": "sha512-k/oycTCpBT2XoEk9dNd/nNYhF0X9fLSB10lT40+NVX1TjOtBq5whksk8MT6oRnSoQ8KWeb7La3G9kFaAeSULkA==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.145.7", "@tanstack/react-store": "^0.8.0", @@ -5590,6 +5596,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.150.0.tgz", "integrity": "sha512-cAm44t/tUbfyzaDH+rE/WO4u3AgaZdpJp00xjQ4gNkC2O95ntVHq5fx+4fhtrkKpgdXoKldgk8OK66djiWpuGQ==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.145.7", "@tanstack/store": "^0.8.0", @@ -5885,8 +5892,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", @@ -5978,6 +5984,7 @@ "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -5988,6 +5995,7 @@ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -5998,6 +6006,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6045,6 +6054,7 @@ "resolved": "https://registry.npmjs.org/@vector-im/compound-design-tokens/-/compound-design-tokens-6.4.3.tgz", "integrity": "sha512-Zyqag2QCAfpRipLHSX/LEXj6OGFgBpilMQvNdr3a0iXeGASfkM3HZGid077kMRp9lvyHiQfPIs1BdllENwDSNQ==", "license": "SEE LICENSE IN README.md", + "peer": true, "peerDependencies": { "@types/react": "*", "react": "^17 || ^18 || ^19.0.0" @@ -6772,6 +6782,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7371,7 +7382,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", @@ -7585,8 +7597,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", @@ -7687,6 +7698,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -8209,6 +8221,7 @@ "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -8643,6 +8656,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -10127,7 +10141,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -10334,6 +10347,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.41.0", @@ -11062,6 +11076,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -11232,6 +11247,7 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -11269,7 +11285,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -11285,7 +11300,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -11345,6 +11359,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11386,6 +11401,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -11425,8 +11441,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", @@ -11777,6 +11792,7 @@ "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -11910,6 +11926,7 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.4.2.tgz", "integrity": "sha512-N3HEHRCZYn3cQbsC4B5ldj9j+tHdf4JZoYPlcI4rRYu0Xy4qN8MQf1Z08EibzB0WpgRG5BGK08FTrmM66eSzKQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -12140,6 +12157,7 @@ "integrity": "sha512-pKP5jXJYM4OjvNklGuHKO53wOCAwfx79KvZyOWHoi9zXUH5WVMFUe/ZfWyxXG/GTcj0maRgHGUjq/0I43r0dDQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.0", @@ -12849,6 +12867,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13116,6 +13135,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -13203,6 +13223,7 @@ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", @@ -13591,6 +13612,7 @@ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, From 87b185343e2aeb95edbfb307197f6694cd6cfb7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:13:27 +0000 Subject: [PATCH 14/43] build(deps-dev): bump the storybook group in /frontend with 3 updates Bumps the storybook group in /frontend with 3 updates: [@storybook/addon-docs](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/docs), [@storybook/react-vite](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/react-vite) and [storybook](https://github.com/storybookjs/storybook/tree/HEAD/code/core). Updates `@storybook/addon-docs` from 10.1.11 to 10.2.6 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v10.2.6/code/addons/docs) Updates `@storybook/react-vite` from 10.1.11 to 10.2.6 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v10.2.6/code/frameworks/react-vite) Updates `storybook` from 10.1.11 to 10.2.6 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v10.2.6/code/core) --- updated-dependencies: - dependency-name: "@storybook/addon-docs" dependency-version: 10.2.6 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: storybook - dependency-name: "@storybook/react-vite" dependency-version: 10.2.6 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: storybook - dependency-name: storybook dependency-version: 10.2.6 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: storybook ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 156 ++++++++++++------------------------- frontend/package.json | 4 +- 2 files changed, 50 insertions(+), 110 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2a8a91f41..fe0ba4db6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -36,8 +36,8 @@ "@graphql-codegen/cli": "^6.1.1", "@graphql-codegen/client-preset": "^5.2.2", "@graphql-codegen/typescript-msw": "^3.0.1", - "@storybook/addon-docs": "^10.1.11", - "@storybook/react-vite": "^10.1.11", + "@storybook/addon-docs": "^10.2.6", + "@storybook/react-vite": "^10.2.6", "@tanstack/react-query-devtools": "^5.91.2", "@tanstack/react-router-devtools": "^1.150.0", "@tanstack/router-plugin": "^1.150.0", @@ -147,7 +147,6 @@ "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", @@ -1808,7 +1807,6 @@ "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" } @@ -1818,7 +1816,6 @@ "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" } @@ -1853,7 +1850,6 @@ "integrity": "sha512-Ni8UdZ6D/UTvLvDtPb6PzshI0lTqtLDnmv/2t1w2SYP92H0MMEdAzxB/ujDWwIXm2LzVPvvrGvzzCTMsyXa+mA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/generator": "^7.18.13", "@babel/template": "^7.18.10", @@ -5058,16 +5054,16 @@ "license": "MIT" }, "node_modules/@storybook/addon-docs": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.1.11.tgz", - "integrity": "sha512-Jwm291Fhim2eVcZIVlkG1B2skb0ZI9oru6nqMbJxceQZlvZmcIa4oxvS1oaMTKw2DJnCv97gLm57P/YvRZ8eUg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.2.6.tgz", + "integrity": "sha512-7a37fu2hdOJRCITS+EimhVN9xVVvSeodmX6QdfxOim+2ECYnvMC9YVYpJ0et3XC8xnl9/3AbQ4y670Uw1hXcmg==", "dev": true, "license": "MIT", "dependencies": { "@mdx-js/react": "^3.0.0", - "@storybook/csf-plugin": "10.1.11", - "@storybook/icons": "^2.0.0", - "@storybook/react-dom-shim": "10.1.11", + "@storybook/csf-plugin": "10.2.6", + "@storybook/icons": "^2.0.1", + "@storybook/react-dom-shim": "10.2.6", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" @@ -5077,18 +5073,17 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.1.11" + "storybook": "^10.2.6" } }, "node_modules/@storybook/builder-vite": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.1.11.tgz", - "integrity": "sha512-MMD09Ap7FyzDfWG961pkIMv/w684XXe1bBEi+wCEpHxvrgAd3j3A9w/Rqp9Am2uRDPCEdi1QgSzS3SGW3aGThQ==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.2.6.tgz", + "integrity": "sha512-O/kvmUrfEn2VpzVOGJV3VvEQ9N4AOI8csdvsUsfjs6PSNwAiwvsx8+qTYYcheMilrO9+1Da9lFmd7oucSPto3Q==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf-plugin": "10.1.11", - "@vitest/mocker": "3.2.4", + "@storybook/csf-plugin": "10.2.6", "ts-dedent": "^2.0.0" }, "funding": { @@ -5096,14 +5091,14 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.1.11", + "storybook": "^10.2.6", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "node_modules/@storybook/csf-plugin": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.1.11.tgz", - "integrity": "sha512-Ant0NhgqHKzQsseeVTSetZCuDHHs0W2HRkHt51Kg/sUl0T/sDtfVA+fWZT8nGzGZqYSFkxqYPWjauPmIhPtaRw==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.2.6.tgz", + "integrity": "sha512-1ZeRfmMTjUweXRaNVNg49m0r+l2VQmfeicTCRwjXrq70eBeWG8Dx2w0simVM9nX0lRGiGL3S36lhrrswuEF/6A==", "dev": true, "license": "MIT", "dependencies": { @@ -5116,7 +5111,7 @@ "peerDependencies": { "esbuild": "*", "rollup": "*", - "storybook": "^10.1.11", + "storybook": "^10.2.6", "vite": "*", "webpack": "*" }, @@ -5154,14 +5149,14 @@ } }, "node_modules/@storybook/react": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.1.11.tgz", - "integrity": "sha512-rmMGmEwBaM2YpB8oDk2moM0MNjNMqtwyoPPZxjyruY9WVhYca8EDPGKEdRzUlb4qZJsTgLi7VU4eqg6LD/mL3Q==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.2.6.tgz", + "integrity": "sha512-0haWDV7A/p3PdOsP6klacN7D5Zbzg08EVLh5br8X3AOxAznhLUTS4WngMtaMFbkheNLmyoVK+4SkTrL4n6/7zQ==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "10.1.11", + "@storybook/react-dom-shim": "10.2.6", "react-docgen": "^8.0.2" }, "funding": { @@ -5171,7 +5166,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.1.11", + "storybook": "^10.2.6", "typescript": ">= 4.9.x" }, "peerDependenciesMeta": { @@ -5181,9 +5176,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.1.11.tgz", - "integrity": "sha512-o8WPhRlZbORUWG9lAgDgJP0pi905VHJUFJr1Kp8980gHqtlemtnzjPxKy5vFwj6glNhAlK8SS8OOYzWP7hloTQ==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.2.6.tgz", + "integrity": "sha512-jgGLf5Ck35+kHa9fY8LreuT+PrtrDXPgy7uh8C3KkRnWoyTHaorW5q3Kw2no0UA79vXA87uX87kQVY6Ka8InzA==", "dev": true, "license": "MIT", "funding": { @@ -5193,20 +5188,20 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.1.11" + "storybook": "^10.2.6" } }, "node_modules/@storybook/react-vite": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.1.11.tgz", - "integrity": "sha512-qh1BCD25nIoiDfqwha+qBkl7pcG4WuzM+c8tsE63YEm8AFIbNKg5K8lVUoclF+4CpFz7IwBpWe61YUTDfp+91w==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.2.6.tgz", + "integrity": "sha512-RYkbzro1dfmLujz2fXm3eAuSyfLRdgl4SoGUa3W+WjTiHu5+sF1i+/CBYaOUmFFiPMibWRy4MNodrSInERb4qA==", "dev": true, "license": "MIT", "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.3", "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "10.1.11", - "@storybook/react": "10.1.11", + "@storybook/builder-vite": "10.2.6", + "@storybook/react": "10.2.6", "empathic": "^2.0.0", "magic-string": "^0.30.0", "react-docgen": "^8.0.0", @@ -5220,7 +5215,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.1.11", + "storybook": "^10.2.6", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, @@ -5489,7 +5484,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.16.tgz", "integrity": "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/query-core": "5.90.16" }, @@ -5524,7 +5518,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.150.0.tgz", "integrity": "sha512-k/oycTCpBT2XoEk9dNd/nNYhF0X9fLSB10lT40+NVX1TjOtBq5whksk8MT6oRnSoQ8KWeb7La3G9kFaAeSULkA==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/history": "1.145.7", "@tanstack/react-store": "^0.8.0", @@ -5596,7 +5589,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.150.0.tgz", "integrity": "sha512-cAm44t/tUbfyzaDH+rE/WO4u3AgaZdpJp00xjQ4gNkC2O95ntVHq5fx+4fhtrkKpgdXoKldgk8OK66djiWpuGQ==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/history": "1.145.7", "@tanstack/store": "^0.8.0", @@ -5892,7 +5884,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -5984,7 +5977,6 @@ "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -5995,7 +5987,6 @@ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6006,7 +5997,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6054,7 +6044,6 @@ "resolved": "https://registry.npmjs.org/@vector-im/compound-design-tokens/-/compound-design-tokens-6.4.3.tgz", "integrity": "sha512-Zyqag2QCAfpRipLHSX/LEXj6OGFgBpilMQvNdr3a0iXeGASfkM3HZGid077kMRp9lvyHiQfPIs1BdllENwDSNQ==", "license": "SEE LICENSE IN README.md", - "peer": true, "peerDependencies": { "@types/react": "*", "react": "^17 || ^18 || ^19.0.0" @@ -6204,43 +6193,6 @@ "node": ">=14.0.0" } }, - "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.2.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/mocker/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/@vitest/pretty-format": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", @@ -6782,7 +6734,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7382,8 +7333,7 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", @@ -7597,7 +7547,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dot-case": { "version": "3.0.4", @@ -7698,7 +7649,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -8221,7 +8171,6 @@ "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -8656,7 +8605,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -10141,6 +10089,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -10347,7 +10296,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.41.0", @@ -11076,7 +11024,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -11247,7 +11194,6 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -11285,6 +11231,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -11300,6 +11247,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -11359,7 +11307,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -11401,7 +11348,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -11441,7 +11387,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-refresh": { "version": "0.18.0", @@ -11792,7 +11739,6 @@ "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -11926,7 +11872,6 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.4.2.tgz", "integrity": "sha512-N3HEHRCZYn3cQbsC4B5ldj9j+tHdf4JZoYPlcI4rRYu0Xy4qN8MQf1Z08EibzB0WpgRG5BGK08FTrmM66eSzKQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -12152,15 +12097,14 @@ } }, "node_modules/storybook": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.1.11.tgz", - "integrity": "sha512-pKP5jXJYM4OjvNklGuHKO53wOCAwfx79KvZyOWHoi9zXUH5WVMFUe/ZfWyxXG/GTcj0maRgHGUjq/0I43r0dDQ==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.2.6.tgz", + "integrity": "sha512-5UY1f0f4TCAmJ19DCvYmWbvZaYwszIOY+PxTzu0S/WJpqm4pzO0bjn6Wfx113Jnh/tffUZ3MVuQgjJq20/ttNg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "^2.0.0", + "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", @@ -12168,7 +12112,7 @@ "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", - "semver": "^7.6.2", + "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, @@ -12867,7 +12811,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13135,7 +13078,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -13223,7 +13165,6 @@ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", @@ -13612,7 +13553,6 @@ "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 c458420f8..93a2593e3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -46,8 +46,8 @@ "@graphql-codegen/cli": "^6.1.1", "@graphql-codegen/client-preset": "^5.2.2", "@graphql-codegen/typescript-msw": "^3.0.1", - "@storybook/addon-docs": "^10.1.11", - "@storybook/react-vite": "^10.1.11", + "@storybook/addon-docs": "^10.2.6", + "@storybook/react-vite": "^10.2.6", "@tanstack/react-query-devtools": "^5.91.2", "@tanstack/react-router-devtools": "^1.150.0", "@tanstack/router-plugin": "^1.150.0", From 1c97d960b78ec483057736015947f96e9fc4823a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:13:41 +0000 Subject: [PATCH 15/43] build(deps-dev): bump @vitejs/plugin-react Bumps the vite group in /frontend with 1 update: [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react). Updates `@vitejs/plugin-react` from 5.1.2 to 5.1.3 - [Release notes](https://github.com/vitejs/vite-plugin-react/releases) - [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@5.1.3/packages/plugin-react) --- updated-dependencies: - dependency-name: "@vitejs/plugin-react" dependency-version: 5.1.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: vite ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 190 ++++++++++++++++--------------------- frontend/package.json | 2 +- 2 files changed, 85 insertions(+), 107 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2a8a91f41..3298400dc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -48,7 +48,7 @@ "@types/react": "19.2.7", "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", - "@vitejs/plugin-react": "^5.1.2", + "@vitejs/plugin-react": "^5.1.3", "@vitest/coverage-v8": "^4.0.18", "autoprefixer": "^10.4.23", "browserslist-to-esbuild": "^2.1.1", @@ -117,13 +117,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -132,9 +132,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -142,22 +142,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -174,14 +173,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -204,13 +203,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -267,29 +266,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -384,27 +383,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -922,33 +921,33 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -956,9 +955,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -1808,7 +1807,6 @@ "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" } @@ -1818,7 +1816,6 @@ "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" } @@ -1853,7 +1850,6 @@ "integrity": "sha512-Ni8UdZ6D/UTvLvDtPb6PzshI0lTqtLDnmv/2t1w2SYP92H0MMEdAzxB/ujDWwIXm2LzVPvvrGvzzCTMsyXa+mA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/generator": "^7.18.13", "@babel/template": "^7.18.10", @@ -4686,9 +4682,9 @@ "license": "MIT" }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", - "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", + "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", "dev": true, "license": "MIT" }, @@ -5489,7 +5485,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.16.tgz", "integrity": "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/query-core": "5.90.16" }, @@ -5524,7 +5519,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.150.0.tgz", "integrity": "sha512-k/oycTCpBT2XoEk9dNd/nNYhF0X9fLSB10lT40+NVX1TjOtBq5whksk8MT6oRnSoQ8KWeb7La3G9kFaAeSULkA==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/history": "1.145.7", "@tanstack/react-store": "^0.8.0", @@ -5596,7 +5590,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.150.0.tgz", "integrity": "sha512-cAm44t/tUbfyzaDH+rE/WO4u3AgaZdpJp00xjQ4gNkC2O95ntVHq5fx+4fhtrkKpgdXoKldgk8OK66djiWpuGQ==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/history": "1.145.7", "@tanstack/store": "^0.8.0", @@ -5892,7 +5885,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -5984,7 +5978,6 @@ "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -5995,7 +5988,6 @@ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6006,7 +5998,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6054,7 +6045,6 @@ "resolved": "https://registry.npmjs.org/@vector-im/compound-design-tokens/-/compound-design-tokens-6.4.3.tgz", "integrity": "sha512-Zyqag2QCAfpRipLHSX/LEXj6OGFgBpilMQvNdr3a0iXeGASfkM3HZGid077kMRp9lvyHiQfPIs1BdllENwDSNQ==", "license": "SEE LICENSE IN README.md", - "peer": true, "peerDependencies": { "@types/react": "*", "react": "^17 || ^18 || ^19.0.0" @@ -6098,16 +6088,16 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", - "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.3.tgz", + "integrity": "sha512-NVUnA6gQCl8jfoYqKqQU5Clv0aPw14KkZYCsX6T9Lfu9slI0LOU10OTwFHS/WmptsMMpshNd/1tuWsHQ2Uk+cg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.28.5", + "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.53", + "@rolldown/pluginutils": "1.0.0-rc.2", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, @@ -6782,7 +6772,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7382,8 +7371,7 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", @@ -7597,7 +7585,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dot-case": { "version": "3.0.4", @@ -7698,7 +7687,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -8221,7 +8209,6 @@ "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -8656,7 +8643,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -10141,6 +10127,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -10347,7 +10334,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.41.0", @@ -11076,7 +11062,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -11247,7 +11232,6 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -11285,6 +11269,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -11300,6 +11285,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -11359,7 +11345,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -11401,7 +11386,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -11441,7 +11425,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-refresh": { "version": "0.18.0", @@ -11792,7 +11777,6 @@ "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -11926,7 +11910,6 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.4.2.tgz", "integrity": "sha512-N3HEHRCZYn3cQbsC4B5ldj9j+tHdf4JZoYPlcI4rRYu0Xy4qN8MQf1Z08EibzB0WpgRG5BGK08FTrmM66eSzKQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -12157,7 +12140,6 @@ "integrity": "sha512-pKP5jXJYM4OjvNklGuHKO53wOCAwfx79KvZyOWHoi9zXUH5WVMFUe/ZfWyxXG/GTcj0maRgHGUjq/0I43r0dDQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.0", @@ -12867,7 +12849,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13135,7 +13116,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -13223,7 +13203,6 @@ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", @@ -13612,7 +13591,6 @@ "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 c458420f8..22e4a23af 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -58,7 +58,7 @@ "@types/react": "19.2.7", "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", - "@vitejs/plugin-react": "^5.1.2", + "@vitejs/plugin-react": "^5.1.3", "@vitest/coverage-v8": "^4.0.18", "autoprefixer": "^10.4.23", "browserslist-to-esbuild": "^2.1.1", From 9bd113df36f60f92aeeaae4d97d377cb01ca6ff5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:14:37 +0000 Subject: [PATCH 16/43] build(deps): bump the sentry group with 3 updates Bumps the sentry group with 3 updates: [sentry](https://github.com/getsentry/sentry-rust), [sentry-tower](https://github.com/getsentry/sentry-rust) and [sentry-tracing](https://github.com/getsentry/sentry-rust). Updates `sentry` from 0.46.0 to 0.46.2 - [Release notes](https://github.com/getsentry/sentry-rust/releases) - [Changelog](https://github.com/getsentry/sentry-rust/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-rust/compare/0.46.0...0.46.2) Updates `sentry-tower` from 0.46.0 to 0.46.2 - [Release notes](https://github.com/getsentry/sentry-rust/releases) - [Changelog](https://github.com/getsentry/sentry-rust/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-rust/compare/0.46.0...0.46.2) Updates `sentry-tracing` from 0.46.0 to 0.46.2 - [Release notes](https://github.com/getsentry/sentry-rust/releases) - [Changelog](https://github.com/getsentry/sentry-rust/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-rust/compare/0.46.0...0.46.2) --- updated-dependencies: - dependency-name: sentry dependency-version: 0.46.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: sentry - dependency-name: sentry-tower dependency-version: 0.46.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: sentry - dependency-name: sentry-tracing dependency-version: 0.46.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: sentry ... Signed-off-by: dependabot[bot] --- Cargo.lock | 59 ++++++++++++++++++++++++++++++------------------------ Cargo.toml | 2 +- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d54d9e89..43e0980fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -882,6 +882,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chacha20" version = "0.9.1" @@ -1736,7 +1742,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4086,7 +4092,7 @@ dependencies = [ "sha1", "sha2", "sprintf", - "thiserror 1.0.69", + "thiserror 2.0.17", "tokio", "tracing", "urlencoding", @@ -4708,7 +4714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn", @@ -4968,9 +4974,9 @@ checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", @@ -5146,7 +5152,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5418,10 +5424,11 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "sentry" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9794f69ad475e76c057e326175d3088509649e3aed98473106b9fe94ba59424" +checksum = "d92d893ba7469d361a6958522fa440e4e2bc8bf4c5803cd1bf40b9af63f8f9a8" dependencies = [ + "cfg_aliases", "httpdate", "reqwest", "sentry-backtrace", @@ -5435,9 +5442,9 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e81137ad53b8592bd0935459ad74c0376053c40084aa170451e74eeea8dbc6c3" +checksum = "5f8784d0a27b5cd4b5f75769ffc84f0b7580e3c35e1af9cd83cb90b612d769cc" dependencies = [ "backtrace", "regex", @@ -5446,9 +5453,9 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfb403c66cc2651a01b9bacda2e7c22cd51f7e8f56f206aa4310147eb3259282" +checksum = "0e5eb42f4cd4f9fdfec9e3b07b25a4c9769df83d218a7e846658984d5948ad3e" dependencies = [ "hostname", "libc", @@ -5460,9 +5467,9 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc409727ae90765ca8ea76fe6c949d6f159a11d02e130b357fa652ee9efcada" +checksum = "b0b1e7ca40f965db239da279bf278d87b7407469b98835f27f0c8e59ed189b06" dependencies = [ "rand 0.9.2", "sentry-types", @@ -5473,9 +5480,9 @@ dependencies = [ [[package]] name = "sentry-panic" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df79f4e1e72b2a8b75a0ebf49e78709ceb9b3f0b451f13adc92a0361b0aaabe" +checksum = "8906f8be87aea5ac7ef937323fb655d66607427f61007b99b7cb3504dc5a156c" dependencies = [ "sentry-backtrace", "sentry-core", @@ -5483,9 +5490,9 @@ dependencies = [ [[package]] name = "sentry-tower" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eec9885bceb8ba374858d015bb6fa39dbb341d94ca088bc8f13bee2e64e2c68" +checksum = "56aebe376310840b49dad4cca55c7b32d9abdc14946cd071d4158ecb149b63a4" dependencies = [ "axum", "http", @@ -5498,9 +5505,9 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2046f527fd4b75e0b6ab3bd656c67dce42072f828dc4d03c206d15dca74a93" +checksum = "5b07eefe04486316c57aba08ab53dd44753c25102d1d3fe05775cc93a13262d9" dependencies = [ "bitflags", "sentry-backtrace", @@ -5511,9 +5518,9 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7b9b4e4c03a4d3643c18c78b8aa91d2cbee5da047d2fa0ca4bb29bc67e6c55c" +checksum = "567711f01f86a842057e1fc17779eba33a336004227e1a1e7e6cc2599e22e259" dependencies = [ "debugid", "hex", @@ -6191,7 +6198,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6491,9 +6498,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags", "bytes", @@ -7318,7 +7325,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0f98906bc..62ebde108 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -567,7 +567,7 @@ features = [ # Sentry error tracking [workspace.dependencies.sentry] -version = "0.46.0" +version = "0.46.2" default-features = false features = ["backtrace", "contexts", "panic", "tower", "reqwest"] From 1b2c2c38f23c2e456ae3923f935d23b22e3b4e08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:15:04 +0000 Subject: [PATCH 17/43] build(deps): bump tracing-subscriber in the tracing group Bumps the tracing group with 1 update: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.20 to 0.3.22 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.20...tracing-subscriber-0.3.22) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: tracing ... Signed-off-by: dependabot[bot] --- Cargo.lock | 28 ++++++++++++++-------------- Cargo.toml | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d54d9e89..88d465c03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1736,7 +1736,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4086,7 +4086,7 @@ dependencies = [ "sha1", "sha2", "sprintf", - "thiserror 1.0.69", + "thiserror 2.0.17", "tokio", "tracing", "urlencoding", @@ -4708,7 +4708,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn", @@ -5146,7 +5146,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6191,7 +6191,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6531,9 +6531,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -6555,9 +6555,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -6566,9 +6566,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -6616,9 +6616,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", @@ -7318,7 +7318,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0f98906bc..a289a2e6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -685,7 +685,7 @@ features = ["cors", "fs", "add-extension", "set-header"] [workspace.dependencies.tracing] version = "0.1.41" [workspace.dependencies.tracing-subscriber] -version = "0.3.20" +version = "0.3.22" features = ["env-filter"] [workspace.dependencies.tracing-appender] version = "0.2.4" From 6f4033a9db3144d0f5a45180c446cbaf8693e4d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:29:00 +0000 Subject: [PATCH 18/43] build(deps): bump arc-swap from 1.7.1 to 1.8.1 Bumps [arc-swap](https://github.com/vorner/arc-swap) from 1.7.1 to 1.8.1. - [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md) - [Commits](https://github.com/vorner/arc-swap/compare/v1.7.1...v1.8.1) --- updated-dependencies: - dependency-name: arc-swap dependency-version: 1.8.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 19 +++++++++++-------- Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d54d9e89..ee854233f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,9 +197,12 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" +dependencies = [ + "rustversion", +] [[package]] name = "argon2" @@ -1736,7 +1739,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4086,7 +4089,7 @@ dependencies = [ "sha1", "sha2", "sprintf", - "thiserror 1.0.69", + "thiserror 2.0.17", "tokio", "tracing", "urlencoding", @@ -4708,7 +4711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn", @@ -5146,7 +5149,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6191,7 +6194,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7318,7 +7321,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0f98906bc..639d409c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ features = ["axum", "axum-extra", "axum-extra-query", "axum-json", "macros"] # An `Arc` that can be atomically updated [workspace.dependencies.arc-swap] -version = "1.7.1" +version = "1.8.1" # GraphQL server [workspace.dependencies.async-graphql] From 08308fa716cd4a34468fb5e1dd0b320e46b3a55b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:30:10 +0000 Subject: [PATCH 19/43] build(deps): bump the react group in /frontend with 2 updates Bumps the react group in /frontend with 2 updates: [react](https://github.com/facebook/react/tree/HEAD/packages/react) and [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom). Updates `react` from 19.2.3 to 19.2.4 - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/v19.2.4/packages/react) Updates `react-dom` from 19.2.3 to 19.2.4 - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/v19.2.4/packages/react-dom) --- updated-dependencies: - dependency-name: react dependency-version: 19.2.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: react - dependency-name: react-dom dependency-version: 19.2.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: react ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 26 +++++++++++++------------- frontend/package.json | 6 +++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bc4e1eaf7..681dbe0bb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -21,8 +21,8 @@ "classnames": "^2.5.1", "date-fns": "^4.1.0", "i18next": "^25.7.4", - "react": "^19.2.3", - "react-dom": "^19.2.3", + "react": "^19.2.4", + "react-dom": "^19.2.4", "react-i18next": "^16.5.3", "swagger-ui-dist": "^5.31.0", "valibot": "^1.2.0", @@ -45,7 +45,7 @@ "@testing-library/react": "^16.3.1", "@testing-library/user-event": "^14.6.1", "@types/node": "^25.0.3", - "@types/react": "19.2.7", + "@types/react": "19.2.11", "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.3", @@ -5982,9 +5982,9 @@ } }, "node_modules/@types/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", - "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "version": "19.2.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.11.tgz", + "integrity": "sha512-tORuanb01iEzWvMGVGv2ZDhYZVeRMrw453DCSAIn/5yvcSVnMoUMTyf33nQJLahYEnv9xqrTNbgz4qY5EfSh0g==", "devOptional": true, "license": "MIT", "dependencies": { @@ -11303,9 +11303,9 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11344,15 +11344,15 @@ } }, "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.3" + "react": "^19.2.4" } }, "node_modules/react-i18next": { diff --git a/frontend/package.json b/frontend/package.json index 42cbce457..52d89c164 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,8 +31,8 @@ "classnames": "^2.5.1", "date-fns": "^4.1.0", "i18next": "^25.7.4", - "react": "^19.2.3", - "react-dom": "^19.2.3", + "react": "^19.2.4", + "react-dom": "^19.2.4", "react-i18next": "^16.5.3", "swagger-ui-dist": "^5.31.0", "valibot": "^1.2.0", @@ -55,7 +55,7 @@ "@testing-library/react": "^16.3.1", "@testing-library/user-event": "^14.6.1", "@types/node": "^25.0.3", - "@types/react": "19.2.7", + "@types/react": "19.2.11", "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.3", From 375164b235c250e58b80e3d1992604b6f08ab808 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 4 Feb 2026 17:49:17 +0100 Subject: [PATCH 20/43] Fix changes in React types --- frontend/src/components/AccountDeleteButton.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/AccountDeleteButton.tsx b/frontend/src/components/AccountDeleteButton.tsx index 6f1b80bec..a5ab30991 100644 --- a/frontend/src/components/AccountDeleteButton.tsx +++ b/frontend/src/components/AccountDeleteButton.tsx @@ -109,8 +109,8 @@ const AccountDeleteButton: React.FC = (props) => { setAllowSubmitting(false); }, [isMaybeValid]); - const onPasswordChange = useCallback( - (e: React.ChangeEvent) => { + const onPasswordInput = useCallback( + (e: React.InputEvent) => { // We don't know if the password is correct, so we consider the form as // valid if the field is not empty setIsMaybeValid(e.currentTarget.value !== ""); @@ -118,8 +118,8 @@ const AccountDeleteButton: React.FC = (props) => { [], ); - const onMxidChange = useCallback( - (e: React.ChangeEvent) => { + const onMxidInput = useCallback( + (e: React.InputEvent) => { setIsMaybeValid(e.currentTarget.value === user.matrix.mxid); }, [user.matrix.mxid], @@ -208,7 +208,7 @@ const AccountDeleteButton: React.FC = (props) => { @@ -232,7 +232,7 @@ const AccountDeleteButton: React.FC = (props) => { From 207c526f00daac477d9d4d599ecf50b81a1365c0 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 4 Feb 2026 17:24:22 +0100 Subject: [PATCH 21/43] Upgrade Rust, opa, regal, cargo-auditable and Node --- .github/actions/build-policies/action.yml | 2 +- .github/workflows/ci.yaml | 4 ++-- Dockerfile | 9 +++++---- policies/Makefile | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/actions/build-policies/action.yml b/.github/actions/build-policies/action.yml index e1dc28547..6264047ef 100644 --- a/.github/actions/build-policies/action.yml +++ b/.github/actions/build-policies/action.yml @@ -13,7 +13,7 @@ runs: uses: open-policy-agent/setup-opa@v2.2.0 with: # Keep in sync with the Dockerfile and policies/Makefile - version: 1.8.0 + version: 1.13.1 - name: Build the policies run: make diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6444b1033..ba90f2be1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,7 +42,7 @@ jobs: uses: StyraInc/setup-regal@v1 with: # Keep in sync with policies/Makefile - version: 0.36.1 + version: 0.38.1 - name: Lint policies working-directory: ./policies @@ -216,7 +216,7 @@ jobs: uses: actions/checkout@v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@1.89.0 + uses: dtolnay/rust-toolchain@1.93.0 with: components: clippy diff --git a/Dockerfile b/Dockerfile index 479dee308..a8d9b14c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,11 +12,12 @@ # The Debian version and version name must be in sync ARG DEBIAN_VERSION=12 ARG DEBIAN_VERSION_NAME=bookworm -ARG RUSTC_VERSION=1.89.0 -ARG NODEJS_VERSION=24.11.0 +# Keep in sync with .github/workflows/ci.yaml +ARG RUSTC_VERSION=1.93.0 +ARG NODEJS_VERSION=24.13.0 # Keep in sync with .github/actions/build-policies/action.yml and policies/Makefile -ARG OPA_VERSION=1.8.0 -ARG CARGO_AUDITABLE_VERSION=0.7.0 +ARG OPA_VERSION=1.13.1 +ARG CARGO_AUDITABLE_VERSION=0.7.2 ########################################## ## Build stage that builds the frontend ## diff --git a/policies/Makefile b/policies/Makefile index db5991672..de4d063ad 100644 --- a/policies/Makefile +++ b/policies/Makefile @@ -7,9 +7,9 @@ DOCKER := 0 PODMAN := 0 # Keep in sync with Dockerfile and .github/actions/build-policies/action.yml -OPA_DOCKER_IMAGE := docker.io/openpolicyagent/opa:1.8.0-debug +OPA_DOCKER_IMAGE := docker.io/openpolicyagent/opa:1.13.1 # Keep in sync with .github/workflows/ci.yaml -REGAL_DOCKER_IMAGE := ghcr.io/open-policy-agent/regal:0.36.1 +REGAL_DOCKER_IMAGE := ghcr.io/open-policy-agent/regal:0.38.1 INPUTS := \ common/common.rego \ From c4c19d10a6a86f1c8633f574fe51efa5b2423830 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 4 Feb 2026 17:25:11 +0100 Subject: [PATCH 22/43] Remove unused code --- crates/config/src/sections/clients.rs | 13 ------------- crates/config/src/sections/email.rs | 9 --------- 2 files changed, 22 deletions(-) diff --git a/crates/config/src/sections/clients.rs b/crates/config/src/sections/clients.rs index 8b9120045..2387d642d 100644 --- a/crates/config/src/sections/clients.rs +++ b/crates/config/src/sections/clients.rs @@ -16,19 +16,6 @@ use url::Url; use super::{ClientSecret, ClientSecretRaw, ConfigurationSection}; -#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "snake_case")] -pub enum JwksOrJwksUri { - Jwks(PublicJsonWebKeySet), - JwksUri(Url), -} - -impl From for JwksOrJwksUri { - fn from(jwks: PublicJsonWebKeySet) -> Self { - Self::Jwks(jwks) - } -} - /// Authentication method used by clients #[derive(JsonSchema, Serialize, Deserialize, Copy, Clone, Debug)] #[serde(rename_all = "snake_case")] diff --git a/crates/config/src/sections/email.rs b/crates/config/src/sections/email.rs index 3df0c99db..8c365e3e5 100644 --- a/crates/config/src/sections/email.rs +++ b/crates/config/src/sections/email.rs @@ -14,15 +14,6 @@ use serde::{Deserialize, Serialize, de::Error}; use super::ConfigurationSection; -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -pub struct Credentials { - /// Username for use to authenticate when connecting to the SMTP server - pub username: String, - - /// Password for use to authenticate when connecting to the SMTP server - pub password: String, -} - /// Encryption mode to use #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "lowercase")] From 202d7337b180e662616d45520830c8697ae16835 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 4 Feb 2026 17:33:38 +0100 Subject: [PATCH 23/43] Update Docker image to Debian 13 --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a8d9b14c8..79ee6cc4f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ # syntax = docker/dockerfile:1.7.1 +# Copyright 2025, 2026 Element Creations Ltd. # Copyright 2025 New Vector Ltd. # # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial @@ -10,8 +11,8 @@ # being the platform being built. # The Debian version and version name must be in sync -ARG DEBIAN_VERSION=12 -ARG DEBIAN_VERSION_NAME=bookworm +ARG DEBIAN_VERSION=13 +ARG DEBIAN_VERSION_NAME=trixie # Keep in sync with .github/workflows/ci.yaml ARG RUSTC_VERSION=1.93.0 ARG NODEJS_VERSION=24.13.0 From f21634ada506a0c58f2bfb7a0ea97aa3a931d8e5 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 4 Feb 2026 17:44:12 +0100 Subject: [PATCH 24/43] Update dockerfile frontend --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 79ee6cc4f..ba2ed16c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# syntax = docker/dockerfile:1.7.1 +# syntax = docker/dockerfile:1.21.0 # Copyright 2025, 2026 Element Creations Ltd. # Copyright 2025 New Vector Ltd. # From c6ebac68a5e8baef540a0cb8c26686e7df809255 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:54:47 +0000 Subject: [PATCH 25/43] build(deps): bump the tanstack-query group in /frontend with 2 updates Bumps the tanstack-query group in /frontend with 2 updates: [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) and [@tanstack/react-query-devtools](https://github.com/TanStack/query/tree/HEAD/packages/react-query-devtools). Updates `@tanstack/react-query` from 5.90.16 to 5.90.20 - [Release notes](https://github.com/TanStack/query/releases) - [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md) - [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.90.20/packages/react-query) Updates `@tanstack/react-query-devtools` from 5.91.2 to 5.91.3 - [Release notes](https://github.com/TanStack/query/releases) - [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query-devtools/CHANGELOG.md) - [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query-devtools@5.91.3/packages/react-query-devtools) --- updated-dependencies: - dependency-name: "@tanstack/react-query" dependency-version: 5.90.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: tanstack-query - dependency-name: "@tanstack/react-query-devtools" dependency-version: 5.91.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: tanstack-query ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 34 +++++++++++++++++----------------- frontend/package.json | 4 ++-- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 681dbe0bb..bcd6d0c95 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,7 +12,7 @@ "@fontsource/inter": "^5.2.8", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", - "@tanstack/react-query": "^5.90.16", + "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.150.0", "@vector-im/compound-design-tokens": "6.4.3", "@vector-im/compound-web": "^8.3.5", @@ -38,7 +38,7 @@ "@graphql-codegen/typescript-msw": "^3.0.1", "@storybook/addon-docs": "^10.2.6", "@storybook/react-vite": "^10.2.6", - "@tanstack/react-query-devtools": "^5.91.2", + "@tanstack/react-query-devtools": "^5.91.3", "@tanstack/react-router-devtools": "^1.150.0", "@tanstack/router-plugin": "^1.150.0", "@testing-library/jest-dom": "^6.9.1", @@ -5459,9 +5459,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.90.16", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.16.tgz", - "integrity": "sha512-MvtWckSVufs/ja463/K4PyJeqT+HMlJWtw6PrCpywznd2NSgO3m4KwO9RqbFqGg6iDE8vVMFWMeQI4Io3eEYww==", + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", + "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", "license": "MIT", "funding": { "type": "github", @@ -5469,9 +5469,9 @@ } }, "node_modules/@tanstack/query-devtools": { - "version": "5.92.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.92.0.tgz", - "integrity": "sha512-N8D27KH1vEpVacvZgJL27xC6yPFUy0Zkezn5gnB3L3gRCxlDeSuiya7fKge8Y91uMTnC8aSxBQhcK6ocY7alpQ==", + "version": "5.93.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.93.0.tgz", + "integrity": "sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg==", "dev": true, "license": "MIT", "funding": { @@ -5480,12 +5480,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.90.16", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.16.tgz", - "integrity": "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==", + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz", + "integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.90.16" + "@tanstack/query-core": "5.90.20" }, "funding": { "type": "github", @@ -5496,20 +5496,20 @@ } }, "node_modules/@tanstack/react-query-devtools": { - "version": "5.91.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.91.2.tgz", - "integrity": "sha512-ZJ1503ay5fFeEYFUdo7LMNFzZryi6B0Cacrgr2h1JRkvikK1khgIq6Nq2EcblqEdIlgB/r7XDW8f8DQ89RuUgg==", + "version": "5.91.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.91.3.tgz", + "integrity": "sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA==", "dev": true, "license": "MIT", "dependencies": { - "@tanstack/query-devtools": "5.92.0" + "@tanstack/query-devtools": "5.93.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-query": "^5.90.14", + "@tanstack/react-query": "^5.90.20", "react": "^18 || ^19" } }, diff --git a/frontend/package.json b/frontend/package.json index 52d89c164..d8d57719b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,7 +22,7 @@ "@fontsource/inter": "^5.2.8", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", - "@tanstack/react-query": "^5.90.16", + "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.150.0", "@vector-im/compound-design-tokens": "6.4.3", "@vector-im/compound-web": "^8.3.5", @@ -48,7 +48,7 @@ "@graphql-codegen/typescript-msw": "^3.0.1", "@storybook/addon-docs": "^10.2.6", "@storybook/react-vite": "^10.2.6", - "@tanstack/react-query-devtools": "^5.91.2", + "@tanstack/react-query-devtools": "^5.91.3", "@tanstack/react-router-devtools": "^1.150.0", "@tanstack/router-plugin": "^1.150.0", "@testing-library/jest-dom": "^6.9.1", From 84eebc44e5fc156eb69251682dca44926021fb64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:55:04 +0000 Subject: [PATCH 26/43] build(deps-dev): bump the types group in /frontend with 2 updates Bumps the types group in /frontend with 2 updates: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react). Updates `@types/node` from 25.0.3 to 25.2.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@types/react` from 19.2.11 to 19.2.13 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 25.2.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: types - dependency-name: "@types/react" dependency-version: 19.2.13 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 16 ++++++++-------- frontend/package.json | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 681dbe0bb..73ab1f453 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -44,8 +44,8 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", "@testing-library/user-event": "^14.6.1", - "@types/node": "^25.0.3", - "@types/react": "19.2.11", + "@types/node": "^25.2.0", + "@types/react": "19.2.13", "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.3", @@ -5972,9 +5972,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", - "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "version": "25.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz", + "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==", "dev": true, "license": "MIT", "dependencies": { @@ -5982,9 +5982,9 @@ } }, "node_modules/@types/react": { - "version": "19.2.11", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.11.tgz", - "integrity": "sha512-tORuanb01iEzWvMGVGv2ZDhYZVeRMrw453DCSAIn/5yvcSVnMoUMTyf33nQJLahYEnv9xqrTNbgz4qY5EfSh0g==", + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", + "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", "devOptional": true, "license": "MIT", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 52d89c164..01767f4d1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -54,8 +54,8 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", "@testing-library/user-event": "^14.6.1", - "@types/node": "^25.0.3", - "@types/react": "19.2.11", + "@types/node": "^25.2.0", + "@types/react": "19.2.13", "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.3", From 0d8d4898eb20b80270b2b02158242dc99639d52c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:55:12 +0000 Subject: [PATCH 27/43] build(deps): bump insta from 1.43.2 to 1.46.3 Bumps [insta](https://github.com/mitsuhiko/insta) from 1.43.2 to 1.46.3. - [Release notes](https://github.com/mitsuhiko/insta/releases) - [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md) - [Commits](https://github.com/mitsuhiko/insta/compare/1.43.2...1.46.3) --- updated-dependencies: - dependency-name: insta dependency-version: 1.46.3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 5 +++-- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d687070f..a8c2d6f19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2788,14 +2788,15 @@ dependencies = [ [[package]] name = "insta" -version = "1.43.2" +version = "1.46.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0" +checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4" dependencies = [ "console", "once_cell", "serde", "similar", + "tempfile", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5d1d9c78e..2bb73d98c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -330,7 +330,7 @@ version = "2.0.6" # Snapshot testing [workspace.dependencies.insta] -version = "1.43.2" +version = "1.46.3" features = ["yaml", "json"] # IP network address types From 6fd27306f6450fc20fab7a5971e654757c630afa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:55:15 +0000 Subject: [PATCH 28/43] build(deps-dev): bump happy-dom from 20.0.11 to 20.5.0 in /frontend Bumps [happy-dom](https://github.com/capricorn86/happy-dom) from 20.0.11 to 20.5.0. - [Release notes](https://github.com/capricorn86/happy-dom/releases) - [Commits](https://github.com/capricorn86/happy-dom/compare/v20.0.11...v20.5.0) --- updated-dependencies: - dependency-name: happy-dom dependency-version: 20.5.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 45 +++++++++++++++++++------------------- frontend/package.json | 2 +- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 681dbe0bb..467c4e7ff 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -53,7 +53,7 @@ "autoprefixer": "^10.4.23", "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.12.0", - "happy-dom": "^20.0.11", + "happy-dom": "^20.5.0", "i18next-cli": "^1.36.1", "knip": "^5.81.0", "msw": "^2.12.8", @@ -7602,6 +7602,19 @@ "node": ">=14" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -8488,37 +8501,23 @@ } }, "node_modules/happy-dom": { - "version": "20.0.11", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.0.11.tgz", - "integrity": "sha512-QsCdAUHAmiDeKeaNojb1OHOPF7NjcWPBR7obdu3NwH2a/oyQaLg5d0aaCy/9My6CdPChYF07dvz5chaXBGaD4g==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.5.0.tgz", + "integrity": "sha512-VQe+Q5CYiGOgcCERXhcfNsbnrN92FDEKciMH/x6LppU9dd0j4aTjCTlqONFOIMcAm/5JxS3+utowbXV1OoFr+g==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "^20.0.0", + "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", - "whatwg-mimetype": "^3.0.0" + "@types/ws": "^8.18.1", + "entities": "^4.5.0", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.18.3" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/happy-dom/node_modules/@types/node": { - "version": "20.19.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", - "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/happy-dom/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 52d89c164..e8706ad64 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -63,7 +63,7 @@ "autoprefixer": "^10.4.23", "browserslist-to-esbuild": "^2.1.1", "graphql": "^16.12.0", - "happy-dom": "^20.0.11", + "happy-dom": "^20.5.0", "i18next-cli": "^1.36.1", "knip": "^5.81.0", "msw": "^2.12.8", From d0f1aab686166818d92d3130ec88d1491c33d08f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 18:51:18 +0000 Subject: [PATCH 29/43] build(deps): bump time from 0.3.43 to 0.3.47 Bumps [time](https://github.com/time-rs/time) from 0.3.43 to 0.3.47. - [Release notes](https://github.com/time-rs/time/releases) - [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md) - [Commits](https://github.com/time-rs/time/compare/v0.3.43...v0.3.47) --- updated-dependencies: - dependency-name: time dependency-version: 0.3.47 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Cargo.lock | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8c2d6f19..e993b690d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3960,9 +3960,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -6287,31 +6287,32 @@ dependencies = [ [[package]] name = "time" -version = "0.3.43" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", + "itoa", "libc", "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", From 91c900588cd8666aa62ac9c31b47c34494c1b7e5 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 6 Feb 2026 09:42:56 +0100 Subject: [PATCH 30/43] Make the compat login SSO redirect query parameters ignore invalid values --- crates/handlers/src/compat/login_sso_redirect.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/handlers/src/compat/login_sso_redirect.rs b/crates/handlers/src/compat/login_sso_redirect.rs index 1ad47c55e..f8b4435a1 100644 --- a/crates/handlers/src/compat/login_sso_redirect.rs +++ b/crates/handlers/src/compat/login_sso_redirect.rs @@ -13,18 +13,21 @@ use mas_router::{CompatLoginSsoAction, CompatLoginSsoComplete, UrlBuilder}; use mas_storage::{BoxRepository, compat::CompatSsoLoginRepository}; use rand::distributions::{Alphanumeric, DistString}; use serde::Deserialize; -use serde_with::serde; +use serde_with::{DefaultOnError, serde, serde_as}; use thiserror::Error; use url::Url; use crate::impl_from_error_for_route; +#[serde_as] #[derive(Debug, Deserialize)] pub struct Params { #[serde(rename = "redirectUrl")] redirect_url: Option, + #[serde_as(deserialize_as = "DefaultOnError")] action: Option, + #[serde_as(deserialize_as = "DefaultOnError")] #[serde(rename = "org.matrix.msc3824.action")] unstable_action: Option, } From 72eef700ce0d0614bc8d8076abb35c46ad4b5722 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 18:51:18 +0000 Subject: [PATCH 31/43] build(deps): bump time from 0.3.43 to 0.3.47 Bumps [time](https://github.com/time-rs/time) from 0.3.43 to 0.3.47. - [Release notes](https://github.com/time-rs/time/releases) - [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md) - [Commits](https://github.com/time-rs/time/compare/v0.3.43...v0.3.47) --- updated-dependencies: - dependency-name: time dependency-version: 0.3.47 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Cargo.lock | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d54d9e89..0699c7203 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3950,9 +3950,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -6276,31 +6276,32 @@ dependencies = [ [[package]] name = "time" -version = "0.3.43" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", + "itoa", "libc", "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", From c66012772102b47e8e15bcba87c692d2d832b13b Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 6 Feb 2026 11:31:39 +0100 Subject: [PATCH 32/43] Use #[serde(other)] for unknown values instead of serde_with::DefaultOnError --- .../handlers/src/compat/login_sso_complete.rs | 4 +- .../handlers/src/compat/login_sso_redirect.rs | 45 +++++++++++++++---- crates/router/src/endpoints.rs | 10 +++++ 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/crates/handlers/src/compat/login_sso_complete.rs b/crates/handlers/src/compat/login_sso_complete.rs index 84c8f8525..df059cd36 100644 --- a/crates/handlers/src/compat/login_sso_complete.rs +++ b/crates/handlers/src/compat/login_sso_complete.rs @@ -83,7 +83,7 @@ pub async fn get( Some(CompatLoginSsoAction::Register) => { url_builder.redirect(&mas_router::Register::and_continue_compat_sso_login(id)) } - Some(CompatLoginSsoAction::Login) | None => { + Some(CompatLoginSsoAction::Login | CompatLoginSsoAction::Unknown) | None => { url_builder.redirect(&mas_router::Login::and_continue_compat_sso_login(id)) } }; @@ -224,7 +224,7 @@ pub async fn post( Some(CompatLoginSsoAction::Register) => { url_builder.redirect(&mas_router::Register::and_continue_compat_sso_login(id)) } - Some(CompatLoginSsoAction::Login) | None => { + Some(CompatLoginSsoAction::Login | CompatLoginSsoAction::Unknown) | None => { url_builder.redirect(&mas_router::Login::and_continue_compat_sso_login(id)) } }; diff --git a/crates/handlers/src/compat/login_sso_redirect.rs b/crates/handlers/src/compat/login_sso_redirect.rs index f8b4435a1..8edb868fd 100644 --- a/crates/handlers/src/compat/login_sso_redirect.rs +++ b/crates/handlers/src/compat/login_sso_redirect.rs @@ -13,25 +13,30 @@ use mas_router::{CompatLoginSsoAction, CompatLoginSsoComplete, UrlBuilder}; use mas_storage::{BoxRepository, compat::CompatSsoLoginRepository}; use rand::distributions::{Alphanumeric, DistString}; use serde::Deserialize; -use serde_with::{DefaultOnError, serde, serde_as}; use thiserror::Error; use url::Url; use crate::impl_from_error_for_route; -#[serde_as] #[derive(Debug, Deserialize)] pub struct Params { #[serde(rename = "redirectUrl")] redirect_url: Option, - #[serde_as(deserialize_as = "DefaultOnError")] + action: Option, - #[serde_as(deserialize_as = "DefaultOnError")] #[serde(rename = "org.matrix.msc3824.action")] unstable_action: Option, } +impl Params { + fn action(&self) -> Option { + self.action + .filter(CompatLoginSsoAction::is_known) + .or(self.unstable_action.filter(CompatLoginSsoAction::is_known)) + } +} + #[derive(Debug, Error)] pub enum RouteError { #[error(transparent)] @@ -65,6 +70,8 @@ pub async fn get( State(url_builder): State, Query(params): Query, ) -> Result { + let action = params.action(); + // Check the redirectUrl parameter let redirect_url = params.redirect_url.ok_or(RouteError::MissingRedirectUrl)?; let redirect_url = Url::parse(&redirect_url).map_err(|_| RouteError::InvalidRedirectUrl)?; @@ -87,10 +94,7 @@ pub async fn get( repo.save().await?; - Ok(url_builder.absolute_redirect(&CompatLoginSsoComplete::new( - login.id, - params.action.or(params.unstable_action), - ))) + Ok(url_builder.absolute_redirect(&CompatLoginSsoComplete::new(login.id, action))) } #[cfg(test)] @@ -124,4 +128,29 @@ mod tests { assert!(location.contains("org.matrix.msc3824.action=register")); assert!(location.contains("action=register")); } + + #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] + async fn test_unknown_action(pool: PgPool) { + let state: TestState = TestState::from_pool(pool).await.unwrap(); + + let request = Request::get( + "/_matrix/client/v3/login/sso/redirect?\ + redirectUrl=http://example.com/\ + &org.matrix.msc3824.action=undefinedaction", + ) + .empty(); + + let response = state.request(request).await; + + response.assert_status(StatusCode::SEE_OTHER); + + let location = response + .headers() + .get("Location") + .unwrap() + .to_str() + .unwrap(); + assert!(!location.contains("org.matrix.msc3824.action")); + assert!(!location.contains("action")); + } } diff --git a/crates/router/src/endpoints.rs b/crates/router/src/endpoints.rs index 37c200aac..6aa18f13d 100644 --- a/crates/router/src/endpoints.rs +++ b/crates/router/src/endpoints.rs @@ -628,6 +628,16 @@ impl SimpleRoute for CompatLoginSsoRedirectIdp { pub enum CompatLoginSsoAction { Login, Register, + #[serde(other)] + Unknown, +} + +impl CompatLoginSsoAction { + /// Returns true if the action is a known action. + #[must_use] + pub fn is_known(&self) -> bool { + !matches!(self, Self::Unknown) + } } #[derive(Debug, Serialize, Deserialize, Clone, Copy)] From e2c0cee6f13f1819d87782742bee229a9105cee4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:02:32 +0000 Subject: [PATCH 33/43] 1.11.0-rc.1 --- Cargo.lock | 56 +++++++++++++++++++++++++------------------------- Cargo.toml | 60 +++++++++++++++++++++++++++--------------------------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0699c7203..6cd5e3ebb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3097,7 +3097,7 @@ dependencies = [ [[package]] name = "mas-axum-utils" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "anyhow", "axum", @@ -3131,7 +3131,7 @@ dependencies = [ [[package]] name = "mas-cli" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "anyhow", "axum", @@ -3206,7 +3206,7 @@ dependencies = [ [[package]] name = "mas-config" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "anyhow", "camino", @@ -3237,7 +3237,7 @@ dependencies = [ [[package]] name = "mas-context" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "console", "opentelemetry", @@ -3253,7 +3253,7 @@ dependencies = [ [[package]] name = "mas-data-model" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "base64ct", "chrono", @@ -3276,7 +3276,7 @@ dependencies = [ [[package]] name = "mas-email" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "async-trait", "lettre", @@ -3287,7 +3287,7 @@ dependencies = [ [[package]] name = "mas-handlers" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "aide", "anyhow", @@ -3367,7 +3367,7 @@ dependencies = [ [[package]] name = "mas-http" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "futures-util", "headers", @@ -3388,7 +3388,7 @@ dependencies = [ [[package]] name = "mas-i18n" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "camino", "icu_calendar", @@ -3410,7 +3410,7 @@ dependencies = [ [[package]] name = "mas-i18n-scan" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "camino", "clap", @@ -3424,7 +3424,7 @@ dependencies = [ [[package]] name = "mas-iana" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "schemars 0.9.0", "serde", @@ -3432,7 +3432,7 @@ dependencies = [ [[package]] name = "mas-iana-codegen" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "anyhow", "async-trait", @@ -3449,7 +3449,7 @@ dependencies = [ [[package]] name = "mas-jose" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "base64ct", "chrono", @@ -3479,7 +3479,7 @@ dependencies = [ [[package]] name = "mas-keystore" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "aead", "base64ct", @@ -3507,7 +3507,7 @@ dependencies = [ [[package]] name = "mas-listener" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "anyhow", "bytes", @@ -3531,7 +3531,7 @@ dependencies = [ [[package]] name = "mas-matrix" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "anyhow", "async-trait", @@ -3541,7 +3541,7 @@ dependencies = [ [[package]] name = "mas-matrix-synapse" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "anyhow", "async-trait", @@ -3558,7 +3558,7 @@ dependencies = [ [[package]] name = "mas-oidc-client" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "assert_matches", "async-trait", @@ -3594,7 +3594,7 @@ dependencies = [ [[package]] name = "mas-policy" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "anyhow", "arc-swap", @@ -3611,7 +3611,7 @@ dependencies = [ [[package]] name = "mas-router" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "axum", "serde", @@ -3622,7 +3622,7 @@ dependencies = [ [[package]] name = "mas-spa" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "camino", "serde", @@ -3631,7 +3631,7 @@ dependencies = [ [[package]] name = "mas-storage" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "async-trait", "chrono", @@ -3653,7 +3653,7 @@ dependencies = [ [[package]] name = "mas-storage-pg" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "async-trait", "chrono", @@ -3683,7 +3683,7 @@ dependencies = [ [[package]] name = "mas-tasks" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "anyhow", "async-trait", @@ -3715,7 +3715,7 @@ dependencies = [ [[package]] name = "mas-templates" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "anyhow", "arc-swap", @@ -3747,7 +3747,7 @@ dependencies = [ [[package]] name = "mas-tower" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "http", "opentelemetry", @@ -4016,7 +4016,7 @@ dependencies = [ [[package]] name = "oauth2-types" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "assert_matches", "base64ct", @@ -6120,7 +6120,7 @@ dependencies = [ [[package]] name = "syn2mas" -version = "1.11.0-rc.0" +version = "1.11.0-rc.1" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index 0f98906bc..8deb5b050 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = ["crates/*"] resolver = "2" # Updated in the CI with a `sed` command -package.version = "1.11.0-rc.0" +package.version = "1.11.0-rc.1" package.license = "AGPL-3.0-only OR LicenseRef-Element-Commercial" package.authors = ["Element Backend Team"] package.edition = "2024" @@ -34,35 +34,35 @@ broken_intra_doc_links = "deny" [workspace.dependencies] # Workspace crates -mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.11.0-rc.0" } -mas-cli = { path = "./crates/cli/", version = "=1.11.0-rc.0" } -mas-config = { path = "./crates/config/", version = "=1.11.0-rc.0" } -mas-context = { path = "./crates/context/", version = "=1.11.0-rc.0" } -mas-data-model = { path = "./crates/data-model/", version = "=1.11.0-rc.0" } -mas-email = { path = "./crates/email/", version = "=1.11.0-rc.0" } -mas-graphql = { path = "./crates/graphql/", version = "=1.11.0-rc.0" } -mas-handlers = { path = "./crates/handlers/", version = "=1.11.0-rc.0" } -mas-http = { path = "./crates/http/", version = "=1.11.0-rc.0" } -mas-i18n = { path = "./crates/i18n/", version = "=1.11.0-rc.0" } -mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.11.0-rc.0" } -mas-iana = { path = "./crates/iana/", version = "=1.11.0-rc.0" } -mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.11.0-rc.0" } -mas-jose = { path = "./crates/jose/", version = "=1.11.0-rc.0" } -mas-keystore = { path = "./crates/keystore/", version = "=1.11.0-rc.0" } -mas-listener = { path = "./crates/listener/", version = "=1.11.0-rc.0" } -mas-matrix = { path = "./crates/matrix/", version = "=1.11.0-rc.0" } -mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.11.0-rc.0" } -mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.11.0-rc.0" } -mas-policy = { path = "./crates/policy/", version = "=1.11.0-rc.0" } -mas-router = { path = "./crates/router/", version = "=1.11.0-rc.0" } -mas-spa = { path = "./crates/spa/", version = "=1.11.0-rc.0" } -mas-storage = { path = "./crates/storage/", version = "=1.11.0-rc.0" } -mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.11.0-rc.0" } -mas-tasks = { path = "./crates/tasks/", version = "=1.11.0-rc.0" } -mas-templates = { path = "./crates/templates/", version = "=1.11.0-rc.0" } -mas-tower = { path = "./crates/tower/", version = "=1.11.0-rc.0" } -oauth2-types = { path = "./crates/oauth2-types/", version = "=1.11.0-rc.0" } -syn2mas = { path = "./crates/syn2mas", version = "=1.11.0-rc.0" } +mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.11.0-rc.1" } +mas-cli = { path = "./crates/cli/", version = "=1.11.0-rc.1" } +mas-config = { path = "./crates/config/", version = "=1.11.0-rc.1" } +mas-context = { path = "./crates/context/", version = "=1.11.0-rc.1" } +mas-data-model = { path = "./crates/data-model/", version = "=1.11.0-rc.1" } +mas-email = { path = "./crates/email/", version = "=1.11.0-rc.1" } +mas-graphql = { path = "./crates/graphql/", version = "=1.11.0-rc.1" } +mas-handlers = { path = "./crates/handlers/", version = "=1.11.0-rc.1" } +mas-http = { path = "./crates/http/", version = "=1.11.0-rc.1" } +mas-i18n = { path = "./crates/i18n/", version = "=1.11.0-rc.1" } +mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.11.0-rc.1" } +mas-iana = { path = "./crates/iana/", version = "=1.11.0-rc.1" } +mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.11.0-rc.1" } +mas-jose = { path = "./crates/jose/", version = "=1.11.0-rc.1" } +mas-keystore = { path = "./crates/keystore/", version = "=1.11.0-rc.1" } +mas-listener = { path = "./crates/listener/", version = "=1.11.0-rc.1" } +mas-matrix = { path = "./crates/matrix/", version = "=1.11.0-rc.1" } +mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.11.0-rc.1" } +mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.11.0-rc.1" } +mas-policy = { path = "./crates/policy/", version = "=1.11.0-rc.1" } +mas-router = { path = "./crates/router/", version = "=1.11.0-rc.1" } +mas-spa = { path = "./crates/spa/", version = "=1.11.0-rc.1" } +mas-storage = { path = "./crates/storage/", version = "=1.11.0-rc.1" } +mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.11.0-rc.1" } +mas-tasks = { path = "./crates/tasks/", version = "=1.11.0-rc.1" } +mas-templates = { path = "./crates/templates/", version = "=1.11.0-rc.1" } +mas-tower = { path = "./crates/tower/", version = "=1.11.0-rc.1" } +oauth2-types = { path = "./crates/oauth2-types/", version = "=1.11.0-rc.1" } +syn2mas = { path = "./crates/syn2mas", version = "=1.11.0-rc.1" } # OpenAPI schema generation and validation [workspace.dependencies.aide] From aa5a9677a4e6947e53b168274dafaaa89c74f6d0 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Thu, 5 Feb 2026 11:56:19 +0100 Subject: [PATCH 34/43] Upgrade Rust to 1.93, Debian base image & other tools (#5471) --- .github/actions/build-policies/action.yml | 2 +- .github/workflows/ci.yaml | 4 ++-- Dockerfile | 16 +++++++++------- crates/config/src/sections/clients.rs | 13 ------------- crates/config/src/sections/email.rs | 9 --------- policies/Makefile | 4 ++-- 6 files changed, 14 insertions(+), 34 deletions(-) diff --git a/.github/actions/build-policies/action.yml b/.github/actions/build-policies/action.yml index e1dc28547..6264047ef 100644 --- a/.github/actions/build-policies/action.yml +++ b/.github/actions/build-policies/action.yml @@ -13,7 +13,7 @@ runs: uses: open-policy-agent/setup-opa@v2.2.0 with: # Keep in sync with the Dockerfile and policies/Makefile - version: 1.8.0 + version: 1.13.1 - name: Build the policies run: make diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8dc71d0d6..2998edb02 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,7 +42,7 @@ jobs: uses: StyraInc/setup-regal@v1 with: # Keep in sync with policies/Makefile - version: 0.36.1 + version: 0.38.1 - name: Lint policies working-directory: ./policies @@ -216,7 +216,7 @@ jobs: uses: actions/checkout@v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@1.89.0 + uses: dtolnay/rust-toolchain@1.93.0 with: components: clippy diff --git a/Dockerfile b/Dockerfile index 479dee308..ba2ed16c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ -# syntax = docker/dockerfile:1.7.1 +# syntax = docker/dockerfile:1.21.0 +# Copyright 2025, 2026 Element Creations Ltd. # Copyright 2025 New Vector Ltd. # # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial @@ -10,13 +11,14 @@ # being the platform being built. # The Debian version and version name must be in sync -ARG DEBIAN_VERSION=12 -ARG DEBIAN_VERSION_NAME=bookworm -ARG RUSTC_VERSION=1.89.0 -ARG NODEJS_VERSION=24.11.0 +ARG DEBIAN_VERSION=13 +ARG DEBIAN_VERSION_NAME=trixie +# Keep in sync with .github/workflows/ci.yaml +ARG RUSTC_VERSION=1.93.0 +ARG NODEJS_VERSION=24.13.0 # Keep in sync with .github/actions/build-policies/action.yml and policies/Makefile -ARG OPA_VERSION=1.8.0 -ARG CARGO_AUDITABLE_VERSION=0.7.0 +ARG OPA_VERSION=1.13.1 +ARG CARGO_AUDITABLE_VERSION=0.7.2 ########################################## ## Build stage that builds the frontend ## diff --git a/crates/config/src/sections/clients.rs b/crates/config/src/sections/clients.rs index 8b9120045..2387d642d 100644 --- a/crates/config/src/sections/clients.rs +++ b/crates/config/src/sections/clients.rs @@ -16,19 +16,6 @@ use url::Url; use super::{ClientSecret, ClientSecretRaw, ConfigurationSection}; -#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "snake_case")] -pub enum JwksOrJwksUri { - Jwks(PublicJsonWebKeySet), - JwksUri(Url), -} - -impl From for JwksOrJwksUri { - fn from(jwks: PublicJsonWebKeySet) -> Self { - Self::Jwks(jwks) - } -} - /// Authentication method used by clients #[derive(JsonSchema, Serialize, Deserialize, Copy, Clone, Debug)] #[serde(rename_all = "snake_case")] diff --git a/crates/config/src/sections/email.rs b/crates/config/src/sections/email.rs index 3df0c99db..8c365e3e5 100644 --- a/crates/config/src/sections/email.rs +++ b/crates/config/src/sections/email.rs @@ -14,15 +14,6 @@ use serde::{Deserialize, Serialize, de::Error}; use super::ConfigurationSection; -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -pub struct Credentials { - /// Username for use to authenticate when connecting to the SMTP server - pub username: String, - - /// Password for use to authenticate when connecting to the SMTP server - pub password: String, -} - /// Encryption mode to use #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "lowercase")] diff --git a/policies/Makefile b/policies/Makefile index db5991672..de4d063ad 100644 --- a/policies/Makefile +++ b/policies/Makefile @@ -7,9 +7,9 @@ DOCKER := 0 PODMAN := 0 # Keep in sync with Dockerfile and .github/actions/build-policies/action.yml -OPA_DOCKER_IMAGE := docker.io/openpolicyagent/opa:1.8.0-debug +OPA_DOCKER_IMAGE := docker.io/openpolicyagent/opa:1.13.1 # Keep in sync with .github/workflows/ci.yaml -REGAL_DOCKER_IMAGE := ghcr.io/open-policy-agent/regal:0.36.1 +REGAL_DOCKER_IMAGE := ghcr.io/open-policy-agent/regal:0.38.1 INPUTS := \ common/common.rego \ From 3e706859f98733c295991c2efc47e76884ec1b7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:55:10 +0000 Subject: [PATCH 35/43] build(deps-dev): bump postcss-nesting from 13.0.2 to 14.0.0 in /frontend Bumps [postcss-nesting](https://github.com/csstools/postcss-plugins/tree/HEAD/plugins/postcss-nesting) from 13.0.2 to 14.0.0. - [Changelog](https://github.com/csstools/postcss-plugins/blob/main/plugins/postcss-nesting/CHANGELOG.md) - [Commits](https://github.com/csstools/postcss-plugins/commits/HEAD/plugins/postcss-nesting) --- updated-dependencies: - dependency-name: postcss-nesting dependency-version: 14.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 36 ++++++++++++++++++------------------ frontend/package.json | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index fd27e8d35..6f0fe74b9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -60,7 +60,7 @@ "msw-storybook-addon": "^2.0.6", "postcss": "^8.5.6", "postcss-import": "^16.1.1", - "postcss-nesting": "^13.0.2", + "postcss-nesting": "^14.0.0", "rimraf": "^6.1.2", "storybook": "^10.1.11", "tailwindcss": "^3.4.19", @@ -1177,9 +1177,9 @@ } }, "node_modules/@csstools/selector-resolve-nested": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", - "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-4.0.0.tgz", + "integrity": "sha512-9vAPxmp+Dx3wQBIUwc1v7Mdisw1kbbaGqXUM8QLTgWg7SoPGYtXBsMXvsFs/0Bn5yoFhcktzxNZGNaUt0VjgjA==", "dev": true, "funding": [ { @@ -1193,16 +1193,16 @@ ], "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "postcss-selector-parser": "^7.0.0" + "postcss-selector-parser": "^7.1.1" } }, "node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-6.0.0.tgz", + "integrity": "sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA==", "dev": true, "funding": [ { @@ -1216,10 +1216,10 @@ ], "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "postcss-selector-parser": "^7.0.0" + "postcss-selector-parser": "^7.1.1" } }, "node_modules/@emnapi/core": { @@ -11160,9 +11160,9 @@ } }, "node_modules/postcss-nesting": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", - "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-14.0.0.tgz", + "integrity": "sha512-YGFOfVrjxYfeGTS5XctP1WCI5hu8Lr9SmntjfRC+iX5hCihEO+QZl9Ra+pkjqkgoVdDKvb2JccpElcowhZtzpw==", "dev": true, "funding": [ { @@ -11176,12 +11176,12 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/selector-resolve-nested": "^3.1.0", - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" + "@csstools/selector-resolve-nested": "^4.0.0", + "@csstools/selector-specificity": "^6.0.0", + "postcss-selector-parser": "^7.1.1" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { "postcss": "^8.4" diff --git a/frontend/package.json b/frontend/package.json index a51b2e554..1bcd9357a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -70,7 +70,7 @@ "msw-storybook-addon": "^2.0.6", "postcss": "^8.5.6", "postcss-import": "^16.1.1", - "postcss-nesting": "^13.0.2", + "postcss-nesting": "^14.0.0", "rimraf": "^6.1.2", "storybook": "^10.1.11", "tailwindcss": "^3.4.19", From f4d9808a21ae32b41740040a3bd48b2f179decbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:55:12 +0000 Subject: [PATCH 36/43] build(deps): bump bcrypt from 0.17.1 to 0.18.0 Bumps [bcrypt](https://github.com/Keats/rust-bcrypt) from 0.17.1 to 0.18.0. - [Commits](https://github.com/Keats/rust-bcrypt/compare/v0.17.1...v0.18.0) --- updated-dependencies: - dependency-name: bcrypt dependency-version: 0.18.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 fa7907595..23182d7d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -680,9 +680,9 @@ checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bcrypt" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abaf6da45c74385272ddf00e1ac074c7d8a6c1a1dda376902bd6a427522a8b2c" +checksum = "9a0f5948f30df5f43ac29d310b7476793be97c50787e6ef4a63d960a0d0be827" dependencies = [ "base64", "blowfish", diff --git a/Cargo.toml b/Cargo.toml index 45cbfd08e..ca47c91ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,7 +124,7 @@ features = ["std"] # Bcrypt password hashing [workspace.dependencies.bcrypt] -version = "0.17.1" +version = "0.18.0" default-features = true # Packed bitfields From 56a37fc7aeec28a2d844ea74492232a4be50d69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Beno=C3=AEt=20Grimaldi?= Date: Sat, 7 Feb 2026 11:44:22 +0100 Subject: [PATCH 37/43] Fix wrong username regex --- frontend/src/entrypoints/templates.ts | 2 +- policies/register/register.rego | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/entrypoints/templates.ts b/frontend/src/entrypoints/templates.ts index 2e0cf93d2..77587ee58 100644 --- a/frontend/src/entrypoints/templates.ts +++ b/frontend/src/entrypoints/templates.ts @@ -10,7 +10,7 @@ // Ideally later on we could find a way to hydrate full React components instead // of doing this, as this can very quickly get out of hands. -const VALID_USERNAME_RE = /^\s*([a-z0-9.=_/-]+|@[a-z0-9.=_/-]+(:.*)?)\s*$/g; +const VALID_USERNAME_RE = /^\s*([a-z0-9.=_/+-]+|@[a-z0-9.=_/+-]+(:.*)?)\s*$/g; /** Grab the nearest error message inserted by the templates by error kind and code */ function grabErrorMessage( diff --git a/policies/register/register.rego b/policies/register/register.rego index 89002a075..93ef86a0d 100644 --- a/policies/register/register.rego +++ b/policies/register/register.rego @@ -49,7 +49,7 @@ violation contains { "field": "username", "code": "username-invalid-chars", "msg": "username contains invalid characters", } if { - not regex.match(`^[a-z0-9.=_/-]+$`, input.username) + not regex.match(`^[a-z0-9.=_/+-]+$`, input.username) } violation contains { From e42a634f265096229f5a17d56d60461e4328796d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:29:34 +0000 Subject: [PATCH 38/43] Translations updates --- frontend/.storybook/locales.ts | 66 +++++++++++++++++----------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/frontend/.storybook/locales.ts b/frontend/.storybook/locales.ts index 9a545003e..b0f0f3731 100644 --- a/frontend/.storybook/locales.ts +++ b/frontend/.storybook/locales.ts @@ -27,7 +27,7 @@ export type LocalazyMetadata = { }; const localazyMetadata: LocalazyMetadata = { - projectUrl: "https://localazy.com/p/matrix-authentication-service", + projectUrl: "https://localazy.com/p/matrix-authentication-service!v1.11", baseLocale: "en", languages: [ { @@ -181,22 +181,22 @@ const localazyMetadata: LocalazyMetadata = { file: "frontend.json", path: "", cdnFiles: { - "cs": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/cs/frontend.json", - "da": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/da/frontend.json", - "de": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/de/frontend.json", - "en": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/en/frontend.json", - "et": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/et/frontend.json", - "fi": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fi/frontend.json", - "fr": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fr/frontend.json", - "hu": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/hu/frontend.json", - "nb_NO": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nb-NO/frontend.json", - "nl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nl/frontend.json", - "pl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pl/frontend.json", - "pt": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt/frontend.json", - "ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json", - "sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json", - "uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json", - "zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json" + "cs": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/cs/frontend.json", + "da": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/da/frontend.json", + "de": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/de/frontend.json", + "en": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/en/frontend.json", + "et": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/et/frontend.json", + "fi": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fi/frontend.json", + "fr": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fr/frontend.json", + "hu": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/hu/frontend.json", + "nb_NO": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nb-NO/frontend.json", + "nl": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nl/frontend.json", + "pl": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pl/frontend.json", + "pt": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt/frontend.json", + "ru": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json", + "sv": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json", + "uk": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json", + "zh#Hans": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json" } }, { @@ -204,22 +204,22 @@ const localazyMetadata: LocalazyMetadata = { file: "file.json", path: "", cdnFiles: { - "cs": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/cs/file.json", - "da": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/da/file.json", - "de": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/de/file.json", - "en": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/en/file.json", - "et": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/et/file.json", - "fi": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fi/file.json", - "fr": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fr/file.json", - "hu": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/hu/file.json", - "nb_NO": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nb-NO/file.json", - "nl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nl/file.json", - "pl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pl/file.json", - "pt": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt/file.json", - "ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json", - "sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json", - "uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json", - "zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json" + "cs": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/cs/file.json", + "da": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/da/file.json", + "de": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/de/file.json", + "en": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/en/file.json", + "et": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/et/file.json", + "fi": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fi/file.json", + "fr": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fr/file.json", + "hu": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/hu/file.json", + "nb_NO": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nb-NO/file.json", + "nl": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nl/file.json", + "pl": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pl/file.json", + "pt": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt/file.json", + "ru": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json", + "sv": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json", + "uk": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json", + "zh#Hans": "https://delivery.localazy.com/_a6642094778790161378d4638916/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json" } } ] From 2b081f79b1515c7debfe25fc03ce22341a963985 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:34:45 +0000 Subject: [PATCH 39/43] 1.11.0 --- Cargo.lock | 56 +++++++++++++++++++++++++------------------------- Cargo.toml | 60 +++++++++++++++++++++++++++--------------------------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6cd5e3ebb..1b7f52065 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3097,7 +3097,7 @@ dependencies = [ [[package]] name = "mas-axum-utils" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "anyhow", "axum", @@ -3131,7 +3131,7 @@ dependencies = [ [[package]] name = "mas-cli" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "anyhow", "axum", @@ -3206,7 +3206,7 @@ dependencies = [ [[package]] name = "mas-config" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "anyhow", "camino", @@ -3237,7 +3237,7 @@ dependencies = [ [[package]] name = "mas-context" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "console", "opentelemetry", @@ -3253,7 +3253,7 @@ dependencies = [ [[package]] name = "mas-data-model" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "base64ct", "chrono", @@ -3276,7 +3276,7 @@ dependencies = [ [[package]] name = "mas-email" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "async-trait", "lettre", @@ -3287,7 +3287,7 @@ dependencies = [ [[package]] name = "mas-handlers" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "aide", "anyhow", @@ -3367,7 +3367,7 @@ dependencies = [ [[package]] name = "mas-http" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "futures-util", "headers", @@ -3388,7 +3388,7 @@ dependencies = [ [[package]] name = "mas-i18n" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "camino", "icu_calendar", @@ -3410,7 +3410,7 @@ dependencies = [ [[package]] name = "mas-i18n-scan" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "camino", "clap", @@ -3424,7 +3424,7 @@ dependencies = [ [[package]] name = "mas-iana" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "schemars 0.9.0", "serde", @@ -3432,7 +3432,7 @@ dependencies = [ [[package]] name = "mas-iana-codegen" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "anyhow", "async-trait", @@ -3449,7 +3449,7 @@ dependencies = [ [[package]] name = "mas-jose" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "base64ct", "chrono", @@ -3479,7 +3479,7 @@ dependencies = [ [[package]] name = "mas-keystore" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "aead", "base64ct", @@ -3507,7 +3507,7 @@ dependencies = [ [[package]] name = "mas-listener" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "anyhow", "bytes", @@ -3531,7 +3531,7 @@ dependencies = [ [[package]] name = "mas-matrix" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "anyhow", "async-trait", @@ -3541,7 +3541,7 @@ dependencies = [ [[package]] name = "mas-matrix-synapse" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "anyhow", "async-trait", @@ -3558,7 +3558,7 @@ dependencies = [ [[package]] name = "mas-oidc-client" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "assert_matches", "async-trait", @@ -3594,7 +3594,7 @@ dependencies = [ [[package]] name = "mas-policy" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "anyhow", "arc-swap", @@ -3611,7 +3611,7 @@ dependencies = [ [[package]] name = "mas-router" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "axum", "serde", @@ -3622,7 +3622,7 @@ dependencies = [ [[package]] name = "mas-spa" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "camino", "serde", @@ -3631,7 +3631,7 @@ dependencies = [ [[package]] name = "mas-storage" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "async-trait", "chrono", @@ -3653,7 +3653,7 @@ dependencies = [ [[package]] name = "mas-storage-pg" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "async-trait", "chrono", @@ -3683,7 +3683,7 @@ dependencies = [ [[package]] name = "mas-tasks" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "anyhow", "async-trait", @@ -3715,7 +3715,7 @@ dependencies = [ [[package]] name = "mas-templates" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "anyhow", "arc-swap", @@ -3747,7 +3747,7 @@ dependencies = [ [[package]] name = "mas-tower" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "http", "opentelemetry", @@ -4016,7 +4016,7 @@ dependencies = [ [[package]] name = "oauth2-types" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "assert_matches", "base64ct", @@ -6120,7 +6120,7 @@ dependencies = [ [[package]] name = "syn2mas" -version = "1.11.0-rc.1" +version = "1.11.0" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index 8deb5b050..77af03c5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = ["crates/*"] resolver = "2" # Updated in the CI with a `sed` command -package.version = "1.11.0-rc.1" +package.version = "1.11.0" package.license = "AGPL-3.0-only OR LicenseRef-Element-Commercial" package.authors = ["Element Backend Team"] package.edition = "2024" @@ -34,35 +34,35 @@ broken_intra_doc_links = "deny" [workspace.dependencies] # Workspace crates -mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.11.0-rc.1" } -mas-cli = { path = "./crates/cli/", version = "=1.11.0-rc.1" } -mas-config = { path = "./crates/config/", version = "=1.11.0-rc.1" } -mas-context = { path = "./crates/context/", version = "=1.11.0-rc.1" } -mas-data-model = { path = "./crates/data-model/", version = "=1.11.0-rc.1" } -mas-email = { path = "./crates/email/", version = "=1.11.0-rc.1" } -mas-graphql = { path = "./crates/graphql/", version = "=1.11.0-rc.1" } -mas-handlers = { path = "./crates/handlers/", version = "=1.11.0-rc.1" } -mas-http = { path = "./crates/http/", version = "=1.11.0-rc.1" } -mas-i18n = { path = "./crates/i18n/", version = "=1.11.0-rc.1" } -mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.11.0-rc.1" } -mas-iana = { path = "./crates/iana/", version = "=1.11.0-rc.1" } -mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.11.0-rc.1" } -mas-jose = { path = "./crates/jose/", version = "=1.11.0-rc.1" } -mas-keystore = { path = "./crates/keystore/", version = "=1.11.0-rc.1" } -mas-listener = { path = "./crates/listener/", version = "=1.11.0-rc.1" } -mas-matrix = { path = "./crates/matrix/", version = "=1.11.0-rc.1" } -mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.11.0-rc.1" } -mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.11.0-rc.1" } -mas-policy = { path = "./crates/policy/", version = "=1.11.0-rc.1" } -mas-router = { path = "./crates/router/", version = "=1.11.0-rc.1" } -mas-spa = { path = "./crates/spa/", version = "=1.11.0-rc.1" } -mas-storage = { path = "./crates/storage/", version = "=1.11.0-rc.1" } -mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.11.0-rc.1" } -mas-tasks = { path = "./crates/tasks/", version = "=1.11.0-rc.1" } -mas-templates = { path = "./crates/templates/", version = "=1.11.0-rc.1" } -mas-tower = { path = "./crates/tower/", version = "=1.11.0-rc.1" } -oauth2-types = { path = "./crates/oauth2-types/", version = "=1.11.0-rc.1" } -syn2mas = { path = "./crates/syn2mas", version = "=1.11.0-rc.1" } +mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.11.0" } +mas-cli = { path = "./crates/cli/", version = "=1.11.0" } +mas-config = { path = "./crates/config/", version = "=1.11.0" } +mas-context = { path = "./crates/context/", version = "=1.11.0" } +mas-data-model = { path = "./crates/data-model/", version = "=1.11.0" } +mas-email = { path = "./crates/email/", version = "=1.11.0" } +mas-graphql = { path = "./crates/graphql/", version = "=1.11.0" } +mas-handlers = { path = "./crates/handlers/", version = "=1.11.0" } +mas-http = { path = "./crates/http/", version = "=1.11.0" } +mas-i18n = { path = "./crates/i18n/", version = "=1.11.0" } +mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.11.0" } +mas-iana = { path = "./crates/iana/", version = "=1.11.0" } +mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.11.0" } +mas-jose = { path = "./crates/jose/", version = "=1.11.0" } +mas-keystore = { path = "./crates/keystore/", version = "=1.11.0" } +mas-listener = { path = "./crates/listener/", version = "=1.11.0" } +mas-matrix = { path = "./crates/matrix/", version = "=1.11.0" } +mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.11.0" } +mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.11.0" } +mas-policy = { path = "./crates/policy/", version = "=1.11.0" } +mas-router = { path = "./crates/router/", version = "=1.11.0" } +mas-spa = { path = "./crates/spa/", version = "=1.11.0" } +mas-storage = { path = "./crates/storage/", version = "=1.11.0" } +mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.11.0" } +mas-tasks = { path = "./crates/tasks/", version = "=1.11.0" } +mas-templates = { path = "./crates/templates/", version = "=1.11.0" } +mas-tower = { path = "./crates/tower/", version = "=1.11.0" } +oauth2-types = { path = "./crates/oauth2-types/", version = "=1.11.0" } +syn2mas = { path = "./crates/syn2mas", version = "=1.11.0" } # OpenAPI schema generation and validation [workspace.dependencies.aide] From b93c4305a33606c19ee1e44de84358b72c9e290e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:55:05 +0000 Subject: [PATCH 40/43] build(deps-dev): bump the storybook group in /frontend with 3 updates --- updated-dependencies: - dependency-name: "@storybook/addon-docs" dependency-version: 10.2.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: storybook - dependency-name: "@storybook/react-vite" dependency-version: 10.2.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: storybook - dependency-name: storybook dependency-version: 10.2.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: storybook ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 70 +++++++++++++++++++------------------- frontend/package.json | 4 +-- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index fd27e8d35..2a31b829f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -36,8 +36,8 @@ "@graphql-codegen/cli": "^6.1.1", "@graphql-codegen/client-preset": "^5.2.2", "@graphql-codegen/typescript-msw": "^3.0.1", - "@storybook/addon-docs": "^10.2.6", - "@storybook/react-vite": "^10.2.6", + "@storybook/addon-docs": "^10.2.8", + "@storybook/react-vite": "^10.2.8", "@tanstack/react-query-devtools": "^5.91.3", "@tanstack/react-router-devtools": "^1.150.0", "@tanstack/router-plugin": "^1.150.0", @@ -5054,16 +5054,16 @@ "license": "MIT" }, "node_modules/@storybook/addon-docs": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.2.6.tgz", - "integrity": "sha512-7a37fu2hdOJRCITS+EimhVN9xVVvSeodmX6QdfxOim+2ECYnvMC9YVYpJ0et3XC8xnl9/3AbQ4y670Uw1hXcmg==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.2.8.tgz", + "integrity": "sha512-cEoWqQrLzrxOwZFee5zrD4cYrdEWKV80POb7jUZO0r5vfl2DuslIr3n/+RfLT52runCV4aZcFEfOfP/IWHNPxg==", "dev": true, "license": "MIT", "dependencies": { "@mdx-js/react": "^3.0.0", - "@storybook/csf-plugin": "10.2.6", + "@storybook/csf-plugin": "10.2.8", "@storybook/icons": "^2.0.1", - "@storybook/react-dom-shim": "10.2.6", + "@storybook/react-dom-shim": "10.2.8", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" @@ -5073,17 +5073,17 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.2.6" + "storybook": "^10.2.8" } }, "node_modules/@storybook/builder-vite": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.2.6.tgz", - "integrity": "sha512-O/kvmUrfEn2VpzVOGJV3VvEQ9N4AOI8csdvsUsfjs6PSNwAiwvsx8+qTYYcheMilrO9+1Da9lFmd7oucSPto3Q==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.2.8.tgz", + "integrity": "sha512-+6/Lwi7W0YIbzHDh798GPp0IHUYDwp0yv0Y1eVNK/StZD0tnv4/1C28NKyP+O7JOsFsuWI1qHiDhw8kNURugZw==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf-plugin": "10.2.6", + "@storybook/csf-plugin": "10.2.8", "ts-dedent": "^2.0.0" }, "funding": { @@ -5091,14 +5091,14 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.2.6", + "storybook": "^10.2.8", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "node_modules/@storybook/csf-plugin": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.2.6.tgz", - "integrity": "sha512-1ZeRfmMTjUweXRaNVNg49m0r+l2VQmfeicTCRwjXrq70eBeWG8Dx2w0simVM9nX0lRGiGL3S36lhrrswuEF/6A==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.2.8.tgz", + "integrity": "sha512-kKkLYhRXb33YtIPdavD2DU25sb14sqPYdcQFpyqu4TaD9truPPqW8P5PLTUgERydt/eRvRlnhauPHavU1kjsnA==", "dev": true, "license": "MIT", "dependencies": { @@ -5111,7 +5111,7 @@ "peerDependencies": { "esbuild": "*", "rollup": "*", - "storybook": "^10.2.6", + "storybook": "^10.2.8", "vite": "*", "webpack": "*" }, @@ -5149,14 +5149,14 @@ } }, "node_modules/@storybook/react": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.2.6.tgz", - "integrity": "sha512-0haWDV7A/p3PdOsP6klacN7D5Zbzg08EVLh5br8X3AOxAznhLUTS4WngMtaMFbkheNLmyoVK+4SkTrL4n6/7zQ==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.2.8.tgz", + "integrity": "sha512-nMFqQFUXq6Zg2O5SeuomyWnrIx61QfpNQMrfor8eCEzHrWNnXrrvVsz2RnHIgXN8RVyaWGDPh1srAECu/kDHXw==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "10.2.6", + "@storybook/react-dom-shim": "10.2.8", "react-docgen": "^8.0.2" }, "funding": { @@ -5166,7 +5166,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.2.6", + "storybook": "^10.2.8", "typescript": ">= 4.9.x" }, "peerDependenciesMeta": { @@ -5176,9 +5176,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.2.6.tgz", - "integrity": "sha512-jgGLf5Ck35+kHa9fY8LreuT+PrtrDXPgy7uh8C3KkRnWoyTHaorW5q3Kw2no0UA79vXA87uX87kQVY6Ka8InzA==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.2.8.tgz", + "integrity": "sha512-Xde9X3VszFV1pTXfc2ZFM89XOCGRxJD8MUIzDwkcT9xaki5a+8srs/fsXj75fMY6gMYfcL5lNRZvCqg37HOmcQ==", "dev": true, "license": "MIT", "funding": { @@ -5188,20 +5188,20 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.2.6" + "storybook": "^10.2.8" } }, "node_modules/@storybook/react-vite": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.2.6.tgz", - "integrity": "sha512-RYkbzro1dfmLujz2fXm3eAuSyfLRdgl4SoGUa3W+WjTiHu5+sF1i+/CBYaOUmFFiPMibWRy4MNodrSInERb4qA==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.2.8.tgz", + "integrity": "sha512-x5kmw+TPhxkQV84n4e9X0q6/rA5T8V2QQFolMuN+U93q1HX1r+GZ6g/nXaaq9ox168PhHUJZQnn+LzSQKGCMBA==", "dev": true, "license": "MIT", "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.3", "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "10.2.6", - "@storybook/react": "10.2.6", + "@storybook/builder-vite": "10.2.8", + "@storybook/react": "10.2.8", "empathic": "^2.0.0", "magic-string": "^0.30.0", "react-docgen": "^8.0.0", @@ -5215,7 +5215,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.2.6", + "storybook": "^10.2.8", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, @@ -12096,9 +12096,9 @@ } }, "node_modules/storybook": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.2.6.tgz", - "integrity": "sha512-5UY1f0f4TCAmJ19DCvYmWbvZaYwszIOY+PxTzu0S/WJpqm4pzO0bjn6Wfx113Jnh/tffUZ3MVuQgjJq20/ttNg==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.2.8.tgz", + "integrity": "sha512-885uSIn8NQw2ZG7vy84K45lHCOSyz1DVsDV8pHiHQj3J0riCuWLNeO50lK9z98zE8kjhgTtxAAkMTy5nkmNRKQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index a51b2e554..e2f392946 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -46,8 +46,8 @@ "@graphql-codegen/cli": "^6.1.1", "@graphql-codegen/client-preset": "^5.2.2", "@graphql-codegen/typescript-msw": "^3.0.1", - "@storybook/addon-docs": "^10.2.6", - "@storybook/react-vite": "^10.2.6", + "@storybook/addon-docs": "^10.2.8", + "@storybook/react-vite": "^10.2.8", "@tanstack/react-query-devtools": "^5.91.3", "@tanstack/react-router-devtools": "^1.150.0", "@tanstack/router-plugin": "^1.150.0", From 08e94a8c0cd8f4abfed362e1fd89d5853ea6ad43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 13:54:26 +0000 Subject: [PATCH 41/43] build(deps-dev): bump the types group across 1 directory with 2 updates Bumps the types group with 2 updates in the /frontend directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react). Updates `@types/node` from 25.2.0 to 25.2.3 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@types/react` from 19.2.13 to 19.2.14 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 25.2.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types - dependency-name: "@types/react" dependency-version: 19.2.14 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 16 ++++++++-------- frontend/package.json | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index fd27e8d35..4f6b8a948 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -44,8 +44,8 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", "@testing-library/user-event": "^14.6.1", - "@types/node": "^25.2.0", - "@types/react": "19.2.13", + "@types/node": "^25.2.3", + "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.3", @@ -5972,9 +5972,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz", - "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==", + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5982,9 +5982,9 @@ } }, "node_modules/@types/react": { - "version": "19.2.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", - "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index a51b2e554..fdc794969 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -54,8 +54,8 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", "@testing-library/user-event": "^14.6.1", - "@types/node": "^25.2.0", - "@types/react": "19.2.13", + "@types/node": "^25.2.3", + "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/swagger-ui-dist": "^3.30.6", "@vitejs/plugin-react": "^5.1.3", From 94c97a5fedc02f9f46c7fb134a84d3291c138e91 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Fri, 13 Feb 2026 14:16:35 +0000 Subject: [PATCH 42/43] Add compat token refresh regression test --- crates/handlers/src/compat/mod.rs | 3 + crates/handlers/src/compat/tests.rs | 234 ++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 crates/handlers/src/compat/tests.rs diff --git a/crates/handlers/src/compat/mod.rs b/crates/handlers/src/compat/mod.rs index 1c30d5e04..dd5600afe 100644 --- a/crates/handlers/src/compat/mod.rs +++ b/crates/handlers/src/compat/mod.rs @@ -25,6 +25,9 @@ pub(crate) mod logout; pub(crate) mod logout_all; pub(crate) mod refresh; +#[cfg(test)] +mod tests; + #[derive(Debug, Serialize)] struct MatrixError { errcode: &'static str, diff --git a/crates/handlers/src/compat/tests.rs b/crates/handlers/src/compat/tests.rs new file mode 100644 index 000000000..f4827f5de --- /dev/null +++ b/crates/handlers/src/compat/tests.rs @@ -0,0 +1,234 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +use axum::http::{Request, StatusCode}; +use mas_matrix::{HomeserverConnection, ProvisionRequest}; +use serde::{Deserialize, Serialize}; +use zeroize::Zeroizing; + +use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup}; + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +enum LoginCredentials { + #[serde(rename = "m.login.password")] + Password { + identifier: LoginIdentifier, + password: String, + }, +} + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +enum LoginIdentifier { + #[serde(rename = "m.id.user")] + User { user: String }, +} + +#[derive(Debug, Serialize)] +struct LoginRequest { + #[serde(flatten)] + credentials: LoginCredentials, + #[serde(default)] + refresh_token: bool, +} + +#[derive(Debug, Deserialize)] +struct LoginResponse { + #[allow(dead_code)] + access_token: String, + #[allow(dead_code)] + user_id: String, + #[allow(dead_code)] + device_id: Option, + refresh_token: Option, +} + +#[derive(Debug, Serialize)] +struct RefreshRequest { + refresh_token: String, +} + +#[derive(Debug, Deserialize, PartialEq, Eq)] +struct RefreshResponse { + access_token: String, + refresh_token: String, + expires_in_ms: i64, +} + +/// Test using a compatibility refresh token. +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_compat_refresh(pool: sqlx::PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + // Create a user + create_test_user(&state, "testuser").await; + + // Login to get initial tokens + let login_request = Request::post("/_matrix/client/v3/login").json(&LoginRequest { + credentials: LoginCredentials::Password { + identifier: LoginIdentifier::User { + user: "testuser".to_owned(), + }, + password: "password".to_owned(), + }, + refresh_token: true, + }); + + let login_response = state.request(login_request).await; + login_response.assert_status(StatusCode::OK); + + let login_response: LoginResponse = login_response.json(); + let initial_refresh_token = login_response + .refresh_token + .expect("Login should return a refresh token"); + + // First refresh + let refresh_request = Request::post("/_matrix/client/v3/refresh").json(&RefreshRequest { + refresh_token: initial_refresh_token.clone(), + }); + + let first_refresh_response = state.request(refresh_request).await; + first_refresh_response.assert_status(StatusCode::OK); + + let first_refresh_response: RefreshResponse = first_refresh_response.json(); + let first_new_refresh_token = first_refresh_response.refresh_token.clone(); + + assert_eq!( + first_refresh_response, + RefreshResponse { + access_token: "mct_fNbm5KAQovodfVQz7IvDc44woP66fR_fsaiD1".to_owned(), + refresh_token: "mcr_42oTpLoieH5IecxG6gZXyvelQWW9Xq_a8g5N3".to_owned(), + expires_in_ms: 300_000 + } + ); + + // Use the token from the /refresh response to /refresh again, + // proving that it works. + // This is a regression test: we were previously consuming the refresh token + // before it was returned from /refresh. + let second_refresh_request = + Request::post("/_matrix/client/v3/refresh").json(&RefreshRequest { + refresh_token: first_new_refresh_token.clone(), + }); + + let second_refresh_response = state.request(second_refresh_request).await; + second_refresh_response.assert_status(StatusCode::OK); + + let second_refresh_response: RefreshResponse = second_refresh_response.json(); + + assert_eq!( + second_refresh_response, + RefreshResponse { + access_token: "???".to_owned(), + refresh_token: "???".to_owned(), + expires_in_ms: 300_000 + } + ); +} + +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_refresh_with_invalid_token(pool: sqlx::PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + let refresh_request = RefreshRequest { + refresh_token: "invalid_token".to_owned(), + }; + + let refresh_request = Request::post("/_matrix/client/v3/refresh").json(&refresh_request); + + let response = state.request(refresh_request).await; + response.assert_status(StatusCode::UNAUTHORIZED); +} + +#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] +async fn test_refresh_with_consumed_token(pool: sqlx::PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + + // Create a user and login + create_test_user(&state, "testuser").await; + + let login_request = LoginRequest { + credentials: LoginCredentials::Password { + identifier: LoginIdentifier::User { + user: "testuser".to_owned(), + }, + password: "password".to_owned(), + }, + refresh_token: true, + }; + + let login_request = Request::post("/_matrix/client/v3/login").json(&login_request); + + let login_response = state.request(login_request).await; + login_response.assert_status(StatusCode::OK); + + let login_response: LoginResponse = login_response.json(); + let refresh_token = login_response + .refresh_token + .expect("Login should return a refresh token"); + + let refresh_request = RefreshRequest { + refresh_token: refresh_token.clone(), + }; + + // Use the refresh token once + let first_refresh_request = Request::post("/_matrix/client/v3/refresh").json(&refresh_request); + let first_refresh_response = state.request(first_refresh_request).await; + first_refresh_response.assert_status(StatusCode::OK); + + let _first_refresh_response: RefreshResponse = first_refresh_response.json(); + + // Try to use the same refresh token again - should fail because it's consumed + let second_refresh_request = + Request::post("/_matrix/client/v3/refresh").json(&refresh_request); + + let second_refresh_response = state.request(second_refresh_request).await; + second_refresh_response.assert_status(StatusCode::UNAUTHORIZED); +} + +async fn create_test_user(state: &TestState, username: &str) -> mas_data_model::User { + let mut repo = state.repository().await.unwrap(); + let mut rng = state.rng(); + + let user = repo + .user() + .add(&mut rng, &state.clock, username.to_owned()) + .await + .unwrap(); + + let password = Zeroizing::new("password".to_owned()); + let (version, hashed_password) = state + .password_manager + .hash(&mut rng, password) + .await + .unwrap(); + + repo.user_password() + .add( + &mut rng, + &state.clock, + &user, + version, + hashed_password, + None, + ) + .await + .unwrap(); + + // Provision the user on the homeserver + state + .homeserver_connection + .provision_user(&ProvisionRequest::new(&user.username, &user.sub)) + .await + .unwrap(); + + repo.save().await.unwrap(); + + user +} From bac118ab12bfddec6d28e11ca2ea16bf8585deb6 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Fri, 13 Feb 2026 14:57:27 +0000 Subject: [PATCH 43/43] Fix compat token refresh giving back a consumed token --- crates/handlers/src/compat/refresh.rs | 2 +- crates/handlers/src/compat/tests.rs | 7 +++--- ...f69eb67adaa4d40fc1910dfcd2640e32ab37.json} | 7 +++--- crates/storage-pg/src/compat/mod.rs | 21 +++++++++++++--- crates/storage-pg/src/compat/refresh_token.rs | 12 +++++++-- crates/storage/src/compat/refresh_token.rs | 25 +++++++++++++------ 6 files changed, 53 insertions(+), 21 deletions(-) rename crates/storage-pg/.sqlx/{query-f75e44b528234dac708640ad9a111f3f6b468a91bf0d5b574795bf8c80605f19.json => query-4e64540bbffe5f4b9c4a6589012cf69eb67adaa4d40fc1910dfcd2640e32ab37.json} (66%) diff --git a/crates/handlers/src/compat/refresh.rs b/crates/handlers/src/compat/refresh.rs index 95679af1d..dabdaefe7 100644 --- a/crates/handlers/src/compat/refresh.rs +++ b/crates/handlers/src/compat/refresh.rs @@ -156,7 +156,7 @@ pub(crate) async fn post( .await?; repo.compat_refresh_token() - .consume(&clock, refresh_token) + .consume_and_replace(&clock, refresh_token, &new_refresh_token) .await?; if let Some(access_token) = access_token { diff --git a/crates/handlers/src/compat/tests.rs b/crates/handlers/src/compat/tests.rs index f4827f5de..cb6b76aba 100644 --- a/crates/handlers/src/compat/tests.rs +++ b/crates/handlers/src/compat/tests.rs @@ -123,8 +123,8 @@ async fn test_compat_refresh(pool: sqlx::PgPool) { assert_eq!( second_refresh_response, RefreshResponse { - access_token: "???".to_owned(), - refresh_token: "???".to_owned(), + access_token: "mct_Wc6Hx4l9DGzqGtgLoYqtrtBUBcWlE4_ZFyTp2".to_owned(), + refresh_token: "mcr_Yp7FM44zJN5qePGMLvvMXC4Ds1A3lC_0YcYCM".to_owned(), expires_in_ms: 300_000 } ); @@ -185,8 +185,7 @@ async fn test_refresh_with_consumed_token(pool: sqlx::PgPool) { let _first_refresh_response: RefreshResponse = first_refresh_response.json(); // Try to use the same refresh token again - should fail because it's consumed - let second_refresh_request = - Request::post("/_matrix/client/v3/refresh").json(&refresh_request); + let second_refresh_request = Request::post("/_matrix/client/v3/refresh").json(&refresh_request); let second_refresh_response = state.request(second_refresh_request).await; second_refresh_response.assert_status(StatusCode::UNAUTHORIZED); diff --git a/crates/storage-pg/.sqlx/query-f75e44b528234dac708640ad9a111f3f6b468a91bf0d5b574795bf8c80605f19.json b/crates/storage-pg/.sqlx/query-4e64540bbffe5f4b9c4a6589012cf69eb67adaa4d40fc1910dfcd2640e32ab37.json similarity index 66% rename from crates/storage-pg/.sqlx/query-f75e44b528234dac708640ad9a111f3f6b468a91bf0d5b574795bf8c80605f19.json rename to crates/storage-pg/.sqlx/query-4e64540bbffe5f4b9c4a6589012cf69eb67adaa4d40fc1910dfcd2640e32ab37.json index 0d597ca3b..54b801174 100644 --- a/crates/storage-pg/.sqlx/query-f75e44b528234dac708640ad9a111f3f6b468a91bf0d5b574795bf8c80605f19.json +++ b/crates/storage-pg/.sqlx/query-4e64540bbffe5f4b9c4a6589012cf69eb67adaa4d40fc1910dfcd2640e32ab37.json @@ -1,15 +1,16 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE compat_refresh_tokens\n SET consumed_at = $2\n WHERE compat_session_id = $1\n AND consumed_at IS NULL\n ", + "query": "\n UPDATE compat_refresh_tokens\n SET consumed_at = $2\n WHERE compat_session_id = $1\n AND consumed_at IS NULL\n AND compat_refresh_token_id <> $3\n ", "describe": { "columns": [], "parameters": { "Left": [ "Uuid", - "Timestamptz" + "Timestamptz", + "Uuid" ] }, "nullable": [] }, - "hash": "f75e44b528234dac708640ad9a111f3f6b468a91bf0d5b574795bf8c80605f19" + "hash": "4e64540bbffe5f4b9c4a6589012cf69eb67adaa4d40fc1910dfcd2640e32ab37" } diff --git a/crates/storage-pg/src/compat/mod.rs b/crates/storage-pg/src/compat/mod.rs index d42c9b1af..b9ce82da4 100644 --- a/crates/storage-pg/src/compat/mod.rs +++ b/crates/storage-pg/src/compat/mod.rs @@ -437,6 +437,7 @@ mod tests { async fn test_refresh_token_repository(pool: PgPool) { const ACCESS_TOKEN: &str = "access_token"; const REFRESH_TOKEN: &str = "refresh_token"; + const REFRESH_TOKEN2: &str = "refresh_token2"; let mut rng = ChaChaRng::seed_from_u64(42); let clock = MockClock::default(); let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed(); @@ -508,16 +509,28 @@ mod tests { assert!(refresh_token_lookup.is_valid()); assert!(!refresh_token_lookup.is_consumed()); - // Consume it + // Consume the first token, but to do so we need a 2nd to replace it with + let refresh_token2 = repo + .compat_refresh_token() + .add( + &mut rng, + &clock, + &session, + &access_token, + REFRESH_TOKEN2.to_owned(), + ) + .await + .unwrap(); + let refresh_token = repo .compat_refresh_token() - .consume(&clock, refresh_token) + .consume_and_replace(&clock, refresh_token, &refresh_token2) .await .unwrap(); assert!(!refresh_token.is_valid()); assert!(refresh_token.is_consumed()); - // Reload it and check again + // Reload the first token and check again let refresh_token_lookup = repo .compat_refresh_token() .find_by_token(REFRESH_TOKEN) @@ -530,7 +543,7 @@ mod tests { // Consuming it again should not work assert!( repo.compat_refresh_token() - .consume(&clock, refresh_token) + .consume_and_replace(&clock, refresh_token, &refresh_token2) .await .is_err() ); diff --git a/crates/storage-pg/src/compat/refresh_token.rs b/crates/storage-pg/src/compat/refresh_token.rs index 5c6add046..3cb9d7944 100644 --- a/crates/storage-pg/src/compat/refresh_token.rs +++ b/crates/storage-pg/src/compat/refresh_token.rs @@ -185,20 +185,26 @@ impl CompatRefreshTokenRepository for PgCompatRefreshTokenRepository<'_> { } #[tracing::instrument( - name = "db.compat_refresh_token.consume", + name = "db.compat_refresh_token.consume_and_replace", skip_all, fields( db.query.text, %compat_refresh_token.id, + %successor_compat_refresh_token.id, compat_session.id = %compat_refresh_token.session_id, ), err, )] - async fn consume( + async fn consume_and_replace( &mut self, clock: &dyn Clock, compat_refresh_token: CompatRefreshToken, + successor_compat_refresh_token: &CompatRefreshToken, ) -> Result { + if compat_refresh_token.session_id != successor_compat_refresh_token.session_id { + return Err(DatabaseError::invalid_operation()); + } + let consumed_at = clock.now(); let res = sqlx::query!( r#" @@ -206,9 +212,11 @@ impl CompatRefreshTokenRepository for PgCompatRefreshTokenRepository<'_> { SET consumed_at = $2 WHERE compat_session_id = $1 AND consumed_at IS NULL + AND compat_refresh_token_id <> $3 "#, Uuid::from(compat_refresh_token.session_id), consumed_at, + Uuid::from(successor_compat_refresh_token.id), ) .traced() .execute(&mut *self.conn) diff --git a/crates/storage/src/compat/refresh_token.rs b/crates/storage/src/compat/refresh_token.rs index 6ffefbc69..d4434569c 100644 --- a/crates/storage/src/compat/refresh_token.rs +++ b/crates/storage/src/compat/refresh_token.rs @@ -69,16 +69,22 @@ pub trait CompatRefreshTokenRepository: Send + Sync { token: String, ) -> Result; - /// Consume a compat refresh token. + /// Consume the given compat refresh token, as well as all other refresh + /// tokens from the same session, except for the given successor compat + /// refresh token. /// - /// This also marks other refresh tokens in the same session as consumed. + /// The given successor refresh token will thereafter be the only valid + /// refresh token for the session. + /// + /// # Historical context + /// + /// When using a refresh token, we must be able to mark multiple other + /// refresh tokens in the same session as consumed. /// This is desirable because the syn2mas migration process can import /// multiple refresh tokens for one device (compat session). /// But once the user uses one of those, the others should no longer /// be valid. /// - /// Returns the consumed compat refresh token - /// /// # Parameters /// /// * `clock`: The clock used to generate timestamps @@ -86,11 +92,15 @@ pub trait CompatRefreshTokenRepository: Send + Sync { /// /// # Errors /// - /// Returns [`Self::Error`] if the underlying repository fails - async fn consume( + /// - Returns [`Self::Error`] if the underlying repository fails + /// - Returns an error if `compat_refresh_token` is not valid to be + /// consumed. + /// - Returns an error if no refresh tokens would be consumed. + async fn consume_and_replace( &mut self, clock: &dyn Clock, compat_refresh_token: CompatRefreshToken, + successor_compat_refresh_token: &CompatRefreshToken, ) -> Result; } @@ -111,9 +121,10 @@ repository_impl!(CompatRefreshTokenRepository: token: String, ) -> Result; - async fn consume( + async fn consume_and_replace( &mut self, clock: &dyn Clock, compat_refresh_token: CompatRefreshToken, + successor_compat_refresh_token: &CompatRefreshToken, ) -> Result; );