This repository has been archived by the owner on Sep 11, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 827
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve decryption error UI by consolidating error messages and provi…
…ding instructions when possible (#9544) * Improve decryption error UI by consolidating error messages and providing instructions when possible * Fix TS strict errors * Rename .scss to .pcss * Avoid accessing clipboard, Cypress doesn't like it * Display DecryptionFailureBar alongside other AuxPanel bars * Add comments * Add small margin off-screen for visible decryption failures * Fix some more TS strict errors * Add unit tests for DecryptionFailureBar * Add button to resend key requests manually * Remove references to matrix-js-sdk crypto internals * Add hysteresis to visible decryption failures * Add comment Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Add comment Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Don't create empty div if we're not showing resend requests button * cancel updateSessions on unmount * Update unit tests * Fix lint and implicit any * Simplify visible event bounds checking * Adjust cypress test descriptions * Add percy snapshots * Update src/components/structures/TimelinePanel.tsx Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Add comments on TimelinePanel IState * comment * Add names to percy snapshots * Show Resend Key Requests button when there are sessions that haven't already been requested via this bar * We no longer request keys from senders * update i18n * update expected text in cypress test * don't download keys ourselves, update device info in response to updates from client * fix ts strict errors * visibledecryptionfailures undefined handling * Fix implicitAny errors Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
- Loading branch information
1 parent
b728b27
commit 4724506
Showing
18 changed files
with
1,779 additions
and
165 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
/* | ||
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 type { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; | ||
import type { ISasEvent } from "matrix-js-sdk/src/crypto/verification/SAS"; | ||
import type { MatrixClient } from "matrix-js-sdk/src/matrix"; | ||
import { SynapseInstance } from "../../plugins/synapsedocker"; | ||
import { UserCredentials } from "../../support/login"; | ||
import Chainable = Cypress.Chainable; | ||
|
||
const ROOM_NAME = "Test room"; | ||
const TEST_USER = "Alia"; | ||
const BOT_USER = "Benjamin"; | ||
|
||
type EmojiMapping = [emoji: string, name: string]; | ||
|
||
const waitForVerificationRequest = (cli: MatrixClient): Promise<VerificationRequest> => { | ||
return new Promise<VerificationRequest>((resolve) => { | ||
const onVerificationRequestEvent = (request: VerificationRequest) => { | ||
// @ts-ignore CryptoEvent is not exported to window.matrixcs; using the string value here | ||
cli.off("crypto.verification.request", onVerificationRequestEvent); | ||
resolve(request); | ||
}; | ||
// @ts-ignore | ||
cli.on("crypto.verification.request", onVerificationRequestEvent); | ||
}); | ||
}; | ||
|
||
const handleVerificationRequest = (request: VerificationRequest): Chainable<EmojiMapping[]> => { | ||
return cy.wrap( | ||
new Promise<EmojiMapping[]>((resolve) => { | ||
const onShowSas = (event: ISasEvent) => { | ||
verifier.off("show_sas", onShowSas); | ||
event.confirm(); | ||
resolve(event.sas.emoji); | ||
}; | ||
|
||
const verifier = request.beginKeyVerification("m.sas.v1"); | ||
verifier.on("show_sas", onShowSas); | ||
verifier.verify(); | ||
}), | ||
); | ||
}; | ||
|
||
describe("Decryption Failure Bar", () => { | ||
let synapse: SynapseInstance | undefined; | ||
let testUser: UserCredentials | undefined; | ||
let bot: MatrixClient | undefined; | ||
let roomId: string; | ||
|
||
beforeEach(function () { | ||
cy.startSynapse("default").then((syn: SynapseInstance) => { | ||
synapse = syn; | ||
cy.initTestUser(synapse, TEST_USER) | ||
.then((creds: UserCredentials) => { | ||
testUser = creds; | ||
}) | ||
.then(() => { | ||
cy.getBot(synapse, { displayName: BOT_USER }).then((cli) => { | ||
bot = cli; | ||
}); | ||
}) | ||
.then(() => { | ||
cy.createRoom({ name: ROOM_NAME }).then((id) => { | ||
roomId = id; | ||
}); | ||
}) | ||
.then(() => { | ||
cy.inviteUser(roomId, bot.getUserId()); | ||
cy.visit("/#/room/" + roomId); | ||
cy.contains(".mx_TextualEvent", BOT_USER + " joined the room").should("exist"); | ||
}) | ||
.then(() => { | ||
cy.getClient() | ||
.then(async (cli) => { | ||
await cli.setRoomEncryption(roomId, { algorithm: "m.megolm.v1.aes-sha2" }); | ||
await bot.setRoomEncryption(roomId, { algorithm: "m.megolm.v1.aes-sha2" }); | ||
}) | ||
.then(() => { | ||
bot.getRoom(roomId).setBlacklistUnverifiedDevices(true); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
cy.stopSynapse(synapse); | ||
}); | ||
|
||
it( | ||
"should prompt the user to verify, if this device isn't verified " + | ||
"and there are other verified devices or backups", | ||
() => { | ||
let otherDevice: MatrixClient | undefined; | ||
cy.loginBot(synapse, testUser.username, testUser.password, {}) | ||
.then(async (cli) => { | ||
otherDevice = cli; | ||
await otherDevice.bootstrapCrossSigning({ | ||
authUploadDeviceSigningKeys: async (makeRequest) => { | ||
await makeRequest({}); | ||
}, | ||
setupNewCrossSigning: true, | ||
}); | ||
}) | ||
.then(() => { | ||
cy.botSendMessage(bot, roomId, "test"); | ||
cy.wait(5000); | ||
cy.get(".mx_DecryptionFailureBar .mx_DecryptionFailureBar_message_headline").should( | ||
"have.text", | ||
"Verify this device to access all messages", | ||
); | ||
|
||
cy.percySnapshot("DecryptionFailureBar prompts user to verify"); | ||
|
||
cy.contains(".mx_DecryptionFailureBar_button", "Resend key requests").should("not.exist"); | ||
cy.contains(".mx_DecryptionFailureBar_button", "Verify").click(); | ||
|
||
const verificationRequestPromise = waitForVerificationRequest(otherDevice); | ||
cy.get(".mx_CompleteSecurity_actionRow .mx_AccessibleButton").click(); | ||
cy.wrap(verificationRequestPromise).then((verificationRequest: VerificationRequest) => { | ||
cy.wrap(verificationRequest.accept()); | ||
handleVerificationRequest(verificationRequest).then((emojis) => { | ||
cy.get(".mx_VerificationShowSas_emojiSas_block").then((emojiBlocks) => { | ||
emojis.forEach((emoji: EmojiMapping, index: number) => { | ||
expect(emojiBlocks[index].textContent.toLowerCase()).to.eq(emoji[0] + emoji[1]); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
cy.contains(".mx_AccessibleButton", "They match").click(); | ||
cy.get(".mx_VerificationPanel_verified_section .mx_E2EIcon_verified").should("exist"); | ||
cy.contains(".mx_AccessibleButton", "Got it").click(); | ||
|
||
cy.get(".mx_DecryptionFailureBar .mx_DecryptionFailureBar_message_headline").should( | ||
"have.text", | ||
"Open another device to load encrypted messages", | ||
); | ||
|
||
cy.percySnapshot( | ||
"DecryptionFailureBar prompts user to open another device, with Resend Key Requests button", | ||
); | ||
|
||
cy.intercept("/_matrix/client/r0/sendToDevice/m.room_key_request/*").as("keyRequest"); | ||
cy.contains(".mx_DecryptionFailureBar_button", "Resend key requests").click(); | ||
cy.wait("@keyRequest"); | ||
cy.contains(".mx_DecryptionFailureBar_button", "Resend key requests").should("not.exist"); | ||
|
||
cy.percySnapshot( | ||
"DecryptionFailureBar prompts user to open another device, " + "without Resend Key Requests button", | ||
); | ||
}, | ||
); | ||
|
||
it( | ||
"should prompt the user to reset keys, if this device isn't verified " + | ||
"and there are no other verified devices or backups", | ||
() => { | ||
cy.loginBot(synapse, testUser.username, testUser.password, {}).then(async (cli) => { | ||
await cli.bootstrapCrossSigning({ | ||
authUploadDeviceSigningKeys: async (makeRequest) => { | ||
await makeRequest({}); | ||
}, | ||
setupNewCrossSigning: true, | ||
}); | ||
await cli.logout(true); | ||
}); | ||
|
||
cy.botSendMessage(bot, roomId, "test"); | ||
cy.wait(5000); | ||
cy.get(".mx_DecryptionFailureBar .mx_DecryptionFailureBar_message_headline").should( | ||
"have.text", | ||
"Reset your keys to prevent future decryption errors", | ||
); | ||
|
||
cy.percySnapshot("DecryptionFailureBar prompts user to reset keys"); | ||
|
||
cy.contains(".mx_DecryptionFailureBar_button", "Reset").click(); | ||
|
||
cy.get(".mx_Dialog").within(() => { | ||
cy.contains(".mx_Dialog_primary", "Continue").click(); | ||
cy.get(".mx_CreateSecretStorageDialog_recoveryKey code").invoke("text").as("securityKey"); | ||
// Clicking download instead of Copy because of https://github.com/cypress-io/cypress/issues/2851 | ||
cy.contains(".mx_AccessibleButton", "Download").click(); | ||
cy.contains(".mx_Dialog_primary:not([disabled])", "Continue").click(); | ||
}); | ||
|
||
cy.get(".mx_DecryptionFailureBar .mx_DecryptionFailureBar_message_headline").should( | ||
"have.text", | ||
"Some messages could not be decrypted", | ||
); | ||
|
||
cy.percySnapshot("DecryptionFailureBar displays general message with no call to action"); | ||
}, | ||
); | ||
|
||
it("should appear and disappear as undecryptable messages enter and leave view", () => { | ||
cy.getClient().then((cli) => { | ||
for (let i = 0; i < 25; i++) { | ||
cy.botSendMessage(cli, roomId, `test ${i}`); | ||
} | ||
}); | ||
cy.botSendMessage(bot, roomId, "test"); | ||
cy.get(".mx_DecryptionFailureBar").should("exist"); | ||
cy.get(".mx_DecryptionFailureBar .mx_Spinner").should("exist"); | ||
|
||
cy.percySnapshot("DecryptionFailureBar displays loading spinner"); | ||
|
||
cy.wait(5000); | ||
cy.get(".mx_DecryptionFailureBar .mx_Spinner").should("not.exist"); | ||
cy.get(".mx_DecryptionFailureBar .mx_DecryptionFailureBar_icon").should("exist"); | ||
|
||
cy.get(".mx_RoomView_messagePanel").scrollTo("top"); | ||
cy.get(".mx_DecryptionFailureBar").should("not.exist"); | ||
|
||
cy.botSendMessage(bot, roomId, "another test"); | ||
cy.get(".mx_DecryptionFailureBar").should("not.exist"); | ||
|
||
cy.get(".mx_RoomView_messagePanel").scrollTo("bottom"); | ||
cy.get(".mx_DecryptionFailureBar").should("exist"); | ||
}); | ||
}); |
Oops, something went wrong.