diff --git a/packages/auth/src/workspace.ts b/packages/auth/src/workspace.ts index d94bc1c20..a6575b7ac 100644 --- a/packages/auth/src/workspace.ts +++ b/packages/auth/src/workspace.ts @@ -33,7 +33,19 @@ export function workspaceDomains(): readonly string[] { } export function primaryWorkspaceDomain(): string | undefined { - return allowList().domains[0]; + const { domains, addresses } = allowList(); + + if (domains[0]) return domains[0]; + + // An address-only allow list still names a workspace domain, and a solo + // self-hoster on `me@acme.com` wants the same account chooser as one on + // `acme.com`. Only when every allowed address shares a domain: `hd` narrows + // the chooser to one, so sending it for one of several would hide the rest. + const hosts = new Set( + addresses.map((address) => address.split("@")[1]).filter(Boolean), + ); + + return hosts.size === 1 ? [...hosts][0] : undefined; } export function hasSignInAllowList(): boolean { diff --git a/packages/auth/test/workspace.spec.ts b/packages/auth/test/workspace.spec.ts new file mode 100644 index 000000000..9ba1c7d73 --- /dev/null +++ b/packages/auth/test/workspace.spec.ts @@ -0,0 +1,44 @@ +import { afterEach, describe, expect, it } from "bun:test"; +import { isWorkspaceEmail, primaryWorkspaceDomain } from "../src/workspace"; + +const original = process.env.ALLOWED_SIGN_IN; + +afterEach(() => { + if (original === undefined) delete process.env.ALLOWED_SIGN_IN; + else process.env.ALLOWED_SIGN_IN = original; +}); + +describe("the domain behind the account chooser", () => { + it("is the configured domain when one is configured", () => { + process.env.ALLOWED_SIGN_IN = "acme.com"; + expect(primaryWorkspaceDomain()).toBe("acme.com"); + }); + + it("is derived from a single address, so a solo self-hoster gets the hint too", () => { + process.env.ALLOWED_SIGN_IN = "rep@acme.com"; + expect(primaryWorkspaceDomain()).toBe("acme.com"); + }); + + it("is withheld when the addresses span more than one domain", () => { + // `hd` narrows the chooser to one domain, so sending it for one of two + // would hide the other rather than help. + process.env.ALLOWED_SIGN_IN = "rep@acme.com,other@beta.com"; + expect(primaryWorkspaceDomain()).toBeUndefined(); + }); + + it("still prefers a configured domain over an address", () => { + process.env.ALLOWED_SIGN_IN = "rep@beta.com,acme.com"; + expect(primaryWorkspaceDomain()).toBe("acme.com"); + }); + + it("is nothing when the list is empty, which fails closed", () => { + process.env.ALLOWED_SIGN_IN = ""; + expect(primaryWorkspaceDomain()).toBeUndefined(); + }); + + it("does not widen who may sign in", () => { + process.env.ALLOWED_SIGN_IN = "rep@acme.com"; + expect(isWorkspaceEmail("rep@acme.com")).toBe(true); + expect(isWorkspaceEmail("someone-else@acme.com")).toBe(false); + }); +});