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

Commit 01a3150

Browse files
authored
Automatically log in after registration (#8654)
1 parent 762d052 commit 01a3150

File tree

6 files changed

+70
-44
lines changed

6 files changed

+70
-44
lines changed

src/components/structures/InteractiveAuth.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ import Spinner from "../views/elements/Spinner";
3131

3232
export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
3333

34+
type InteractiveAuthCallbackSuccess = (
35+
success: true,
36+
response: IAuthData,
37+
extra?: { emailSid?: string, clientSecret?: string }
38+
) => void;
39+
type InteractiveAuthCallbackFailure = (
40+
success: false,
41+
response: IAuthData | Error,
42+
) => void;
43+
export type InteractiveAuthCallback = InteractiveAuthCallbackSuccess & InteractiveAuthCallbackFailure;
44+
3445
interface IProps {
3546
// matrix client to use for UI auth requests
3647
matrixClient: MatrixClient;
@@ -66,11 +77,7 @@ interface IProps {
6677
// the auth session.
6778
// * clientSecret {string} The client secret used in auth
6879
// sessions with the ID server.
69-
onAuthFinished(
70-
status: boolean,
71-
result: IAuthData | Error,
72-
extra?: { emailSid?: string, clientSecret?: string },
73-
): void;
80+
onAuthFinished: InteractiveAuthCallback;
7481
// As js-sdk interactive-auth
7582
requestEmailToken?(email: string, secret: string, attempt: number, session: string): Promise<{ sid: string }>;
7683
// Called when the stage changes, or the stage's phase changes. First

src/components/structures/auth/Registration.tsx

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { createClient } from 'matrix-js-sdk/src/matrix';
17+
import { AuthType, createClient } from 'matrix-js-sdk/src/matrix';
1818
import React, { Fragment, ReactNode } from 'react';
1919
import { MatrixClient } from "matrix-js-sdk/src/client";
2020
import classNames from "classnames";
@@ -34,10 +34,17 @@ import RegistrationForm from '../../views/auth/RegistrationForm';
3434
import AccessibleButton from '../../views/elements/AccessibleButton';
3535
import AuthBody from "../../views/auth/AuthBody";
3636
import AuthHeader from "../../views/auth/AuthHeader";
37-
import InteractiveAuth from "../InteractiveAuth";
37+
import InteractiveAuth, { InteractiveAuthCallback } from "../InteractiveAuth";
3838
import Spinner from "../../views/elements/Spinner";
3939
import { AuthHeaderDisplay } from './header/AuthHeaderDisplay';
4040
import { AuthHeaderProvider } from './header/AuthHeaderProvider';
41+
import SettingsStore from '../../../settings/SettingsStore';
42+
43+
const debuglog = (...args: any[]) => {
44+
if (SettingsStore.getValue("debug_registration")) {
45+
logger.log.call(console, "Registration debuglog:", ...args);
46+
}
47+
};
4148

4249
interface IProps {
4350
serverConfig: ValidatedServerConfig;
@@ -287,9 +294,10 @@ export default class Registration extends React.Component<IProps, IState> {
287294
);
288295
};
289296

290-
private onUIAuthFinished = async (success: boolean, response: any) => {
297+
private onUIAuthFinished: InteractiveAuthCallback = async (success, response) => {
298+
debuglog("Registration: ui authentication finished: ", { success, response });
291299
if (!success) {
292-
let errorText = response.message || response.toString();
300+
let errorText: ReactNode = response.message || response.toString();
293301
// can we give a better error message?
294302
if (response.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
295303
const errorTop = messageForResourceLimitError(
@@ -312,10 +320,10 @@ export default class Registration extends React.Component<IProps, IState> {
312320
<p>{ errorTop }</p>
313321
<p>{ errorDetail }</p>
314322
</div>;
315-
} else if (response.required_stages && response.required_stages.indexOf('m.login.msisdn') > -1) {
323+
} else if (response.required_stages && response.required_stages.includes(AuthType.Msisdn)) {
316324
let msisdnAvailable = false;
317325
for (const flow of response.available_flows) {
318-
msisdnAvailable = msisdnAvailable || flow.stages.includes('m.login.msisdn');
326+
msisdnAvailable = msisdnAvailable || flow.stages.includes(AuthType.Msisdn);
319327
}
320328
if (!msisdnAvailable) {
321329
errorText = _t('This server does not support authentication with a phone number.');
@@ -351,14 +359,31 @@ export default class Registration extends React.Component<IProps, IState> {
351359
// starting the registration process. This isn't perfect since it's possible
352360
// the user had a separate guest session they didn't actually mean to replace.
353361
const [sessionOwner, sessionIsGuest] = await Lifecycle.getStoredSessionOwner();
354-
if (sessionOwner && !sessionIsGuest && sessionOwner !== response.userId) {
362+
if (sessionOwner && !sessionIsGuest && sessionOwner !== response.user_id) {
355363
logger.log(
356-
`Found a session for ${sessionOwner} but ${response.userId} has just registered.`,
364+
`Found a session for ${sessionOwner} but ${response.user_id} has just registered.`,
357365
);
358366
newState.differentLoggedInUserId = sessionOwner;
359367
}
360368

361-
if (response.access_token) {
369+
// if we don't have an email at all, only one client can be involved in this flow, and we can directly log in.
370+
//
371+
// if we've got an email, it needs to be verified. in that case, two clients can be involved in this flow, the
372+
// original client starting the process and the client that submitted the verification token. After the token
373+
// has been submitted, it can not be used again.
374+
//
375+
// we can distinguish them based on whether the client has form values saved (if so, it's the one that started
376+
// the registration), or whether it doesn't have any form values saved (in which case it's the client that
377+
// verified the email address)
378+
//
379+
// as the client that started registration may be gone by the time we've verified the email, and only the client
380+
// that verified the email is guaranteed to exist, we'll always do the login in that client.
381+
const hasEmail = Boolean(this.state.formVals.email);
382+
const hasAccessToken = Boolean(response.access_token);
383+
debuglog("Registration: ui auth finished:", { hasEmail, hasAccessToken });
384+
if (!hasEmail && hasAccessToken) {
385+
// we'll only try logging in if we either have no email to verify at all or we're the client that verified
386+
// the email, not the client that started the registration flow
362387
await this.props.onLoggedIn({
363388
userId: response.user_id,
364389
deviceId: response.device_id,
@@ -416,26 +441,17 @@ export default class Registration extends React.Component<IProps, IState> {
416441
};
417442

418443
private makeRegisterRequest = auth => {
419-
// We inhibit login if we're trying to register with an email address: this
420-
// avoids a lot of complex race conditions that can occur if we try to log
421-
// the user in one one or both of the tabs they might end up with after
422-
// clicking the email link.
423-
let inhibitLogin = Boolean(this.state.formVals.email);
424-
425-
// Only send inhibitLogin if we're sending username / pw params
426-
// (Since we need to send no params at all to use the ones saved in the
427-
// session).
428-
if (!this.state.formVals.password) inhibitLogin = null;
429-
430444
const registerParams = {
431445
username: this.state.formVals.username,
432446
password: this.state.formVals.password,
433447
initial_device_display_name: this.props.defaultDeviceDisplayName,
434448
auth: undefined,
449+
// we still want to avoid the race conditions involved with multiple clients handling registration, but
450+
// we'll handle these after we've received the access_token in onUIAuthFinished
435451
inhibit_login: undefined,
436452
};
437453
if (auth) registerParams.auth = auth;
438-
if (inhibitLogin !== undefined && inhibitLogin !== null) registerParams.inhibit_login = inhibitLogin;
454+
debuglog("Registration: sending registration request:", auth);
439455
return this.state.matrixClient.registerRequest(registerParams);
440456
};
441457

@@ -597,22 +613,22 @@ export default class Registration extends React.Component<IProps, IState> {
597613
{ _t("Continue with previous account") }
598614
</AccessibleButton></p>
599615
</div>;
600-
} else if (this.state.formVals.password) {
601-
// We're the client that started the registration
602-
regDoneText = <h3>{ _t(
603-
"<a>Log in</a> to your new account.", {},
604-
{
605-
a: (sub) => <a href="#/login" onClick={this.onLoginClickWithCheck}>{ sub }</a>,
606-
},
607-
) }</h3>;
608616
} else {
609-
// We're not the original client: the user probably got to us by clicking the
610-
// email validation link. We can't offer a 'go straight to your account' link
611-
// as we don't have the original creds.
617+
// regardless of whether we're the client that started the registration or not, we should
618+
// try our credentials anyway
612619
regDoneText = <h3>{ _t(
613-
"You can now close this window or <a>log in</a> to your new account.", {},
620+
"<a>Log in</a> to your new account.", {},
614621
{
615-
a: (sub) => <a href="#/login" onClick={this.onLoginClickWithCheck}>{ sub }</a>,
622+
a: (sub) => <AccessibleButton
623+
element="span"
624+
className="mx_linkButton"
625+
onClick={async event => {
626+
const sessionLoaded = await this.onLoginClickWithCheck(event);
627+
if (sessionLoaded) {
628+
dis.dispatch({ action: "view_home_page" });
629+
}
630+
}}
631+
>{ sub }</AccessibleButton>,
616632
},
617633
) }</h3>;
618634
}

src/components/views/dialogs/DeactivateAccountDialog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { logger } from "matrix-js-sdk/src/logger";
2222
import Analytics from '../../../Analytics';
2323
import { MatrixClientPeg } from '../../../MatrixClientPeg';
2424
import { _t } from '../../../languageHandler';
25-
import InteractiveAuth, { ERROR_USER_CANCELLED } from "../../structures/InteractiveAuth";
25+
import InteractiveAuth, { ERROR_USER_CANCELLED, InteractiveAuthCallback } from "../../structures/InteractiveAuth";
2626
import { DEFAULT_PHASE, PasswordAuthEntry, SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
2727
import StyledCheckbox from "../elements/StyledCheckbox";
2828
import BaseDialog from "./BaseDialog";
@@ -104,7 +104,7 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
104104
this.setState({ bodyText, continueText, continueKind });
105105
};
106106

107-
private onUIAuthFinished = (success: boolean, result: Error) => {
107+
private onUIAuthFinished: InteractiveAuthCallback = (success, result) => {
108108
if (success) return; // great! makeRequest() will be called too.
109109

110110
if (result === ERROR_USER_CANCELLED) {

src/components/views/dialogs/InteractiveAuthDialog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { IAuthData } from "matrix-js-sdk/src/interactive-auth";
2222

2323
import { _t } from '../../../languageHandler';
2424
import AccessibleButton from '../elements/AccessibleButton';
25-
import InteractiveAuth, { ERROR_USER_CANCELLED } from "../../structures/InteractiveAuth";
25+
import InteractiveAuth, { ERROR_USER_CANCELLED, InteractiveAuthCallback } from "../../structures/InteractiveAuth";
2626
import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
2727
import BaseDialog from "./BaseDialog";
2828
import { IDialogProps } from "./IDialogProps";
@@ -117,7 +117,7 @@ export default class InteractiveAuthDialog extends React.Component<IProps, IStat
117117
};
118118
}
119119

120-
private onAuthFinished = (success: boolean, result: Error): void => {
120+
private onAuthFinished: InteractiveAuthCallback = (success, result): void => {
121121
if (success) {
122122
this.props.onFinished(true, result);
123123
} else {

src/i18n/strings/en_EN.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3232,7 +3232,6 @@
32323232
"Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).",
32333233
"Continue with previous account": "Continue with previous account",
32343234
"<a>Log in</a> to your new account.": "<a>Log in</a> to your new account.",
3235-
"You can now close this window or <a>log in</a> to your new account.": "You can now close this window or <a>log in</a> to your new account.",
32363235
"Registration Successful": "Registration Successful",
32373236
"Create account": "Create account",
32383237
"Host account on": "Host account on",

src/settings/Settings.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,10 @@ export const SETTINGS: {[setting: string]: ISetting} = {
929929
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
930930
default: false,
931931
},
932+
"debug_registration": {
933+
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
934+
default: false,
935+
},
932936
"audioInputMuted": {
933937
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
934938
default: false,

0 commit comments

Comments
 (0)