Adapt OAuth2 implementation to Matrix Spec v1.18 (#34026)

* Adapt OAuth2 implementation to Matrix Spec v1.18

* Handle more cases of oidc->oauth

* Fix test

* Fix read back of oauth2 context

* Iterate

* Fix tests

* Discard changes to apps/web/playwright/e2e/settings/account-user-settings-tab.spec.ts

* Fix test

* Fix test

* Fix test

* Potential fix for pull request finding 'Unused variable, import, function or class'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Iterate

* Iterate

* Fix test

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
This commit is contained in:
Michael Telatynski
2026-07-08 08:02:25 +00:00
committed by GitHub
co-authored by Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
parent 38e29c51c4
commit 2bc9656957
69 changed files with 761 additions and 1738 deletions
@@ -16,7 +16,7 @@ import {
RendezvousError,
RendezvousIntent,
} from "matrix-js-sdk/src/rendezvous";
import { mockOpenIdConfiguration } from "matrix-js-sdk/src/testing";
import { makeDelegatedAuthMetadata } from "matrix-js-sdk/src/testing";
import {
AutoDiscovery,
AutoDiscoveryAction,
@@ -61,7 +61,7 @@ function makeClient() {
getClientWellKnown: jest.fn().mockReturnValue({}),
getCrypto: jest.fn().mockReturnValue({}),
getDomain: jest.fn(),
getAuthMetadata: jest.fn().mockReturnValue(mockOpenIdConfiguration()),
getAuthMetadata: jest.fn().mockReturnValue(makeDelegatedAuthMetadata()),
} as unknown as MatrixClient);
cli.http = new MatrixHttpApi(cli, {
@@ -330,24 +330,23 @@ describe("<LoginWithQR />", () => {
test("should handle qr login", async () => {
fetchMock.get("https://hs/_matrix/client/versions", {
unstable_features: {},
versions: ["v1.1", "v1.5", "v1.6", "v1.8", "v1.9"],
versions: ["v1.1", "v1.5", "v1.6", "v1.8", "v1.9", "v1.15"],
});
const authMetadata = {
...mockOpenIdConfiguration("https://auth.org/", [OAuthGrantType.DeviceAuthorization]),
jwks_uri: undefined,
};
fetchMock.get("https://hs/_matrix/client/unstable/org.matrix.msc2965/auth_metadata", authMetadata);
const authMetadata = makeDelegatedAuthMetadata("https://auth.org/", [
OAuthGrantType.DeviceAuthorization,
]);
fetchMock.get("https://hs/_matrix/client/v1/auth_metadata", authMetadata);
fetchMock.post(authMetadata.registration_endpoint!, {
client_id: "!client_id!",
});
mockPlatformPeg({
getOidcClientMetadata: jest.fn().mockReturnValue({
clientName: "App name",
clientUri: "https://company",
redirectUris: ["https://app"],
logoUri: "https://company/logo.png",
applicationType: "web",
getOAuthClientMetadata: jest.fn().mockReturnValue({
client_name: "App name",
client_uri: "https://company",
redirect_uris: ["https://app"],
logo_uri: "https://company/logo.png",
application_type: "web",
}),
});
@@ -26,7 +26,6 @@ import {
flushPromises,
} from "../../../../../../test-utils";
import { UIFeature } from "../../../../../../../src/settings/UIFeature";
import { type OidcClientStore } from "../../../../../../../src/stores/oidc/OidcClientStore";
import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext";
import Modal from "../../../../../../../src/Modal";
@@ -77,6 +76,8 @@ describe("<AccountUserSettingsTab />", () => {
getThreePids: jest.fn(),
getIdentityServerUrl: jest.fn(),
deleteThreePid: jest.fn(),
getMediaConfig: jest.fn(),
getAuthMetadata: jest.fn().mockRejectedValue(new Error("not implemented")),
});
mockClient.getCapabilities.mockResolvedValue({});
@@ -89,9 +90,6 @@ describe("<AccountUserSettingsTab />", () => {
stores = new TestSDKContext();
stores._client = mockClient;
// stub out this store completely to avoid mocking initialisation
const mockOidcClientStore = {} as unknown as OidcClientStore;
jest.spyOn(stores, "oidcClientStore", "get").mockReturnValue(mockOidcClientStore);
});
afterEach(() => {
@@ -107,10 +105,9 @@ describe("<AccountUserSettingsTab />", () => {
it("show account management link in expected format", async () => {
const accountManagementLink = "https://id.server.org/my-account";
const mockOidcClientStore = {
accountManagementEndpoint: accountManagementLink,
} as unknown as OidcClientStore;
jest.spyOn(stores, "oidcClientStore", "get").mockReturnValue(mockOidcClientStore);
mockClient.getAuthMetadata.mockResolvedValue({
account_management_uri: accountManagementLink,
} as any);
render(getComponent());
@@ -134,10 +131,9 @@ describe("<AccountUserSettingsTab />", () => {
);
// account is managed externally when we have delegated auth configured
const accountManagementLink = "https://id.server.org/my-account";
const mockOidcClientStore = {
accountManagementEndpoint: accountManagementLink,
} as unknown as OidcClientStore;
jest.spyOn(stores, "oidcClientStore", "get").mockReturnValue(mockOidcClientStore);
mockClient.getAuthMetadata.mockResolvedValue({
account_management_uri: accountManagementLink,
} as any);
render(getComponent());
await flushPromises();
@@ -207,11 +203,6 @@ describe("<AccountUserSettingsTab />", () => {
describe("3pids", () => {
beforeEach(() => {
const mockOidcClientStore = {
accountManagementEndpoint: undefined,
} as unknown as OidcClientStore;
jest.spyOn(stores, "oidcClientStore", "get").mockReturnValue(mockOidcClientStore);
mockClient.getCapabilities.mockResolvedValue({
"m.3pid_changes": {
enabled: true,
@@ -34,7 +34,6 @@ import {
type MatrixClient,
} from "matrix-js-sdk/src/matrix";
import { mocked, type MockedObject } from "jest-mock-vitest-adapter";
import fetchMock from "@fetch-mock/jest";
import {
clearAllModals,
@@ -58,8 +57,7 @@ import SettingsStore from "../../../../../../../src/settings/SettingsStore";
import { getClientInformationEventType } from "../../../../../../../src/utils/device/clientInformation";
import { SDKContext } from "../../../../../../../src/contexts/SDKContext";
import { TestSDKContext } from "../../../../../TestSDKContext.ts";
import { type OidcClientStore } from "../../../../../../../src/stores/oidc/OidcClientStore";
import { makeDelegatedAuthConfig } from "../../../../../../test-utils/oidc";
import { makeDelegatedAuthMetadata } from "../../../../../../test-utils/auth";
import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext";
mockPlatformPeg();
@@ -1180,10 +1178,10 @@ describe("<SessionManagerTab />", () => {
describe("for an OIDC-aware server", () => {
beforeEach(() => {
// just do an ugly mock here to avoid mocking initialisation
const mockOidcClientStore = {
accountManagementEndpoint: "https://issuer.org/account",
} as unknown as OidcClientStore;
jest.spyOn(sdkContext, "oidcClientStore", "get").mockReturnValue(mockOidcClientStore);
mockClient.getAuthMetadata.mockResolvedValue({
...makeDelegatedAuthMetadata(),
account_management_uri: "https://issuer.org/account",
} as any);
});
// signing out the current device works as usual
@@ -1637,7 +1635,7 @@ describe("<SessionManagerTab />", () => {
enabled: true,
},
});
const delegatedAuthConfig = makeDelegatedAuthConfig(issuer);
const delegatedAuthConfig = makeDelegatedAuthMetadata(issuer);
mockClient.getAuthMetadata.mockResolvedValue({
...delegatedAuthConfig,
grant_types_supported: [
@@ -1646,13 +1644,6 @@ describe("<SessionManagerTab />", () => {
],
});
mockCrypto.exportSecretsBundle = jest.fn();
fetchMock.route(delegatedAuthConfig.jwks_uri!, {
status: 200,
headers: {
"Content-Type": "application/json",
},
keys: [],
});
});
it("renders qr code login section", async () => {