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
@@ -9,22 +9,26 @@ import React from "react";
import { fireEvent, render, screen, waitForElementToBeRemoved } from "jest-matrix-react";
import { mocked, type MockedObject } from "jest-mock-vitest-adapter";
import fetchMock from "@fetch-mock/jest";
import { DELEGATED_OIDC_COMPATIBILITY, IdentityProviderBrand, type OidcClientConfig } from "matrix-js-sdk/src/matrix";
import {
OAUTH_AWARE_PREFERRED_FLOW_FIELD,
IdentityProviderBrand,
type ValidatedAuthMetadata,
} from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import * as Matrix from "matrix-js-sdk/src/matrix";
import { OidcError } from "matrix-js-sdk/src/oidc/error";
import { OAuth2Error } from "matrix-js-sdk/src/matrix";
import SdkConfig from "../../../../../src/SdkConfig";
import { mkServerConfig, mockPlatformPeg, unmockPlatformPeg } from "../../../../test-utils";
import Login from "../../../../../src/components/structures/auth/Login";
import type BasePlatform from "../../../../../src/BasePlatform";
import * as registerClientUtils from "../../../../../src/utils/oidc/registerClient";
import { makeDelegatedAuthConfig } from "../../../../test-utils/oidc";
import * as registerClientUtils from "../../../../../src/utils/oauth/registerClient";
import { makeDelegatedAuthMetadata } from "../../../../test-utils/auth";
import { ModuleApi } from "../../../../../src/modules/Api.ts";
jest.useRealTimers();
const oidcStaticClientsConfig = {
const oauthStaticClientsConfig = {
"https://staticallyregisteredissuer.org/": {
client_id: "static-clientId-123",
},
@@ -42,7 +46,7 @@ describe("Login", function () {
SdkConfig.put({
brand: "test-brand",
disable_custom_urls: true,
oidc_static_clients: oidcStaticClientsConfig,
oidc_static_clients: oauthStaticClientsConfig,
});
mockClient.login.mockClear().mockResolvedValue({
access_token: "TOKEN",
@@ -72,7 +76,7 @@ describe("Login", function () {
function getRawComponent(
hsUrl = "https://matrix.org",
isUrl = "https://vector.im",
delegatedAuthentication?: OidcClientConfig,
delegatedAuthentication?: ValidatedAuthMetadata,
) {
return (
<Login
@@ -84,7 +88,7 @@ describe("Login", function () {
);
}
function getComponent(hsUrl?: string, isUrl?: string, delegatedAuthentication?: OidcClientConfig) {
function getComponent(hsUrl?: string, isUrl?: string, delegatedAuthentication?: ValidatedAuthMetadata) {
return render(getRawComponent(hsUrl, isUrl, delegatedAuthentication));
}
@@ -269,7 +273,7 @@ describe("Login", function () {
flows: [
{
type: "m.login.sso",
[DELEGATED_OIDC_COMPATIBILITY.name]: true,
[OAUTH_AWARE_PREFERRED_FLOW_FIELD.name]: true,
},
{
type: "m.login.password",
@@ -393,7 +397,7 @@ describe("Login", function () {
const hsUrl = "https://matrix.org";
const isUrl = "https://vector.im";
const issuer = "https://test.com/";
const delegatedAuth = makeDelegatedAuthConfig(issuer);
const delegatedAuth = makeDelegatedAuthMetadata(issuer);
beforeEach(() => {
jest.spyOn(logger, "error");
});
@@ -402,9 +406,9 @@ describe("Login", function () {
jest.spyOn(logger, "error").mockRestore();
});
it("should attempt to register oidc client", async () => {
it("should attempt to register oauth client", async () => {
// dont mock, spy so we can check config values were correctly passed
jest.spyOn(registerClientUtils, "getOidcClientId");
jest.spyOn(registerClientUtils, "getOAuthClientId");
fetchMock.post(delegatedAuth.registration_endpoint!, { status: 500 });
getComponent(hsUrl, isUrl, delegatedAuth);
@@ -413,7 +417,7 @@ describe("Login", function () {
// tried to register
expect(fetchMock).toHaveFetched(delegatedAuth.registration_endpoint);
// called with values from config
expect(registerClientUtils.getOidcClientId).toHaveBeenCalledWith(delegatedAuth, oidcStaticClientsConfig);
expect(registerClientUtils.getOAuthClientId).toHaveBeenCalledWith(delegatedAuth, oauthStaticClientsConfig);
});
it("should fallback to normal login when client registration fails", async () => {
@@ -425,8 +429,8 @@ describe("Login", function () {
// tried to register
expect(fetchMock).toHaveFetched(delegatedAuth.registration_endpoint);
expect(logger.error).toHaveBeenCalledWith(
"Failed to get oidc native flow",
new Error(OidcError.DynamicRegistrationFailed),
"Failed to get OAuth2 native flow",
new Error(OAuth2Error.DynamicRegistrationFailed),
);
// continued with normal setup
@@ -436,7 +440,7 @@ describe("Login", function () {
});
// short term during active development, UI will be added in next PRs
it("should show continue button when oidc native flow is correctly configured", async () => {
it("should show continue button when oauth native flow is correctly configured", async () => {
fetchMock.post(delegatedAuth.registration_endpoint!, { client_id: "abc123" });
getComponent(hsUrl, isUrl, delegatedAuth);
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { fireEvent, render, screen, waitFor, waitForElementToBeRemoved } from "jest-matrix-react";
import { createClient, type MatrixClient, MatrixError, type OidcClientConfig } from "matrix-js-sdk/src/matrix";
import { createClient, type MatrixClient, MatrixError, type ValidatedAuthMetadata } from "matrix-js-sdk/src/matrix";
import { mocked, type MockedObject } from "jest-mock-vitest-adapter";
import fetchMock from "@fetch-mock/jest";
@@ -21,11 +21,11 @@ import {
unmockPlatformPeg,
} from "../../../../test-utils";
import Registration from "../../../../../src/components/structures/auth/Registration";
import { makeDelegatedAuthConfig } from "../../../../test-utils/oidc";
import { startOidcLogin } from "../../../../../src/utils/oidc/authorize";
import { makeDelegatedAuthMetadata } from "../../../../test-utils/auth";
import { startOAuthLogin } from "../../../../../src/utils/oauth/authorize";
jest.mock("../../../../../src/utils/oidc/authorize", () => ({
startOidcLogin: jest.fn(),
jest.mock("../../../../../src/utils/oauth/authorize", () => ({
startOAuthLogin: jest.fn(),
}));
jest.mock("matrix-js-sdk/src/matrix", () => ({
@@ -92,7 +92,7 @@ describe("Registration", function () {
function getRawComponent(
hsUrl = defaultHsUrl,
isUrl = defaultIsUrl,
authConfig?: OidcClientConfig,
authConfig?: ValidatedAuthMetadata,
mobileRegister?: boolean,
) {
return (
@@ -104,7 +104,12 @@ describe("Registration", function () {
);
}
function getComponent(hsUrl?: string, isUrl?: string, authConfig?: OidcClientConfig, mobileRegister?: boolean) {
function getComponent(
hsUrl?: string,
isUrl?: string,
authConfig?: ValidatedAuthMetadata,
mobileRegister?: boolean,
) {
return render(getRawComponent(hsUrl, isUrl, authConfig, mobileRegister));
}
@@ -155,7 +160,7 @@ describe("Registration", function () {
});
describe("when delegated authentication is configured and enabled", () => {
const authConfig = makeDelegatedAuthConfig();
const authConfig = makeDelegatedAuthMetadata();
const clientId = "test-client-id";
authConfig.prompt_values_supported = ["create"];
@@ -168,15 +173,6 @@ describe("Registration", function () {
},
},
});
fetchMock.get(`${defaultHsUrl}/_matrix/client/unstable/org.matrix.msc2965/auth_issuer`, {
issuer: authConfig.issuer,
});
fetchMock.get("https://auth.org/.well-known/openid-configuration", {
...authConfig,
signingKeys: undefined,
});
fetchMock.get(authConfig.jwks_uri!, { keys: [] });
});
it("should display oidc-native continue button", async () => {
@@ -194,7 +190,7 @@ describe("Registration", function () {
fireEvent.click(await screen.findByText("Continue"));
expect(startOidcLogin).toHaveBeenCalledWith(
expect(startOAuthLogin).toHaveBeenCalledWith(
authConfig,
clientId,
defaultHsUrl,