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

Commit eb6278d

Browse files
authored
Do not prompt for a password when doing a „reset all“ after login (#10208)
1 parent 2665213 commit eb6278d

File tree

7 files changed

+202
-14
lines changed

7 files changed

+202
-14
lines changed

src/components/structures/MatrixChat.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
226226

227227
private screenAfterLogin?: IScreen;
228228
private tokenLogin?: boolean;
229-
private accountPassword?: string;
230-
private accountPasswordTimer?: number;
231229
private focusComposer: boolean;
232230
private subTitleStatus: string;
233231
private prevWindowWidth: number;
@@ -296,9 +294,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
296294
Lifecycle.loadSession();
297295
}
298296

299-
this.accountPassword = null;
300-
this.accountPasswordTimer = null;
301-
302297
this.dispatcherRef = dis.register(this.onAction);
303298

304299
this.themeWatcher = new ThemeWatcher();
@@ -439,7 +434,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
439434
this.state.resizeNotifier.removeListener("middlePanelResized", this.dispatchTimelineResize);
440435
window.removeEventListener("resize", this.onWindowResized);
441436

442-
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
437+
this.stores.accountPasswordStore.clearPassword();
443438
if (this.voiceBroadcastResumer) this.voiceBroadcastResumer.destroy();
444439
}
445440

