Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 6961c25

Browse files
govynnusturt2liveandybalaam
authored
Support registration tokens (#7275)
* Support token authenticated registration https://spec.matrix.org/v1.2/client-server-api/#token-authenticated-registration Signed-off-by: Callum Brown <callum@calcuode.com> * Backwards compatibility with unstable auth type Some server installs are not updated to use the stable version of the registration token authentication type, so also handle the unstable version defined in MSC3231. Signed-off-by: Callum Brown <callum@calcuode.com> * Make LOGIN_TYPE public and readonly Co-authored-by: Travis Ralston <travpc@gmail.com> * Remove line related to skinning Signed-off-by: Callum Brown <callum@calcuode.com> * Change empty string to null Signed-off-by: Callum Brown <callum@calcuode.com> * Use "public"s for new code style Signed-off-by: Callum Brown <callum@calcuode.com> * Change input to AccessibleButton Signed-off-by: Callum Brown <callum@calcuode.com> * Add more detail regarding source of token Signed-off-by: Callum Brown <callum@calcuode.com> * Fix lint error The text and button type will be the same every time for registration tokens, unlike (possibly) for SSO. Signed-off-by: Callum Brown <callum@calcuode.com> * Change null back to "" Due to the following warning when attempting to test: > Warning: `value` prop on `input` should not be null. > Consider using an empty string to clear the component or > `undefined` for uncontrolled components. Signed-off-by: Callum Brown <callum@calcuode.com> * Disable submit button when no token entered Signed-off-by: Callum Brown <callum@calcuode.com> * Add test for registration tokens Adapted from test/components/views/dialogs/InteractiveAuthDialog-test.tsx Signed-off-by: Callum Brown <callum@calcuode.com> * Fix linting errors Signed-off-by: Callum Brown <callum@calcuode.com> * Fix test for registration tokens Signed-off-by: Callum Brown <callum@calcuode.com> Signed-off-by: Callum Brown <callum@calcuode.com> Co-authored-by: Travis Ralston <travpc@gmail.com> Co-authored-by: Andy Balaam <andy.balaam@matrix.org>
1 parent 29c1932 commit 6961c25

File tree

3 files changed

+195
-1
lines changed

3 files changed

+195
-1
lines changed

src/components/views/auth/InteractiveAuthEntryComponents.tsx

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,89 @@ export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsi
692692
}
693693
}
694694

