import { afterEach, describe, expect, it } from "vitest"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { browserMatesOf, loginHeldBackFor } from "../shared/logins.ts"; import { BotLogins, cardLoginPlace, cardSignInElsewhere, observeSignInPage } from "./bot-logins.ts"; import { VaultStore } from "./vault-store.ts"; import type { OnePasswordLogins } from "./onepassword.ts"; import { FakeLoginPage, LOGIN_FORM } from "./testing/fake-login-page.ts"; let page: FakeLoginPage | null = null; afterEach(() => { page?.close(); page = null; }); describe("a login saved from a sign-in card", () => { it("fills on the card's site, at the sign-in page open on that site", () => { expect(cardLoginPlace("example.com", { host: "accounts.example.com", signIn: true }, undefined)) .toEqual({ site: "example.com", hosts: ["accounts.example.com"], exactHost: false }); expect(cardLoginPlace("example.com", { host: "www.example.com", signIn: false }, undefined)) .toEqual({ site: "example.com", hosts: ["example.com"], exactHost: false }); expect(cardLoginPlace("example.com", null, undefined)).toEqual({ site: "example.com", hosts: ["example.com"], exactHost: false }); }); it("adds a sign-in page on another site only when the person confirmed that host", () => { const microsoft = { host: "login.microsoftonline.com", signIn: true }; expect(cardSignInElsewhere("example.com", microsoft)).toBe("login.microsoftonline.com"); expect(cardLoginPlace("example.com", microsoft, "login.microsoftonline.com")) .toEqual({ site: "example.com", hosts: ["example.com", "login.microsoftonline.com"], exactHost: false }); expect(cardLoginPlace("example.com", microsoft, undefined).hosts).toEqual(["example.com"]); expect(cardLoginPlace("example.com", microsoft, "evil.test").hosts).toEqual(["example.com"]); expect(cardSignInElsewhere("example.com", { host: "evil.test", signIn: false })).toBeNull(); expect(cardLoginPlace("example.com", { host: "evil.test", signIn: false }, "evil.test").hosts).toEqual(["example.com"]); }); }); describe("the bot's open page", () => { it("is read as its host and whether it shows a password, username or email box, only for a page a login could be filled on", async () => { page = new FakeLoginPage("https://login.microsoftonline.com/common/oauth2/authorize?client_id=x", LOGIN_FORM); expect(await observeSignInPage((args) => page!.engine(args))).toEqual({ host: "login.microsoftonline.com", signIn: true }); page.navigate("https://login.example.net/u/login/identifier", `
`); expect(await observeSignInPage((args) => page!.engine(args))).toEqual({ host: "login.example.net", signIn: true }); page.navigate("https://example.com/dashboard", "

Hello

"); expect(await observeSignInPage((args) => page!.engine(args))).toEqual({ host: "example.com", signIn: false }); page.navigate("http://example.com/login", LOGIN_FORM); expect(await observeSignInPage((args) => page!.engine(args))).toBeNull(); expect(await observeSignInPage(async () => null)).toBeNull(); expect(await observeSignInPage(async () => { throw new Error("The browser did not answer."); })).toBeNull(); }); }); describe("a login kept to some bots, and bots that share a browser", () => { // Payroll and Infra share the "finance" browser profile; Notes and Audit have their own; each guest bot gets its own // temporary browser. const bots = [ { id: "payroll", browserProfile: "finance" }, { id: "infra", browserProfile: "finance" }, { id: "notes" }, { id: "audit", browserProfile: null }, { id: "scout", browserProfile: "guest" }, { id: "visitor", browserProfile: "guest" }, ]; const onePassword = { id: "1password", label: "1Password", fresh: async () => {}, items: () => [], read: async () => ({}), markUsed: () => {} } as unknown as OnePasswordLogins; const world = () => { const store = new VaultStore({ dataDir: mkdtempSync(join(tmpdir(), "omb-bot-logins-")) }); const logins = new BotLogins(store, onePassword, () => bots); const place = { site: "capacity.example", hosts: ["capacity.example"], exactHost: false }; return { store, logins, place }; }; it("finds the bots on the same named profile, and none for a bot on its own or a guest browser", () => { expect(browserMatesOf(bots, "payroll")).toEqual(["infra"]); expect(browserMatesOf(bots, "notes")).toEqual([]); expect(browserMatesOf(bots, "audit")).toEqual([]); expect(browserMatesOf(bots, "scout")).toEqual([]); }); it("keeps a login saved from a sign-in card to every bot that browser signs in, so it fills for the bot that asked", async () => { const { store, logins, place } = world(); expect(logins.cardBots("payroll")).toEqual(["payroll", "infra"]); expect(logins.cardBots("notes")).toEqual(["notes"]); expect(logins.cardBots("scout")).toEqual(["scout"]); store.create({ ...place, username: "from-card", password: "x", source: "card", bots: logins.cardBots("payroll") }); expect((await logins.candidates("payroll", "capacity.example")).map((login) => login.username)).toEqual(["from-card"]); }); it("fills a login kept to one guest bot for that bot alone", async () => { const { store, logins, place } = world(); store.create({ ...place, username: "guest-only", password: "x", source: "settings", bots: ["scout"] }); expect(logins.sitesFor("scout").map((login) => login.username)).toEqual(["guest-only"]); expect(logins.sitesFor("visitor")).toEqual([]); expect(loginHeldBackFor({ bots: ["scout"] }, bots)).toEqual([]); }); it("is never filled into a browser a bot it isn't kept to also signs in through", async () => { const { store, logins, place } = world(); store.create({ ...place, username: "ops", password: "hunter2", source: "settings", bots: ["payroll"] }); expect(logins.sitesFor("payroll")).toEqual([]); expect(await logins.candidates("payroll", "capacity.example")).toEqual([]); expect(logins.sitesFor("infra")).toEqual([]); expect(loginHeldBackFor({ bots: ["payroll"] }, bots)).toEqual(["payroll"]); }); it("fills when every bot on the browser may use it, when it is for every bot, and for a bot with its own browser", async () => { const { store, logins, place } = world(); store.create({ ...place, username: "both", password: "a", source: "settings", bots: ["payroll", "infra"] }); store.create({ ...place, username: "everyone", password: "b", source: "settings", bots: "all" }); store.create({ ...place, username: "notes-only", password: "c", source: "settings", bots: ["notes"] }); expect(logins.sitesFor("payroll").map((login) => login.username)).toEqual(["both", "everyone"]); expect((await logins.candidates("infra", "capacity.example")).map((login) => login.username)).toEqual(["both", "everyone"]); expect(logins.sitesFor("notes").map((login) => login.username)).toEqual(["everyone", "notes-only"]); expect(loginHeldBackFor({ bots: ["payroll", "infra"] }, bots)).toEqual([]); expect(loginHeldBackFor({ bots: "all" }, bots)).toEqual([]); }); }); describe("the secrets a bot may use", () => { it("are named from the Vault and any linked manager, never by value, and a linked one is read only when asked for", async () => { const reads: string[] = []; const held: string[] = []; const provider = { id: "fake", label: "Fake manager", fresh: async () => {}, markUsed: () => {}, items: () => [ { kind: "secret", id: "fake:1", name: "Deploy key", fields: ["user"], source: "linked", provider: "fake", bots: "all", readOnly: true }, { kind: "secret", id: "fake:2", name: "Payroll token", fields: [], source: "linked", provider: "fake", bots: ["payroll"], readOnly: true }, { kind: "login", id: "fake:3", site: "example.com", hosts: ["example.com"], exactHost: false, username: "u", hasTotp: false, source: "1password", bots: "all", createdAt: 0 }, ], read: async (id: string, field?: string) => { reads.push(`${id}/${field ?? ""}`); return { value: `value-of-${id}-${field ?? "main"}` }; }, }; const store = new VaultStore({ dataDir: mkdtempSync(join(tmpdir(), "omb-bot-logins-")) }); store.saveSecret({ name: "Stripe key", value: "sk_fake-saved", source: "card", bots: ["notes"], refOnly: true }); const logins = new BotLogins(store, provider as unknown as OnePasswordLogins, () => [], (value) => held.push(value)); expect(logins.secretNamesFor("notes")).toEqual([{ name: "Stripe key", refOnly: true }, { name: "Deploy key" }, { name: "Deploy key user" }]); expect(logins.secretNamesFor("payroll")).toEqual([{ name: "Deploy key" }, { name: "Deploy key user" }, { name: "Payroll token" }]); expect(logins.sitesFor("notes")).toEqual([{ site: "example.com", hosts: ["example.com"], exactHost: false, username: "u" }]); expect(await logins.linkedSecret("payroll token", "notes")).toBeNull(); expect(reads).toEqual([]); const deploy = await logins.linkedSecret("deploy-key-user", "notes"); expect(deploy).toMatchObject({ name: "Deploy key user", value: "value-of-fake:1-user" }); expect(deploy!.id).toMatch(/^linked-[0-9a-f]{32}$/u); expect(reads).toEqual(["fake:1/user"]); expect(held).toEqual(["value-of-fake:1-user"]); }); });