Clean up the MatrixChat initSession code (#11403)
`async` functions are a thing, and they make this much more comprehensible.
This commit is contained in:
parent
f65c6726c9
commit
672ad98ec7
4 changed files with 223 additions and 50 deletions
|
@ -204,6 +204,7 @@ export async function attemptDelegatedAuthLogin(
|
||||||
fragmentAfterLogin?: string,
|
fragmentAfterLogin?: string,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (queryParams.code && queryParams.state) {
|
if (queryParams.code && queryParams.state) {
|
||||||
|
console.log("We have OIDC params - attempting OIDC login");
|
||||||
return attemptOidcNativeLogin(queryParams);
|
return attemptOidcNativeLogin(queryParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,6 +298,8 @@ export function attemptTokenLogin(
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("We have token login params - attempting token login");
|
||||||
|
|
||||||
const homeserver = localStorage.getItem(SSO_HOMESERVER_URL_KEY);
|
const homeserver = localStorage.getItem(SSO_HOMESERVER_URL_KEY);
|
||||||
const identityServer = localStorage.getItem(SSO_ID_SERVER_URL_KEY) ?? undefined;
|
const identityServer = localStorage.getItem(SSO_ID_SERVER_URL_KEY) ?? undefined;
|
||||||
if (!homeserver) {
|
if (!homeserver) {
|
||||||
|
|
|
@ -293,14 +293,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
RoomNotificationStateStore.instance.on(UPDATE_STATUS_INDICATOR, this.onUpdateStatusIndicator);
|
RoomNotificationStateStore.instance.on(UPDATE_STATUS_INDICATOR, this.onUpdateStatusIndicator);
|
||||||
|
|
||||||
// Force users to go through the soft logout page if they're soft logged out
|
|
||||||
if (Lifecycle.isSoftLogout()) {
|
|
||||||
// When the session loads it'll be detected as soft logged out and a dispatch
|
|
||||||
// will be sent out to say that, triggering this MatrixChat to show the soft
|
|
||||||
// logout page.
|
|
||||||
Lifecycle.loadSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
|
|
||||||
this.themeWatcher = new ThemeWatcher();
|
this.themeWatcher = new ThemeWatcher();
|
||||||
|
@ -314,52 +306,77 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// we don't do it as react state as i'm scared about triggering needless react refreshes.
|
// we don't do it as react state as i'm scared about triggering needless react refreshes.
|
||||||
this.subTitleStatus = "";
|
this.subTitleStatus = "";
|
||||||
|
|
||||||
// the first thing to do is to try the token params in the query-string
|
initSentry(SdkConfig.get("sentry"));
|
||||||
// if the session isn't soft logged out (ie: is a clean session being logged in)
|
|
||||||
if (!Lifecycle.isSoftLogout()) {
|
|
||||||
Lifecycle.attemptDelegatedAuthLogin(
|
|
||||||
this.props.realQueryParams,
|
|
||||||
this.props.defaultDeviceDisplayName,
|
|
||||||
this.getFragmentAfterLogin(),
|
|
||||||
).then(async (loggedIn): Promise<boolean | void> => {
|
|
||||||
if (
|
|
||||||
this.props.realQueryParams?.loginToken ||
|
|
||||||
this.props.realQueryParams?.code ||
|
|
||||||
this.props.realQueryParams?.state
|
|
||||||
) {
|
|
||||||
// remove the loginToken or auth code from the URL regardless
|
|
||||||
this.props.onTokenLoginCompleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loggedIn) {
|
this.initSession().catch((err) => {
|
||||||
this.tokenLogin = true;
|
// TODO: show an error screen, rather than a spinner of doom
|
||||||
|
logger.error("Error initialising Matrix session", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Create and start the client
|
/**
|
||||||
// accesses the new credentials just set in storage during attemptTokenLogin
|
* Do what we can to establish a Matrix session.
|
||||||
// and sets logged in state
|
*
|
||||||
await Lifecycle.restoreFromLocalStorage({
|
* * Special-case soft-logged-out sessions
|
||||||
ignoreGuest: true,
|
* * If we have OIDC or token login parameters, follow them
|
||||||
});
|
* * If we have a guest access token in the query params, use that
|
||||||
return this.postLoginSetup();
|
* * If we have parameters in local storage, use them
|
||||||
}
|
* * Attempt to auto-register as a guest
|
||||||
|
* * If all else fails, present a login screen.
|
||||||
// if the user has followed a login or register link, don't reanimate
|
*/
|
||||||
// the old creds, but rather go straight to the relevant page
|
private async initSession(): Promise<void> {
|
||||||
const firstScreen = this.screenAfterLogin ? this.screenAfterLogin.screen : null;
|
// If the user was soft-logged-out, we want to make the SoftLogout component responsible for doing any
|
||||||
const restoreSuccess = await this.loadSession();
|
// token auth (rather than Lifecycle.attemptDelegatedAuthLogin), since SoftLogout knows about submitting the
|
||||||
if (restoreSuccess) {
|
// device ID and preserving the session.
|
||||||
return true;
|
//
|
||||||
}
|
// So, we start by special-casing soft-logged-out sessions.
|
||||||
|
if (Lifecycle.isSoftLogout()) {
|
||||||
if (firstScreen === "login" || firstScreen === "register" || firstScreen === "forgot_password") {
|
// When the session loads it'll be detected as soft logged out and a dispatch
|
||||||
this.showScreenAfterLogin();
|
// will be sent out to say that, triggering this MatrixChat to show the soft
|
||||||
}
|
// logout page.
|
||||||
|
Lifecycle.loadSession();
|
||||||
return false;
|
return;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initSentry(SdkConfig.get("sentry"));
|
// Otherwise, the first thing to do is to try the token params in the query-string
|
||||||
|
const delegatedAuthSucceeded = await Lifecycle.attemptDelegatedAuthLogin(
|
||||||
|
this.props.realQueryParams,
|
||||||
|
this.props.defaultDeviceDisplayName,
|
||||||
|
this.getFragmentAfterLogin(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// remove the loginToken or auth code from the URL regardless
|
||||||
|
if (
|
||||||
|
this.props.realQueryParams?.loginToken ||
|
||||||
|
this.props.realQueryParams?.code ||
|
||||||
|
this.props.realQueryParams?.state
|
||||||
|
) {
|
||||||
|
this.props.onTokenLoginCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delegatedAuthSucceeded) {
|
||||||
|
// token auth/OIDC worked! Time to fire up the client.
|
||||||
|
this.tokenLogin = true;
|
||||||
|
|
||||||
|
// Create and start the client
|
||||||
|
// accesses the new credentials just set in storage during attemptDelegatedAuthLogin
|
||||||
|
// and sets logged in state
|
||||||
|
await Lifecycle.restoreFromLocalStorage({ ignoreGuest: true });
|
||||||
|
await this.postLoginSetup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user has followed a login or register link, don't reanimate
|
||||||
|
// the old creds, but rather go straight to the relevant page
|
||||||
|
const firstScreen = this.screenAfterLogin ? this.screenAfterLogin.screen : null;
|
||||||
|
const restoreSuccess = await this.loadSession();
|
||||||
|
if (restoreSuccess) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstScreen === "login" || firstScreen === "register" || firstScreen === "forgot_password") {
|
||||||
|
this.showScreenAfterLogin();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async postLoginSetup(): Promise<void> {
|
private async postLoginSetup(): Promise<void> {
|
||||||
|
|
|
@ -520,6 +520,36 @@ describe("<MatrixChat />", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("with a soft-logged-out session", () => {
|
||||||
|
const mockidb: Record<string, Record<string, string>> = {};
|
||||||
|
const mockLocalStorage: Record<string, string> = {
|
||||||
|
mx_hs_url: serverConfig.hsUrl,
|
||||||
|
mx_is_url: serverConfig.isUrl,
|
||||||
|
mx_access_token: accessToken,
|
||||||
|
mx_user_id: userId,
|
||||||
|
mx_device_id: deviceId,
|
||||||
|
mx_soft_logout: "true",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorageGetSpy.mockImplementation((key: unknown) => mockLocalStorage[key as string] || "");
|
||||||
|
|
||||||
|
mockClient.loginFlows.mockResolvedValue({ flows: [{ type: "m.login.password" }] });
|
||||||
|
|
||||||
|
jest.spyOn(StorageManager, "idbLoad").mockImplementation(async (table, key) => {
|
||||||
|
const safeKey = Array.isArray(key) ? key[0] : key;
|
||||||
|
return mockidb[table]?.[safeKey];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show the soft-logout page", async () => {
|
||||||
|
const result = getComponent();
|
||||||
|
|
||||||
|
await result.findByText("You're signed out");
|
||||||
|
expect(result.container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("login via key/pass", () => {
|
describe("login via key/pass", () => {
|
||||||
let loginClient!: ReturnType<typeof getMockClientWithEventEmitter>;
|
let loginClient!: ReturnType<typeof getMockClientWithEventEmitter>;
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,129 @@ exports[`<MatrixChat /> should render spinner while app is loading 1`] = `
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`<MatrixChat /> with a soft-logged-out session should show the soft-logout page 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="mx_AuthPage"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_AuthPage_modal"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_AuthHeader"
|
||||||
|
>
|
||||||
|
<aside
|
||||||
|
class="mx_AuthHeaderLogo"
|
||||||
|
>
|
||||||
|
Matrix
|
||||||
|
</aside>
|
||||||
|
<div
|
||||||
|
class="mx_Dropdown mx_AuthBody_language"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-describedby="mx_LanguageDropdown_value"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-haspopup="listbox"
|
||||||
|
aria-label="Language Dropdown"
|
||||||
|
aria-owns="mx_LanguageDropdown_input"
|
||||||
|
class="mx_AccessibleButton mx_Dropdown_input mx_no_textinput"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_Dropdown_option"
|
||||||
|
id="mx_LanguageDropdown_value"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
English
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="mx_Dropdown_arrow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<main
|
||||||
|
class="mx_AuthBody"
|
||||||
|
>
|
||||||
|
<h1>
|
||||||
|
You're signed out
|
||||||
|
</h1>
|
||||||
|
<h2>
|
||||||
|
Sign in
|
||||||
|
</h2>
|
||||||
|
<div>
|
||||||
|
<form>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="mx_Field mx_Field_input"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="mx_Field_1"
|
||||||
|
label="Password"
|
||||||
|
placeholder="Password"
|
||||||
|
type="password"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
for="mx_Field_1"
|
||||||
|
>
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Forgotten your password?
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<h2>
|
||||||
|
Clear personal data
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Clear all data
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<footer
|
||||||
|
class="mx_AuthFooter"
|
||||||
|
role="contentinfo"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://matrix.org"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
powered by Matrix
|
||||||
|
</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`<MatrixChat /> with an existing session onAction() room actions leave_room for a room should launch a confirmation modal 1`] = `
|
exports[`<MatrixChat /> with an existing session onAction() room actions leave_room for a room should launch a confirmation modal 1`] = `
|
||||||
<div
|
<div
|
||||||
aria-describedby="mx_Dialog_content"
|
aria-describedby="mx_Dialog_content"
|
||||||
|
|
Loading…
Reference in a new issue