695+
interface IRegistrationTokenAuthEntryState {
696+
registrationToken: string;
697+
}
698+
699+
export class RegistrationTokenAuthEntry extends React.Component<IAuthEntryProps, IRegistrationTokenAuthEntryState> {
700+
public static readonly LOGIN_TYPE = AuthType.RegistrationToken;
701+
702+
public constructor(props: IAuthEntryProps) {
703+
super(props);
704+
705+
this.state = {
706+
registrationToken: "",
707+
};
708+
}
709+
710+
public componentDidMount(): void {
711+
this.props.onPhaseChange(DEFAULT_PHASE);
712+
}
713+
714+
private onSubmit = (e: FormEvent): void => {
715+
e.preventDefault();
716+
if (this.props.busy) return;
717+
718+
this.props.submitAuthDict({
719+
// Could be AuthType.RegistrationToken or AuthType.UnstableRegistrationToken
720+
type: this.props.loginType,
721+
token: this.state.registrationToken,
722+
});
723+
};
724+
725+
private onRegistrationTokenFieldChange = (ev: ChangeEvent<HTMLInputElement>): void => {
726+
// enable the submit button if the registration token is non-empty
727+
this.setState({
728+
registrationToken: ev.target.value,
729+
});
730+
};
731+
732+
public render(): JSX.Element {
733+
const registrationTokenBoxClass = classNames({
734+
error: this.props.errorText,
735+
});
736+
737+
let submitButtonOrSpinner;
738+
if (this.props.busy) {
739+
submitButtonOrSpinner = <Spinner />;
740+
} else {
741+
submitButtonOrSpinner = (
742+
<AccessibleButton onClick={this.onSubmit} kind="primary" disabled={!this.state.registrationToken}>
743+
{_t("Continue")}
744+
</AccessibleButton>
745+
);
746+
}
747+
748+
let errorSection;
749+
if (this.props.errorText) {
750+
errorSection = (
751+
<div className="error" role="alert">
752+
{this.props.errorText}
753+
</div>
754+
);
755+
}
756+
757+
return (
758+
<div>
759+
<p>{_t("Enter a registration token provided by the homeserver administrator.")}</p>
760+
<form onSubmit={this.onSubmit} className="mx_InteractiveAuthEntryComponents_registrationTokenSection">
761+
<Field
762+
className={registrationTokenBoxClass}
763+
type="text"
764+
name="registrationTokenField"
765+
label={_t("Registration token")}
766+
autoFocus={true}
767+
value={this.state.registrationToken}
768+
onChange={this.onRegistrationTokenFieldChange}
769+
/>
770+
{errorSection}
771+
<div className="mx_button_row">{submitButtonOrSpinner}</div>
772+
</form>
773+
</div>
774+
);
775+
}
776+
}
777+
695778
interface ISSOAuthEntryProps extends IAuthEntryProps {
696779
continueText?: string;
697780
continueKind?: string;
@@ -713,7 +796,7 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
713796
private ssoUrl: string;
714797
private popupWindow: Window;
715798

716-
public constructor(props) {
799+
public constructor(props: ISSOAuthEntryProps) {
717800
super(props);
718801

719802
// We actually send the user through fallback auth so we don't have to
@@ -916,6 +999,9 @@ export default function getEntryComponentForLoginType(loginType: AuthType): ISta
916999
return MsisdnAuthEntry;
9171000
case AuthType.Terms:
9181001
return TermsAuthEntry;
1002+
case AuthType.RegistrationToken:
1003+
case AuthType.UnstableRegistrationToken:
1004+
return RegistrationTokenAuthEntry;
9191005
case AuthType.Sso:
9201006
case AuthType.SsoUnstable:
9211007
return SSOAuthEntry;

src/i18n/strings/en_EN.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3270,6 +3270,8 @@
32703270
"A text message has been sent to %(msisdn)s": "A text message has been sent to %(msisdn)s",
32713271
"Please enter the code it contains:": "Please enter the code it contains:",
32723272
"Submit": "Submit",
3273+
"Enter a registration token provided by the homeserver administrator.": "Enter a registration token provided by the homeserver administrator.",
3274+
"Registration token": "Registration token",
32733275
"Something went wrong in confirming your identity. Cancel and try again.": "Something went wrong in confirming your identity. Cancel and try again.",
32743276
"Start authentication": "Start authentication",
32753277
"Sign in new device": "Sign in new device",
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
Copyright 2016 OpenMarket Ltd
3+
Copyright 2022 The Matrix.org Foundation C.I.C.
4+
Copyright 2022 Callum Brown
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
import React from "react";
20+
import { act } from "react-dom/test-utils";
21+
// eslint-disable-next-line deprecate/import
22+
import { mount, ReactWrapper } from "enzyme";
23+
24+
import InteractiveAuthComponent from "../../../../src/components/structures/InteractiveAuth";
25+
import { flushPromises, getMockClientWithEventEmitter, unmockClientPeg } from "../../../test-utils";
26+
27+
describe("InteractiveAuthComponent", function () {
28+
const mockClient = getMockClientWithEventEmitter({
29+
generateClientSecret: jest.fn().mockReturnValue("t35tcl1Ent5ECr3T"),
30+
});
31+
32+
const defaultProps = {
33+
matrixClient: mockClient,
34+
makeRequest: jest.fn().mockResolvedValue(undefined),
35+
onAuthFinished: jest.fn(),
36+
};
37+
const getComponent = (props = {}) => mount(<InteractiveAuthComponent {...defaultProps} {...props} />);
38+
39+
beforeEach(function () {
40+
jest.clearAllMocks();
41+
});
42+
43+
afterAll(() => {
44+
unmockClientPeg();
45+
});
46+
47+
const getSubmitButton = (wrapper: ReactWrapper) => wrapper.find('AccessibleButton[kind="primary"]').at(0);
48+
const getRegistrationTokenInput = (wrapper: ReactWrapper) =>
49+
wrapper.find('input[name="registrationTokenField"]').at(0);
50+
51+
it("Should successfully complete a registration token flow", async () => {
52+
const onAuthFinished = jest.fn();
53+
const makeRequest = jest.fn().mockResolvedValue({ a: 1 });
54+
55+
const authData = {
56+
session: "sess",
57+
flows: [{ stages: ["m.login.registration_token"] }],
58+
};
59+
60+
const wrapper = getComponent({ makeRequest, onAuthFinished, authData });
61+
62+
const registrationTokenNode = getRegistrationTokenInput(wrapper);
63+
const submitNode = getSubmitButton(wrapper);
64+
const formNode = wrapper.find("form").at(0);
65+
66+
expect(registrationTokenNode).toBeTruthy();
67+
expect(submitNode).toBeTruthy();
68+
expect(formNode).toBeTruthy();
69+
70+
// submit should be disabled
71+
expect(submitNode.props().disabled).toBe(true);
72+
73+
// put something in the registration token box
74+
act(() => {
75+
registrationTokenNode.simulate("change", { target: { value: "s3kr3t" } });
76+
wrapper.setProps({});
77+
});
78+
79+
expect(getRegistrationTokenInput(wrapper).props().value).toEqual("s3kr3t");
80+
expect(getSubmitButton(wrapper).props().disabled).toBe(false);
81+
82+
// hit enter; that should trigger a request
83+
act(() => {
84+
formNode.simulate("submit");
85+
});
86+
87+
// wait for auth request to resolve
88+
await flushPromises();
89+
90+
expect(makeRequest).toHaveBeenCalledTimes(1);
91+
expect(makeRequest).toBeCalledWith(
92+
expect.objectContaining({
93+
session: "sess",
94+
type: "m.login.registration_token",
95+
token: "s3kr3t",
96+
}),
97+
);
98+
99+
expect(onAuthFinished).toBeCalledTimes(1);
100+
expect(onAuthFinished).toBeCalledWith(
101+
true,
102+
{ a: 1 },
103+
{ clientSecret: "t35tcl1Ent5ECr3T", emailSid: undefined },
104+
);
105+
});
106+
});

0 commit comments

Comments
 (0)