Support for stable m.oauth UIA stage from MSC4312 (#31704)

* Support for stable m.oauth UIA stage from MSC4312

* Unit tests

* readonly props
This commit is contained in:
Hugh Nimmo-Smith
2026-01-13 12:18:03 +00:00
committed by GitHub
parent f079224dd6
commit fb060721dc
3 changed files with 114 additions and 2 deletions

View File

@@ -117,6 +117,7 @@ export default class InteractiveAuthComponent<T> extends React.Component<Interac
AuthType.UnstableRegistrationToken,
AuthType.Sso,
AuthType.SsoUnstable,
AuthType.OAuth,
CustomAuthType.MasCrossSigningReset,
],
});

View File

@@ -951,7 +951,7 @@ export class FallbackAuthEntry<T extends object> extends React.Component<IAuthEn
}
export enum CustomAuthType {
// Workaround for MAS requiring non-UIA authentication for resetting cross-signing.
// This is the unstable value from MSC4312
MasCrossSigningReset = "org.matrix.cross_signing_reset",
}
@@ -960,7 +960,8 @@ export class MasUnlockCrossSigningAuthEntry extends FallbackAuthEntry<{
url?: string;
};
}> {
public static LOGIN_TYPE = CustomAuthType.MasCrossSigningReset;
public static readonly LOGIN_TYPE = AuthType.OAuth;
public static readonly UNSTABLE_LOGIN_TYPE = CustomAuthType.MasCrossSigningReset;
private onGoToAccountClick = (): void => {
if (!this.props.stageParams?.url) return;
@@ -1017,6 +1018,7 @@ export interface IStageComponent extends React.ComponentClass<React.PropsWithRef
export default function getEntryComponentForLoginType(loginType: AuthType | CustomAuthType): IStageComponent {
switch (loginType) {
case AuthType.OAuth:
case CustomAuthType.MasCrossSigningReset:
return MasUnlockCrossSigningAuthEntry;
case AuthType.Password:

View File

@@ -0,0 +1,109 @@
/*
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.
*/
import React from "react";
import { fireEvent, render, type RenderResult } from "jest-matrix-react";
import InteractiveAuthComponent from "../../../../../src/components/structures/InteractiveAuth";
import { getMockClientWithEventEmitter, unmockClientPeg } from "../../../../test-utils";
describe("InteractiveAuthComponent", function () {
const mockClient = getMockClientWithEventEmitter({
generateClientSecret: jest.fn().mockReturnValue("t35tcl1Ent5ECr3T"),
getDomain: jest.fn().mockReturnValue("test.local"),
});
const authUrl = "https://test.local/oauth?action=foo";
const onAuthFinished = jest.fn();
const makeRequest = jest.fn().mockResolvedValue({ a: 1 });
const defaultProps = {
matrixClient: mockClient,
makeRequest: jest.fn().mockResolvedValue(undefined),
onAuthFinished: jest.fn(),
};
const getComponent = (props = {}) => render(<InteractiveAuthComponent {...defaultProps} {...props} />);
beforeEach(function () {
jest.clearAllMocks();
jest.spyOn(global.window, "open").mockImplementation();
});
afterAll(() => {
unmockClientPeg();
});
const getSubmitButton = ({ container }: RenderResult) => container.querySelector(".mx_Dialog_nonDialogButton");
it("should use an m.oauth stage", async () => {
const authData = {
session: "sess",
flows: [{ stages: ["m.oauth"] }],
params: {
"m.oauth": {
url: authUrl,
},
},
};
const wrapper = getComponent({ makeRequest, onAuthFinished, authData });
const submitNode = getSubmitButton(wrapper);
expect(submitNode).toBeTruthy();
// click button; should trigger the auth URL to be opened
fireEvent.click(submitNode!);
expect(global.window.open).toHaveBeenCalledWith(authUrl, "_blank");
});
it("should use an unstable org.matrix.cross_signing_reset stage", async () => {
const authData = {
session: "sess",
flows: [{ stages: ["org.matrix.cross_signing_reset"] }],
params: {
"org.matrix.cross_signing_reset": {
url: authUrl,
},
},
};
const wrapper = getComponent({ makeRequest, onAuthFinished, authData });
const submitNode = getSubmitButton(wrapper);
expect(submitNode).toBeTruthy();
// click button; should trigger the auth URL to be opened
fireEvent.click(submitNode!);
expect(global.window.open).toHaveBeenCalledWith(authUrl, "_blank");
});
it("should use the first flow when both stable and unstable are present", async () => {
const authData = {
session: "sess",
flows: [{ stages: ["org.matrix.cross_signing_reset"] }, { stages: ["m.oauth"] }],
params: {
"org.matrix.cross_signing_reset": {
url: authUrl,
},
"m.oauth": {
url: "https://should.not.be/opened",
},
},
};
const wrapper = getComponent({ makeRequest, onAuthFinished, authData });
const submitNode = getSubmitButton(wrapper);
expect(submitNode).toBeTruthy();
// click button; should trigger the auth URL to be opened
fireEvent.click(submitNode!);
expect(global.window.open).toHaveBeenCalledWith(authUrl, "_blank");
});
});