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

Commit 67cb8b7

Browse files
authored
Force verification even for refreshed clients (#44)
* Force verification even for refreshed cients Set a flag on login to remember that the device needs to be verified so that we don't forget if the user refreshes the page, but still allow user with an existing unverified session to stay logged in. * Hopefully make matrixchat tests pass? Much, much tweaking to make the matrixchat tests pass again. Should hopefully make them a bit more solid in general with judicious use of waitFor rather than flushPromises(). Also lots of fun to stop the state bleeding between tests. * Manual yarn.lock manipulation to hopefully resolve infinite package sadness * Make final test pass(?) Mock out the createClient method to return the same client, because we've mocked the peg to always return that client, so if we let the code make another one having still overridden the peg, everything becomes cursed. Also mock out the autodiscovery stuff rather than relying on fetch-mock. * another waitFor * death to flushPromises * Put the logged in dispatch back Actually it breaks all sorts of other things too, having fixed all the MatrixChat tests (although this is useful anyway). * Try displaying the screen in onClientStarted instead * Put post login screen back in logged in but move ready transition to avoid flash of main UI * Rejig more in the hope it does the right thing * Make hook work before push rules are fetched * Add test for unskippable verification * Add test for use case selection * Fix test * Add playwright test for unskippable verification * Remove console log * Add log message to log line * Add tsdoc * Use useTypedEventEmitter * Remove commented code * Use catch instead of empty then on unawaited promises or in one case just await it because the caller was async anyway * Add new mock
1 parent 5d6c19c commit 67cb8b7

File tree

11 files changed

+391
-159
lines changed

11 files changed

+391
-159
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@
198198
"axe-core": "4.10.0",
199199
"babel-jest": "^29.0.0",
200200
"blob-polyfill": "^9.0.0",
201+
"core-js": "^3.38.1",
201202
"eslint": "8.57.1",
202203
"eslint-config-google": "^0.14.0",
203204
"eslint-config-prettier": "^9.0.0",

playwright/e2e/login/login.spec.ts

+155-10
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,85 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
66
Please see LICENSE files in the repository root for full details.
77
*/
88

9+
import { Page } from "playwright-core";
10+
911
import { expect, test } from "../../element-web-test";
1012
import { doTokenRegistration } from "./utils";
1113
import { isDendrite } from "../../plugins/homeserver/dendrite";
1214
import { selectHomeserver } from "../utils";
15+
import { Credentials, HomeserverInstance } from "../../plugins/homeserver";
16+
17+
const username = "user1234";
18+
const password = "p4s5W0rD";
19+
20+
// Pre-generated dummy signing keys to create an account that has signing keys set.
21+
// Note the signatures are specific to the username and must be valid or the HS will reject the keys.
22+
const DEVICE_SIGNING_KEYS_BODY = {
23+
master_key: {
24+
keys: {
25+
"ed25519:6qCouJsi2j7DzOmpxPTBALpvDTqa8p2mjrQR2P8wEbg": "6qCouJsi2j7DzOmpxPTBALpvDTqa8p2mjrQR2P8wEbg",
26+
},
27+
signatures: {
28+
"@user1234:localhost": {
29+
"ed25519:6qCouJsi2j7DzOmpxPTBALpvDTqa8p2mjrQR2P8wEbg":
30+
"mvwqsYiGa2gPH6ueJsiJnceHMrZhf1pqIMGxkvKisN3ucz8sU7LwyzndbYaLkUKEDx1JuOKFfZ9Mb3mqc7PMBQ",
31+
"ed25519:SRHVWTNVBH":
32+
"HVGmVIzsJe3d+Un/6S9tXPsU7YA8HjZPdxogVzdjEFIU8OjLyElccvjupow0rVWgkEqU8sO21LIHw9cWRZEmDw",
33+
},
34+
},
35+
usage: ["master"],
36+
user_id: "@user1234:localhost",
37+
},
38+
self_signing_key: {
39+
keys: {
40+
"ed25519:eqzRly4S1GvTA36v48hOKokHMtYBLm02zXRgPHue5/8": "eqzRly4S1GvTA36v48hOKokHMtYBLm02zXRgPHue5/8",
41+
},
42+
signatures: {
43+
"@user1234:localhost": {
44+
"ed25519:6qCouJsi2j7DzOmpxPTBALpvDTqa8p2mjrQR2P8wEbg":
45+
"M2rt5xs+23egbVUwUcZuU7pMpn0chBNC5rpdyZGayfU3FDlx1DbopbakIcl5v4uOSGMbqUotyzkE6CchB+dgDw",
46+
},
47+
},
48+
usage: ["self_signing"],
49+
user_id: "@user1234:localhost",
50+
},
51+
user_signing_key: {
52+
keys: {
53+
"ed25519:h6C7sonjKSSa/VMvmpmFnwMA02H2rKIMSYZ2ddwgJn4": "h6C7sonjKSSa/VMvmpmFnwMA02H2rKIMSYZ2ddwgJn4",
54+
},
55+
signatures: {
56+
"@user1234:localhost": {
57+
"ed25519:6qCouJsi2j7DzOmpxPTBALpvDTqa8p2mjrQR2P8wEbg":
58+
"5ZMJ7SG2qr76vU2nITKap88AxLZ/RZQmF/mBcAcVZ9Bknvos3WQp8qN9jKuiqOHCq/XpPORA6XBmiDIyPqTFAA",
59+
},
60+
},
61+
usage: ["user_signing"],
62+
user_id: "@user1234:localhost",
63+
},
64+
auth: {
65+
type: "m.login.password",
66+
identifier: { type: "m.id.user", user: "@user1234:localhost" },
67+
password: password,
68+
},
69+
};
70+
71+
async function login(page: Page, homeserver: HomeserverInstance) {
72+
await page.getByRole("link", { name: "Sign in" }).click();
73+
await selectHomeserver(page, homeserver.config.baseUrl);
74+
75+
await page.getByRole("textbox", { name: "Username" }).fill(username);
76+
await page.getByPlaceholder("Password").fill(password);
77+
await page.getByRole("button", { name: "Sign in" }).click();
78+
}
1379

1480
test.describe("Login", () => {
1581
test.describe("Password login", () => {
1682
test.use({ startHomeserverOpts: "consent" });
1783

18-
const username = "user1234";
19-
const password = "p4s5W0rD";
84+
let creds: Credentials;
2085

2186
test.beforeEach(async ({ homeserver }) => {
22-
await homeserver.registerUser(username, password);
87+
creds = await homeserver.registerUser(username, password);
2388
});
2489

2590
test("Loads the welcome page by default; then logs in with an existing account and lands on the home screen", async ({
@@ -65,17 +130,97 @@ test.describe("Login", () => {
65130

66131
test("Follows the original link after login", async ({ page, homeserver }) => {
67132
await page.goto("/#/room/!room:id"); // should redirect to the welcome page
68-
await page.getByRole("link", { name: "Sign in" }).click();
69-
70-
await selectHomeserver(page, homeserver.config.baseUrl);
71-
72-
await page.getByRole("textbox", { name: "Username" }).fill(username);
73-
await page.getByPlaceholder("Password").fill(password);
74-
await page.getByRole("button", { name: "Sign in" }).click();
133+
await login(page, homeserver);
75134

76135
await expect(page).toHaveURL(/\/#\/room\/!room:id$/);
77136
await expect(page.getByRole("button", { name: "Join the discussion" })).toBeVisible();
78137
});
138+
139+
test.describe("verification after login", () => {
140+
test("Shows verification prompt after login if signing keys are set up, skippable by default", async ({
141+
page,
142+
homeserver,
143+
request,
144+
}) => {
145+
const res = await request.post(
146+
`${homeserver.config.baseUrl}/_matrix/client/v3/keys/device_signing/upload`,
147+
{ headers: { Authorization: `Bearer ${creds.accessToken}` }, data: DEVICE_SIGNING_KEYS_BODY },
148+
);
149+
if (res.status() / 100 !== 2) {
150+
console.log("Uploading dummy keys failed", await res.json());
151+
}
152+
expect(res.status() / 100).toEqual(2);
153+
154+
await page.goto("/");
155+
await login(page, homeserver);
156+
157+
await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible();
158+
159+
await expect(page.getByRole("button", { name: "Skip verification for now" })).toBeVisible();
160+
});
161+
162+
test.describe("with force_verification off", () => {
163+
test.use({
164+
config: {
165+
force_verification: false,
166+
},
167+
});
168+
169+
test("Shows skippable verification prompt after login if signing keys are set up", async ({
170+
page,
171+
homeserver,
172+
request,
173+
}) => {
174+
const res = await request.post(
175+
`${homeserver.config.baseUrl}/_matrix/client/v3/keys/device_signing/upload`,
176+
{ headers: { Authorization: `Bearer ${creds.accessToken}` }, data: DEVICE_SIGNING_KEYS_BODY },
177+
);
178+
if (res.status() / 100 !== 2) {
179+
console.log("Uploading dummy keys failed", await res.json());
180+
}
181+
expect(res.status() / 100).toEqual(2);
182+
183+
await page.goto("/");
184+
await login(page, homeserver);
185+
186+
await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible();
187+
188+
await expect(page.getByRole("button", { name: "Skip verification for now" })).toBeVisible();
189+
});
190+
});
191+
192+
test.describe("with force_verification on", () => {
193+
test.use({
194+
config: {
195+
force_verification: true,
196+
},
197+
});
198+
199+
test("Shows unskippable verification prompt after login if signing keys are set up", async ({
200+
page,
201+
homeserver,
202+
request,
203+
}) => {
204+
console.log(`uid ${creds.userId} body`, DEVICE_SIGNING_KEYS_BODY);
205+
const res = await request.post(
206+
`${homeserver.config.baseUrl}/_matrix/client/v3/keys/device_signing/upload`,
207+
{ headers: { Authorization: `Bearer ${creds.accessToken}` }, data: DEVICE_SIGNING_KEYS_BODY },
208+
);
209+
if (res.status() / 100 !== 2) {
210+
console.log("Uploading dummy keys failed", await res.json());
211+
}
212+
expect(res.status() / 100).toEqual(2);
213+
214+
await page.goto("/");
215+
await login(page, homeserver);
216+
217+
const h1 = await page.getByRole("heading", { name: "Verify this device", level: 1 });
218+
await expect(h1).toBeVisible();
219+
220+
expect(h1.locator(".mx_CompleteSecurity_skip")).not.toBeVisible();
221+
});
222+
});
223+
});
79224
});
80225

81226
// tests for old-style SSO login, in which we exchange tokens with Synapse, and Synapse talks to an auth server

src/Lifecycle.ts

+8
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,8 @@ async function doSetLoggedIn(
824824
}
825825
checkSessionLock();
826826

827+
// We are now logged in, so fire this. We have yet to start the client but the
828+
// client_started dispatch is for that.
827829
dis.fire(Action.OnLoggedIn);
828830

829831
const clientPegOpts: MatrixClientPegAssignOpts = {};
@@ -846,6 +848,12 @@ async function doSetLoggedIn(
846848
// Run the migrations after the MatrixClientPeg has been assigned
847849
SettingsStore.runMigrations(isFreshLogin);
848850

851+
if (isFreshLogin && !credentials.guest) {
852+
// For newly registered users, set a flag so that we force them to verify,
853+
// (we don't want to force users with existing sessions to verify though)
854+
localStorage.setItem("must_verify_device", "true");
855+
}
856+
849857
return client;
850858
}
851859

src/SdkConfig.ts

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const DEFAULTS: DeepReadonly<IConfigOptions> = {
2424
integrations_rest_url: "https://scalar.vector.im/api",
2525
uisi_autorageshake_app: "element-auto-uisi",
2626
show_labs_settings: false,
27+
force_verification: false,
2728

2829
jitsi: {
2930
preferred_domain: "meet.element.io",

0 commit comments

Comments
 (0)