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:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user