Switch OIDC to response_mode=fragment (#33100)

* Refactor: kill off `parseQs` in favour of URLSearchParams

* Consolidate app-load url parameter handling

* Switch to responseMode=fragment
This commit is contained in:
Michael Telatynski
2026-04-15 09:35:02 +00:00
committed by GitHub
parent 5475edbbc5
commit de4a1e6d35
16 changed files with 309 additions and 206 deletions
+1 -1
View File
@@ -85,7 +85,7 @@ describe("sso_redirect_options", () => {
await loadApp({}, jest.fn());
expect(startOidcLoginSpy).toHaveBeenCalledWith(
"https://auth.org/auth?client_id=12345&redirect_uri=https%3A%2F%2Fapp.element.io%2F%3Fno_universal_links%3Dtrue&response_type=code&scope=openid+urn%3Amatrix%3Aorg.matrix.msc2967.client%3Aapi%3A*+urn%3Amatrix%3Aorg.matrix.msc2967.client%3Adevice%3AwKpa6hpi3Y&nonce=38QgU2Pomx&state=10000000100040008000100000000000&code_challenge=awE81eIsGff70JahvrTqWRbGKLI10ooyo_Xm1sxuZvU&code_challenge_method=S256&response_mode=query",
"https://auth.org/auth?client_id=12345&redirect_uri=https%3A%2F%2Fapp.element.io%2F%3Fno_universal_links%3Dtrue&response_type=code&scope=openid+urn%3Amatrix%3Aorg.matrix.msc2967.client%3Aapi%3A*+urn%3Amatrix%3Aorg.matrix.msc2967.client%3Adevice%3AwKpa6hpi3Y&nonce=38QgU2Pomx&state=10000000100040008000100000000000&code_challenge=awE81eIsGff70JahvrTqWRbGKLI10ooyo_Xm1sxuZvU&code_challenge_method=S256&response_mode=fragment",
);
});
});
+5 -8
View File
@@ -1,6 +1,6 @@
/**
* @jest-environment jest-fixed-jsdom
* @jest-environment-options {"url": "https://app.element.io/?loginToken=123&state=abc&code=xyz&no_universal_links&something_else=value"}
* @jest-environment-options {"url": "https://app.element.io/?loginToken=123&no_universal_links&something_else=value#/home?state=abc&code=xyz"}
*/
/*
@@ -16,6 +16,7 @@ import { waitFor, screen } from "jest-matrix-react";
import { loadApp, showError, showIncompatibleBrowser } from "../../../src/vector/init.tsx";
import SdkConfig from "../../../src/SdkConfig.ts";
import MatrixChat from "../../../src/components/structures/MatrixChat.tsx";
import { parseAppUrl } from "../../../src/vector/url_utils.ts";
function setUpMatrixChatDiv() {
document.getElementById("matrixchat")?.remove();
@@ -57,17 +58,13 @@ describe("loadApp", () => {
await waitFor(() => expect(window.matrixChat).toBeInstanceOf(MatrixChat));
});
it("should pass onTokenLoginCompleted which strips searchParams to MatrixChat", async () => {
it("should pass onTokenLoginCompleted which strips searchParams & fragment to MatrixChat", async () => {
const spy = jest.spyOn(window.history, "replaceState");
await loadApp({});
await waitFor(() => expect(window.matrixChat).toBeInstanceOf(MatrixChat));
window.matrixChat!.props.onTokenLoginCompleted();
window.matrixChat!.props.onTokenLoginCompleted(parseAppUrl(window.location).params, "/home");
expect(spy).toHaveBeenCalledWith(
null,
"",
expect.stringContaining("https://app.element.io/?something_else=value"),
);
expect(spy).toHaveBeenCalledWith(null, "", "https://app.element.io/?something_else=value#/home");
});
});
@@ -5,37 +5,54 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { parseQsFromFragment, parseQs } from "../../../src/vector/url_utils";
import { parseAppUrl, parseQsFromFragment, searchParamsToQueryDict } from "../../../src/vector/url_utils";
describe("url_utils.ts", function () {
// @ts-ignore
const location: Location = {
hash: "",
search: "",
};
// @ts-ignore
const location: Location = {
hash: "",
search: "",
};
it("parseQsFromFragment", function () {
location.hash = "/home?foo=bar";
describe("parseQsFromFragment", () => {
it("should parse correctly", () => {
location.hash = "#/home?foo=bar";
expect(parseQsFromFragment(location)).toEqual({
location: "home",
params: {
location: "/home",
params: new URLSearchParams({
foo: "bar",
},
});
});
it("parseQs", function () {
location.search = "?foo=bar";
expect(parseQs(location)).toEqual({
foo: "bar",
});
});
it("parseQs with arrays", function () {
location.search = "?via=s1&via=s2&via=s2&foo=bar";
expect(parseQs(location)).toEqual({
via: ["s1", "s2", "s2"],
foo: "bar",
}),
});
});
});
describe("searchParamsToQueryDict", () => {
it("should handle arrays correctly", () => {
const u = new URLSearchParams("a=b&b=c&c=d&a=e&a=f");
expect(searchParamsToQueryDict(u)).toEqual({
a: ["b", "e", "f"],
b: "c",
c: "d",
});
});
});
describe("parseUrlParameters", () => {
it("should parse legacy sso parameters from query", () => {
const u = new URL("https://app.element.io?loginToken=foobar");
const parsed = parseAppUrl(u);
expect(parsed.params.legacy_sso?.loginToken).toEqual("foobar");
});
it("should parse oidc parameters from oauth-fragment", () => {
const u = new URL("https://app.element.io/#code=foobar&state=barfoo");
const parsed = parseAppUrl(u);
expect(parsed.params.oidc?.code).toEqual("foobar");
expect(parsed.params.oidc?.state).toEqual("barfoo");
});
it("should parse guest parameters", () => {
const u = new URL("https://app.element.io?foo=bar#/room/!roomId:server?guest_access_token=foobar");
const parsed = parseAppUrl(u);
expect(parsed.params.guest?.guest_access_token).toEqual("foobar");
});
});