From 0f7ef6912fc9d8c4fc3d87a1b33df4f78350c91d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 21 Mar 2023 20:56:06 +0530 Subject: [PATCH 01/33] WIP: Add views/view-models --- src/domain/navigation/index.ts | 4 +- src/domain/session/SessionViewModel.js | 25 +++++- .../session/settings/KeyBackupViewModel.ts | 4 + .../DeviceVerificationViewModel.ts | 89 +++++++++++++++++++ .../stages/SelectMethodViewModel.ts | 50 +++++++++++ .../stages/VerificationCancelledViewModel.ts | 38 ++++++++ .../stages/VerificationCompleteViewModel.ts | 35 ++++++++ .../stages/VerifyEmojisViewModel.ts | 42 +++++++++ .../stages/WaitingForOtherUserViewModel.ts | 29 ++++++ .../verification/SAS/SASVerification.ts | 7 ++ .../verification/SAS/channel/Channel.ts | 24 +++-- .../verification/SAS/channel/MockChannel.ts | 1 + .../stages/SelectVerificationMethodStage.ts | 9 ++ .../verification/SAS/stages/SendDoneStage.ts | 3 +- src/matrix/verification/SAS/types.ts | 3 + .../ui/css/themes/element/icons/verified.svg | 2 +- .../web/ui/css/themes/element/theme.css | 88 ++++++++++++++++++ src/platform/web/ui/session/SessionView.js | 3 + .../session/settings/KeyBackupSettingsView.ts | 23 +++-- .../verification/DeviceVerificationView.ts | 57 ++++++++++++ .../verification/stages/SelectMethodView.ts | 62 +++++++++++++ .../stages/VerificationCancelledView.ts | 81 +++++++++++++++++ .../stages/VerificationCompleteView.ts | 45 ++++++++++ .../verification/stages/VerifyEmojisView.ts | 79 ++++++++++++++++ .../stages/WaitingForOtherUserView.ts | 46 ++++++++++ 25 files changed, 831 insertions(+), 18 deletions(-) create mode 100644 src/domain/session/verification/DeviceVerificationViewModel.ts create mode 100644 src/domain/session/verification/stages/SelectMethodViewModel.ts create mode 100644 src/domain/session/verification/stages/VerificationCancelledViewModel.ts create mode 100644 src/domain/session/verification/stages/VerificationCompleteViewModel.ts create mode 100644 src/domain/session/verification/stages/VerifyEmojisViewModel.ts create mode 100644 src/domain/session/verification/stages/WaitingForOtherUserViewModel.ts create mode 100644 src/platform/web/ui/session/verification/DeviceVerificationView.ts create mode 100644 src/platform/web/ui/session/verification/stages/SelectMethodView.ts create mode 100644 src/platform/web/ui/session/verification/stages/VerificationCancelledView.ts create mode 100644 src/platform/web/ui/session/verification/stages/VerificationCompleteView.ts create mode 100644 src/platform/web/ui/session/verification/stages/VerifyEmojisView.ts create mode 100644 src/platform/web/ui/session/verification/stages/WaitingForOtherUserView.ts diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index a2705944..c8f62a7b 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -34,6 +34,8 @@ export type SegmentType = { "details": true; "members": true; "member": string; + "device-verification": true; + "join-room": true; }; export function createNavigation(): Navigation { @@ -51,7 +53,7 @@ function allowsChild(parent: Segment | undefined, child: Segment { + this._updateVerification(verificationOpen); + })); + this._updateVerification(verification.get()); + const lightbox = this.navigation.observe("lightbox"); this.track(lightbox.subscribe(eventId => { this._updateLightbox(eventId); @@ -143,7 +151,8 @@ export class SessionViewModel extends ViewModel { this._gridViewModel || this._settingsViewModel || this._createRoomViewModel || - this._joinRoomViewModel + this._joinRoomViewModel || + this._verificationViewModel ); } @@ -179,6 +188,10 @@ export class SessionViewModel extends ViewModel { return this._joinRoomViewModel; } + get verificationViewModel() { + return this._verificationViewModel; + } + get toastCollectionViewModel() { return this._toastCollectionViewModel; } @@ -327,6 +340,16 @@ export class SessionViewModel extends ViewModel { this.emitChange("activeMiddleViewModel"); } + _updateVerification(verificationOpen) { + if (this._verificationViewModel) { + this._verificationViewModel = this.disposeTracked(this._verificationViewModel); + } + if (verificationOpen) { + this._verificationViewModel = this.track(new DeviceVerificationViewModel(this.childOptions({ session: this._client.session }))); + } + this.emitChange("activeMiddleViewModel"); + } + _updateLightbox(eventId) { if (this._lightboxViewModel) { this._lightboxViewModel = this.disposeTracked(this._lightboxViewModel); diff --git a/src/domain/session/settings/KeyBackupViewModel.ts b/src/domain/session/settings/KeyBackupViewModel.ts index 663d1ea6..43681a29 100644 --- a/src/domain/session/settings/KeyBackupViewModel.ts +++ b/src/domain/session/settings/KeyBackupViewModel.ts @@ -157,6 +157,10 @@ export class KeyBackupViewModel extends ViewModel { } } + navigateToVerification(): void { + this.navigation.push("device-verification", true); + } + get backupWriteStatus(): BackupWriteStatus { const keyBackup = this._keyBackup; if (!keyBackup || keyBackup.version === undefined) { diff --git a/src/domain/session/verification/DeviceVerificationViewModel.ts b/src/domain/session/verification/DeviceVerificationViewModel.ts new file mode 100644 index 00000000..2c471ca5 --- /dev/null +++ b/src/domain/session/verification/DeviceVerificationViewModel.ts @@ -0,0 +1,89 @@ +/* +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 {Options as BaseOptions} from "../../ViewModel"; +import {SegmentType} from "../../navigation/index"; +import {ErrorReportViewModel} from "../../ErrorReportViewModel"; +import {WaitingForOtherUserViewModel} from "./stages/WaitingForOtherUserViewModel"; +import {VerificationCancelledViewModel} from "./stages/VerificationCancelledViewModel"; +import {SelectMethodViewModel} from "./stages/SelectMethodViewModel"; +import {VerifyEmojisViewModel} from "./stages/VerifyEmojisViewModel"; +import {VerificationCompleteViewModel} from "./stages/VerificationCompleteViewModel"; +import type {Session} from "../../../matrix/Session.js"; +import type {SASVerification} from "../../../matrix/verification/SAS/SASVerification"; + +type Options = BaseOptions & { + session: Session; +}; + +export class DeviceVerificationViewModel extends ErrorReportViewModel { + private session: Session; + private sas: SASVerification; + private _currentStageViewModel: any; + + constructor(options: Readonly) { + super(options); + this.session = options.session; + this.createAndStartSasVerification(); + this._currentStageViewModel = this.track( + new WaitingForOtherUserViewModel( + this.childOptions({ sas: this.sas }) + ) + ); + } + + async createAndStartSasVerification(): Promise { + await this.logAndCatch("DeviceVerificationViewModel.createAndStartSasVerification", (log) => { + // todo: can crossSigning be undefined? + const crossSigning = this.session.crossSigning; + // todo: should be called createSasVerification + this.sas = crossSigning.startVerification(this.session.userId, undefined, log); + const emitter = this.sas.eventEmitter; + this.track(emitter.disposableOn("SelectVerificationStage", (stage) => { + this.createViewModelAndEmit( + new SelectMethodViewModel(this.childOptions({ sas: this.sas, stage: stage!, })) + ); + })); + this.track(emitter.disposableOn("EmojiGenerated", (stage) => { + this.createViewModelAndEmit( + new VerifyEmojisViewModel(this.childOptions({ stage: stage!, })) + ); + })); + this.track(emitter.disposableOn("VerificationCancelled", (cancellation) => { + this.createViewModelAndEmit( + new VerificationCancelledViewModel( + this.childOptions({ cancellationCode: cancellation!.code, cancelledByUs: cancellation!.cancelledByUs, }) + )); + })); + this.track(emitter.disposableOn("VerificationCompleted", (deviceId) => { + this.createViewModelAndEmit( + new VerificationCompleteViewModel(this.childOptions({ deviceId: deviceId! })) + ); + })); + return this.sas.start(); + }); + } + + private createViewModelAndEmit(vm) { + this._currentStageViewModel = this.disposeTracked(this._currentStageViewModel); + this._currentStageViewModel = this.track(vm); + this.emitChange("currentStageViewModel"); + } + + get currentStageViewModel() { + return this._currentStageViewModel; + } +} diff --git a/src/domain/session/verification/stages/SelectMethodViewModel.ts b/src/domain/session/verification/stages/SelectMethodViewModel.ts new file mode 100644 index 00000000..681a2e46 --- /dev/null +++ b/src/domain/session/verification/stages/SelectMethodViewModel.ts @@ -0,0 +1,50 @@ +/* +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 {SegmentType} from "../../../navigation/index"; +import {ErrorReportViewModel} from "../../../ErrorReportViewModel"; +import type {Options as BaseOptions} from "../../../ViewModel"; +import type {Session} from "../../../../matrix/Session.js"; +import type {SASVerification} from "../../../../matrix/verification/SAS/SASVerification"; +import type {SelectVerificationMethodStage} from "../../../../matrix/verification/SAS/stages/SelectVerificationMethodStage"; + +type Options = BaseOptions & { + sas: SASVerification; + stage: SelectVerificationMethodStage; + session: Session; +}; + +export class SelectMethodViewModel extends ErrorReportViewModel { + public hasProceeded: boolean = false; + + async proceed() { + await this.logAndCatch("SelectMethodViewModel.proceed", async (log) => { + await this.options.stage.selectEmojiMethod(log); + this.hasProceeded = true; + this.emitChange("hasProceeded"); + }); + } + + async cancel() { + await this.logAndCatch("SelectMethodViewModel.cancel", async () => { + await this.options.sas.abort(); + }); + } + + get deviceName() { + return this.options.stage.otherDeviceName; + } +} diff --git a/src/domain/session/verification/stages/VerificationCancelledViewModel.ts b/src/domain/session/verification/stages/VerificationCancelledViewModel.ts new file mode 100644 index 00000000..ad01d312 --- /dev/null +++ b/src/domain/session/verification/stages/VerificationCancelledViewModel.ts @@ -0,0 +1,38 @@ +/* +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 {ViewModel, Options as BaseOptions} from "../../../ViewModel"; +import {SegmentType} from "../../../navigation/index"; +import {CancelTypes} from "../../../../matrix/verification/SAS/channel/types"; + +type Options = BaseOptions & { + cancellationCode: CancelTypes; + cancelledByUs: boolean; +}; + +export class VerificationCancelledViewModel extends ViewModel { + get cancelCode(): CancelTypes { + return this.options.cancellationCode; + } + + get isCancelledByUs(): boolean { + return this.options.cancelledByUs; + } + + gotoSettings() { + this.navigation.push("settings", true); + } +} diff --git a/src/domain/session/verification/stages/VerificationCompleteViewModel.ts b/src/domain/session/verification/stages/VerificationCompleteViewModel.ts new file mode 100644 index 00000000..a6982eb9 --- /dev/null +++ b/src/domain/session/verification/stages/VerificationCompleteViewModel.ts @@ -0,0 +1,35 @@ +/* +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 {SegmentType} from "../../../navigation/index"; +import {ErrorReportViewModel} from "../../../ErrorReportViewModel"; +import type {Options as BaseOptions} from "../../../ViewModel"; +import type {Session} from "../../../../matrix/Session.js"; + +type Options = BaseOptions & { + deviceId: string; + session: Session; +}; + +export class VerificationCompleteViewModel extends ErrorReportViewModel { + get otherDeviceId(): string { + return this.options.deviceId; + } + + gotoSettings() { + this.navigation.push("settings", true); + } +} diff --git a/src/domain/session/verification/stages/VerifyEmojisViewModel.ts b/src/domain/session/verification/stages/VerifyEmojisViewModel.ts new file mode 100644 index 00000000..14868176 --- /dev/null +++ b/src/domain/session/verification/stages/VerifyEmojisViewModel.ts @@ -0,0 +1,42 @@ +/* +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 {SegmentType} from "../../../navigation/index"; +import {ErrorReportViewModel} from "../../../ErrorReportViewModel"; +import type {Options as BaseOptions} from "../../../ViewModel"; +import type {Session} from "../../../../matrix/Session.js"; +import type {CalculateSASStage} from "../../../../matrix/verification/SAS/stages/CalculateSASStage"; + +type Options = BaseOptions & { + stage: CalculateSASStage; + session: Session; +}; + +export class VerifyEmojisViewModel extends ErrorReportViewModel { + public isWaiting: boolean = false; + + async setEmojiMatch(match: boolean) { + await this.logAndCatch("VerifyEmojisViewModel.setEmojiMatch", async () => { + await this.options.stage.setEmojiMatch(match); + this.isWaiting = true; + this.emitChange("isWaiting"); + }); + } + + get emojis() { + return this.options.stage.emoji; + } +} diff --git a/src/domain/session/verification/stages/WaitingForOtherUserViewModel.ts b/src/domain/session/verification/stages/WaitingForOtherUserViewModel.ts new file mode 100644 index 00000000..408ef884 --- /dev/null +++ b/src/domain/session/verification/stages/WaitingForOtherUserViewModel.ts @@ -0,0 +1,29 @@ +/* +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 {ViewModel, Options as BaseOptions} from "../../../ViewModel"; +import {SegmentType} from "../../../navigation/index"; +import type {SASVerification} from "../../../../matrix/verification/SAS/SASVerification"; + +type Options = BaseOptions & { + sas: SASVerification; +}; + +export class WaitingForOtherUserViewModel extends ViewModel { + async cancel() { + await this.options.sas.abort(); + } +} diff --git a/src/matrix/verification/SAS/SASVerification.ts b/src/matrix/verification/SAS/SASVerification.ts index 5850ba5d..42a7e47d 100644 --- a/src/matrix/verification/SAS/SASVerification.ts +++ b/src/matrix/verification/SAS/SASVerification.ts @@ -84,6 +84,10 @@ export class SASVerification extends EventEmitter { } } + async abort() { + await this.channel.cancelVerification(CancelTypes.UserCancelled); + } + async start() { try { let stage = this.startStage; @@ -98,6 +102,9 @@ export class SASVerification extends EventEmitter { } } finally { + if (this.channel.isCancelled) { + this.eventEmitter.emit("VerificationCancelled", this.channel.cancellation); + } this.olmSas.free(); this.timeout.abort(); this.finished = true; diff --git a/src/matrix/verification/SAS/channel/Channel.ts b/src/matrix/verification/SAS/channel/Channel.ts index 6492a6ae..9763b95c 100644 --- a/src/matrix/verification/SAS/channel/Channel.ts +++ b/src/matrix/verification/SAS/channel/Channel.ts @@ -49,6 +49,8 @@ export interface IChannel { acceptMessage: any; startMessage: any; initiatedByUs: boolean; + isCancelled: boolean; + cancellation: { code: CancelTypes, cancelledByUs: boolean }; id: string; otherUserDeviceId: string; } @@ -78,7 +80,7 @@ export class ToDeviceChannel extends Disposables implements IChannel { public startMessage: any; public id: string; private _initiatedByUs: boolean; - private _isCancelled = false; + private _cancellation: { code: CancelTypes, cancelledByUs: boolean }; /** * @@ -116,8 +118,12 @@ export class ToDeviceChannel extends Disposables implements IChannel { } } + get cancellation() { + return this._cancellation; + }; + get isCancelled(): boolean { - return this._isCancelled; + return !!this._cancellation; } async send(eventType: VerificationEventType, content: any, log: ILogItem): Promise { @@ -198,8 +204,8 @@ export class ToDeviceChannel extends Disposables implements IChannel { this.handleReadyMessage(event, log); return; } - if (event.type === VerificationEventType.Cancel) { - this._isCancelled = true; + if (event.type === VerificationEventTypes.Cancel) { + this._cancellation = { code: event.content.code, cancelledByUs: false }; this.dispose(); return; } @@ -234,7 +240,7 @@ export class ToDeviceChannel extends Disposables implements IChannel { const payload = { messages: { [this.otherUserId]: { - [this.otherUserDeviceId]: { + [this.otherUserDeviceId ?? "*"]: { code: cancellationType, reason: messageFromErrorType[cancellationType], transaction_id: this.id, @@ -242,8 +248,8 @@ export class ToDeviceChannel extends Disposables implements IChannel { } } } - await this.hsApi.sendToDevice(VerificationEventType.Cancel, payload, makeTxnId(), { log }).response(); - this._isCancelled = true; + await this.hsApi.sendToDevice(VerificationEventTypes.Cancel, payload, makeTxnId(), { log }).response(); + this._cancellation = { code: cancellationType, cancelledByUs: true }; this.dispose(); }); } @@ -257,8 +263,8 @@ export class ToDeviceChannel extends Disposables implements IChannel { } } - waitForEvent(eventType: VerificationEventType): Promise { - if (this._isCancelled) { + waitForEvent(eventType: VerificationEventTypes): Promise { + if (this.isCancelled) { throw new VerificationCancelledError(); } // Check if we already received the message diff --git a/src/matrix/verification/SAS/channel/MockChannel.ts b/src/matrix/verification/SAS/channel/MockChannel.ts index 50197ba4..9553a92d 100644 --- a/src/matrix/verification/SAS/channel/MockChannel.ts +++ b/src/matrix/verification/SAS/channel/MockChannel.ts @@ -17,6 +17,7 @@ export class MockChannel implements ITestChannel { public initiatedByUs: boolean; public startMessage: any; public isCancelled: boolean = false; + public cancellation: { code: CancelTypes; cancelledByUs: boolean; }; private olmSas: any; constructor( diff --git a/src/matrix/verification/SAS/stages/SelectVerificationMethodStage.ts b/src/matrix/verification/SAS/stages/SelectVerificationMethodStage.ts index da6099ee..db499d2e 100644 --- a/src/matrix/verification/SAS/stages/SelectVerificationMethodStage.ts +++ b/src/matrix/verification/SAS/stages/SelectVerificationMethodStage.ts @@ -23,9 +23,11 @@ import type {ILogItem} from "../../../../logging/types"; export class SelectVerificationMethodStage extends BaseSASVerificationStage { private hasSentStartMessage = false; private allowSelection = true; + public otherDeviceName: string; async completeStage() { await this.log.wrap("SelectVerificationMethodStage.completeStage", async (log) => { + await this.findDeviceName(log); this.eventEmitter.emit("SelectVerificationStage", this); const startMessage = this.channel.waitForEvent(VerificationEventType.Start); const acceptMessage = this.channel.waitForEvent(VerificationEventType.Accept); @@ -81,6 +83,13 @@ export class SelectVerificationMethodStage extends BaseSASVerificationStage { }); } + private async findDeviceName(log: ILogItem) { + await log.wrap("SelectVerificationMethodStage.findDeviceName", async () => { + const device = await this.options.deviceTracker.deviceForId(this.otherUserId, this.otherUserDeviceId, this.options.hsApi, log); + this.otherDeviceName = device.displayName; + }) + } + async selectEmojiMethod(log: ILogItem) { if (!this.allowSelection) { return; } const content = { diff --git a/src/matrix/verification/SAS/stages/SendDoneStage.ts b/src/matrix/verification/SAS/stages/SendDoneStage.ts index 2d3195b1..53b8a37e 100644 --- a/src/matrix/verification/SAS/stages/SendDoneStage.ts +++ b/src/matrix/verification/SAS/stages/SendDoneStage.ts @@ -19,7 +19,8 @@ import {VerificationEventType} from "../channel/types"; export class SendDoneStage extends BaseSASVerificationStage { async completeStage() { await this.log.wrap("SendDoneStage.completeStage", async (log) => { - await this.channel.send(VerificationEventType.Done, {}, log); + this.eventEmitter.emit("VerificationCompleted", this.otherUserDeviceId); + await this.channel.send(VerificationEventTypes.Done, {}, log); }); } } diff --git a/src/matrix/verification/SAS/types.ts b/src/matrix/verification/SAS/types.ts index d7be6921..3bfd742e 100644 --- a/src/matrix/verification/SAS/types.ts +++ b/src/matrix/verification/SAS/types.ts @@ -13,10 +13,13 @@ 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 {CancelTypes} from "./channel/types"; import {CalculateSASStage} from "./stages/CalculateSASStage"; import {SelectVerificationMethodStage} from "./stages/SelectVerificationMethodStage"; export type SASProgressEvents = { SelectVerificationStage: SelectVerificationMethodStage; EmojiGenerated: CalculateSASStage; + VerificationCompleted: string; + VerificationCancelled: { code: CancelTypes, cancelledByUs: boolean }; } diff --git a/src/platform/web/ui/css/themes/element/icons/verified.svg b/src/platform/web/ui/css/themes/element/icons/verified.svg index 340891f1..d158e607 100644 --- a/src/platform/web/ui/css/themes/element/icons/verified.svg +++ b/src/platform/web/ui/css/themes/element/icons/verified.svg @@ -1,3 +1,3 @@ - \ No newline at end of file + diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index ca64e15a..3f2c19e3 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -1354,3 +1354,91 @@ button.RoomDetailsView_row::after { width: 100px; height: 40px; } + +.VerificationCompleteView, +.DeviceVerificationView, +.SelectMethodView { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} + +.VerificationCompleteView__heading, +.VerifyEmojisView__heading, +.SelectMethodView__heading, +.WaitingForOtherUserView__heading { + display: flex; + align-items: center; + gap: 16px; + flex-wrap: wrap; + justify-content: center; + padding: 8px; +} + +.VerificationCompleteView>*, +.SelectMethodView>*, +.VerifyEmojisView>*, +.WaitingForOtherUserView>* { + padding: 16px; +} + +.VerificationCompleteView__title, +.VerifyEmojisView__title, +.SelectMethodView__title, +.WaitingForOtherUserView__title { + text-align: center; + margin: 0; +} + +.VerificationCancelledView__description, +.VerificationCompleteView__description, +.VerifyEmojisView__description, +.SelectMethodView__description, +.WaitingForOtherUserView__description { + text-align: center; + margin: 0; +} + +.VerificationCancelledView__actions, +.SelectMethodView__actions, +.VerifyEmojisView__actions, +.WaitingForOtherUserView__actions { + display: flex; + justify-content: center; + gap: 12px; + padding: 16px; +} + +.EmojiCollection { + display: flex; + justify-content: center; + gap: 16px; +} + +.EmojiContainer__emoji { + font-size: 3.2rem; +} + +.VerifyEmojisView__waiting, +.EmojiContainer__name, +.EmojiContainer__emoji { + display: flex; + justify-content: center; + align-items: center; +} + +.EmojiContainer__name { + font-weight: bold; +} + +.VerifyEmojisView__waiting { + gap: 12px; +} + +.VerificationCompleteView__icon { + background: url("./icons//verified.svg?primary=accent-color") no-repeat; + background-size: contain; + width: 128px; + height: 128px; +} diff --git a/src/platform/web/ui/session/SessionView.js b/src/platform/web/ui/session/SessionView.js index 9f84e872..8156085c 100644 --- a/src/platform/web/ui/session/SessionView.js +++ b/src/platform/web/ui/session/SessionView.js @@ -30,6 +30,7 @@ import {CreateRoomView} from "./CreateRoomView.js"; import {RightPanelView} from "./rightpanel/RightPanelView.js"; import {viewClassForTile} from "./room/common"; import {JoinRoomView} from "./JoinRoomView"; +import {DeviceVerificationView} from "./verification/DeviceVerificationView"; import {ToastCollectionView} from "./toast/ToastCollectionView"; export class SessionView extends TemplateView { @@ -53,6 +54,8 @@ export class SessionView extends TemplateView { return new CreateRoomView(vm.createRoomViewModel); } else if (vm.joinRoomViewModel) { return new JoinRoomView(vm.joinRoomViewModel); + } else if (vm.verificationViewModel) { + return new DeviceVerificationView(vm.verificationViewModel); } else if (vm.currentRoomViewModel) { if (vm.currentRoomViewModel.kind === "invite") { return new InviteView(vm.currentRoomViewModel); diff --git a/src/platform/web/ui/session/settings/KeyBackupSettingsView.ts b/src/platform/web/ui/session/settings/KeyBackupSettingsView.ts index 668f4f17..7c3d6491 100644 --- a/src/platform/web/ui/session/settings/KeyBackupSettingsView.ts +++ b/src/platform/web/ui/session/settings/KeyBackupSettingsView.ts @@ -62,11 +62,24 @@ export class KeyBackupSettingsView extends TemplateView { return t.p("Cross-signing master key found and trusted.") }), t.if(vm => vm.canSignOwnDevice, t => { - return t.button({ - onClick: disableTargetCallback(async () => { - await vm.signOwnDevice(); - }) - }, "Sign own device"); + return t.div([ + t.button( + { + onClick: disableTargetCallback(async (evt) => { + await vm.signOwnDevice(); + }), + }, + "Sign own device" + ), + t.button( + { + onClick: disableTargetCallback(async () => { + vm.navigateToVerification(); + }), + }, + "Verify by emoji" + ), + ]); }), ]); diff --git a/src/platform/web/ui/session/verification/DeviceVerificationView.ts b/src/platform/web/ui/session/verification/DeviceVerificationView.ts new file mode 100644 index 00000000..ed599031 --- /dev/null +++ b/src/platform/web/ui/session/verification/DeviceVerificationView.ts @@ -0,0 +1,57 @@ +/* +Copyright 2022 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 {TemplateView} from "../../general/TemplateView"; +import {WaitingForOtherUserViewModel} from "../../../../../domain/session/verification/stages/WaitingForOtherUserViewModel"; +import {DeviceVerificationViewModel} from "../../../../../domain/session/verification/DeviceVerificationViewModel"; +import {VerificationCancelledViewModel} from "../../../../../domain/session/verification/stages/VerificationCancelledViewModel"; +import {WaitingForOtherUserView} from "./stages/WaitingForOtherUserView"; +import {VerificationCancelledView} from "./stages/VerificationCancelledView"; +import {SelectMethodViewModel} from "../../../../../domain/session/verification/stages/SelectMethodViewModel"; +import {SelectMethodView} from "./stages/SelectMethodView"; +import {VerifyEmojisViewModel} from "../../../../../domain/session/verification/stages/VerifyEmojisViewModel"; +import {VerifyEmojisView} from "./stages/VerifyEmojisView"; +import {VerificationCompleteViewModel} from "../../../../../domain/session/verification/stages/VerificationCompleteViewModel"; +import {VerificationCompleteView} from "./stages/VerificationCompleteView"; + +export class DeviceVerificationView extends TemplateView { + render(t, vm) { + return t.div({ + className: { + "middle": true, + "DeviceVerificationView": true, + } + }, [ + t.mapView(vm => vm.currentStageViewModel, (stageVm) => { + if (stageVm instanceof WaitingForOtherUserViewModel) { + return new WaitingForOtherUserView(stageVm); + } + else if (stageVm instanceof VerificationCancelledViewModel) { + return new VerificationCancelledView(stageVm); + } + else if (stageVm instanceof SelectMethodViewModel) { + return new SelectMethodView(stageVm); + } + else if (stageVm instanceof VerifyEmojisViewModel) { + return new VerifyEmojisView(stageVm); + } + else if (stageVm instanceof VerificationCompleteViewModel) { + return new VerificationCompleteView(stageVm); + } + }) + ]) + } +} diff --git a/src/platform/web/ui/session/verification/stages/SelectMethodView.ts b/src/platform/web/ui/session/verification/stages/SelectMethodView.ts new file mode 100644 index 00000000..9e665f31 --- /dev/null +++ b/src/platform/web/ui/session/verification/stages/SelectMethodView.ts @@ -0,0 +1,62 @@ +/* +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 {TemplateView} from "../../../general/TemplateView"; +import {spinner} from "../../../common.js" +import type {SelectMethodViewModel} from "../../../../../../domain/session/verification/stages/SelectMethodViewModel"; + +export class SelectMethodView extends TemplateView { + render(t) { + return t.div({ className: "SelectMethodView" }, [ + t.map(vm => vm.hasProceeded, (hasProceeded, t, vm) => { + if (hasProceeded) { + return spinner(t); + } + else return t.div([ + t.div({ className: "SelectMethodView__heading" }, [ + t.h2( { className: "SelectMethodView__title" }, vm.i18n`Verify device '${vm.deviceName}' by comparing emojis?`), + ]), + t.p({ className: "SelectMethodView__description" }, + vm.i18n`You are about to verify your other device by comparing emojis.` + ), + t.div({ className: "SelectMethodView__actions" }, [ + t.button( + { + className: { + "button-action": true, + primary: true, + destructive: true, + }, + onclick: () => vm.cancel(), + }, + "Cancel" + ), + t.button( + { + className: { + "button-action": true, + primary: true, + }, + onclick: () => vm.proceed(), + }, + "Proceed" + ), + ]), + ]); + }), + ]); + } +} diff --git a/src/platform/web/ui/session/verification/stages/VerificationCancelledView.ts b/src/platform/web/ui/session/verification/stages/VerificationCancelledView.ts new file mode 100644 index 00000000..d2afa98d --- /dev/null +++ b/src/platform/web/ui/session/verification/stages/VerificationCancelledView.ts @@ -0,0 +1,81 @@ +/* +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 {TemplateView} from "../../../general/TemplateView"; +import {VerificationCancelledViewModel} from "../../../../../../domain/session/verification/stages/VerificationCancelledViewModel"; +import {CancelTypes} from "../../../../../../matrix/verification/SAS/channel/types"; + +export class VerificationCancelledView extends TemplateView { + render(t, vm: VerificationCancelledViewModel) { + const headerTextStart = vm.isCancelledByUs ? "You" : "The other device"; + + return t.div( + { + className: "VerificationCancelledView", + }, + [ + t.h2( + { className: "VerificationCancelledView__title" }, + vm.i18n`${headerTextStart} cancelled the verification!` + ), + t.p( + { className: "VerificationCancelledView__description" }, + vm.i18n`${this.getDescriptionFromCancellationCode(vm.cancelCode, vm.isCancelledByUs)}` + ), + t.div({ className: "VerificationCancelledView__actions" }, [ + t.button({ + className: { + "button-action": true, + "primary": true, + }, + onclick: () => vm.gotoSettings(), + }, "Got it") + ]), + ] + ); + } + + getDescriptionFromCancellationCode(code: CancelTypes, isCancelledByUs: boolean): string { + const descriptionsWhenWeCancelled = { + // [CancelTypes.UserCancelled]: NO_NEED_FOR_DESCRIPTION_HERE + [CancelTypes.InvalidMessage]: "You other device sent an invalid message.", + [CancelTypes.KeyMismatch]: "The key could not be verified.", + // [CancelTypes.OtherDeviceAccepted]: "Another device has accepted this request.", + [CancelTypes.TimedOut]: "The verification process timed out.", + [CancelTypes.UnexpectedMessage]: "Your other device sent an unexpected message.", + [CancelTypes.UnknownMethod]: "Your other device is using an unknown method for verification.", + [CancelTypes.UnknownTransaction]: "Your other device sent a message with an unknown transaction id.", + [CancelTypes.UserMismatch]: "The expected user did not match the user verified.", + [CancelTypes.MismatchedCommitment]: "The hash commitment does not match.", + [CancelTypes.MismatchedSAS]: "The emoji/decimal did not match.", + } + const descriptionsWhenTheyCancelled = { + [CancelTypes.UserCancelled]: "Your other device cancelled the verification!", + [CancelTypes.InvalidMessage]: "Invalid message sent to the other device.", + [CancelTypes.KeyMismatch]: "The other device could not verify our keys", + // [CancelTypes.OtherDeviceAccepted]: "Another device has accepted this request.", + [CancelTypes.TimedOut]: "The verification process timed out.", + [CancelTypes.UnexpectedMessage]: "Unexpected message sent to the other device.", + [CancelTypes.UnknownMethod]: "Your other device does not understand the method you chose", + [CancelTypes.UnknownTransaction]: "Your other device rejected our message.", + [CancelTypes.UserMismatch]: "The expected user did not match the user verified.", + [CancelTypes.MismatchedCommitment]: "Your other device was not able to verify the hash commitment", + [CancelTypes.MismatchedSAS]: "The emoji/decimal did not match.", + } + const map = isCancelledByUs ? descriptionsWhenWeCancelled : descriptionsWhenTheyCancelled; + return map[code] ?? ""; + } +} diff --git a/src/platform/web/ui/session/verification/stages/VerificationCompleteView.ts b/src/platform/web/ui/session/verification/stages/VerificationCompleteView.ts new file mode 100644 index 00000000..7edc4e40 --- /dev/null +++ b/src/platform/web/ui/session/verification/stages/VerificationCompleteView.ts @@ -0,0 +1,45 @@ +/* +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 {TemplateView} from "../../../general/TemplateView"; +import type {VerificationCompleteViewModel} from "../../../../../../domain/session/verification/stages/VerificationCompleteViewModel"; + +export class VerificationCompleteView extends TemplateView { + render(t, vm: VerificationCompleteViewModel) { + return t.div({ className: "VerificationCompleteView" }, [ + t.div({className: "VerificationCompleteView__icon"}), + t.div({ className: "VerificationCompleteView__heading" }, [ + t.h2( + { className: "VerificationCompleteView__title" }, + vm.i18n`Verification completed successfully!` + ), + ]), + t.p( + { className: "VerificationCompleteView__description" }, + vm.i18n`You successfully verified device ${vm.otherDeviceId}` + ), + t.div({ className: "VerificationCompleteView__actions" }, [ + t.button({ + className: { + "button-action": true, + "primary": true, + }, + onclick: () => vm.gotoSettings(), + }, "Got it") + ]), + ]); + } +} diff --git a/src/platform/web/ui/session/verification/stages/VerifyEmojisView.ts b/src/platform/web/ui/session/verification/stages/VerifyEmojisView.ts new file mode 100644 index 00000000..9f7b312b --- /dev/null +++ b/src/platform/web/ui/session/verification/stages/VerifyEmojisView.ts @@ -0,0 +1,79 @@ +/* +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 {TemplateView} from "../../../general/TemplateView"; +import {spinner} from "../../../common.js" +import type {VerifyEmojisViewModel} from "../../../../../../domain/session/verification/stages/VerifyEmojisViewModel"; + +export class VerifyEmojisView extends TemplateView { + render(t, vm: VerifyEmojisViewModel) { + const emojiList = vm.emojis.reduce((acc, [emoji, name]) => { + const e = t.div({ className: "EmojiContainer" }, [ + t.div({ className: "EmojiContainer__emoji" }, emoji), + t.div({ className: "EmojiContainer__name" }, name), + ]); + acc.push(e); + return acc; + }, [] as any); + const emojiCollection = t.div({ className: "EmojiCollection" }, emojiList); + return t.div({ className: "VerifyEmojisView" }, [ + t.div({ className: "VerifyEmojisView__heading" }, [ + t.h2( + { className: "VerifyEmojisView__title" }, + vm.i18n`Do the emojis match?` + ), + ]), + t.p( + { className: "VerifyEmojisView__description" }, + vm.i18n`Confirm the emoji below are displayed on both devices, in the same order:` + ), + t.div({ className: "VerifyEmojisView__emojis" }, emojiCollection), + t.map(vm => vm.isWaiting, (isWaiting, t, vm) => { + if (isWaiting) { + return t.div({ className: "VerifyEmojisView__waiting" }, [ + spinner(t), + t.span(vm.i18n`Waiting for you to verify on your other device`), + ]); + } + else { + return t.div({ className: "VerifyEmojisView__actions" }, [ + t.button( + { + className: { + "button-action": true, + primary: true, + destructive: true, + }, + onclick: () => vm.setEmojiMatch(false), + }, + vm.i18n`They don't match` + ), + t.button( + { + className: { + "button-action": true, + primary: true, + }, + onclick: () => vm.setEmojiMatch(true), + }, + vm.i18n`They match` + ), + ]); + } + }) + ]); + } +} diff --git a/src/platform/web/ui/session/verification/stages/WaitingForOtherUserView.ts b/src/platform/web/ui/session/verification/stages/WaitingForOtherUserView.ts new file mode 100644 index 00000000..007b258e --- /dev/null +++ b/src/platform/web/ui/session/verification/stages/WaitingForOtherUserView.ts @@ -0,0 +1,46 @@ +/* +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 {TemplateView} from "../../../general/TemplateView"; +import {spinner} from "../../../common.js"; +import {WaitingForOtherUserViewModel} from "../../../../../../domain/session/verification/stages/WaitingForOtherUserViewModel"; + +export class WaitingForOtherUserView extends TemplateView { + render(t, vm) { + return t.div({ className: "WaitingForOtherUserView" }, [ + t.div({ className: "WaitingForOtherUserView__heading" }, [ + spinner(t), + t.h2( + { className: "WaitingForOtherUserView__title" }, + vm.i18n`Waiting for any of your device to accept the verification request` + ), + ]), + t.p({ className: "WaitingForOtherUserView__description" }, + vm.i18n`Accept the request from the device you wish to verify!` + ), + t.div({ className: "WaitingForOtherUserView__actions" }, + t.button({ + className: { + "button-action": true, + "primary": true, + "destructive": true, + }, + onclick: () => vm.cancel(), + }, "Cancel") + ), + ]); + } +} From 4c6a240e74278f9513a7b0477b19b3bd692dbfdd Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 24 Mar 2023 13:54:45 +0530 Subject: [PATCH 02/33] WIP: Toast notification --- .../session/toast/ToastCollectionViewModel.ts | 4 +- .../VerificationToastCollectionViewModel.ts | 59 ++++++++++++++ .../VerificationToastNotificationViewModel.ts | 53 +++++++++++++ .../DeviceVerificationViewModel.ts | 79 ++++++++++++------- src/matrix/verification/CrossSigning.ts | 10 ++- .../verification/SAS/SASVerification.ts | 11 +++ .../web/ui/css/themes/element/theme.css | 46 ++++++++++- .../ui/session/toast/ToastCollectionView.ts | 4 + .../VerificationToastNotificationView.ts | 45 +++++++++++ 9 files changed, 277 insertions(+), 34 deletions(-) create mode 100644 src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts create mode 100644 src/domain/session/toast/verification/VerificationToastNotificationViewModel.ts create mode 100644 src/platform/web/ui/session/toast/VerificationToastNotificationView.ts diff --git a/src/domain/session/toast/ToastCollectionViewModel.ts b/src/domain/session/toast/ToastCollectionViewModel.ts index 44a75144..60dfe2e0 100644 --- a/src/domain/session/toast/ToastCollectionViewModel.ts +++ b/src/domain/session/toast/ToastCollectionViewModel.ts @@ -17,6 +17,7 @@ limitations under the License. import {ConcatList} from "../../../observable"; import {ViewModel, Options as BaseOptions} from "../../ViewModel"; import {CallToastCollectionViewModel} from "./calls/CallsToastCollectionViewModel"; +import {VerificationToastCollectionViewModel} from "./verification/VerificationToastCollectionViewModel"; import type {Session} from "../../../matrix/Session.js"; import type {SegmentType} from "../../navigation"; import type {BaseToastNotificationViewModel} from "./BaseToastNotificationViewModel"; @@ -31,8 +32,9 @@ export class ToastCollectionViewModel extends ViewModel { constructor(options: Options) { super(options); const session = this.getOption("session"); - const vms = [ + const vms: any = [ this.track(new CallToastCollectionViewModel(this.childOptions({ session }))), + this.track(new VerificationToastCollectionViewModel(this.childOptions({session}))), ].map(vm => vm.toastViewModels); this.toastViewModels = new ConcatList(...vms); } diff --git a/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts b/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts new file mode 100644 index 00000000..0a0f6619 --- /dev/null +++ b/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts @@ -0,0 +1,59 @@ + +/* +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 {VerificationToastNotificationViewModel} from "./VerificationToastNotificationViewModel"; +import {ObservableArray} from "../../../../observable"; +import {ViewModel, Options as BaseOptions} from "../../../ViewModel"; +import type {Session} from "../../../../matrix/Session.js"; +import type {SegmentType} from "../../../navigation"; +import type {IToastCollection} from "../IToastCollection"; +import { SASVerification } from "../../../../matrix/verification/SAS/SASVerification"; + +type Options = { + session: Session; +} & BaseOptions; + + + +export class VerificationToastCollectionViewModel extends ViewModel implements IToastCollection { + public readonly toastViewModels: ObservableArray = new ObservableArray(); + + constructor(options: Options) { + super(options); + this.observeSASRequests(); + } + + async observeSASRequests() { + const session = this.getOption("session"); + if (this.features.crossSigning) { + // todo: hack; remove + await new Promise(r => setTimeout(r, 3000)); + const sasObservable = session.crossSigning.receivedSASVerification; + this.track(sasObservable.subscribe((sas) => this.createToast(sas))); + } + } + + private createToast(sas: SASVerification) { + const dismiss = () => { + const idx = this.toastViewModels.array.findIndex(vm => vm.sas === sas); + if (idx !== -1) { + this.toastViewModels.remove(idx); + } + }; + this.toastViewModels.append(new VerificationToastNotificationViewModel(this.childOptions({ sas, dismiss }))); + } +} diff --git a/src/domain/session/toast/verification/VerificationToastNotificationViewModel.ts b/src/domain/session/toast/verification/VerificationToastNotificationViewModel.ts new file mode 100644 index 00000000..9b1770fb --- /dev/null +++ b/src/domain/session/toast/verification/VerificationToastNotificationViewModel.ts @@ -0,0 +1,53 @@ +/* +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 {BaseClassOptions, BaseToastNotificationViewModel} from ".././BaseToastNotificationViewModel"; +import {SegmentType} from "../../../navigation"; +import type {SASVerification} from "../../../../matrix/verification/SAS/SASVerification"; + +type Options = { + sas: SASVerification; +} & BaseClassOptions; + +type MinimumNeededSegmentType = { + "device-verification": true; +}; + +export class VerificationToastNotificationViewModel = Options> extends BaseToastNotificationViewModel { + constructor(options: O) { + super(options); + } + + get kind(): "verification" { + return "verification"; + } + + get sas(): SASVerification { + return this.getOption("sas"); + } + + get otherDeviceId(): string { + return this.sas.otherDeviceId; + } + + accept() { + // @ts-ignore + this.navigation.push("device-verification", true); + this.dismiss(); + } + +} + + diff --git a/src/domain/session/verification/DeviceVerificationViewModel.ts b/src/domain/session/verification/DeviceVerificationViewModel.ts index 2c471ca5..f8cff30e 100644 --- a/src/domain/session/verification/DeviceVerificationViewModel.ts +++ b/src/domain/session/verification/DeviceVerificationViewModel.ts @@ -27,6 +27,7 @@ import type {SASVerification} from "../../../matrix/verification/SAS/SASVerifica type Options = BaseOptions & { session: Session; + sas: SASVerification; }; export class DeviceVerificationViewModel extends ErrorReportViewModel { @@ -37,46 +38,66 @@ export class DeviceVerificationViewModel extends ErrorReportViewModel) { super(options); this.session = options.session; - this.createAndStartSasVerification(); - this._currentStageViewModel = this.track( - new WaitingForOtherUserViewModel( - this.childOptions({ sas: this.sas }) - ) - ); + const existingSas = this.session.crossSigning.receivedSASVerification.get(); + if (existingSas) { + // SAS already created from request + this.startWithExistingSAS(existingSas); + } + else { + // We are about to send the request + this.createAndStartSasVerification(); + this._currentStageViewModel = this.track( + new WaitingForOtherUserViewModel( + this.childOptions({ sas: this.sas }) + ) + ); + } + } + + private async startWithExistingSAS(sas: SASVerification) { + await this.logAndCatch("DeviceVerificationViewModel.startWithExistingSAS", (log) => { + this.sas = sas; + this.hookToEvents(); + return this.sas.start(); + }); } - async createAndStartSasVerification(): Promise { + private async createAndStartSasVerification(): Promise { await this.logAndCatch("DeviceVerificationViewModel.createAndStartSasVerification", (log) => { // todo: can crossSigning be undefined? const crossSigning = this.session.crossSigning; // todo: should be called createSasVerification this.sas = crossSigning.startVerification(this.session.userId, undefined, log); - const emitter = this.sas.eventEmitter; - this.track(emitter.disposableOn("SelectVerificationStage", (stage) => { - this.createViewModelAndEmit( - new SelectMethodViewModel(this.childOptions({ sas: this.sas, stage: stage!, })) - ); - })); - this.track(emitter.disposableOn("EmojiGenerated", (stage) => { - this.createViewModelAndEmit( - new VerifyEmojisViewModel(this.childOptions({ stage: stage!, })) - ); - })); - this.track(emitter.disposableOn("VerificationCancelled", (cancellation) => { - this.createViewModelAndEmit( - new VerificationCancelledViewModel( - this.childOptions({ cancellationCode: cancellation!.code, cancelledByUs: cancellation!.cancelledByUs, }) - )); - })); - this.track(emitter.disposableOn("VerificationCompleted", (deviceId) => { - this.createViewModelAndEmit( - new VerificationCompleteViewModel(this.childOptions({ deviceId: deviceId! })) - ); - })); + this.hookToEvents(); return this.sas.start(); }); } + private hookToEvents() { + const emitter = this.sas.eventEmitter; + this.track(emitter.disposableOn("SelectVerificationStage", (stage) => { + this.createViewModelAndEmit( + new SelectMethodViewModel(this.childOptions({ sas: this.sas, stage: stage!, })) + ); + })); + this.track(emitter.disposableOn("EmojiGenerated", (stage) => { + this.createViewModelAndEmit( + new VerifyEmojisViewModel(this.childOptions({ stage: stage!, })) + ); + })); + this.track(emitter.disposableOn("VerificationCancelled", (cancellation) => { + this.createViewModelAndEmit( + new VerificationCancelledViewModel( + this.childOptions({ cancellationCode: cancellation!.code, cancelledByUs: cancellation!.cancelledByUs, }) + )); + })); + this.track(emitter.disposableOn("VerificationCompleted", (deviceId) => { + this.createViewModelAndEmit( + new VerificationCompleteViewModel(this.childOptions({ deviceId: deviceId! })) + ); + })); + } + private createViewModelAndEmit(vm) { this._currentStageViewModel = this.disposeTracked(this._currentStageViewModel); this._currentStageViewModel = this.track(vm); diff --git a/src/matrix/verification/CrossSigning.ts b/src/matrix/verification/CrossSigning.ts index ba90bc3b..5d4ffef4 100644 --- a/src/matrix/verification/CrossSigning.ts +++ b/src/matrix/verification/CrossSigning.ts @@ -19,6 +19,7 @@ import {pkSign} from "./common"; import {SASVerification} from "./SAS/SASVerification"; import {ToDeviceChannel} from "./SAS/channel/Channel"; import {VerificationEventType} from "./SAS/channel/types"; +import {ObservableValue} from "../../observable/value/ObservableValue"; import type {SecretStorage} from "../ssss/SecretStorage"; import type {Storage} from "../storage/idb/Storage"; import type {Platform} from "../../platform/web/Platform"; @@ -29,6 +30,7 @@ import type {ILogItem} from "../../logging/types"; import type {DeviceMessageHandler} from "../DeviceMessageHandler.js"; import type {SignedValue, DeviceKey} from "../e2ee/common"; import type * as OlmNamespace from "@matrix-org/olm"; + type Olm = typeof OlmNamespace; // we store cross-signing (and device) keys in the format we get them from the server @@ -88,6 +90,7 @@ export class CrossSigning { private _isMasterKeyTrusted: boolean = false; private readonly deviceId: string; private sasVerificationInProgress?: SASVerification; + public receivedSASVerification: ObservableValue = new ObservableValue(undefined); constructor(options: { storage: Storage, @@ -125,9 +128,10 @@ export class CrossSigning { } if (unencryptedEvent.type === VerificationEventType.Request || unencryptedEvent.type === VerificationEventType.Start) { - await this.platform.logger.run("Start verification from request", async (log) => { - const sas = this.startVerification(unencryptedEvent.sender, unencryptedEvent, log); - await sas?.start(); + this.platform.logger.run("Start verification from request", (log) => { + //todo: We can have more than one sas requests + this.sasVerificationInProgress = this.startVerification(unencryptedEvent.sender, unencryptedEvent, log); + this.receivedSASVerification.set(this.sasVerificationInProgress!); }); } }) diff --git a/src/matrix/verification/SAS/SASVerification.ts b/src/matrix/verification/SAS/SASVerification.ts index 42a7e47d..41a42b45 100644 --- a/src/matrix/verification/SAS/SASVerification.ts +++ b/src/matrix/verification/SAS/SASVerification.ts @@ -110,6 +110,17 @@ export class SASVerification extends EventEmitter { this.finished = true; } } + + get otherDeviceId() { + return this.channel?.otherUserDeviceId; + } + + /** + * Returns true if we were created because a "request" message was received + */ + get isStartingWithRequestMessage(): boolean { + return this.startStage instanceof SendReadyStage; + } } import {HomeServer} from "../../../mocks/HomeServer.js"; diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index 3f2c19e3..31e62754 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -1264,27 +1264,52 @@ button.RoomDetailsView_row::after { padding: 0; } +.VerificationToastNotificationView:not(:first-child), .CallToastNotificationView:not(:first-child) { margin-top: 12px; } +.VerificationToastNotificationView { + display: flex; + flex-direction: column; +} + .CallToastNotificationView { display: grid; grid-template-rows: 40px 1fr 1fr 48px; row-gap: 4px; - width: 260px; +} + + +.VerificationToastNotificationView, +.CallToastNotificationView { background-color: var(--background-color-secondary); border-radius: 8px; color: var(--text-color); box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.5); } +.CallToastNotificationView { + width: 260px; +} + +.VerificationToastNotificationView { + width: 248px; +} + +.VerificationToastNotificationView__top { + padding: 8px; + display: flex; +} + .CallToastNotificationView__top { display: grid; grid-template-columns: auto 176px auto; align-items: center; justify-items: center; } + +.VerificationToastNotificationView__dismiss-btn, .CallToastNotificationView__dismiss-btn { background: center var(--background-color-secondary--darker-5) url("./icons/dismiss.svg?primary=text-color") no-repeat; border-radius: 100%; @@ -1292,11 +1317,16 @@ button.RoomDetailsView_row::after { width: 15px; } +.VerificationToastNotificationView__title, .CallToastNotificationView__name { font-weight: 600; width: 100%; } +.VerificationToastNotificationView__description { + padding: 8px; +} + .CallToastNotificationView__description { margin-left: 42px; } @@ -1350,11 +1380,25 @@ button.RoomDetailsView_row::after { margin-right: 10px; } +.VerificationToastNotificationView__action { + display: flex; + justify-content: space-between; + padding: 8px; +} + .CallToastNotificationView__action .button-action { width: 100px; height: 40px; } +.VerificationToastNotificationView__action .button-action { + width: 100px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; +} + .VerificationCompleteView, .DeviceVerificationView, .SelectMethodView { diff --git a/src/platform/web/ui/session/toast/ToastCollectionView.ts b/src/platform/web/ui/session/toast/ToastCollectionView.ts index a3d734d2..cf4fd58f 100644 --- a/src/platform/web/ui/session/toast/ToastCollectionView.ts +++ b/src/platform/web/ui/session/toast/ToastCollectionView.ts @@ -15,17 +15,21 @@ limitations under the License. */ import {CallToastNotificationView} from "./CallToastNotificationView"; +import {VerificationToastNotificationView} from "./VerificationToastNotificationView"; import {ListView} from "../../general/ListView"; import {TemplateView, Builder} from "../../general/TemplateView"; import type {IView} from "../../general/types"; import type {CallToastNotificationViewModel} from "../../../../../domain/session/toast/calls/CallToastNotificationViewModel"; import type {ToastCollectionViewModel} from "../../../../../domain/session/toast/ToastCollectionViewModel"; import type {BaseToastNotificationViewModel} from "../../../../../domain/session/toast/BaseToastNotificationViewModel"; +import type {VerificationToastNotificationViewModel} from "../../../../../domain/session/toast/verification/VerificationToastNotificationViewModel"; function toastViewModelToView(vm: BaseToastNotificationViewModel): IView { switch (vm.kind) { case "calls": return new CallToastNotificationView(vm as CallToastNotificationViewModel); + case "verification": + return new VerificationToastNotificationView(vm as VerificationToastNotificationViewModel); default: throw new Error(`Cannot find view class for notification kind ${vm.kind}`); } diff --git a/src/platform/web/ui/session/toast/VerificationToastNotificationView.ts b/src/platform/web/ui/session/toast/VerificationToastNotificationView.ts new file mode 100644 index 00000000..691dc30e --- /dev/null +++ b/src/platform/web/ui/session/toast/VerificationToastNotificationView.ts @@ -0,0 +1,45 @@ +/* +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 {TemplateView, Builder} from "../../general/TemplateView"; +import type {VerificationToastNotificationViewModel} from "../../../../../domain/session/toast/verification/VerificationToastNotificationViewModel"; + +export class VerificationToastNotificationView extends TemplateView { + render(t: Builder, vm: VerificationToastNotificationViewModel) { + return t.div({ className: "VerificationToastNotificationView" }, [ + t.div({ className: "VerificationToastNotificationView__top" }, [ + t.span({ className: "VerificationToastNotificationView__title" }, + vm.i18n`Device Verification`), + t.button({ + className: "button-action VerificationToastNotificationView__dismiss-btn", + onClick: () => vm.dismiss(), + }), + ]), + t.div({ className: "VerificationToastNotificationView__description" }, [ + t.span(vm.i18n`Do you want to verify device ${vm.otherDeviceId}?`), + ]), + t.div({ className: "VerificationToastNotificationView__action" }, [ + t.button({ + className: "button-action primary destructive", + onClick: () => vm.dismiss(), + }, vm.i18n`Ignore`), + t.button({ + className: "button-action primary", + onClick: () => vm.accept(), + }, vm.i18n`Accept`), + ]), + ]); + } +} From 90ce3f5d86295183a51e299093e82ad811b4fa9d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 24 Mar 2023 15:26:05 +0530 Subject: [PATCH 03/33] Remove toast when receiving cancel --- .../VerificationToastCollectionViewModel.ts | 11 ++++++++++- src/matrix/verification/CrossSigning.ts | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts b/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts index 0a0f6619..fab17a20 100644 --- a/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts +++ b/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts @@ -43,7 +43,16 @@ export class VerificationToastCollectionViewModel extends ViewModel setTimeout(r, 3000)); const sasObservable = session.crossSigning.receivedSASVerification; - this.track(sasObservable.subscribe((sas) => this.createToast(sas))); + this.track( + sasObservable.subscribe((sas) => { + if (sas) { + this.createToast(sas); + } + else { + this.toastViewModels.remove(0); + } + }) + ); } } diff --git a/src/matrix/verification/CrossSigning.ts b/src/matrix/verification/CrossSigning.ts index 5d4ffef4..d355a81a 100644 --- a/src/matrix/verification/CrossSigning.ts +++ b/src/matrix/verification/CrossSigning.ts @@ -118,6 +118,10 @@ export class CrossSigning { this.deviceMessageHandler = options.deviceMessageHandler; this.deviceMessageHandler.on("message", async ({ unencrypted: unencryptedEvent }) => { + if (unencryptedEvent.type === VerificationEventTypes.Cancel && + this.sasVerificationInProgress?.channel.id === unencryptedEvent.content.transaction_id) { + this.receivedSASVerification.set(undefined); + } if (this.sasVerificationInProgress && ( !this.sasVerificationInProgress.finished || From 4aa86c6dd2c347cc0c6e988ef805b91b3afd41c8 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 24 Mar 2023 17:47:20 +0530 Subject: [PATCH 04/33] Support multiple requests --- src/domain/navigation/index.ts | 2 +- src/domain/session/SessionViewModel.js | 11 ++--- .../VerificationToastCollectionViewModel.ts | 43 +++++++++++-------- .../VerificationToastNotificationViewModel.ts | 14 +++--- .../DeviceVerificationViewModel.ts | 17 ++++---- src/matrix/verification/CrossSigning.ts | 30 +++++++------ src/matrix/verification/SAS/SASRequest.ts | 31 +++++++++++++ 7 files changed, 97 insertions(+), 51 deletions(-) create mode 100644 src/matrix/verification/SAS/SASRequest.ts diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index c8f62a7b..5904e715 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -34,7 +34,7 @@ export type SegmentType = { "details": true; "members": true; "member": string; - "device-verification": true; + "device-verification": string; "join-room": true; }; diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js index b952b329..e0e77729 100644 --- a/src/domain/session/SessionViewModel.js +++ b/src/domain/session/SessionViewModel.js @@ -98,8 +98,8 @@ export class SessionViewModel extends ViewModel { this._updateJoinRoom(joinRoom.get()); const verification = this.navigation.observe("device-verification"); - this.track(verification.subscribe((verificationOpen) => { - this._updateVerification(verificationOpen); + this.track(verification.subscribe((txnId) => { + this._updateVerification(txnId); })); this._updateVerification(verification.get()); @@ -340,12 +340,13 @@ export class SessionViewModel extends ViewModel { this.emitChange("activeMiddleViewModel"); } - _updateVerification(verificationOpen) { + _updateVerification(txnId) { if (this._verificationViewModel) { this._verificationViewModel = this.disposeTracked(this._verificationViewModel); } - if (verificationOpen) { - this._verificationViewModel = this.track(new DeviceVerificationViewModel(this.childOptions({ session: this._client.session }))); + if (txnId) { + const request = this._client.session.crossSigning.receivedSASVerifications.get(txnId); + this._verificationViewModel = this.track(new DeviceVerificationViewModel(this.childOptions({ session: this._client.session, request }))); } this.emitChange("activeMiddleViewModel"); } diff --git a/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts b/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts index fab17a20..e2ba96ae 100644 --- a/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts +++ b/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts @@ -21,14 +21,12 @@ import {ViewModel, Options as BaseOptions} from "../../../ViewModel"; import type {Session} from "../../../../matrix/Session.js"; import type {SegmentType} from "../../../navigation"; import type {IToastCollection} from "../IToastCollection"; -import { SASVerification } from "../../../../matrix/verification/SAS/SASVerification"; +import type {SASRequest} from "../../../../matrix/verification/SAS/SASRequest"; type Options = { session: Session; } & BaseOptions; - - export class VerificationToastCollectionViewModel extends ViewModel implements IToastCollection { public readonly toastViewModels: ObservableArray = new ObservableArray(); @@ -42,27 +40,38 @@ export class VerificationToastCollectionViewModel extends ViewModel setTimeout(r, 3000)); - const sasObservable = session.crossSigning.receivedSASVerification; - this.track( - sasObservable.subscribe((sas) => { - if (sas) { - this.createToast(sas); - } - else { - this.toastViewModels.remove(0); - } - }) - ); + const map = session.crossSigning.receivedSASVerifications; + this.track(map.subscribe(this)); } } - private createToast(sas: SASVerification) { + async onAdd(_, request: SASRequest) { const dismiss = () => { - const idx = this.toastViewModels.array.findIndex(vm => vm.sas === sas); + const idx = this.toastViewModels.array.findIndex(vm => vm.request.id === request.id); if (idx !== -1) { this.toastViewModels.remove(idx); } }; - this.toastViewModels.append(new VerificationToastNotificationViewModel(this.childOptions({ sas, dismiss }))); + this.toastViewModels.append(new VerificationToastNotificationViewModel(this.childOptions({ request, dismiss }))); + } + + onRemove(_, request: SASRequest) { + const idx = this.toastViewModels.array.findIndex(vm => vm.request.id === request.id); + if (idx !== -1) { + this.toastViewModels.remove(idx); + } + } + + onUpdate(_, request: SASRequest) { + const idx = this.toastViewModels.array.findIndex(vm => vm.request.id === request.id); + if (idx !== -1) { + this.toastViewModels.update(idx, this.toastViewModels.at(idx)!); + } + } + + onReset() { + for (let i = 0; i < this.toastViewModels.length; ++i) { + this.toastViewModels.remove(i); + } } } diff --git a/src/domain/session/toast/verification/VerificationToastNotificationViewModel.ts b/src/domain/session/toast/verification/VerificationToastNotificationViewModel.ts index 9b1770fb..b3d7de35 100644 --- a/src/domain/session/toast/verification/VerificationToastNotificationViewModel.ts +++ b/src/domain/session/toast/verification/VerificationToastNotificationViewModel.ts @@ -15,14 +15,14 @@ limitations under the License. */ import {BaseClassOptions, BaseToastNotificationViewModel} from ".././BaseToastNotificationViewModel"; import {SegmentType} from "../../../navigation"; -import type {SASVerification} from "../../../../matrix/verification/SAS/SASVerification"; +import {SASRequest} from "../../../../matrix/verification/SAS/SASRequest"; type Options = { - sas: SASVerification; + request: SASRequest; } & BaseClassOptions; type MinimumNeededSegmentType = { - "device-verification": true; + "device-verification": string; }; export class VerificationToastNotificationViewModel = Options> extends BaseToastNotificationViewModel { @@ -34,17 +34,17 @@ export class VerificationToastNotificationViewModel { @@ -38,10 +39,10 @@ export class DeviceVerificationViewModel extends ErrorReportViewModel) { super(options); this.session = options.session; - const existingSas = this.session.crossSigning.receivedSASVerification.get(); - if (existingSas) { + const sasRequest = options.request; + if (options.request) { // SAS already created from request - this.startWithExistingSAS(existingSas); + this.startWithSASRequest(sasRequest); } else { // We are about to send the request @@ -54,9 +55,10 @@ export class DeviceVerificationViewModel extends ErrorReportViewModel { - this.sas = sas; + const crossSigning = this.session.crossSigning; + this.sas = crossSigning.startVerification(request, log); this.hookToEvents(); return this.sas.start(); }); @@ -66,8 +68,7 @@ export class DeviceVerificationViewModel extends ErrorReportViewModel { // todo: can crossSigning be undefined? const crossSigning = this.session.crossSigning; - // todo: should be called createSasVerification - this.sas = crossSigning.startVerification(this.session.userId, undefined, log); + this.sas = crossSigning.startVerification(this.session.userId, log); this.hookToEvents(); return this.sas.start(); }); diff --git a/src/matrix/verification/CrossSigning.ts b/src/matrix/verification/CrossSigning.ts index d355a81a..2273c442 100644 --- a/src/matrix/verification/CrossSigning.ts +++ b/src/matrix/verification/CrossSigning.ts @@ -19,7 +19,8 @@ import {pkSign} from "./common"; import {SASVerification} from "./SAS/SASVerification"; import {ToDeviceChannel} from "./SAS/channel/Channel"; import {VerificationEventType} from "./SAS/channel/types"; -import {ObservableValue} from "../../observable/value/ObservableValue"; +import {ObservableMap} from "../../observable/map"; +import {SASRequest} from "./SAS/SASRequest"; import type {SecretStorage} from "../ssss/SecretStorage"; import type {Storage} from "../storage/idb/Storage"; import type {Platform} from "../../platform/web/Platform"; @@ -90,7 +91,7 @@ export class CrossSigning { private _isMasterKeyTrusted: boolean = false; private readonly deviceId: string; private sasVerificationInProgress?: SASVerification; - public receivedSASVerification: ObservableValue = new ObservableValue(undefined); + public receivedSASVerifications: ObservableMap = new ObservableMap(); constructor(options: { storage: Storage, @@ -118,24 +119,23 @@ export class CrossSigning { this.deviceMessageHandler = options.deviceMessageHandler; this.deviceMessageHandler.on("message", async ({ unencrypted: unencryptedEvent }) => { - if (unencryptedEvent.type === VerificationEventTypes.Cancel && - this.sasVerificationInProgress?.channel.id === unencryptedEvent.content.transaction_id) { - this.receivedSASVerification.set(undefined); + const txnId = unencryptedEvent.content.transaction_id; + if (unencryptedEvent.type === VerificationEventType.Cancel) { + this.receivedSASVerifications.remove(txnId); + return; } if (this.sasVerificationInProgress && ( !this.sasVerificationInProgress.finished || // If the start message is for the previous sasverification, ignore it. - this.sasVerificationInProgress.channel.id === unencryptedEvent.content.transaction_id + this.sasVerificationInProgress.channel.id === txnId )) { return; } if (unencryptedEvent.type === VerificationEventType.Request || unencryptedEvent.type === VerificationEventType.Start) { - this.platform.logger.run("Start verification from request", (log) => { - //todo: We can have more than one sas requests - this.sasVerificationInProgress = this.startVerification(unencryptedEvent.sender, unencryptedEvent, log); - this.receivedSASVerification.set(this.sasVerificationInProgress!); + this.platform.logger.run("Start verification from request", () => { + this.receivedSASVerifications.set(txnId, new SASRequest(unencryptedEvent)); }); } }) @@ -190,14 +190,18 @@ export class CrossSigning { return this._isMasterKeyTrusted; } - startVerification(userId: string, startingMessage: any, log: ILogItem): SASVerification | undefined { + startVerification(requestOrUserId: SASRequest, log: ILogItem): SASVerification | undefined; + startVerification(requestOrUserId: string, log: ILogItem): SASVerification | undefined; + startVerification(requestOrUserId: string | SASRequest, log: ILogItem): SASVerification | undefined { if (this.sasVerificationInProgress && !this.sasVerificationInProgress.finished) { return; } + const otherUserId = requestOrUserId instanceof SASRequest ? requestOrUserId.sender : requestOrUserId; + const startingMessage = requestOrUserId instanceof SASRequest ? requestOrUserId.startingMessage : undefined; const channel = new ToDeviceChannel({ deviceTracker: this.deviceTracker, hsApi: this.hsApi, - otherUserId: userId, + otherUserId, clock: this.platform.clock, deviceMessageHandler: this.deviceMessageHandler, ourUserDeviceId: this.deviceId, @@ -209,7 +213,7 @@ export class CrossSigning { olmUtil: this.olmUtil, ourUserId: this.ownUserId, ourUserDeviceId: this.deviceId, - otherUserId: userId, + otherUserId, log, channel, e2eeAccount: this.e2eeAccount, diff --git a/src/matrix/verification/SAS/SASRequest.ts b/src/matrix/verification/SAS/SASRequest.ts new file mode 100644 index 00000000..69bc197a --- /dev/null +++ b/src/matrix/verification/SAS/SASRequest.ts @@ -0,0 +1,31 @@ +/* +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. +*/ + +export class SASRequest { + constructor(public readonly startingMessage: any) {} + + get deviceId(): string { + return this.startingMessage.content.from_device; + } + + get sender(): string { + return this.startingMessage.sender; + } + + get id(): string { + return this.startingMessage.content.transaction_id; + } +} From 16c144868af455071869e2a0f5fcc12d91cdd6a3 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 24 Mar 2023 17:51:17 +0530 Subject: [PATCH 05/33] Refactor code --- .../VerificationToastCollectionViewModel.ts | 2 +- .../DeviceVerificationViewModel.ts | 23 +++++-------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts b/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts index e2ba96ae..2acd0412 100644 --- a/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts +++ b/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts @@ -38,7 +38,7 @@ export class VerificationToastCollectionViewModel extends ViewModel setTimeout(r, 3000)); const map = session.crossSigning.receivedSASVerifications; this.track(map.subscribe(this)); diff --git a/src/domain/session/verification/DeviceVerificationViewModel.ts b/src/domain/session/verification/DeviceVerificationViewModel.ts index 5807898f..b30fd61f 100644 --- a/src/domain/session/verification/DeviceVerificationViewModel.ts +++ b/src/domain/session/verification/DeviceVerificationViewModel.ts @@ -41,12 +41,11 @@ export class DeviceVerificationViewModel extends ErrorReportViewModel { const crossSigning = this.session.crossSigning; - this.sas = crossSigning.startVerification(request, log); - this.hookToEvents(); + this.sas = crossSigning.startVerification(requestOrUserId, log); + this.addEventListeners(); return this.sas.start(); }); } - private async createAndStartSasVerification(): Promise { - await this.logAndCatch("DeviceVerificationViewModel.createAndStartSasVerification", (log) => { - // todo: can crossSigning be undefined? - const crossSigning = this.session.crossSigning; - this.sas = crossSigning.startVerification(this.session.userId, log); - this.hookToEvents(); - return this.sas.start(); - }); - } - - private hookToEvents() { + private addEventListeners() { const emitter = this.sas.eventEmitter; this.track(emitter.disposableOn("SelectVerificationStage", (stage) => { this.createViewModelAndEmit( From 15ab7e7a728006c3eb5addd5801a659aa9b99cff Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 24 Mar 2023 18:03:39 +0530 Subject: [PATCH 06/33] Create viewmodel inside start method --- .../session/verification/DeviceVerificationViewModel.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/domain/session/verification/DeviceVerificationViewModel.ts b/src/domain/session/verification/DeviceVerificationViewModel.ts index b30fd61f..4b351668 100644 --- a/src/domain/session/verification/DeviceVerificationViewModel.ts +++ b/src/domain/session/verification/DeviceVerificationViewModel.ts @@ -46,11 +46,6 @@ export class DeviceVerificationViewModel extends ErrorReportViewModel Date: Fri, 24 Mar 2023 21:05:18 +0530 Subject: [PATCH 07/33] Fix rebase --- .../DeviceVerificationViewModel.ts | 9 ++-- .../stages/VerificationCancelledViewModel.ts | 6 +-- .../verification/SAS/SASVerification.ts | 2 +- .../verification/SAS/channel/Channel.ts | 10 ++-- .../verification/SAS/channel/MockChannel.ts | 2 +- .../verification/SAS/stages/SendDoneStage.ts | 2 +- src/matrix/verification/SAS/types.ts | 4 +- .../stages/VerificationCancelledView.ts | 48 +++++++++---------- 8 files changed, 41 insertions(+), 42 deletions(-) diff --git a/src/domain/session/verification/DeviceVerificationViewModel.ts b/src/domain/session/verification/DeviceVerificationViewModel.ts index 4b351668..4ca00800 100644 --- a/src/domain/session/verification/DeviceVerificationViewModel.ts +++ b/src/domain/session/verification/DeviceVerificationViewModel.ts @@ -62,24 +62,23 @@ export class DeviceVerificationViewModel extends ErrorReportViewModel { + this.track(this.sas.disposableOn("SelectVerificationStage", (stage) => { this.createViewModelAndEmit( new SelectMethodViewModel(this.childOptions({ sas: this.sas, stage: stage!, })) ); })); - this.track(emitter.disposableOn("EmojiGenerated", (stage) => { + this.track(this.sas.disposableOn("EmojiGenerated", (stage) => { this.createViewModelAndEmit( new VerifyEmojisViewModel(this.childOptions({ stage: stage!, })) ); })); - this.track(emitter.disposableOn("VerificationCancelled", (cancellation) => { + this.track(this.sas.disposableOn("VerificationCancelled", (cancellation) => { this.createViewModelAndEmit( new VerificationCancelledViewModel( this.childOptions({ cancellationCode: cancellation!.code, cancelledByUs: cancellation!.cancelledByUs, }) )); })); - this.track(emitter.disposableOn("VerificationCompleted", (deviceId) => { + this.track(this.sas.disposableOn("VerificationCompleted", (deviceId) => { this.createViewModelAndEmit( new VerificationCompleteViewModel(this.childOptions({ deviceId: deviceId! })) ); diff --git a/src/domain/session/verification/stages/VerificationCancelledViewModel.ts b/src/domain/session/verification/stages/VerificationCancelledViewModel.ts index ad01d312..9f2bd180 100644 --- a/src/domain/session/verification/stages/VerificationCancelledViewModel.ts +++ b/src/domain/session/verification/stages/VerificationCancelledViewModel.ts @@ -16,15 +16,15 @@ limitations under the License. import {ViewModel, Options as BaseOptions} from "../../../ViewModel"; import {SegmentType} from "../../../navigation/index"; -import {CancelTypes} from "../../../../matrix/verification/SAS/channel/types"; +import {CancelReason} from "../../../../matrix/verification/SAS/channel/types"; type Options = BaseOptions & { - cancellationCode: CancelTypes; + cancellationCode: CancelReason; cancelledByUs: boolean; }; export class VerificationCancelledViewModel extends ViewModel { - get cancelCode(): CancelTypes { + get cancelCode(): CancelReason { return this.options.cancellationCode; } diff --git a/src/matrix/verification/SAS/SASVerification.ts b/src/matrix/verification/SAS/SASVerification.ts index 41a42b45..fcd86eb7 100644 --- a/src/matrix/verification/SAS/SASVerification.ts +++ b/src/matrix/verification/SAS/SASVerification.ts @@ -85,7 +85,7 @@ export class SASVerification extends EventEmitter { } async abort() { - await this.channel.cancelVerification(CancelTypes.UserCancelled); + await this.channel.cancelVerification(CancelReason.UserCancelled); } async start() { diff --git a/src/matrix/verification/SAS/channel/Channel.ts b/src/matrix/verification/SAS/channel/Channel.ts index 9763b95c..fc2260fa 100644 --- a/src/matrix/verification/SAS/channel/Channel.ts +++ b/src/matrix/verification/SAS/channel/Channel.ts @@ -50,7 +50,7 @@ export interface IChannel { startMessage: any; initiatedByUs: boolean; isCancelled: boolean; - cancellation: { code: CancelTypes, cancelledByUs: boolean }; + cancellation: { code: CancelReason, cancelledByUs: boolean }; id: string; otherUserDeviceId: string; } @@ -80,7 +80,7 @@ export class ToDeviceChannel extends Disposables implements IChannel { public startMessage: any; public id: string; private _initiatedByUs: boolean; - private _cancellation: { code: CancelTypes, cancelledByUs: boolean }; + private _cancellation: { code: CancelReason, cancelledByUs: boolean }; /** * @@ -204,7 +204,7 @@ export class ToDeviceChannel extends Disposables implements IChannel { this.handleReadyMessage(event, log); return; } - if (event.type === VerificationEventTypes.Cancel) { + if (event.type === VerificationEventType.Cancel) { this._cancellation = { code: event.content.code, cancelledByUs: false }; this.dispose(); return; @@ -248,7 +248,7 @@ export class ToDeviceChannel extends Disposables implements IChannel { } } } - await this.hsApi.sendToDevice(VerificationEventTypes.Cancel, payload, makeTxnId(), { log }).response(); + await this.hsApi.sendToDevice(VerificationEventType.Cancel, payload, makeTxnId(), { log }).response(); this._cancellation = { code: cancellationType, cancelledByUs: true }; this.dispose(); }); @@ -263,7 +263,7 @@ export class ToDeviceChannel extends Disposables implements IChannel { } } - waitForEvent(eventType: VerificationEventTypes): Promise { + waitForEvent(eventType: VerificationEventType): Promise { if (this.isCancelled) { throw new VerificationCancelledError(); } diff --git a/src/matrix/verification/SAS/channel/MockChannel.ts b/src/matrix/verification/SAS/channel/MockChannel.ts index 9553a92d..64ae1456 100644 --- a/src/matrix/verification/SAS/channel/MockChannel.ts +++ b/src/matrix/verification/SAS/channel/MockChannel.ts @@ -17,7 +17,7 @@ export class MockChannel implements ITestChannel { public initiatedByUs: boolean; public startMessage: any; public isCancelled: boolean = false; - public cancellation: { code: CancelTypes; cancelledByUs: boolean; }; + public cancellation: { code: CancelReason; cancelledByUs: boolean; }; private olmSas: any; constructor( diff --git a/src/matrix/verification/SAS/stages/SendDoneStage.ts b/src/matrix/verification/SAS/stages/SendDoneStage.ts index 53b8a37e..95167e24 100644 --- a/src/matrix/verification/SAS/stages/SendDoneStage.ts +++ b/src/matrix/verification/SAS/stages/SendDoneStage.ts @@ -20,7 +20,7 @@ export class SendDoneStage extends BaseSASVerificationStage { async completeStage() { await this.log.wrap("SendDoneStage.completeStage", async (log) => { this.eventEmitter.emit("VerificationCompleted", this.otherUserDeviceId); - await this.channel.send(VerificationEventTypes.Done, {}, log); + await this.channel.send(VerificationEventType.Done, {}, log); }); } } diff --git a/src/matrix/verification/SAS/types.ts b/src/matrix/verification/SAS/types.ts index 3bfd742e..528a2e55 100644 --- a/src/matrix/verification/SAS/types.ts +++ b/src/matrix/verification/SAS/types.ts @@ -13,7 +13,7 @@ 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 {CancelTypes} from "./channel/types"; +import {CancelReason} from "./channel/types"; import {CalculateSASStage} from "./stages/CalculateSASStage"; import {SelectVerificationMethodStage} from "./stages/SelectVerificationMethodStage"; @@ -21,5 +21,5 @@ export type SASProgressEvents = { SelectVerificationStage: SelectVerificationMethodStage; EmojiGenerated: CalculateSASStage; VerificationCompleted: string; - VerificationCancelled: { code: CancelTypes, cancelledByUs: boolean }; + VerificationCancelled: { code: CancelReason, cancelledByUs: boolean }; } diff --git a/src/platform/web/ui/session/verification/stages/VerificationCancelledView.ts b/src/platform/web/ui/session/verification/stages/VerificationCancelledView.ts index d2afa98d..28cebf80 100644 --- a/src/platform/web/ui/session/verification/stages/VerificationCancelledView.ts +++ b/src/platform/web/ui/session/verification/stages/VerificationCancelledView.ts @@ -16,7 +16,7 @@ limitations under the License. import {TemplateView} from "../../../general/TemplateView"; import {VerificationCancelledViewModel} from "../../../../../../domain/session/verification/stages/VerificationCancelledViewModel"; -import {CancelTypes} from "../../../../../../matrix/verification/SAS/channel/types"; +import {CancelReason} from "../../../../../../matrix/verification/SAS/channel/types"; export class VerificationCancelledView extends TemplateView { render(t, vm: VerificationCancelledViewModel) { @@ -48,32 +48,32 @@ export class VerificationCancelledView extends TemplateView Date: Mon, 27 Mar 2023 14:20:20 +0530 Subject: [PATCH 08/33] Track view-model instance --- .../verification/VerificationToastCollectionViewModel.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts b/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts index 2acd0412..d6b0795b 100644 --- a/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts +++ b/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts @@ -52,7 +52,9 @@ export class VerificationToastCollectionViewModel extends ViewModel Date: Mon, 27 Mar 2023 14:25:10 +0530 Subject: [PATCH 09/33] Remove "any" type --- src/domain/session/toast/ToastCollectionViewModel.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/domain/session/toast/ToastCollectionViewModel.ts b/src/domain/session/toast/ToastCollectionViewModel.ts index 60dfe2e0..6b2bdceb 100644 --- a/src/domain/session/toast/ToastCollectionViewModel.ts +++ b/src/domain/session/toast/ToastCollectionViewModel.ts @@ -21,6 +21,7 @@ import {VerificationToastCollectionViewModel} from "./verification/VerificationT import type {Session} from "../../../matrix/Session.js"; import type {SegmentType} from "../../navigation"; import type {BaseToastNotificationViewModel} from "./BaseToastNotificationViewModel"; +import type {IToastCollection} from "./IToastCollection"; type Options = { session: Session; @@ -32,7 +33,7 @@ export class ToastCollectionViewModel extends ViewModel { constructor(options: Options) { super(options); const session = this.getOption("session"); - const vms: any = [ + const vms: IToastCollection["toastViewModels"][] = [ this.track(new CallToastCollectionViewModel(this.childOptions({ session }))), this.track(new VerificationToastCollectionViewModel(this.childOptions({session}))), ].map(vm => vm.toastViewModels); From 918ee6bf1daad0cf2efb8ee39ebc48af8f0cd066 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 27 Mar 2023 14:32:10 +0530 Subject: [PATCH 10/33] Change log string --- src/domain/session/verification/DeviceVerificationViewModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/session/verification/DeviceVerificationViewModel.ts b/src/domain/session/verification/DeviceVerificationViewModel.ts index 4ca00800..7a9248f0 100644 --- a/src/domain/session/verification/DeviceVerificationViewModel.ts +++ b/src/domain/session/verification/DeviceVerificationViewModel.ts @@ -50,7 +50,7 @@ export class DeviceVerificationViewModel extends ErrorReportViewModel { + await this.logAndCatch("DeviceVerificationViewModel.start", (log) => { const crossSigning = this.session.crossSigning; this.sas = crossSigning.startVerification(requestOrUserId, log); this.addEventListeners(); From d32d0def369ae72a2650454f5c94990fbef20c94 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 27 Mar 2023 14:34:38 +0530 Subject: [PATCH 11/33] Fix emit --- src/matrix/verification/SAS/SASVerification.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/verification/SAS/SASVerification.ts b/src/matrix/verification/SAS/SASVerification.ts index fcd86eb7..9d2de3df 100644 --- a/src/matrix/verification/SAS/SASVerification.ts +++ b/src/matrix/verification/SAS/SASVerification.ts @@ -103,7 +103,7 @@ export class SASVerification extends EventEmitter { } finally { if (this.channel.isCancelled) { - this.eventEmitter.emit("VerificationCancelled", this.channel.cancellation); + this.emit("VerificationCancelled", this.channel.cancellation); } this.olmSas.free(); this.timeout.abort(); From 0588d04742626e76f0709a4d7cf1d8bacbaa61aa Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 27 Mar 2023 14:39:25 +0530 Subject: [PATCH 12/33] Pass in cancellation object --- .../verification/DeviceVerificationViewModel.ts | 7 ++++--- .../stages/VerificationCancelledViewModel.ts | 10 +++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/domain/session/verification/DeviceVerificationViewModel.ts b/src/domain/session/verification/DeviceVerificationViewModel.ts index 7a9248f0..58debd28 100644 --- a/src/domain/session/verification/DeviceVerificationViewModel.ts +++ b/src/domain/session/verification/DeviceVerificationViewModel.ts @@ -75,9 +75,10 @@ export class DeviceVerificationViewModel extends ErrorReportViewModel { this.createViewModelAndEmit( new VerificationCancelledViewModel( - this.childOptions({ cancellationCode: cancellation!.code, cancelledByUs: cancellation!.cancelledByUs, }) - )); - })); + this.childOptions({ cancellation: cancellation! }) + ) + ); + })); this.track(this.sas.disposableOn("VerificationCompleted", (deviceId) => { this.createViewModelAndEmit( new VerificationCompleteViewModel(this.childOptions({ deviceId: deviceId! })) diff --git a/src/domain/session/verification/stages/VerificationCancelledViewModel.ts b/src/domain/session/verification/stages/VerificationCancelledViewModel.ts index 9f2bd180..01f11768 100644 --- a/src/domain/session/verification/stages/VerificationCancelledViewModel.ts +++ b/src/domain/session/verification/stages/VerificationCancelledViewModel.ts @@ -16,20 +16,20 @@ limitations under the License. import {ViewModel, Options as BaseOptions} from "../../../ViewModel"; import {SegmentType} from "../../../navigation/index"; -import {CancelReason} from "../../../../matrix/verification/SAS/channel/types"; +import type {CancelReason} from "../../../../matrix/verification/SAS/channel/types"; +import type {IChannel} from "../../../../matrix/verification/SAS/channel/Channel"; type Options = BaseOptions & { - cancellationCode: CancelReason; - cancelledByUs: boolean; + cancellation: IChannel["cancellation"]; }; export class VerificationCancelledViewModel extends ViewModel { get cancelCode(): CancelReason { - return this.options.cancellationCode; + return this.options.cancellation.code; } get isCancelledByUs(): boolean { - return this.options.cancelledByUs; + return this.options.cancellation.cancelledByUs; } gotoSettings() { From ac1a16d548adef23fac3ac6bdd355c1ed913e93d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 27 Mar 2023 14:45:57 +0530 Subject: [PATCH 13/33] Remove unused code --- src/matrix/verification/SAS/SASVerification.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/matrix/verification/SAS/SASVerification.ts b/src/matrix/verification/SAS/SASVerification.ts index 9d2de3df..5587eccd 100644 --- a/src/matrix/verification/SAS/SASVerification.ts +++ b/src/matrix/verification/SAS/SASVerification.ts @@ -110,17 +110,6 @@ export class SASVerification extends EventEmitter { this.finished = true; } } - - get otherDeviceId() { - return this.channel?.otherUserDeviceId; - } - - /** - * Returns true if we were created because a "request" message was received - */ - get isStartingWithRequestMessage(): boolean { - return this.startStage instanceof SendReadyStage; - } } import {HomeServer} from "../../../mocks/HomeServer.js"; From 7e2823be5e8f391d79c406db612e6a8c56b08aae Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 27 Mar 2023 14:49:29 +0530 Subject: [PATCH 14/33] Import as types --- src/matrix/verification/SAS/SASVerification.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/matrix/verification/SAS/SASVerification.ts b/src/matrix/verification/SAS/SASVerification.ts index 5587eccd..d4b17143 100644 --- a/src/matrix/verification/SAS/SASVerification.ts +++ b/src/matrix/verification/SAS/SASVerification.ts @@ -19,14 +19,14 @@ import type {BaseSASVerificationStage} from "./stages/BaseSASVerificationStage"; import type {Account} from "../../e2ee/Account.js"; import type {DeviceTracker} from "../../e2ee/DeviceTracker.js"; import type * as OlmNamespace from "@matrix-org/olm"; -import {IChannel} from "./channel/Channel"; -import {HomeServerApi} from "../../net/HomeServerApi"; +import type {IChannel} from "./channel/Channel"; +import type {HomeServerApi} from "../../net/HomeServerApi"; +import type {Timeout} from "../../../platform/types/types"; +import type {Clock} from "../../../platform/web/dom/Clock.js"; import {CancelReason, VerificationEventType} from "./channel/types"; import {SendReadyStage} from "./stages/SendReadyStage"; import {SelectVerificationMethodStage} from "./stages/SelectVerificationMethodStage"; import {VerificationCancelledError} from "./VerificationCancelledError"; -import {Timeout} from "../../../platform/types/types"; -import {Clock} from "../../../platform/web/dom/Clock.js"; import {EventEmitter} from "../../../utils/EventEmitter"; import {SASProgressEvents} from "./types"; From 8becb2b60567891a6828d356aa7bd0b4f68550c3 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 27 Mar 2023 14:51:01 +0530 Subject: [PATCH 15/33] Import as type --- src/matrix/verification/SAS/types.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/matrix/verification/SAS/types.ts b/src/matrix/verification/SAS/types.ts index 528a2e55..a46ee085 100644 --- a/src/matrix/verification/SAS/types.ts +++ b/src/matrix/verification/SAS/types.ts @@ -13,13 +13,13 @@ 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 {CancelReason} from "./channel/types"; -import {CalculateSASStage} from "./stages/CalculateSASStage"; -import {SelectVerificationMethodStage} from "./stages/SelectVerificationMethodStage"; +import type {IChannel} from "./channel/Channel"; +import type {CalculateSASStage} from "./stages/CalculateSASStage"; +import type {SelectVerificationMethodStage} from "./stages/SelectVerificationMethodStage"; export type SASProgressEvents = { SelectVerificationStage: SelectVerificationMethodStage; EmojiGenerated: CalculateSASStage; VerificationCompleted: string; - VerificationCancelled: { code: CancelReason, cancelledByUs: boolean }; + VerificationCancelled: IChannel["cancellation"]; } From 5fa4afa021649e73603e5942cdac4fe7a430d183 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 27 Mar 2023 14:53:50 +0530 Subject: [PATCH 16/33] Combine css styles --- src/platform/web/ui/css/themes/element/theme.css | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index 31e62754..11501bce 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -1430,11 +1430,7 @@ button.RoomDetailsView_row::after { .VerificationCompleteView__title, .VerifyEmojisView__title, .SelectMethodView__title, -.WaitingForOtherUserView__title { - text-align: center; - margin: 0; -} - +.WaitingForOtherUserView__title, .VerificationCancelledView__description, .VerificationCompleteView__description, .VerifyEmojisView__description, From 41ebf13156380054365714f600bdabd72032580e Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 27 Mar 2023 16:36:27 +0530 Subject: [PATCH 17/33] Some more changes --- .../stages/SelectMethodViewModel.ts | 4 ++++ .../stages/VerificationCancelledViewModel.ts | 4 ++++ .../stages/VerificationCompleteViewModel.ts | 4 ++++ .../stages/VerifyEmojisViewModel.ts | 4 ++++ .../stages/WaitingForOtherUserViewModel.ts | 4 ++++ .../verification/DeviceVerificationView.ts | 20 ++++++++----------- .../verification/stages/SelectMethodView.ts | 4 ++-- .../stages/VerificationCancelledView.ts | 7 ++----- .../stages/VerificationCompleteView.ts | 4 ++-- .../verification/stages/VerifyEmojisView.ts | 4 ++-- .../stages/WaitingForOtherUserView.ts | 4 ++-- 11 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/domain/session/verification/stages/SelectMethodViewModel.ts b/src/domain/session/verification/stages/SelectMethodViewModel.ts index 681a2e46..e88d0f83 100644 --- a/src/domain/session/verification/stages/SelectMethodViewModel.ts +++ b/src/domain/session/verification/stages/SelectMethodViewModel.ts @@ -47,4 +47,8 @@ export class SelectMethodViewModel extends ErrorReportViewModel { - render(t, vm) { + render(t: Builder) { return t.div({ className: { "middle": true, @@ -36,21 +31,22 @@ export class DeviceVerificationView extends TemplateView vm.currentStageViewModel, (stageVm) => { - if (stageVm instanceof WaitingForOtherUserViewModel) { + if (stageVm.kind === "waiting-for-user") { return new WaitingForOtherUserView(stageVm); } - else if (stageVm instanceof VerificationCancelledViewModel) { + else if (stageVm.kind === "verification-cancelled") { return new VerificationCancelledView(stageVm); } - else if (stageVm instanceof SelectMethodViewModel) { + else if (stageVm.kind === "select-method") { return new SelectMethodView(stageVm); } - else if (stageVm instanceof VerifyEmojisViewModel) { + else if (stageVm.kind === "verify-emojis") { return new VerifyEmojisView(stageVm); } - else if (stageVm instanceof VerificationCompleteViewModel) { + else if (stageVm.kind === "verification-completed") { return new VerificationCompleteView(stageVm); } + return null; }) ]) } diff --git a/src/platform/web/ui/session/verification/stages/SelectMethodView.ts b/src/platform/web/ui/session/verification/stages/SelectMethodView.ts index 9e665f31..e7603700 100644 --- a/src/platform/web/ui/session/verification/stages/SelectMethodView.ts +++ b/src/platform/web/ui/session/verification/stages/SelectMethodView.ts @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {TemplateView} from "../../../general/TemplateView"; +import {Builder, TemplateView} from "../../../general/TemplateView"; import {spinner} from "../../../common.js" import type {SelectMethodViewModel} from "../../../../../../domain/session/verification/stages/SelectMethodViewModel"; export class SelectMethodView extends TemplateView { - render(t) { + render(t: Builder) { return t.div({ className: "SelectMethodView" }, [ t.map(vm => vm.hasProceeded, (hasProceeded, t, vm) => { if (hasProceeded) { diff --git a/src/platform/web/ui/session/verification/stages/VerificationCancelledView.ts b/src/platform/web/ui/session/verification/stages/VerificationCancelledView.ts index 28cebf80..d2832ddb 100644 --- a/src/platform/web/ui/session/verification/stages/VerificationCancelledView.ts +++ b/src/platform/web/ui/session/verification/stages/VerificationCancelledView.ts @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {TemplateView} from "../../../general/TemplateView"; +import {Builder, TemplateView} from "../../../general/TemplateView"; import {VerificationCancelledViewModel} from "../../../../../../domain/session/verification/stages/VerificationCancelledViewModel"; import {CancelReason} from "../../../../../../matrix/verification/SAS/channel/types"; export class VerificationCancelledView extends TemplateView { - render(t, vm: VerificationCancelledViewModel) { + render(t: Builder, vm: VerificationCancelledViewModel) { const headerTextStart = vm.isCancelledByUs ? "You" : "The other device"; return t.div( @@ -50,10 +50,8 @@ export class VerificationCancelledView extends TemplateView { - render(t, vm: VerificationCompleteViewModel) { + render(t: Builder, vm: VerificationCompleteViewModel) { return t.div({ className: "VerificationCompleteView" }, [ t.div({className: "VerificationCompleteView__icon"}), t.div({ className: "VerificationCompleteView__heading" }, [ diff --git a/src/platform/web/ui/session/verification/stages/VerifyEmojisView.ts b/src/platform/web/ui/session/verification/stages/VerifyEmojisView.ts index 9f7b312b..32aba691 100644 --- a/src/platform/web/ui/session/verification/stages/VerifyEmojisView.ts +++ b/src/platform/web/ui/session/verification/stages/VerifyEmojisView.ts @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {TemplateView} from "../../../general/TemplateView"; +import {Builder, TemplateView} from "../../../general/TemplateView"; import {spinner} from "../../../common.js" import type {VerifyEmojisViewModel} from "../../../../../../domain/session/verification/stages/VerifyEmojisViewModel"; export class VerifyEmojisView extends TemplateView { - render(t, vm: VerifyEmojisViewModel) { + render(t: Builder, vm: VerifyEmojisViewModel) { const emojiList = vm.emojis.reduce((acc, [emoji, name]) => { const e = t.div({ className: "EmojiContainer" }, [ t.div({ className: "EmojiContainer__emoji" }, emoji), diff --git a/src/platform/web/ui/session/verification/stages/WaitingForOtherUserView.ts b/src/platform/web/ui/session/verification/stages/WaitingForOtherUserView.ts index 007b258e..0018a4b3 100644 --- a/src/platform/web/ui/session/verification/stages/WaitingForOtherUserView.ts +++ b/src/platform/web/ui/session/verification/stages/WaitingForOtherUserView.ts @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {TemplateView} from "../../../general/TemplateView"; +import {Builder, TemplateView} from "../../../general/TemplateView"; import {spinner} from "../../../common.js"; import {WaitingForOtherUserViewModel} from "../../../../../../domain/session/verification/stages/WaitingForOtherUserViewModel"; export class WaitingForOtherUserView extends TemplateView { - render(t, vm) { + render(t: Builder, vm: WaitingForOtherUserViewModel) { return t.div({ className: "WaitingForOtherUserView" }, [ t.div({ className: "WaitingForOtherUserView__heading" }, [ spinner(t), From e0b3e9f4c46f7e203997d8864ec263ae7cec479b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 27 Mar 2023 16:52:32 +0530 Subject: [PATCH 18/33] Use optional chaining --- .../verification/DeviceVerificationView.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/platform/web/ui/session/verification/DeviceVerificationView.ts b/src/platform/web/ui/session/verification/DeviceVerificationView.ts index 93672b89..a64f1464 100644 --- a/src/platform/web/ui/session/verification/DeviceVerificationView.ts +++ b/src/platform/web/ui/session/verification/DeviceVerificationView.ts @@ -30,21 +30,21 @@ export class DeviceVerificationView extends TemplateView vm.currentStageViewModel, (stageVm) => { - if (stageVm.kind === "waiting-for-user") { - return new WaitingForOtherUserView(stageVm); + t.mapView(vm => vm.currentStageViewModel, (vm) => { + if (vm?.kind === "waiting-for-user") { + return new WaitingForOtherUserView(vm); } - else if (stageVm.kind === "verification-cancelled") { - return new VerificationCancelledView(stageVm); + else if (vm?.kind === "verification-cancelled") { + return new VerificationCancelledView(vm); } - else if (stageVm.kind === "select-method") { - return new SelectMethodView(stageVm); + else if (vm?.kind === "select-method") { + return new SelectMethodView(vm); } - else if (stageVm.kind === "verify-emojis") { - return new VerifyEmojisView(stageVm); + else if (vm?.kind === "verify-emojis") { + return new VerifyEmojisView(vm); } - else if (stageVm.kind === "verification-completed") { - return new VerificationCompleteView(stageVm); + else if (vm?.kind === "verification-completed") { + return new VerificationCompleteView(vm); } return null; }) From f822a7a3449705825b4e82a6c89264be3475ebd9 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 28 Mar 2023 11:50:20 +0530 Subject: [PATCH 19/33] Wrap in feature flag --- src/domain/session/SessionViewModel.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js index e0e77729..689f7c01 100644 --- a/src/domain/session/SessionViewModel.js +++ b/src/domain/session/SessionViewModel.js @@ -97,11 +97,14 @@ export class SessionViewModel extends ViewModel { })); this._updateJoinRoom(joinRoom.get()); + if (this._client.session.features.crossSigning) { + const verification = this.navigation.observe("device-verification"); - this.track(verification.subscribe((txnId) => { - this._updateVerification(txnId); - })); - this._updateVerification(verification.get()); + this.track(verification.subscribe((txnId) => { + this._updateVerification(txnId); + })); + this._updateVerification(verification.get()); + } const lightbox = this.navigation.observe("lightbox"); this.track(lightbox.subscribe(eventId => { From 53c0fc2934a170c5f07f84275df35f50c4d220bf Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 28 Mar 2023 15:08:47 +0530 Subject: [PATCH 20/33] Fix rebase --- src/domain/navigation/index.ts | 2 +- src/domain/session/SessionViewModel.js | 13 ++++++------- .../session/toast/ToastCollectionViewModel.ts | 16 +++++++++++----- .../VerificationToastCollectionViewModel.ts | 15 +++++---------- .../VerificationToastNotificationViewModel.ts | 2 +- .../verification/DeviceVerificationViewModel.ts | 2 +- .../web/ui/session/toast/ToastCollectionView.ts | 3 ++- 7 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index 5904e715..4a573e15 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -34,7 +34,7 @@ export type SegmentType = { "details": true; "members": true; "member": string; - "device-verification": string; + "device-verification": string | boolean; "join-room": true; }; diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js index 689f7c01..b80c1c05 100644 --- a/src/domain/session/SessionViewModel.js +++ b/src/domain/session/SessionViewModel.js @@ -97,13 +97,12 @@ export class SessionViewModel extends ViewModel { })); this._updateJoinRoom(joinRoom.get()); - if (this._client.session.features.crossSigning) { - - const verification = this.navigation.observe("device-verification"); - this.track(verification.subscribe((txnId) => { - this._updateVerification(txnId); - })); - this._updateVerification(verification.get()); + if (this.features.crossSigning) { + const verification = this.navigation.observe("device-verification"); + this.track(verification.subscribe((txnId) => { + this._updateVerification(txnId); + })); + this._updateVerification(verification.get()); } const lightbox = this.navigation.observe("lightbox"); diff --git a/src/domain/session/toast/ToastCollectionViewModel.ts b/src/domain/session/toast/ToastCollectionViewModel.ts index 6b2bdceb..0d31b6ee 100644 --- a/src/domain/session/toast/ToastCollectionViewModel.ts +++ b/src/domain/session/toast/ToastCollectionViewModel.ts @@ -33,10 +33,16 @@ export class ToastCollectionViewModel extends ViewModel { constructor(options: Options) { super(options); const session = this.getOption("session"); - const vms: IToastCollection["toastViewModels"][] = [ - this.track(new CallToastCollectionViewModel(this.childOptions({ session }))), - this.track(new VerificationToastCollectionViewModel(this.childOptions({session}))), - ].map(vm => vm.toastViewModels); - this.toastViewModels = new ConcatList(...vms); + const collectionVms: IToastCollection[] = []; + if (this.features.calls) { + collectionVms.push(this.track(new CallToastCollectionViewModel(this.childOptions({ session })))); + } + if (this.features.crossSigning) { + collectionVms.push(this.track(new VerificationToastCollectionViewModel(this.childOptions({ session })))); + } + const vms: IToastCollection["toastViewModels"][] = collectionVms.map(vm => vm.toastViewModels); + if (vms.length !== 0) { + this.toastViewModels = new ConcatList(...vms); + } } } diff --git a/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts b/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts index d6b0795b..5dfb76eb 100644 --- a/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts +++ b/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts @@ -32,18 +32,13 @@ export class VerificationToastCollectionViewModel extends ViewModel { + this.track(crossSigning.receivedSASVerifications.subscribe(this)); + }) + ); } - async observeSASRequests() { - const session = this.getOption("session"); - if (this.features.crossSigning) { - // todo: hack to wait for crossSigning; remove - await new Promise(r => setTimeout(r, 3000)); - const map = session.crossSigning.receivedSASVerifications; - this.track(map.subscribe(this)); - } - } async onAdd(_, request: SASRequest) { const dismiss = () => { diff --git a/src/domain/session/toast/verification/VerificationToastNotificationViewModel.ts b/src/domain/session/toast/verification/VerificationToastNotificationViewModel.ts index b3d7de35..9bf1e2f7 100644 --- a/src/domain/session/toast/verification/VerificationToastNotificationViewModel.ts +++ b/src/domain/session/toast/verification/VerificationToastNotificationViewModel.ts @@ -22,7 +22,7 @@ type Options = { } & BaseClassOptions; type MinimumNeededSegmentType = { - "device-verification": string; + "device-verification": string | boolean; }; export class VerificationToastNotificationViewModel = Options> extends BaseToastNotificationViewModel { diff --git a/src/domain/session/verification/DeviceVerificationViewModel.ts b/src/domain/session/verification/DeviceVerificationViewModel.ts index 58debd28..33be113f 100644 --- a/src/domain/session/verification/DeviceVerificationViewModel.ts +++ b/src/domain/session/verification/DeviceVerificationViewModel.ts @@ -51,7 +51,7 @@ export class DeviceVerificationViewModel extends ErrorReportViewModel { - const crossSigning = this.session.crossSigning; + const crossSigning = this.session.crossSigning.get(); this.sas = crossSigning.startVerification(requestOrUserId, log); this.addEventListeners(); if (typeof requestOrUserId === "string") { diff --git a/src/platform/web/ui/session/toast/ToastCollectionView.ts b/src/platform/web/ui/session/toast/ToastCollectionView.ts index cf4fd58f..18a9c8ed 100644 --- a/src/platform/web/ui/session/toast/ToastCollectionView.ts +++ b/src/platform/web/ui/session/toast/ToastCollectionView.ts @@ -41,8 +41,9 @@ export class ToastCollectionView extends TemplateView list: vm.toastViewModels, parentProvidesUpdates: false, }, (vm: CallToastNotificationViewModel) => toastViewModelToView(vm)); + return t.div({ className: "ToastCollectionView" }, [ - t.view(view), + t.if(vm => !!vm.toastViewModels, (t) => t.view(view)), ]); } } From 6e2cd3597fee4a84013063d4a9ac0beffdd44614 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 28 Mar 2023 16:52:25 +0530 Subject: [PATCH 21/33] Fix rebase again --- .../VerificationToastCollectionViewModel.ts | 12 +++++++----- src/matrix/Session.js | 1 + .../SAS/stages/SelectVerificationMethodStage.ts | 6 +++++- .../web/ui/session/toast/ToastCollectionView.ts | 12 ++++++------ 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts b/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts index 5dfb76eb..2cd3da01 100644 --- a/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts +++ b/src/domain/session/toast/verification/VerificationToastCollectionViewModel.ts @@ -32,11 +32,13 @@ export class VerificationToastCollectionViewModel extends ViewModel { - this.track(crossSigning.receivedSASVerifications.subscribe(this)); - }) - ); + this.subscribeToSASRequests(); + } + + private async subscribeToSASRequests() { + await this.getOption("session").crossSigning.waitFor(v => !!v).promise; + const crossSigning = this.getOption("session").crossSigning.get(); + this.track(crossSigning.receivedSASVerifications.subscribe(this)); } diff --git a/src/matrix/Session.js b/src/matrix/Session.js index b999ba6b..5d16f03b 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -365,6 +365,7 @@ export class Session { olm: this._olm, olmUtil: this._olmUtil, deviceTracker: this._deviceTracker, + deviceMessageHandler: this._deviceMessageHandler, hsApi: this._hsApi, ownUserId: this.userId, e2eeAccount: this._e2eeAccount diff --git a/src/matrix/verification/SAS/stages/SelectVerificationMethodStage.ts b/src/matrix/verification/SAS/stages/SelectVerificationMethodStage.ts index db499d2e..aa2302fb 100644 --- a/src/matrix/verification/SAS/stages/SelectVerificationMethodStage.ts +++ b/src/matrix/verification/SAS/stages/SelectVerificationMethodStage.ts @@ -86,7 +86,11 @@ export class SelectVerificationMethodStage extends BaseSASVerificationStage { private async findDeviceName(log: ILogItem) { await log.wrap("SelectVerificationMethodStage.findDeviceName", async () => { const device = await this.options.deviceTracker.deviceForId(this.otherUserId, this.otherUserDeviceId, this.options.hsApi, log); - this.otherDeviceName = device.displayName; + if (!device) { + log.log({ l: "Cannot find device", userId: this.otherUserId, deviceId: this.otherUserDeviceId }); + throw new Error("Cannot find device"); + } + this.otherDeviceName = device.unsigned.device_display_name ?? device.device_id; }) } diff --git a/src/platform/web/ui/session/toast/ToastCollectionView.ts b/src/platform/web/ui/session/toast/ToastCollectionView.ts index 18a9c8ed..7bce15ae 100644 --- a/src/platform/web/ui/session/toast/ToastCollectionView.ts +++ b/src/platform/web/ui/session/toast/ToastCollectionView.ts @@ -37,13 +37,13 @@ function toastViewModelToView(vm: BaseToastNotificationViewModel): IView { export class ToastCollectionView extends TemplateView { render(t: Builder, vm: ToastCollectionViewModel) { - const view = new ListView({ - list: vm.toastViewModels, - parentProvidesUpdates: false, - }, (vm: CallToastNotificationViewModel) => toastViewModelToView(vm)); - return t.div({ className: "ToastCollectionView" }, [ - t.if(vm => !!vm.toastViewModels, (t) => t.view(view)), + t.ifView(vm => !!vm.toastViewModels, t => { + return new ListView({ + list: vm.toastViewModels, + parentProvidesUpdates: false, + }, (vm: CallToastNotificationViewModel) => toastViewModelToView(vm)); + }), ]); } } From 9080263bc6f4b038aa15729ddc52da8000db7c21 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 28 Mar 2023 17:41:55 +0530 Subject: [PATCH 22/33] Fix SAS failing --- src/domain/session/SessionViewModel.js | 2 +- src/matrix/Session.js | 3 ++- src/matrix/verification/SAS/SASVerification.ts | 3 +++ src/matrix/verification/SAS/stages/SendMacStage.ts | 8 ++++++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js index b80c1c05..dc8589d1 100644 --- a/src/domain/session/SessionViewModel.js +++ b/src/domain/session/SessionViewModel.js @@ -347,7 +347,7 @@ export class SessionViewModel extends ViewModel { this._verificationViewModel = this.disposeTracked(this._verificationViewModel); } if (txnId) { - const request = this._client.session.crossSigning.receivedSASVerifications.get(txnId); + const request = this._client.session.crossSigning.get()?.receivedSASVerifications.get(txnId); this._verificationViewModel = this.track(new DeviceVerificationViewModel(this.childOptions({ session: this._client.session, request }))); } this.emitChange("activeMiddleViewModel"); diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 5d16f03b..c5aedcb3 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -368,7 +368,8 @@ export class Session { deviceMessageHandler: this._deviceMessageHandler, hsApi: this._hsApi, ownUserId: this.userId, - e2eeAccount: this._e2eeAccount + e2eeAccount: this._e2eeAccount, + deviceId: this.deviceId, }); if (await crossSigning.load(log)) { this._crossSigning.set(crossSigning); diff --git a/src/matrix/verification/SAS/SASVerification.ts b/src/matrix/verification/SAS/SASVerification.ts index d4b17143..ba2dc713 100644 --- a/src/matrix/verification/SAS/SASVerification.ts +++ b/src/matrix/verification/SAS/SASVerification.ts @@ -170,6 +170,9 @@ export function tests() { device_id: deviceId, keys: { [`ed25519:${deviceId}`]: "D8w9mrokGdEZPdPgrU0kQkYi4vZyzKEBfvGyZsGK7+Q", + }, + unsigned: { + device_display_name: "lala10", } }; }, diff --git a/src/matrix/verification/SAS/stages/SendMacStage.ts b/src/matrix/verification/SAS/stages/SendMacStage.ts index 14384d3a..3973e309 100644 --- a/src/matrix/verification/SAS/stages/SendMacStage.ts +++ b/src/matrix/verification/SAS/stages/SendMacStage.ts @@ -44,8 +44,12 @@ export class SendMacStage extends BaseSASVerificationStage { this.channel.id; const deviceKeyId = `ed25519:${this.ourUserDeviceId}`; - const deviceKeys = this.e2eeAccount.getDeviceKeysToSignWithCrossSigning(); - mac[deviceKeyId] = calculateMAC(deviceKeys.keys[deviceKeyId], baseInfo + deviceKeyId); + const device = await this.deviceTracker.deviceForId(this.ourUserId, this.ourUserDeviceId, this.hsApi, log); + if (!device) { + log.log({ l: "Fetching device failed", userId: this.ourUserId, deviceId: this.ourUserDeviceId }); + throw new Error("Fetching device for user failed!"); + } + mac[deviceKeyId] = calculateMAC(device.keys[deviceKeyId], baseInfo + deviceKeyId); keyList.push(deviceKeyId); const key = await this.deviceTracker.getCrossSigningKeyForUser(this.ourUserId, KeyUsage.Master, this.hsApi, log); From 6e054fcb805ec3250735590e762504c4923b1c60 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Tue, 28 Mar 2023 17:43:48 +0530 Subject: [PATCH 23/33] Update src/platform/web/ui/css/themes/element/theme.css Co-authored-by: Bruno Windels <274386+bwindels@users.noreply.github.com> --- src/platform/web/ui/css/themes/element/theme.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index 11501bce..5f13bb7c 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -1477,7 +1477,7 @@ button.RoomDetailsView_row::after { } .VerificationCompleteView__icon { - background: url("./icons//verified.svg?primary=accent-color") no-repeat; + background: url("./icons/verified.svg?primary=accent-color") no-repeat; background-size: contain; width: 128px; height: 128px; From 6fefc1549ef66e71ab0d205973d59269077423f6 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 28 Mar 2023 17:45:03 +0530 Subject: [PATCH 24/33] Change method name --- .../verification/DeviceVerificationViewModel.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/domain/session/verification/DeviceVerificationViewModel.ts b/src/domain/session/verification/DeviceVerificationViewModel.ts index 33be113f..4747dfdd 100644 --- a/src/domain/session/verification/DeviceVerificationViewModel.ts +++ b/src/domain/session/verification/DeviceVerificationViewModel.ts @@ -55,7 +55,7 @@ export class DeviceVerificationViewModel extends ErrorReportViewModel { - this.createViewModelAndEmit( + this.updateCurrentStageViewModel( new SelectMethodViewModel(this.childOptions({ sas: this.sas, stage: stage!, })) ); })); this.track(this.sas.disposableOn("EmojiGenerated", (stage) => { - this.createViewModelAndEmit( + this.updateCurrentStageViewModel( new VerifyEmojisViewModel(this.childOptions({ stage: stage!, })) ); })); this.track(this.sas.disposableOn("VerificationCancelled", (cancellation) => { - this.createViewModelAndEmit( + this.updateCurrentStageViewModel( new VerificationCancelledViewModel( this.childOptions({ cancellation: cancellation! }) ) ); })); this.track(this.sas.disposableOn("VerificationCompleted", (deviceId) => { - this.createViewModelAndEmit( + this.updateCurrentStageViewModel( new VerificationCompleteViewModel(this.childOptions({ deviceId: deviceId! })) ); })); } - private createViewModelAndEmit(vm) { + private updateCurrentStageViewModel(vm) { this._currentStageViewModel = this.disposeTracked(this._currentStageViewModel); this._currentStageViewModel = this.track(vm); this.emitChange("currentStageViewModel"); From b2d6a783659b1a3c896595080479ed21fa93b0c1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 28 Mar 2023 17:55:40 +0530 Subject: [PATCH 25/33] Remove property --- .../session/verification/DeviceVerificationViewModel.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/domain/session/verification/DeviceVerificationViewModel.ts b/src/domain/session/verification/DeviceVerificationViewModel.ts index 4747dfdd..76dab1a5 100644 --- a/src/domain/session/verification/DeviceVerificationViewModel.ts +++ b/src/domain/session/verification/DeviceVerificationViewModel.ts @@ -32,26 +32,24 @@ type Options = BaseOptions & { }; export class DeviceVerificationViewModel extends ErrorReportViewModel { - private session: Session; private sas: SASVerification; private _currentStageViewModel: any; constructor(options: Readonly) { super(options); - this.session = options.session; const sasRequest = options.request; if (options.request) { this.start(sasRequest); } else { // We are about to send the request - this.start(this.session.userId); + this.start(this.getOption("session").userId); } } private async start(requestOrUserId: SASRequest | string) { await this.logAndCatch("DeviceVerificationViewModel.start", (log) => { - const crossSigning = this.session.crossSigning.get(); + const crossSigning = this.getOption("session").crossSigning.get(); this.sas = crossSigning.startVerification(requestOrUserId, log); this.addEventListeners(); if (typeof requestOrUserId === "string") { From 38a82b2cb229dfae0a6bc81288c795399ec45bf0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 28 Mar 2023 17:57:19 +0530 Subject: [PATCH 26/33] Use getter --- .../session/verification/stages/VerifyEmojisViewModel.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/domain/session/verification/stages/VerifyEmojisViewModel.ts b/src/domain/session/verification/stages/VerifyEmojisViewModel.ts index 9f6dc92b..061a8e08 100644 --- a/src/domain/session/verification/stages/VerifyEmojisViewModel.ts +++ b/src/domain/session/verification/stages/VerifyEmojisViewModel.ts @@ -26,12 +26,12 @@ type Options = BaseOptions & { }; export class VerifyEmojisViewModel extends ErrorReportViewModel { - public isWaiting: boolean = false; + private _isWaiting: boolean = false; async setEmojiMatch(match: boolean) { await this.logAndCatch("VerifyEmojisViewModel.setEmojiMatch", async () => { await this.options.stage.setEmojiMatch(match); - this.isWaiting = true; + this._isWaiting = true; this.emitChange("isWaiting"); }); } @@ -43,4 +43,8 @@ export class VerifyEmojisViewModel extends ErrorReportViewModel Date: Tue, 28 Mar 2023 17:59:58 +0530 Subject: [PATCH 27/33] cancellation can be undefined --- .../verification/stages/VerificationCancelledViewModel.ts | 4 ++-- src/matrix/verification/SAS/channel/Channel.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/domain/session/verification/stages/VerificationCancelledViewModel.ts b/src/domain/session/verification/stages/VerificationCancelledViewModel.ts index 9e4d3e91..75cc0e5d 100644 --- a/src/domain/session/verification/stages/VerificationCancelledViewModel.ts +++ b/src/domain/session/verification/stages/VerificationCancelledViewModel.ts @@ -25,11 +25,11 @@ type Options = BaseOptions & { export class VerificationCancelledViewModel extends ViewModel { get cancelCode(): CancelReason { - return this.options.cancellation.code; + return this.options.cancellation!.code; } get isCancelledByUs(): boolean { - return this.options.cancellation.cancelledByUs; + return this.options.cancellation!.cancelledByUs; } gotoSettings() { diff --git a/src/matrix/verification/SAS/channel/Channel.ts b/src/matrix/verification/SAS/channel/Channel.ts index fc2260fa..10adbd7f 100644 --- a/src/matrix/verification/SAS/channel/Channel.ts +++ b/src/matrix/verification/SAS/channel/Channel.ts @@ -50,7 +50,7 @@ export interface IChannel { startMessage: any; initiatedByUs: boolean; isCancelled: boolean; - cancellation: { code: CancelReason, cancelledByUs: boolean }; + cancellation?: { code: CancelReason, cancelledByUs: boolean }; id: string; otherUserDeviceId: string; } @@ -80,7 +80,7 @@ export class ToDeviceChannel extends Disposables implements IChannel { public startMessage: any; public id: string; private _initiatedByUs: boolean; - private _cancellation: { code: CancelReason, cancelledByUs: boolean }; + private _cancellation?: { code: CancelReason, cancelledByUs: boolean }; /** * @@ -118,7 +118,7 @@ export class ToDeviceChannel extends Disposables implements IChannel { } } - get cancellation() { + get cancellation(): IChannel["cancellation"] { return this._cancellation; }; From 6a8007fe28f8b96c6a72448944eab9a2cc705eea Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 28 Mar 2023 18:03:08 +0530 Subject: [PATCH 28/33] Use switch case --- .../verification/DeviceVerificationView.ts | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/platform/web/ui/session/verification/DeviceVerificationView.ts b/src/platform/web/ui/session/verification/DeviceVerificationView.ts index a64f1464..94afbe90 100644 --- a/src/platform/web/ui/session/verification/DeviceVerificationView.ts +++ b/src/platform/web/ui/session/verification/DeviceVerificationView.ts @@ -31,22 +31,14 @@ export class DeviceVerificationView extends TemplateView vm.currentStageViewModel, (vm) => { - if (vm?.kind === "waiting-for-user") { - return new WaitingForOtherUserView(vm); + switch (vm) { + case "waiting-for-user": return new WaitingForOtherUserView(vm); + case "verification-cancelled": return new VerificationCancelledView(vm); + case "select-method": return new SelectMethodView(vm); + case "verify-emojis": return new VerifyEmojisView(vm); + case "verification-completed": return new VerificationCompleteView(vm); + default: return null; } - else if (vm?.kind === "verification-cancelled") { - return new VerificationCancelledView(vm); - } - else if (vm?.kind === "select-method") { - return new SelectMethodView(vm); - } - else if (vm?.kind === "verify-emojis") { - return new VerifyEmojisView(vm); - } - else if (vm?.kind === "verification-completed") { - return new VerificationCompleteView(vm); - } - return null; }) ]) } From 9884ee24eb5812bebcc6e466a3716f0a672572a0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 28 Mar 2023 18:13:38 +0530 Subject: [PATCH 29/33] Fix render error --- .../web/ui/session/verification/DeviceVerificationView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ui/session/verification/DeviceVerificationView.ts b/src/platform/web/ui/session/verification/DeviceVerificationView.ts index 94afbe90..d107ca13 100644 --- a/src/platform/web/ui/session/verification/DeviceVerificationView.ts +++ b/src/platform/web/ui/session/verification/DeviceVerificationView.ts @@ -31,7 +31,7 @@ export class DeviceVerificationView extends TemplateView vm.currentStageViewModel, (vm) => { - switch (vm) { + switch (vm?.kind) { case "waiting-for-user": return new WaitingForOtherUserView(vm); case "verification-cancelled": return new VerificationCancelledView(vm); case "select-method": return new SelectMethodView(vm); From 7eb1c09a7546514f81b1aff7304c41a0164d946f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 28 Mar 2023 18:51:09 +0530 Subject: [PATCH 30/33] Use e2ee account --- src/matrix/verification/SAS/stages/SendMacStage.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/matrix/verification/SAS/stages/SendMacStage.ts b/src/matrix/verification/SAS/stages/SendMacStage.ts index 3973e309..30a45e6e 100644 --- a/src/matrix/verification/SAS/stages/SendMacStage.ts +++ b/src/matrix/verification/SAS/stages/SendMacStage.ts @@ -44,12 +44,8 @@ export class SendMacStage extends BaseSASVerificationStage { this.channel.id; const deviceKeyId = `ed25519:${this.ourUserDeviceId}`; - const device = await this.deviceTracker.deviceForId(this.ourUserId, this.ourUserDeviceId, this.hsApi, log); - if (!device) { - log.log({ l: "Fetching device failed", userId: this.ourUserId, deviceId: this.ourUserDeviceId }); - throw new Error("Fetching device for user failed!"); - } - mac[deviceKeyId] = calculateMAC(device.keys[deviceKeyId], baseInfo + deviceKeyId); + const deviceKeys = this.e2eeAccount.getUnsignedDeviceKey(); + mac[deviceKeyId] = calculateMAC(deviceKeys.keys[deviceKeyId], baseInfo + deviceKeyId); keyList.push(deviceKeyId); const key = await this.deviceTracker.getCrossSigningKeyForUser(this.ourUserId, KeyUsage.Master, this.hsApi, log); From ce018781f112368685482cfaadcae98d3635f37c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 29 Mar 2023 14:58:43 +0530 Subject: [PATCH 31/33] Make code more clear --- src/matrix/verification/CrossSigning.ts | 49 +++++++++++++++---------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/matrix/verification/CrossSigning.ts b/src/matrix/verification/CrossSigning.ts index 2273c442..b75ed1f4 100644 --- a/src/matrix/verification/CrossSigning.ts +++ b/src/matrix/verification/CrossSigning.ts @@ -119,25 +119,7 @@ export class CrossSigning { this.deviceMessageHandler = options.deviceMessageHandler; this.deviceMessageHandler.on("message", async ({ unencrypted: unencryptedEvent }) => { - const txnId = unencryptedEvent.content.transaction_id; - if (unencryptedEvent.type === VerificationEventType.Cancel) { - this.receivedSASVerifications.remove(txnId); - return; - } - if (this.sasVerificationInProgress && - ( - !this.sasVerificationInProgress.finished || - // If the start message is for the previous sasverification, ignore it. - this.sasVerificationInProgress.channel.id === txnId - )) { - return; - } - if (unencryptedEvent.type === VerificationEventType.Request || - unencryptedEvent.type === VerificationEventType.Start) { - this.platform.logger.run("Start verification from request", () => { - this.receivedSASVerifications.set(txnId, new SASRequest(unencryptedEvent)); - }); - } + this._handleSASDeviceMessage(unencryptedEvent); }) } @@ -224,6 +206,35 @@ export class CrossSigning { return this.sasVerificationInProgress; } + private _handleSASDeviceMessage(event: any) { + const txnId = event.content.transaction_id; + /** + * If we receive an event for the current/previously finished + * SAS verification, we should ignore it because SASVerification + * object will take care of it (if needed). + */ + const shouldIgnoreEvent = this.sasVerificationInProgress?.channel.id === txnId; + if (shouldIgnoreEvent) { return; } + /** + * 1. If we receive the cancel message, we need to update the requests map. + * 2. If we receive an starting message (viz request/start), we need to create the SASRequest from it. + */ + switch (event.type) { + case VerificationEventType.Cancel: + this.receivedSASVerifications.remove(txnId); + return; + case VerificationEventType.Request: + case VerificationEventType.Start: + this.platform.logger.run("Create SASRequest", () => { + this.receivedSASVerifications.set(txnId, new SASRequest(event)); + }); + return; + default: + // we don't care about this event! + return; + } + } + /** returns our own device key signed by our self-signing key. Other signatures will be missing. */ async signOwnDevice(log: ILogItem): Promise { return log.wrap("CrossSigning.signOwnDevice", async log => { From 67cc426b855a804323989a2f446e38c9b278c36e Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 30 Mar 2023 09:37:46 +0000 Subject: [PATCH 32/33] Update src/matrix/verification/CrossSigning.ts --- src/matrix/verification/CrossSigning.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/verification/CrossSigning.ts b/src/matrix/verification/CrossSigning.ts index b75ed1f4..b3d21443 100644 --- a/src/matrix/verification/CrossSigning.ts +++ b/src/matrix/verification/CrossSigning.ts @@ -210,7 +210,7 @@ export class CrossSigning { const txnId = event.content.transaction_id; /** * If we receive an event for the current/previously finished - * SAS verification, we should ignore it because SASVerification + * SAS verification, we should ignore it because the device channel * object will take care of it (if needed). */ const shouldIgnoreEvent = this.sasVerificationInProgress?.channel.id === txnId; From f158197685d43c83cc636e2a24bc0f0015a60a4a Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 30 Mar 2023 09:37:54 +0000 Subject: [PATCH 33/33] Update src/matrix/verification/CrossSigning.ts --- src/matrix/verification/CrossSigning.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/verification/CrossSigning.ts b/src/matrix/verification/CrossSigning.ts index b3d21443..a6fb3091 100644 --- a/src/matrix/verification/CrossSigning.ts +++ b/src/matrix/verification/CrossSigning.ts @@ -211,7 +211,7 @@ export class CrossSigning { /** * If we receive an event for the current/previously finished * SAS verification, we should ignore it because the device channel - * object will take care of it (if needed). + * object (who also listens for to_device messages) will take care of it (if needed). */ const shouldIgnoreEvent = this.sasVerificationInProgress?.channel.id === txnId; if (shouldIgnoreEvent) { return; }