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:
@@ -117,6 +117,7 @@ export default class InteractiveAuthComponent<T> extends React.Component<Interac
|
||||
AuthType.UnstableRegistrationToken,
|
||||
AuthType.Sso,
|
||||
AuthType.SsoUnstable,
|
||||
AuthType.OAuth,
|
||||
CustomAuthType.MasCrossSigningReset,
|
||||
],
|
||||
});
|
||||
|
||||
@@ -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:
|
||||
|
||||
109
test/unit-tests/components/views/auth/OAuthStage-test.tsx
Normal file
109
test/unit-tests/components/views/auth/OAuthStage-test.tsx
Normal 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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user