1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-28 07:13:29 +00:00

Complete emergency access test

This commit is contained in:
Matt Gibson
2026-01-26 16:56:26 -08:00
parent 1d551501b7
commit dd419eabfd
2 changed files with 57 additions and 81 deletions

View File

@@ -1,67 +1,21 @@
import { test as base, expect, Page } from "@playwright/test";
import { expect } from "@playwright/test";
import {
test,
EmergencyAccessInviteQuery,
Play,
Scene,
SingleUserSceneTemplate,
expectUnlockedAs,
} from "@bitwarden/playwright-helpers";
import { UserId } from "@bitwarden/user-core";
async function authenticate(page: Page, email: string) {
const scene = await Play.scene(new SingleUserSceneTemplate({ email, premium: true }));
test.describe("Emergency Access", () => {
test("Account takeover", async ({ auth }) => {
const grantee = await auth.authenticate("grantee@bitwarden.com", "asdfasdfasdf", {
premium: false,
});
const grantor = await auth.authenticate("grantor@bitwarden.com", "asdfasdfasdf", {
premium: true,
});
await page.goto("/#/login");
await page.getByRole("textbox", { name: "Email address (required)" }).click();
await page.getByRole("textbox", { name: "Email address (required)" }).fill(scene.mangle(email));
await page.getByRole("textbox", { name: "Email address (required)" }).press("Enter");
await page.getByRole("textbox", { name: "Master password (required)" }).click();
await page
.getByRole("textbox", { name: "Master password (required)" })
.fill(scene.mangle("asdfasdfasdf"));
await page.getByRole("button", { name: "Log in with master password" }).click();
await expect(page.getByRole("button", { name: "Add it later" })).toBeVisible();
await page.getByRole("button", { name: "Add it later" }).click();
await expect(page.locator("bit-simple-dialog")).toContainText(
"You can't autofill passwords without the browser extension",
);
await page.getByRole("link", { name: "Skip to web app" }).click();
await expect(page.locator("app-vault")).toContainText("There are no items to list. New item");
return scene;
}
type MyFixtures = {
grantee: MyFixture2;
grantor: MyFixture2;
};
type MyFixture2 = {
page: Page;
scene: Scene<UserId>;
};
const test = base.extend<MyFixtures>({
// Person getting access
grantee: async ({ browser }, use) => {
const context = await browser.newContext();
const page = await context.newPage();
const scene = await authenticate(page, "grantee@bitwarden.com");
await use({ page, scene });
//await context.close();
},
// Person giving access
grantor: async ({ browser }, use) => {
const context = await browser.newContext();
const page = await context.newPage();
const scene = await authenticate(page, "grantor@bitwarden.com");
await use({ page, scene });
//await context.close();
},
});
base.describe("Emergency Access", () => {
test.skip("Account takeover", async ({ grantee, grantor }) => {
const granteeEmail = grantee.scene.mangle("grantee@bitwarden.com");
// Add a new emergency contact
@@ -80,8 +34,8 @@ base.describe("Emergency Access", () => {
await grantee.page.goto(`/#${inviteUrl}`);
// Confirm the invite
await grantor.page.goto("/#");
await grantor.page.goto("/#/settings/emergency-access");
await grantor.page.reload();
await expect(await grantor.page.getByRole("cell", { name: granteeEmail })).toBeVisible();
await grantor.page.getByRole("button", { name: "Options" }).click();
await grantor.page.getByRole("menuitem", { name: "Confirm" }).click();
@@ -89,20 +43,21 @@ base.describe("Emergency Access", () => {
// Request access
await grantee.page.goto("/#/settings/emergency-access");
await grantee.page.reload();
await grantee.page.getByRole("button", { name: "Options" }).click();
await grantee.page.getByRole("menuitem", { name: "Request Access" }).click();
await grantee.page.getByRole("button", { name: "Request Access" }).click();
// Approve access
await grantor.page.goto("/#");
await grantor.page.goto("/#/settings/emergency-access");
await grantor.page.reload();
await grantor.page.getByRole("button", { name: "Options" }).click();
await grantor.page.getByRole("menuitem", { name: "Approve" }).click();
await grantor.page.getByRole("button", { name: "Approve" }).click();
// Initiate takeover
await grantee.page.goto("/#");
await grantee.page.goto("/#/settings/emergency-access");
await grantee.page.reload();
await grantee.page.getByRole("button", { name: "Options" }).click();
await grantee.page.getByRole("menuitem", { name: "Takeover" }).click();
@@ -115,8 +70,8 @@ base.describe("Emergency Access", () => {
await grantee.page.getByRole("button", { name: "Save" }).click();
await grantee.page.getByRole("button", { name: "Yes" }).click();
// TODO: Confirm the new password works by logging out and back in
// await new Promise(() => {});
// Confirm with
const { page: newGranteePage } = await auth.authenticateForScene(grantee.scene, "qwertyqwerty");
await expectUnlockedAs(granteeEmail, newGranteePage);
});
});

View File

@@ -4,11 +4,14 @@ import * as path from "path";
import { Browser, Page, test, TestFixture } from "@playwright/test";
import { webServerBaseUrl } from "@playwright-config";
import * as playwright from "playwright";
import { Except, Simplify } from "type-fest";
// Playwright doesn't expose this type, so we duplicate it here
type BrowserName = "chromium" | "firefox" | "webkit";
import { Play, SingleUserScene, SingleUserSceneTemplate } from "@bitwarden/playwright-helpers";
import { extractTUpType } from "../scene-templates/scene-template";
import { addInitScriptForPlayId } from "./page-extension";
const hostname = new URL(webServerBaseUrl).hostname;
@@ -41,6 +44,8 @@ type AuthenticatedContext = {
scene: SingleUserScene;
};
type SessionOptions = Simplify<Except<extractTUpType<SingleUserSceneTemplate>, "email">>;
/**
* A map of already authenticated emails to their scenes.
*/
@@ -74,16 +79,14 @@ export class AuthFixture {
}
}
async page(): Promise<Page> {
if (!this._page) {
if (!this._browser) {
await this.init();
}
const context = await this._browser.newContext();
this._page = await context.newPage();
await addInitScriptForPlayId(this._page, process.env.PLAY_ID!);
async newPage(): Promise<Page> {
if (!this._browser) {
await this.init();
}
return this._page;
const context = await this._browser.newContext();
const page = await context.newPage();
await addInitScriptForPlayId(page, process.env.PLAY_ID!);
return page;
}
/**
@@ -95,17 +98,21 @@ export class AuthFixture {
* @param password password of the user
* @returns The authenticated page and scene used to scaffold the user
*/
async authenticate(email: string, password: string): Promise<AuthenticatedContext> {
async authenticate(
email: string,
password: string,
options: SessionOptions = {},
): Promise<AuthenticatedContext> {
if (AuthenticatedEmails.has(email)) {
return await this.resumeSession(email, password);
}
// start a new session
return await this.newSession(email, password);
return await this.newSession(email, password, options);
}
async resumeSession(email: string, password: string): Promise<AuthenticatedContext> {
const page = await this.page();
const page = await this.newPage();
if (AuthenticatedEmails.get(email)!.password !== password) {
throw new Error(
`Email ${email} is already authenticated with a different password (${
@@ -133,15 +140,26 @@ export class AuthFixture {
};
}
async newSession(email: string, password: string): Promise<AuthenticatedContext> {
const page = await this.page();
const scene = await Play.scene(new SingleUserSceneTemplate({ email }));
async newSession(
email: string,
password: string,
options: SessionOptions = {},
): Promise<AuthenticatedContext> {
const scene = await Play.scene(new SingleUserSceneTemplate({ ...options, email }));
return await this.authenticateForScene(scene, password);
}
async authenticateForScene(
scene: SingleUserScene,
password: string,
): Promise<AuthenticatedContext> {
const page = await this.newPage();
const email = scene.upArgs.email;
const mangledEmail = scene.mangle(email);
await page.goto("/#/login");
await page
.getByRole("textbox", { name: "Email address (required)" })
.fill(scene.mangle("test@example.com"));
await page.getByRole("textbox", { name: "Email address (required)" }).fill(scene.mangle(email));
await page.getByRole("textbox", { name: "Email address (required)" }).press("Enter");
await page
.getByRole("textbox", { name: "Master password (required)" })
@@ -149,6 +167,9 @@ export class AuthFixture {
await page.getByRole("button", { name: "Log in with master password" }).click();
await page.getByRole("button", { name: "Add it later" }).click();
await page.getByRole("link", { name: "Skip to web app" }).click();
if (!scene.upArgs.premium) {
await page.getByRole("button", { name: "Continue without upgrading" }).click();
}
// Store the scene for future use
AuthenticatedEmails.set(email, { email, password, scene });