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

Commit 672ad98

Browse files
authored
Clean up the MatrixChat initSession code (#11403)
`async` functions are a thing, and they make this much more comprehensible.
1 parent f65c672 commit 672ad98

File tree

4 files changed

+220
-47
lines changed

4 files changed

+220
-47
lines changed

src/Lifecycle.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ export async function attemptDelegatedAuthLogin(
204204
fragmentAfterLogin?: string,
205205
): Promise<boolean> {
206206
if (queryParams.code && queryParams.state) {
207+
console.log("We have OIDC params - attempting OIDC login");
207208
return attemptOidcNativeLogin(queryParams);
208209
}
209210

@@ -297,6 +298,8 @@ export function attemptTokenLogin(
297298
return Promise.resolve(false);
298299
}
299300

301+
console.log("We have token login params - attempting token login");
302+
300303
const homeserver = localStorage.getItem(SSO_HOMESERVER_URL_KEY);
301304
const identityServer = localStorage.getItem(SSO_ID_SERVER_URL_KEY) ?? undefined;
302305
if (!homeserver) {

src/components/structures/MatrixChat.tsx

Lines changed: 64 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -293,14 +293,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
293293

294294
RoomNotificationStateStore.instance.on(UPDATE_STATUS_INDICATOR, this.onUpdateStatusIndicator);
295295

296-
// Force users to go through the soft logout page if they're soft logged out
297-
if (Lifecycle.isSoftLogout()) {
298-
// When the session loads it'll be detected as soft logged out and a dispatch
299-
// will be sent out to say that, triggering this MatrixChat to show the soft
300-
// logout page.
301-
Lifecycle.loadSession();
302-
}
303-
304296
this.dispatcherRef = dis.register(this.onAction);
305297

306298
this.themeWatcher = new ThemeWatcher();
@@ -314,52 +306,77 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
314306
// we don't do it as react state as i'm scared about triggering needless react refreshes.
315307
this.subTitleStatus = "";
316308

317-
// the first thing to do is to try the token params in the query-string
318-
// if the session isn't soft logged out (ie: is a clean session being logged in)
319-
if (!Lifecycle.isSoftLogout()) {
320-
Lifecycle.attemptDelegatedAuthLogin(
321-
this.props.realQueryParams,
322-
this.props.defaultDeviceDisplayName,
323-
this.getFragmentAfterLogin(),
324-
).then(async (loggedIn): Promise<boolean | void> => {
325-
if (
326-
this.props.realQueryParams?.loginToken ||
327-
this.props.realQueryParams?.code ||
328-
this.props.realQueryParams?.state
329-
) {
330-
// remove the loginToken or auth code from the URL regardless
331-
this.props.onTokenLoginCompleted();
332-
}
309+
initSentry(SdkConfig.get("sentry"));
333310

334-
if (loggedIn) {
335-
this.tokenLogin = true;
311+
this.initSession().catch((err) => {
312+
// TODO: show an error screen, rather than a spinner of doom
313+
logger.error("Error initialising Matrix session", err);
314+
});
315+
}
336316

337-
// Create and start the client
338-
// accesses the new credentials just set in storage during attemptTokenLogin
339-
// and sets logged in state
340-
await Lifecycle.restoreFromLocalStorage({
341-
ignoreGuest: true,
342-
});
343-
return this.postLoginSetup();
344-
}
317+
/**
318+
* Do what we can to establish a Matrix session.
319+
*
320+
* * Special-case soft-logged-out sessions
321+
* * If we have OIDC or token login parameters, follow them
322+
* * If we have a guest access token in the query params, use that
323+
* * If we have parameters in local storage, use them
324+
* * Attempt to auto-register as a guest
325+
* * If all else fails, present a login screen.
326+
*/
327+
private async initSession(): Promise<void> {
328+
// If the user was soft-logged-out, we want to make the SoftLogout component responsible for doing any
329+
// token auth (rather than Lifecycle.attemptDelegatedAuthLogin), since SoftLogout knows about submitting the
330+
// device ID and preserving the session.
331+
//
332+
// So, we start by special-casing soft-logged-out sessions.
333+
if (Lifecycle.isSoftLogout()) {
334+
// When the session loads it'll be detected as soft logged out and a dispatch
335+
// will be sent out to say that, triggering this MatrixChat to show the soft
336+
// logout page.
337+
Lifecycle.loadSession();
338+
return;
339+
}
345340

346-
// if the user has followed a login or register link, don't reanimate
347-
// the old creds, but rather go straight to the relevant page
348-
const firstScreen = this.screenAfterLogin ? this.screenAfterLogin.screen : null;
349-
const restoreSuccess = await this.loadSession();
350-
if (restoreSuccess) {
351-
return true;
352-
}
341+
// Otherwise, the first thing to do is to try the token params in the query-string
342+
const delegatedAuthSucceeded = await Lifecycle.attemptDelegatedAuthLogin(
343+
this.props.realQueryParams,
344+
this.props.defaultDeviceDisplayName,
345+
this.getFragmentAfterLogin(),
346+
);
353347

354-
if (firstScreen === "login" || firstScreen === "register" || firstScreen === "forgot_password") {
355-
this.showScreenAfterLogin();
356-
}
348+
// remove the loginToken or auth code from the URL regardless
349+
if (
350+
this.props.realQueryParams?.loginToken ||
351+
this.props.realQueryParams?.code ||
352+
this.props.realQueryParams?.state
353+
) {
354+
this.props.onTokenLoginCompleted();
355+
}
357356

358-
return false;
359-
});
357+
if (delegatedAuthSucceeded) {
358+
// token auth/OIDC worked! Time to fire up the client.
359+
this.tokenLogin = true;
360+
361+
// Create and start the client
362+
// accesses the new credentials just set in storage during attemptDelegatedAuthLogin
363+
// and sets logged in state
364+
await Lifecycle.restoreFromLocalStorage({ ignoreGuest: true });
365+
await this.postLoginSetup();
366+
return;
360367
}
361368

362-
initSentry(SdkConfig.get("sentry"));
369+
// if the user has followed a login or register link, don't reanimate
370+
// the old creds, but rather go straight to the relevant page
371+
const firstScreen = this.screenAfterLogin ? this.screenAfterLogin.screen : null;
372+
const restoreSuccess = await this.loadSession();
373+
if (restoreSuccess) {
374+
return;
375+
}
376+
377+
if (firstScreen === "login" || firstScreen === "register" || firstScreen === "forgot_password") {
378+
this.showScreenAfterLogin();
379+
}
363380
}
364381

365382
private async postLoginSetup(): Promise<void> {

test/components/structures/MatrixChat-test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,36 @@ describe("<MatrixChat />", () => {
520520
});
521521
});
522522

523+
describe("with a soft-logged-out session", () => {
524+
const mockidb: Record<string, Record<string, string>> = {};
525+
const mockLocalStorage: Record<string, string> = {
526+
mx_hs_url: serverConfig.hsUrl,
527+
mx_is_url: serverConfig.isUrl,
528+
mx_access_token: accessToken,
529+
mx_user_id: userId,
530+
mx_device_id: deviceId,
531+
mx_soft_logout: "true",
532+
};
533+
534+
beforeEach(() => {
535+
localStorageGetSpy.mockImplementation((key: unknown) => mockLocalStorage[key as string] || "");
536+
537+
mockClient.loginFlows.mockResolvedValue({ flows: [{ type: "m.login.password" }] });
538+
539+
jest.spyOn(StorageManager, "idbLoad").mockImplementation(async (table, key) => {
540+
const safeKey = Array.isArray(key) ? key[0] : key;
541+
return mockidb[table]?.[safeKey];
542+
});
543+
});
544+
545+
it("should show the soft-logout page", async () => {
546+
const result = getComponent();
547+
548+
await result.findByText("You're signed out");
549+
expect(result.container).toMatchSnapshot();
550+
});
551+
});
552+
523553
describe("login via key/pass", () => {
524554
let loginClient!: ReturnType<typeof getMockClientWithEventEmitter>;
525555

test/components/structures/__snapshots__/MatrixChat-test.tsx.snap

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,129 @@ exports[`<MatrixChat /> should render spinner while app is loading 1`] = `
2020
</div>
2121
`;
2222

23+
exports[`<MatrixChat /> with a soft-logged-out session should show the soft-logout page 1`] = `
24+
<div>
25+
<div
26+
class="mx_AuthPage"
27+
>
28+
<div
29+
class="mx_AuthPage_modal"
30+
>
31+
<div
32+
class="mx_AuthHeader"
33+
>
34+
<aside
35+
class="mx_AuthHeaderLogo"
36+
>
37+
Matrix
38+
</aside>
39+
<div
40+
class="mx_Dropdown mx_AuthBody_language"
41+
>
42+
<div
43+
aria-describedby="mx_LanguageDropdown_value"
44+
aria-expanded="false"
45+
aria-haspopup="listbox"
46+
aria-label="Language Dropdown"
47+
aria-owns="mx_LanguageDropdown_input"
48+
class="mx_AccessibleButton mx_Dropdown_input mx_no_textinput"
49+
role="button"
50+
tabindex="0"
51+
>
52+
<div
53+
class="mx_Dropdown_option"
54+
id="mx_LanguageDropdown_value"
55+
>
56+
<div>
57+
English
58+
</div>
59+
</div>
60+
<span
61+
class="mx_Dropdown_arrow"
62+
/>
63+
</div>
64+
</div>
65+
</div>
66+
<main
67+
class="mx_AuthBody"
68+
>
69+
<h1>
70+
You're signed out
71+
</h1>
72+
<h2>
73+
Sign in
74+
</h2>
75+
<div>
76+
<form>
77+
<p>
78+
Regain access to your account and recover encryption keys stored in this session. Without them, you won't be able to read all of your secure messages in any session.
79+
</p>
80+
<div
81+
class="mx_Field mx_Field_input"
82+
>
83+
<input
84+
id="mx_Field_1"
85+
label="Password"
86+
placeholder="Password"
87+
type="password"
88+
value=""
89+
/>
90+
<label
91+
for="mx_Field_1"
92+
>
93+
Password
94+
</label>
95+
</div>
96+
<div
97+
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
98+
role="button"
99+
tabindex="0"
100+
type="submit"
101+
>
102+
Sign In
103+
</div>
104+
<div
105+
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
106+
role="button"
107+
tabindex="0"
108+
>
109+
Forgotten your password?
110+
</div>
111+
</form>
112+
</div>
113+
<h2>
114+
Clear personal data
115+
</h2>
116+
<p>
117+
Warning: your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.
118+
</p>
119+
<div>
120+
<div
121+
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger"
122+
role="button"
123+
tabindex="0"
124+
>
125+
Clear all data
126+
</div>
127+
</div>
128+
</main>
129+
</div>
130+
<footer
131+
class="mx_AuthFooter"
132+
role="contentinfo"
133+
>
134+
<a
135+
href="https://matrix.org"
136+
rel="noreferrer noopener"
137+
target="_blank"
138+
>
139+
powered by Matrix
140+
</a>
141+
</footer>
142+
</div>
143+
</div>
144+
`;
145+
23146
exports[`<MatrixChat /> with an existing session onAction() room actions leave_room for a room should launch a confirmation modal 1`] = `
24147
<div
25148
aria-describedby="mx_Dialog_content"

0 commit comments

Comments
 (0)