@@ -1987,13 +1982,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
19871982
* this, as they instead jump straight into the app after `attemptTokenLogin`.
19881983
*/
19891984
private onUserCompletedLoginFlow = async (credentials: IMatrixClientCreds, password: string): Promise<void> => {
1990-
this.accountPassword = password;
1991-
// self-destruct the password after 5mins
1992-
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
1993-
this.accountPasswordTimer = window.setTimeout(() => {
1994-
this.accountPassword = null;
1995-
this.accountPasswordTimer = null;
1996-
}, 60 * 5 * 1000);
1985+
this.stores.accountPasswordStore.setPassword(password);
19971986

19981987
// Create and start the client
19991988
await Lifecycle.setLoggedIn(credentials);
@@ -2037,7 +2026,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
20372026
view = (
20382027
<E2eSetup
20392028
onFinished={this.onCompleteSecurityE2eSetupFinished}
2040-
accountPassword={this.accountPassword}
2029+
accountPassword={this.stores.accountPasswordStore.getPassword()}
20412030
tokenLogin={!!this.tokenLogin}
20422031
/>
20432032
);

src/contexts/SDKContext.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import defaultDispatcher from "../dispatcher/dispatcher";
2121
import LegacyCallHandler from "../LegacyCallHandler";
2222
import { PosthogAnalytics } from "../PosthogAnalytics";
2323
import { SlidingSyncManager } from "../SlidingSyncManager";
24+
import { AccountPasswordStore } from "../stores/AccountPasswordStore";
2425
import { MemberListStore } from "../stores/MemberListStore";
2526
import { RoomNotificationStateStore } from "../stores/notifications/RoomNotificationStateStore";
2627
import RightPanelStore from "../stores/right-panel/RightPanelStore";
@@ -73,6 +74,7 @@ export class SdkContextClass {
7374
protected _VoiceBroadcastRecordingsStore?: VoiceBroadcastRecordingsStore;
7475
protected _VoiceBroadcastPreRecordingStore?: VoiceBroadcastPreRecordingStore;
7576
protected _VoiceBroadcastPlaybacksStore?: VoiceBroadcastPlaybacksStore;
77+
protected _AccountPasswordStore?: AccountPasswordStore;
7678

7779
/**
7880
* Automatically construct stores which need to be created eagerly so they can register with
@@ -176,4 +178,11 @@ export class SdkContextClass {
176178
}
177179
return this._VoiceBroadcastPlaybacksStore;
178180
}
181+
182+
public get accountPasswordStore(): AccountPasswordStore {
183+
if (!this._AccountPasswordStore) {
184+
this._AccountPasswordStore = new AccountPasswordStore();
185+
}
186+
return this._AccountPasswordStore;
187+
}
179188
}

src/stores/AccountPasswordStore.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
const PASSWORD_TIMEOUT = 5 * 60 * 1000; // five minutes
18+
19+
/**
20+
* Store for the account password.
21+
* This password can be used for a short time after login
22+
* to avoid requestin the password all the time for instance during e2ee setup.
23+
*/
24+
export class AccountPasswordStore {
25+
private password?: string;
26+
private passwordTimeoutId?: ReturnType<typeof setTimeout>;
27+
28+
public setPassword(password: string): void {
29+
this.password = password;
30+
clearTimeout(this.passwordTimeoutId);
31+
this.passwordTimeoutId = setTimeout(this.clearPassword, PASSWORD_TIMEOUT);
32+
}
33+
34+
public getPassword(): string | undefined {
35+
return this.password;
36+
}
37+
38+
public clearPassword = (): void => {
39+
clearTimeout(this.passwordTimeoutId);
40+
this.passwordTimeoutId = undefined;
41+
this.password = undefined;
42+
};
43+
}

src/stores/SetupEncryptionStore.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { AccessCancelledError, accessSecretStorage } from "../SecurityManager";
3030
import Modal from "../Modal";
3131
import InteractiveAuthDialog from "../components/views/dialogs/InteractiveAuthDialog";
3232
import { _t } from "../languageHandler";
33+
import { SdkContextClass } from "../contexts/SDKContext";
3334

3435
export enum Phase {
3536
Loading = 0,
@@ -224,6 +225,21 @@ export class SetupEncryptionStore extends EventEmitter {
224225
const cli = MatrixClientPeg.get();
225226
await cli.bootstrapCrossSigning({
226227
authUploadDeviceSigningKeys: async (makeRequest): Promise<void> => {
228+
const cachedPassword = SdkContextClass.instance.accountPasswordStore.getPassword();
229+
230+
if (cachedPassword) {
231+
await makeRequest({
232+
type: "m.login.password",
233+
identifier: {
234+
type: "m.id.user",
235+
user: cli.getUserId(),
236+
},
237+
user: cli.getUserId(),
238+
password: cachedPassword,
239+
});
240+
return;
241+
}
242+
227243
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
228244
title: _t("Setting up keys"),
229245
matrixClient: cli,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { AccountPasswordStore } from "../../src/stores/AccountPasswordStore";
18+
19+
jest.useFakeTimers();
20+
21+
describe("AccountPasswordStore", () => {
22+
let accountPasswordStore: AccountPasswordStore;
23+
24+
beforeEach(() => {
25+
accountPasswordStore = new AccountPasswordStore();
26+
});
27+
28+
it("should not have a password by default", () => {
29+
expect(accountPasswordStore.getPassword()).toBeUndefined();
30+
});
31+
32+
describe("when setting a password", () => {
33+
beforeEach(() => {
34+
accountPasswordStore.setPassword("pass1");
35+
});
36+
37+
it("should return the password", () => {
38+
expect(accountPasswordStore.getPassword()).toBe("pass1");
39+
});
40+
41+
describe("and the password timeout exceed", () => {
42+
beforeEach(() => {
43+
jest.advanceTimersToNextTimer();
44+
});
45+
46+
it("should clear the password", () => {
47+
expect(accountPasswordStore.getPassword()).toBeUndefined();
48+
});
49+
});
50+
51+
describe("and setting another password", () => {
52+
beforeEach(() => {
53+
accountPasswordStore.setPassword("pass2");
54+
});
55+
56+
it("should return the other password", () => {
57+
expect(accountPasswordStore.getPassword()).toBe("pass2");
58+
});
59+
});
60+
});
61+
});
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { mocked, Mocked } from "jest-mock";
18+
import { IBootstrapCrossSigningOpts } from "matrix-js-sdk/src/crypto";
19+
import { MatrixClient } from "matrix-js-sdk/src/matrix";
20+
21+
import { SdkContextClass } from "../../src/contexts/SDKContext";
22+
import { accessSecretStorage } from "../../src/SecurityManager";
23+
import { SetupEncryptionStore } from "../../src/stores/SetupEncryptionStore";
24+
import { stubClient } from "../test-utils";
25+
26+
jest.mock("../../src/SecurityManager", () => ({
27+
accessSecretStorage: jest.fn(),
28+
}));
29+
30+
describe("SetupEncryptionStore", () => {
31+
const cachedPassword = "p4assword";
32+
let client: Mocked<MatrixClient>;
33+
let setupEncryptionStore: SetupEncryptionStore;
34+
35+
beforeEach(() => {
36+
client = mocked(stubClient());
37+
setupEncryptionStore = new SetupEncryptionStore();
38+
SdkContextClass.instance.accountPasswordStore.setPassword(cachedPassword);
39+
});
40+
41+
afterEach(() => {
42+
SdkContextClass.instance.accountPasswordStore.clearPassword();
43+
});
44+
45+
it("resetConfirm should work with a cached account password", async () => {
46+
const makeRequest = jest.fn();
47+
client.hasSecretStorageKey.mockResolvedValue(true);
48+
client.bootstrapCrossSigning.mockImplementation(async (opts: IBootstrapCrossSigningOpts) => {
49+
await opts?.authUploadDeviceSigningKeys(makeRequest);
50+
});
51+
mocked(accessSecretStorage).mockImplementation(async (func: () => Promise<void>) => {
52+
await func();
53+
});
54+
55+
await setupEncryptionStore.resetConfirm();
56+
57+
expect(mocked(accessSecretStorage)).toHaveBeenCalledWith(expect.any(Function), true);
58+
expect(makeRequest).toHaveBeenCalledWith({
59+
identifier: {
60+
type: "m.id.user",
61+
user: "@userId:matrix.org",
62+
},
63+
password: cachedPassword,
64+
type: "m.login.password",
65+
user: "@userId:matrix.org",
66+
});
67+
});
68+
});

test/test-utils/test-utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ export function createTestClient(): MatrixClient {
102102
getDevices: jest.fn().mockResolvedValue({ devices: [{ device_id: "ABCDEFGHI" }] }),
103103
getSessionId: jest.fn().mockReturnValue("iaszphgvfku"),
104104
credentials: { userId: "@userId:matrix.org" },
105+
bootstrapCrossSigning: jest.fn(),
106+
hasSecretStorageKey: jest.fn(),
105107

106108
store: {
107109
getPendingEvents: jest.fn().mockResolvedValue([]),

0 commit comments

Comments
 (0)