element-web/src/stores/SetupEncryptionStore.js
Bruno Windels 4ff847c8a3 put CompleteSecurity state management in store
and split off a child component (SetupCrossSigningBody) that can
be reused from the "Verify this session" toast.
2020-03-24 16:10:15 +01:00

147 lines
5 KiB
JavaScript

/*
Copyright 2020 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 EventEmitter from 'events';
import { MatrixClientPeg } from '../MatrixClientPeg';
import { accessSecretStorage, AccessCancelledError } from '../CrossSigningManager';
export const PHASE_INTRO = 0;
export const PHASE_BUSY = 1;
export const PHASE_DONE = 2; //final done stage, but still showing UX
export const PHASE_CONFIRM_SKIP = 3;
export const PHASE_FINISHED = 4; //UX can be closed
/**
* Holds the active "Complete Security" session
*/
export class SetupEncryptionStore extends EventEmitter {
static sharedInstance() {
if (!global.mx_SetupEncryptionStore) global.mx_SetupEncryptionStore = new SetupEncryptionStore();
return global.mx_SetupEncryptionStore;
}
start() {
if (this._started) {
return;
}
this._started = true;
this.phase = PHASE_INTRO;
this.verificationRequest = null;
this.backupInfo = null;
MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequest);
}
stop() {
if (!this._started) {
return;
}
this._started = false;
if (this.verificationRequest) {
this.verificationRequest.off("change", this.onVerificationRequestChange);
}
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("crypto.verification.request", this.onVerificationRequest);
}
}
async usePassPhrase() {
this.phase = PHASE_BUSY;
this.emit("update");
const cli = MatrixClientPeg.get();
try {
const backupInfo = await cli.getKeyBackupVersion();
this.backupInfo = backupInfo;
this.emit("update");
// The control flow is fairly twisted here...
// For the purposes of completing security, we only wait on getting
// as far as the trust check and then show a green shield.
// We also begin the key backup restore as well, which we're
// awaiting inside `accessSecretStorage` only so that it keeps your
// passphase cached for that work. This dialog itself will only wait
// on the first trust check, and the key backup restore will happen
// in the background.
await new Promise((resolve, reject) => {
try {
accessSecretStorage(async () => {
await cli.checkOwnCrossSigningTrust();
resolve();
if (backupInfo) {
// A complete restore can take many minutes for large
// accounts / slow servers, so we allow the dialog
// to advance before this.
await cli.restoreKeyBackupWithSecretStorage(backupInfo);
}
}).catch(reject);
} catch (e) {
console.error(e);
reject(e);
}
});
if (cli.getCrossSigningId()) {
this.phase = PHASE_DONE;
this.emit("update");
}
} catch (e) {
if (!(e instanceof AccessCancelledError)) {
console.log(e);
}
// this will throw if the user hits cancel, so ignore
this.phase = PHASE_INTRO;
this.emit("update");
}
}
onVerificationRequest = async (request) => {
if (request.otherUserId !== MatrixClientPeg.get().getUserId()) return;
if (this.verificationRequest) {
this.verificationRequest.off("change", this.onVerificationRequestChange);
}
this.verificationRequest = request;
await request.accept();
request.on("change", this.onVerificationRequestChange);
this.emit("update");
}
onVerificationRequestChange = () => {
if (this.verificationRequest.cancelled) {
this.verificationRequest.off("change", this.onVerificationRequestChange);
this.verificationRequest = null;
this.emit("update");
}
}
skip() {
this.phase = PHASE_CONFIRM_SKIP;
this.emit("update");
}
skipConfirm() {
this.phase = PHASE_FINISHED;
this.emit("update");
}
returnAfterSkip() {
this.phase = PHASE_INTRO;
this.emit("update");
}
done() {
this.phase = PHASE_FINISHED;
this.emit("update");
}
}