mirror of https://github.com/vector-im/riot-web
Do not prompt for a password when doing a „reset all“ after login (#10208)
parent
26652138bd
commit
eb6278df1d
|
@ -226,8 +226,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
private screenAfterLogin?: IScreen;
|
private screenAfterLogin?: IScreen;
|
||||||
private tokenLogin?: boolean;
|
private tokenLogin?: boolean;
|
||||||
private accountPassword?: string;
|
|
||||||
private accountPasswordTimer?: number;
|
|
||||||
private focusComposer: boolean;
|
private focusComposer: boolean;
|
||||||
private subTitleStatus: string;
|
private subTitleStatus: string;
|
||||||
private prevWindowWidth: number;
|
private prevWindowWidth: number;
|
||||||
|
@ -296,9 +294,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
Lifecycle.loadSession();
|
Lifecycle.loadSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.accountPassword = null;
|
|
||||||
this.accountPasswordTimer = null;
|
|
||||||
|
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
|
|
||||||
this.themeWatcher = new ThemeWatcher();
|
this.themeWatcher = new ThemeWatcher();
|
||||||
|
@ -439,7 +434,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.state.resizeNotifier.removeListener("middlePanelResized", this.dispatchTimelineResize);
|
this.state.resizeNotifier.removeListener("middlePanelResized", this.dispatchTimelineResize);
|
||||||
window.removeEventListener("resize", this.onWindowResized);
|
window.removeEventListener("resize", this.onWindowResized);
|
||||||
|
|
||||||
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
|
this.stores.accountPasswordStore.clearPassword();
|
||||||
if (this.voiceBroadcastResumer) this.voiceBroadcastResumer.destroy();
|
if (this.voiceBroadcastResumer) this.voiceBroadcastResumer.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1987,13 +1982,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
* this, as they instead jump straight into the app after `attemptTokenLogin`.
|
* this, as they instead jump straight into the app after `attemptTokenLogin`.
|
||||||
*/
|
*/
|
||||||
private onUserCompletedLoginFlow = async (credentials: IMatrixClientCreds, password: string): Promise<void> => {
|
private onUserCompletedLoginFlow = async (credentials: IMatrixClientCreds, password: string): Promise<void> => {
|
||||||
this.accountPassword = password;
|
this.stores.accountPasswordStore.setPassword(password);
|
||||||
// self-destruct the password after 5mins
|
|
||||||
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
|
|
||||||
this.accountPasswordTimer = window.setTimeout(() => {
|
|
||||||
this.accountPassword = null;
|
|
||||||
this.accountPasswordTimer = null;
|
|
||||||
}, 60 * 5 * 1000);
|
|
||||||
|
|
||||||
// Create and start the client
|
// Create and start the client
|
||||||
await Lifecycle.setLoggedIn(credentials);
|
await Lifecycle.setLoggedIn(credentials);
|
||||||
|
@ -2037,7 +2026,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
view = (
|
view = (
|
||||||
<E2eSetup
|
<E2eSetup
|
||||||
onFinished={this.onCompleteSecurityE2eSetupFinished}
|
onFinished={this.onCompleteSecurityE2eSetupFinished}
|
||||||
accountPassword={this.accountPassword}
|
accountPassword={this.stores.accountPasswordStore.getPassword()}
|
||||||
tokenLogin={!!this.tokenLogin}
|
tokenLogin={!!this.tokenLogin}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -21,6 +21,7 @@ import defaultDispatcher from "../dispatcher/dispatcher";
|
||||||
import LegacyCallHandler from "../LegacyCallHandler";
|
import LegacyCallHandler from "../LegacyCallHandler";
|
||||||
import { PosthogAnalytics } from "../PosthogAnalytics";
|
import { PosthogAnalytics } from "../PosthogAnalytics";
|
||||||
import { SlidingSyncManager } from "../SlidingSyncManager";
|
import { SlidingSyncManager } from "../SlidingSyncManager";
|
||||||
|
import { AccountPasswordStore } from "../stores/AccountPasswordStore";
|
||||||
import { MemberListStore } from "../stores/MemberListStore";
|
import { MemberListStore } from "../stores/MemberListStore";
|
||||||
import { RoomNotificationStateStore } from "../stores/notifications/RoomNotificationStateStore";
|
import { RoomNotificationStateStore } from "../stores/notifications/RoomNotificationStateStore";
|
||||||
import RightPanelStore from "../stores/right-panel/RightPanelStore";
|
import RightPanelStore from "../stores/right-panel/RightPanelStore";
|
||||||
|
@ -73,6 +74,7 @@ export class SdkContextClass {
|
||||||
protected _VoiceBroadcastRecordingsStore?: VoiceBroadcastRecordingsStore;
|
protected _VoiceBroadcastRecordingsStore?: VoiceBroadcastRecordingsStore;
|
||||||
protected _VoiceBroadcastPreRecordingStore?: VoiceBroadcastPreRecordingStore;
|
protected _VoiceBroadcastPreRecordingStore?: VoiceBroadcastPreRecordingStore;
|
||||||
protected _VoiceBroadcastPlaybacksStore?: VoiceBroadcastPlaybacksStore;
|
protected _VoiceBroadcastPlaybacksStore?: VoiceBroadcastPlaybacksStore;
|
||||||
|
protected _AccountPasswordStore?: AccountPasswordStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically construct stores which need to be created eagerly so they can register with
|
* Automatically construct stores which need to be created eagerly so they can register with
|
||||||
|
@ -176,4 +178,11 @@ export class SdkContextClass {
|
||||||
}
|
}
|
||||||
return this._VoiceBroadcastPlaybacksStore;
|
return this._VoiceBroadcastPlaybacksStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get accountPasswordStore(): AccountPasswordStore {
|
||||||
|
if (!this._AccountPasswordStore) {
|
||||||
|
this._AccountPasswordStore = new AccountPasswordStore();
|
||||||
|
}
|
||||||
|
return this._AccountPasswordStore;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const PASSWORD_TIMEOUT = 5 * 60 * 1000; // five minutes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store for the account password.
|
||||||
|
* This password can be used for a short time after login
|
||||||
|
* to avoid requestin the password all the time for instance during e2ee setup.
|
||||||
|
*/
|
||||||
|
export class AccountPasswordStore {
|
||||||
|
private password?: string;
|
||||||
|
private passwordTimeoutId?: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
|
public setPassword(password: string): void {
|
||||||
|
this.password = password;
|
||||||
|
clearTimeout(this.passwordTimeoutId);
|
||||||
|
this.passwordTimeoutId = setTimeout(this.clearPassword, PASSWORD_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPassword(): string | undefined {
|
||||||
|
return this.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearPassword = (): void => {
|
||||||
|
clearTimeout(this.passwordTimeoutId);
|
||||||
|
this.passwordTimeoutId = undefined;
|
||||||
|
this.password = undefined;
|
||||||
|
};
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ import { AccessCancelledError, accessSecretStorage } from "../SecurityManager";
|
||||||
import Modal from "../Modal";
|
import Modal from "../Modal";
|
||||||
import InteractiveAuthDialog from "../components/views/dialogs/InteractiveAuthDialog";
|
import InteractiveAuthDialog from "../components/views/dialogs/InteractiveAuthDialog";
|
||||||
import { _t } from "../languageHandler";
|
import { _t } from "../languageHandler";
|
||||||
|
import { SdkContextClass } from "../contexts/SDKContext";
|
||||||
|
|
||||||
export enum Phase {
|
export enum Phase {
|
||||||
Loading = 0,
|
Loading = 0,
|
||||||
|
@ -224,6 +225,21 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
await cli.bootstrapCrossSigning({
|
await cli.bootstrapCrossSigning({
|
||||||
authUploadDeviceSigningKeys: async (makeRequest): Promise<void> => {
|
authUploadDeviceSigningKeys: async (makeRequest): Promise<void> => {
|
||||||
|
const cachedPassword = SdkContextClass.instance.accountPasswordStore.getPassword();
|
||||||
|
|
||||||
|
if (cachedPassword) {
|
||||||
|
await makeRequest({
|
||||||
|
type: "m.login.password",
|
||||||
|
identifier: {
|
||||||
|
type: "m.id.user",
|
||||||
|
user: cli.getUserId(),
|
||||||
|
},
|
||||||
|
user: cli.getUserId(),
|
||||||
|
password: cachedPassword,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
||||||
title: _t("Setting up keys"),
|
title: _t("Setting up keys"),
|
||||||
matrixClient: cli,
|
matrixClient: cli,
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AccountPasswordStore } from "../../src/stores/AccountPasswordStore";
|
||||||
|
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
describe("AccountPasswordStore", () => {
|
||||||
|
let accountPasswordStore: AccountPasswordStore;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
accountPasswordStore = new AccountPasswordStore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not have a password by default", () => {
|
||||||
|
expect(accountPasswordStore.getPassword()).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when setting a password", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
accountPasswordStore.setPassword("pass1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the password", () => {
|
||||||
|
expect(accountPasswordStore.getPassword()).toBe("pass1");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("and the password timeout exceed", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.advanceTimersToNextTimer();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should clear the password", () => {
|
||||||
|
expect(accountPasswordStore.getPassword()).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("and setting another password", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
accountPasswordStore.setPassword("pass2");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the other password", () => {
|
||||||
|
expect(accountPasswordStore.getPassword()).toBe("pass2");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { mocked, Mocked } from "jest-mock";
|
||||||
|
import { IBootstrapCrossSigningOpts } from "matrix-js-sdk/src/crypto";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import { SdkContextClass } from "../../src/contexts/SDKContext";
|
||||||
|
import { accessSecretStorage } from "../../src/SecurityManager";
|
||||||
|
import { SetupEncryptionStore } from "../../src/stores/SetupEncryptionStore";
|
||||||
|
import { stubClient } from "../test-utils";
|
||||||
|
|
||||||
|
jest.mock("../../src/SecurityManager", () => ({
|
||||||
|
accessSecretStorage: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("SetupEncryptionStore", () => {
|
||||||
|
const cachedPassword = "p4assword";
|
||||||
|
let client: Mocked<MatrixClient>;
|
||||||
|
let setupEncryptionStore: SetupEncryptionStore;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
client = mocked(stubClient());
|
||||||
|
setupEncryptionStore = new SetupEncryptionStore();
|
||||||
|
SdkContextClass.instance.accountPasswordStore.setPassword(cachedPassword);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
SdkContextClass.instance.accountPasswordStore.clearPassword();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resetConfirm should work with a cached account password", async () => {
|
||||||
|
const makeRequest = jest.fn();
|
||||||
|
client.hasSecretStorageKey.mockResolvedValue(true);
|
||||||
|
client.bootstrapCrossSigning.mockImplementation(async (opts: IBootstrapCrossSigningOpts) => {
|
||||||
|
await opts?.authUploadDeviceSigningKeys(makeRequest);
|
||||||
|
});
|
||||||
|
mocked(accessSecretStorage).mockImplementation(async (func: () => Promise<void>) => {
|
||||||
|
await func();
|
||||||
|
});
|
||||||
|
|
||||||
|
await setupEncryptionStore.resetConfirm();
|
||||||
|
|
||||||
|
expect(mocked(accessSecretStorage)).toHaveBeenCalledWith(expect.any(Function), true);
|
||||||
|
expect(makeRequest).toHaveBeenCalledWith({
|
||||||
|
identifier: {
|
||||||
|
type: "m.id.user",
|
||||||
|
user: "@userId:matrix.org",
|
||||||
|
},
|
||||||
|
password: cachedPassword,
|
||||||
|
type: "m.login.password",
|
||||||
|
user: "@userId:matrix.org",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -102,6 +102,8 @@ export function createTestClient(): MatrixClient {
|
||||||
getDevices: jest.fn().mockResolvedValue({ devices: [{ device_id: "ABCDEFGHI" }] }),
|
getDevices: jest.fn().mockResolvedValue({ devices: [{ device_id: "ABCDEFGHI" }] }),
|
||||||
getSessionId: jest.fn().mockReturnValue("iaszphgvfku"),
|
getSessionId: jest.fn().mockReturnValue("iaszphgvfku"),
|
||||||
credentials: { userId: "@userId:matrix.org" },
|
credentials: { userId: "@userId:matrix.org" },
|
||||||
|
bootstrapCrossSigning: jest.fn(),
|
||||||
|
hasSecretStorageKey: jest.fn(),
|
||||||
|
|
||||||
store: {
|
store: {
|
||||||
getPendingEvents: jest.fn().mockResolvedValue([]),
|
getPendingEvents: jest.fn().mockResolvedValue([]),
|
||||||
|
|
Loading…
Reference in New Issue