access.test.ts
Language TypeScript10.6 KB253 lines
/*** Who can read what.** Every briefing addresses its reader directly as "you", so a page carrying all* of them with the others `hidden`, or the executive brief in a `<details>`* that opens on click, would let anyone through the Access door read them all* from view-source.** Cloudflare Access cannot prevent that: it gates a URL, and has no view inside* the response. So enforcement is here, at render time, and these tests assert* the negative: that content is absent from the bytes, not merely hidden in* them. A test that only checked the visible view would pass on a page that* leaks.*/import { describe, expect, it } from 'vitest'import { FIXTURE_SNAPSHOT } from '../src/fixtures.js'import { headline } from '../src/collect.js'import { render } from '../src/render.js'import { MERIDIAN_PEOPLE } from '../../../config/meridian-people.js'import { COMPANY_VIEWER, allViewers, companyBriefingKey, viewerFor } from '../src/viewer.js'import type { Briefings } from '../src/briefings.js'const EXEC_TEXT = 'EXEC-ONLY-SENTINEL-nora-capacity-view'const COMPANY_TEXT = 'COMPANY-FULL-SENTINEL-names-leadership-todos'const ALLHANDS_TEXT = 'ALLHANDS-SENTINEL-no-leadership-titles'const briefings: Briefings = {generatedAt: '2026-08-25T20:40:14+00:00',day: '2026-08-12',model: 'claude-opus-5',payloadGeneratedAt: FIXTURE_SNAPSHOT.syncedAt,mode: 'fixtures',briefings: {company: COMPANY_TEXT,'company-allhands': ALLHANDS_TEXT,exec: EXEC_TEXT,...Object.fromEntries(MERIDIAN_PEOPLE.map((p) => [p.name, `PERSONAL-SENTINEL-${p.name}`])),},}const pageFor = (email: string | null) =>render(FIXTURE_SNAPSHOT, headline(FIXTURE_SNAPSHOT), [], '2026-08-12', briefings, viewerFor(email))const EMAIL = Object.fromEntries(MERIDIAN_PEOPLE.map((p) => [p.name, p.email!])) as Record<string, string>describe('the executive briefing is Nora only, hard', () => {it('reaches Nora', () => {expect(pageFor(EMAIL['Nora Lindqvist'])).toContain(EXEC_TEXT)})it('is ABSENT from every other roster page, not hidden in it', () => {for (const p of MERIDIAN_PEOPLE) {if (p.name === 'Nora Lindqvist') continueexpect(pageFor(p.email!), `${p.name} must not receive the exec brief`).not.toContain(EXEC_TEXT)}})it('is absent for an unrecognised address', () => {expect(pageFor('someone@meridiandisplay.example')).not.toContain(EXEC_TEXT)expect(pageFor(null)).not.toContain(EXEC_TEXT)})it('is absent even from a leadership reader who is not Nora', () => {// Sam may read Leadership to-dos. That is a different permission.expect(pageFor(EMAIL['Sam Whitaker'])).not.toContain(EXEC_TEXT)})it('is held by exactly one person on the roster', () => {expect(MERIDIAN_PEOPLE.filter((p) => p.seesExec).map((p) => p.name)).toEqual(['Nora Lindqvist'])})})describe('a personal briefing reaches one person', () => {it('gives each person their own', () => {for (const p of MERIDIAN_PEOPLE) {expect(pageFor(p.email!), p.name).toContain(`PERSONAL-SENTINEL-${p.name}`)}})it('gives nobody the briefing of anyone else', () => {// The whole failure in one assertion: seven people, forty-two pairings.for (const reader of MERIDIAN_PEOPLE) {const html = pageFor(reader.email!)for (const other of MERIDIAN_PEOPLE) {if (other.name === reader.name) continueexpect(html, `${reader.name} must not see ${other.name}'s briefing`).not.toContain(`PERSONAL-SENTINEL-${other.name}`)}}})it('gives an unrecognised address none of them', () => {const html = pageFor('nobody@meridiandisplay.example')for (const p of MERIDIAN_PEOPLE) {expect(html, p.name).not.toContain(`PERSONAL-SENTINEL-${p.name}`)}})it('no longer ships a person picker, which was the leak', () => {// The dropdown implied a choice between people. It only ever worked because// every person's card was already in the document.const html = pageFor(EMAIL['Marco Ibáñez'])expect(html).not.toContain('data-person-picker')})it('still shows the whole team on the board, which is not a leak', () => {// Deliberate, and the line is worth knowing. What is restricted is the// personalized briefings and the Leadership to-dos, not the existence of// colleagues or their load. "Who is loaded, who is not, at a glance" is the// board's original purpose and the reason it is organised by person at all.// Hiding names here would gut it while protecting nothing that was asked to// be protected.const html = pageFor(EMAIL['Marco Ibáñez'])expect(html).toContain('Theo Bassett')// ...but still none of his prose.expect(html).not.toContain('PERSONAL-SENTINEL-Theo Bassett')})})describe('the company brief comes in two versions', () => {it('serves the full one to leadership readers', () => {// An inbox grant for answering mail is not a grant to read the Leadership// Team's to-dos: that permission is set per person by name. Getting more// than you should is the failure that matters. One line in// meridian-people.ts changes it.for (const name of ['Nora Lindqvist', 'Sam Whitaker', 'Theo Bassett', 'Ines Carvalho']) {expect(pageFor(EMAIL[name]!), name).toContain(COMPANY_TEXT)}})it('serves the all-hands one to everyone else', () => {for (const name of ['Marco Ibáñez', 'Rafael Montero', 'Rosa Galván']) {const html = pageFor(EMAIL[name]!)expect(html, name).toContain(ALLHANDS_TEXT)expect(html, `${name} must not get the Leadership-naming brief`).not.toContain(COMPANY_TEXT)}})it('serves the all-hands one to an unrecognised address', () => {const html = pageFor('new.hire@meridiandisplay.example')expect(html).toContain(ALLHANDS_TEXT)expect(html).not.toContain(COMPANY_TEXT)})it('never falls back from all-hands to the full brief', () => {// If the redacted brief failed to generate, the page must say it is missing// rather than quietly serving the one that names Leadership work.const partial: Briefings = { ...briefings, briefings: { company: COMPANY_TEXT } }const html = render(FIXTURE_SNAPSHOT, headline(FIXTURE_SNAPSHOT), [], '2026-08-12', partial,viewerFor(EMAIL['Marco Ibáñez']),)expect(html).not.toContain(COMPANY_TEXT)expect(html).toContain('Not generated for this sync')})it('picks the key from the permission, not from the name', () => {expect(companyBriefingKey(viewerFor(EMAIL['Nora Lindqvist']))).toBe('company')expect(companyBriefingKey(viewerFor(EMAIL['Marco Ibáñez']))).toBe('company-allhands')expect(companyBriefingKey(COMPANY_VIEWER)).toBe('company-allhands')})})describe('identity resolution refuses to guess', () => {it('matches the roster addresses exactly', () => {expect(viewerFor('nora.l@meridiandisplay.example').person?.name).toBe('Nora Lindqvist')expect(viewerFor(' NORA.L@MeridianDisplay.example ').person?.name).toBe('Nora Lindqvist')})it('does not infer an address from a name', () => {// Six of seven are role mailboxes: hello@, orderdesk@, accounts@, frontdesk@,// projects@. Any name-derived rule would be wrong for almost everyone.for (const guess of ['sam@meridiandisplay.example', 'nora@meridiandisplay.example', 'marco@meridiandisplay.example']) {expect(viewerFor(guess).person, guess).toBeNull()}})it('falls back to the company viewer, which grants nothing', () => {for (const bad of [null, undefined, '', ' ', 'attacker@evil.com']) {const v = viewerFor(bad)expect(v.person, String(bad)).toBeNull()expect(v.seesExec).toBe(false)expect(v.seesLeadership).toBe(false)expect(v.ownBriefing).toBeNull()}})it('defaults render() to the company viewer when none is passed', () => {// A caller that forgets the argument must leak nothing. Getting less than you// should is a bug report; getting more is a breach.const html = render(FIXTURE_SNAPSHOT, headline(FIXTURE_SNAPSHOT), [], '2026-08-12', briefings)expect(html).not.toContain(EXEC_TEXT)expect(html).not.toContain(COMPANY_TEXT)})it('renders a variant for every person plus the company fallback', () => {const vs = allViewers(MERIDIAN_PEOPLE)expect(vs.map((v) => v.key)).toContain('company')for (const p of MERIDIAN_PEOPLE) expect(vs.map((v) => v.key)).toContain(p.name)expect(new Set(vs.map((v) => v.key)).size).toBe(vs.length)})})/*** An off-roster address keeps its email and gains nothing by it.** Shared desks such as `help@` are granted the inbox by address, in both* INBOX_READERS and INBOX_SENDERS, and both lists key on the viewer's email.* A viewer with `email: null` would make those lines present, reviewed and* inert. A permission that silently does nothing is worse than a missing one,* because the missing one gets reported by the person who needed it.** The two halves are both load-bearing and are tested as a pair: the address* is carried, and carrying it grants nothing on its own.*/describe('an authenticated address nobody claimed', () => {it('keeps the address Access asserted, so a written grant can find it', () => {const v = viewerFor('help@meridiandisplay.example')expect(v.email).toBe('help@meridiandisplay.example')})it('normalizes it, so a grant cannot be dodged by casing or whitespace', () => {expect(viewerFor(' HELP@MeridianDisplay.example ').email).toBe('help@meridiandisplay.example')})it('still gets the company view and every permission false', () => {// Carrying the name is not carrying anything else. This is the half that// stops the fix from being a widening.const v = viewerFor('nobody-has-claimed-this@meridiandisplay.example')expect(v.key).toBe('company')expect(v.person).toBeNull()expect(v.seesExec).toBe(false)expect(v.seesLeadership).toBe(false)expect(v.ownBriefing).toBeNull()expect(companyBriefingKey(v)).toBe('company-allhands')})it('gives no identity at all to an absent or empty address', () => {// No address and an unclaimed address are different: the first can hold no// grant, the second can be granted one by name.for (const bad of [null, undefined, '', ' ']) {expect(viewerFor(bad).email, String(bad)).toBeNull()}})it('does not add a rendered variant: there is still one company page', () => {// allViewers filters on a roster match, so an off-roster address shares the// company variant rather than getting a KV key of its own.const vs = allViewers(MERIDIAN_PEOPLE)expect(vs.filter((v) => v.key === 'company')).toHaveLength(1)})})