diff --git a/platform/packages/api/test/integration/platform-bootstrap.test.ts b/platform/packages/api/test/integration/platform-bootstrap.test.ts index a0369f297..f915a1c1a 100644 --- a/platform/packages/api/test/integration/platform-bootstrap.test.ts +++ b/platform/packages/api/test/integration/platform-bootstrap.test.ts @@ -654,133 +654,140 @@ describe('provider-independent first administrator setup', () => { expect(await env.db.select().from(users)).toHaveLength(0); }); - it('accepts native browser submissions for setup and administrator confirmation without exposing callback details', async () => { - expect((await initialize()).status).toBe(200); - const browser = await chromium.launch(); - const submissions: Array<{ - path: string; - origin: string | undefined; - referrer: string | undefined; - status: number; - }> = []; - let suppressSetupOrigin = false; - try { - const page = await browser.newPage(); - // Fulfill every browser request locally, retaining real browser form, - // cookie, referrer-policy, and Origin behavior without external traffic. - await page.route('**/*', async (route) => { - const request = route.request(); - const url = new URL(request.url()); - if (url.origin === issuer && url.pathname === '/authorize') { - authorization = url; - await route.fulfill({ - contentType: 'text/html', - body: '

Local identity provider

', - }); - return; - } - if (url.origin !== origin) { - await route.abort(); - return; - } - const headers = await request.allHeaders(); - const response = await app.fetch( - new Request(request.url(), { - method: request.method(), - headers, - ...(request.method() === 'POST' ? { body: request.postData() ?? '' } : {}), - }), - bindings(), - ); - if (request.method() === 'POST') { - submissions.push({ - path: url.pathname, - origin: headers.origin, - referrer: headers.referer, - status: response.status, - }); - } - // Playwright does not route subsequent requests in a redirect chain. - // Keep the real API response evidence, then serve a local handoff page - // so the browser never resolves an external provider or fixture origin. - if (request.method() === 'POST' && response.status === 302) { - if (url.pathname === '/auth/bootstrap') { - authorization = new URL(response.headers.get('Location')!); - } else { - expect(response.headers.get('Location')).toBe('/admin/'); + it( + 'accepts native browser submissions for setup and administrator confirmation without exposing callback details', + async () => { + expect((await initialize()).status).toBe(200); + const browser = await chromium.launch(); + const submissions: Array<{ + path: string; + origin: string | undefined; + referrer: string | undefined; + status: number; + }> = []; + let suppressSetupOrigin = false; + try { + const page = await browser.newPage(); + // Fulfill every browser request locally, retaining real browser form, + // cookie, referrer-policy, and Origin behavior without external traffic. + await page.route('**/*', async (route) => { + const request = route.request(); + const url = new URL(request.url()); + if (url.origin === issuer && url.pathname === '/authorize') { + authorization = url; + await route.fulfill({ + contentType: 'text/html', + body: '

Local identity provider

', + }); + return; + } + if (url.origin !== origin) { + await route.abort(); + return; + } + const headers = await request.allHeaders(); + const response = await app.fetch( + new Request(request.url(), { + method: request.method(), + headers, + ...(request.method() === 'POST' ? { body: request.postData() ?? '' } : {}), + }), + bindings(), + ); + if (request.method() === 'POST') { + submissions.push({ + path: url.pathname, + origin: headers.origin, + referrer: headers.referer, + status: response.status, + }); + } + // Playwright does not route subsequent requests in a redirect chain. + // Keep the real API response evidence, then serve a local handoff page + // so the browser never resolves an external provider or fixture origin. + if (request.method() === 'POST' && response.status === 302) { + if (url.pathname === '/auth/bootstrap') { + authorization = new URL(response.headers.get('Location')!); + } else { + expect(response.headers.get('Location')).toBe('/admin/'); + } + const responseHeaders = Object.fromEntries(response.headers); + delete responseHeaders.location; + await route.fulfill({ + status: 200, + headers: responseHeaders, + body: + url.pathname === '/auth/bootstrap' + ? '

Local provider handoff

' + : '

Administrator ready

', + }); + return; } const responseHeaders = Object.fromEntries(response.headers); - delete responseHeaders.location; + if (suppressSetupOrigin && url.pathname === '/setup') { + responseHeaders['referrer-policy'] = 'no-referrer'; + } await route.fulfill({ - status: 200, + status: response.status, headers: responseHeaders, - body: - url.pathname === '/auth/bootstrap' - ? '

Local provider handoff

' - : '

Administrator ready

', + body: Buffer.from(await response.arrayBuffer()), }); - return; - } - const responseHeaders = Object.fromEntries(response.headers); - if (suppressSetupOrigin && url.pathname === '/setup') { - responseHeaders['referrer-policy'] = 'no-referrer'; - } - await route.fulfill({ - status: response.status, - headers: responseHeaders, - body: Buffer.from(await response.arrayBuffer()), }); - }); - // A no-referrer setup page makes the browser send `Origin: null`, which the - // real Origin guard rejects. - suppressSetupOrigin = true; - await page.goto(`${origin}/setup`); - await page.getByLabel('Setup code').fill(code); - await Promise.all([ - page.waitForURL(`${origin}/auth/bootstrap`), - page.getByRole('button', { name: 'Continue with Company SSO' }).click(), - ]); - expect(submissions).toEqual([ - { path: '/auth/bootstrap', origin: 'null', referrer: undefined, status: 403 }, - ]); - expect(await env.db.select().from(platformAuthAttempts)).toHaveLength(0); - submissions.length = 0; - suppressSetupOrigin = false; - await page.goto(`${origin}/setup`); - await page.getByRole('button', { name: 'Continue with Company SSO' }).click(); - expect(submissions).toEqual([]); - expect(await page.locator('input[name="code"]:invalid').count()).toBe(1); - await page.getByLabel('Setup code').fill(code); - await Promise.all([ - page.waitForURL(`${origin}/auth/bootstrap`), - page.getByRole('button', { name: 'Continue with Company SSO' }).click(), - ]); - expect(submissions).toEqual([ - { path: '/auth/bootstrap', origin, referrer: `${origin}/`, status: 302 }, - ]); - await page.getByRole('heading', { name: 'Local provider handoff' }).waitFor(); - await page.goto(authorization.href); - await page.goto( - `${origin}/auth/callback?code=provider-code&state=${authorization.searchParams.get('state')}`, - ); - await page.getByRole('heading', { name: 'Confirm the first administrator' }).waitFor(); - expect(await env.db.select().from(users)).toHaveLength(0); - await Promise.all([ - page.waitForURL(`${origin}/auth/bootstrap/confirm`), - page.getByRole('button', { name: 'Make this account the platform administrator' }).click(), - ]); - expect(submissions).toEqual([ - { path: '/auth/bootstrap', origin, referrer: `${origin}/`, status: 302 }, - { path: '/auth/bootstrap/confirm', origin, referrer: `${origin}/`, status: 302 }, - ]); - await page.getByRole('heading', { name: 'Administrator ready' }).waitFor(); - const accounts = await env.db.select().from(users); - expect(accounts).toHaveLength(1); - expect(accounts[0]).toMatchObject({ role: 'admin', provider_id: 'administrator-subject' }); - } finally { - await browser.close(); - } - }); + // A no-referrer setup page makes the browser send `Origin: null`, which the + // real Origin guard rejects. + suppressSetupOrigin = true; + await page.goto(`${origin}/setup`); + await page.getByLabel('Setup code').fill(code); + await Promise.all([ + page.waitForURL(`${origin}/auth/bootstrap`), + page.getByRole('button', { name: 'Continue with Company SSO' }).click(), + ]); + expect(submissions).toEqual([ + { path: '/auth/bootstrap', origin: 'null', referrer: undefined, status: 403 }, + ]); + expect(await env.db.select().from(platformAuthAttempts)).toHaveLength(0); + submissions.length = 0; + suppressSetupOrigin = false; + await page.goto(`${origin}/setup`); + await page.getByRole('button', { name: 'Continue with Company SSO' }).click(); + expect(submissions).toEqual([]); + expect(await page.locator('input[name="code"]:invalid').count()).toBe(1); + await page.getByLabel('Setup code').fill(code); + await Promise.all([ + page.waitForURL(`${origin}/auth/bootstrap`), + page.getByRole('button', { name: 'Continue with Company SSO' }).click(), + ]); + expect(submissions).toEqual([ + { path: '/auth/bootstrap', origin, referrer: `${origin}/`, status: 302 }, + ]); + await page.getByRole('heading', { name: 'Local provider handoff' }).waitFor(); + await page.goto(authorization.href); + await page.goto( + `${origin}/auth/callback?code=provider-code&state=${authorization.searchParams.get('state')}`, + ); + await page.getByRole('heading', { name: 'Confirm the first administrator' }).waitFor(); + expect(await env.db.select().from(users)).toHaveLength(0); + await Promise.all([ + page.waitForURL(`${origin}/auth/bootstrap/confirm`), + page + .getByRole('button', { name: 'Make this account the platform administrator' }) + .click(), + ]); + expect(submissions).toEqual([ + { path: '/auth/bootstrap', origin, referrer: `${origin}/`, status: 302 }, + { path: '/auth/bootstrap/confirm', origin, referrer: `${origin}/`, status: 302 }, + ]); + await page.getByRole('heading', { name: 'Administrator ready' }).waitFor(); + const accounts = await env.db.select().from(users); + expect(accounts).toHaveLength(1); + expect(accounts[0]).toMatchObject({ role: 'admin', provider_id: 'administrator-subject' }); + } finally { + await browser.close(); + } + }, + // Launching Chromium alone can take longer than the default under a full parallel run. + 30_000, + ); it('bootstraps an OIDC-only installation only after the owner reviews and confirms the identity', async () => { expect((await initialize()).status).toBe(200); diff --git a/platform/packages/api/test/integration/platform-login.test.ts b/platform/packages/api/test/integration/platform-login.test.ts index 69a634a06..a509b3bf3 100644 --- a/platform/packages/api/test/integration/platform-login.test.ts +++ b/platform/packages/api/test/integration/platform-login.test.ts @@ -243,116 +243,121 @@ describe('configured platform login routes', () => { env, ); } - it('confirms a real native linking form while withholding OAuth callback details', async () => { - const human = await ordinarySession(); - const browser = await chromium.launch(); - const submissions: Array<{ - origin: string | undefined; - referrer: string | undefined; - status: number; - }> = []; - let suppressOrigin = true; - try { - const context = await browser.newContext(); - await context.addCookies([ - { - name: 'void_account_session', - value: human.token, - url: origin, - secure: true, - httpOnly: true, - }, - ]); - const page = await context.newPage(); - await page.route('**/*', async (route) => { - const request = route.request(); - const url = new URL(request.url()); - if (url.origin === issuer && url.pathname === '/authorize') { - authorization = url; + it( + 'confirms a real native linking form while withholding OAuth callback details', + async () => { + const human = await ordinarySession(); + const browser = await chromium.launch(); + const submissions: Array<{ + origin: string | undefined; + referrer: string | undefined; + status: number; + }> = []; + let suppressOrigin = true; + try { + const context = await browser.newContext(); + await context.addCookies([ + { + name: 'void_account_session', + value: human.token, + url: origin, + secure: true, + httpOnly: true, + }, + ]); + const page = await context.newPage(); + await page.route('**/*', async (route) => { + const request = route.request(); + const url = new URL(request.url()); + if (url.origin === issuer && url.pathname === '/authorize') { + authorization = url; + await route.fulfill({ + contentType: 'text/html', + body: '

Local identity provider

', + }); + return; + } + if (url.origin !== origin) { + await route.abort(); + return; + } + const headers = await request.allHeaders(); + const response = await app.fetch( + new Request(request.url(), { + method: request.method(), + headers, + ...(request.method() === 'POST' ? { body: request.postData() ?? '' } : {}), + }), + env, + ); + if (request.method() === 'POST') { + submissions.push({ + origin: headers.origin, + referrer: headers.referer, + status: response.status, + }); + } + const responseHeaders = Object.fromEntries(response.headers); + if (response.status === 302) { + authorization = new URL(response.headers.get('Location')!); + delete responseHeaders.location; + await route.fulfill({ + status: 200, + headers: responseHeaders, + body: '

Local provider handoff

', + }); + return; + } + if (url.pathname === '/auth/callback') { + expect(response.headers.get('Referrer-Policy')).toBe('strict-origin'); + if (suppressOrigin) { + responseHeaders['referrer-policy'] = 'no-referrer'; + } + } await route.fulfill({ - contentType: 'text/html', - body: '

Local identity provider

', - }); - return; - } - if (url.origin !== origin) { - await route.abort(); - return; - } - const headers = await request.allHeaders(); - const response = await app.fetch( - new Request(request.url(), { - method: request.method(), - headers, - ...(request.method() === 'POST' ? { body: request.postData() ?? '' } : {}), - }), - env, - ); - if (request.method() === 'POST') { - submissions.push({ - origin: headers.origin, - referrer: headers.referer, status: response.status, - }); - } - const responseHeaders = Object.fromEntries(response.headers); - if (response.status === 302) { - authorization = new URL(response.headers.get('Location')!); - delete responseHeaders.location; - await route.fulfill({ - status: 200, headers: responseHeaders, - body: '

Local provider handoff

', + body: Buffer.from(await response.arrayBuffer()), }); - return; - } - if (url.pathname === '/auth/callback') { - expect(response.headers.get('Referrer-Policy')).toBe('strict-origin'); - if (suppressOrigin) { - responseHeaders['referrer-policy'] = 'no-referrer'; - } - } - await route.fulfill({ - status: response.status, - headers: responseHeaders, - body: Buffer.from(await response.arrayBuffer()), }); - }); - async function confirmInBrowser() { - const launch = await launchLink(human.token); - expect(launch.status).toBe(200); - await page.goto((await launch.json<{ url: string }>()).url); - await page.goto(authorization.href); - await page.goto( - `${origin}/auth/callback?code=provider-code&state=${authorization.searchParams.get('state')}`, - ); - await page.getByRole('heading', { name: 'Link this login method?' }).waitFor(); - await Promise.all([ - page.waitForURL(`${origin}/auth/link/confirm`), - page.getByRole('button', { name: 'Link this identity' }).click(), - ]); + async function confirmInBrowser() { + const launch = await launchLink(human.token); + expect(launch.status).toBe(200); + await page.goto((await launch.json<{ url: string }>()).url); + await page.goto(authorization.href); + await page.goto( + `${origin}/auth/callback?code=provider-code&state=${authorization.searchParams.get('state')}`, + ); + await page.getByRole('heading', { name: 'Link this login method?' }).waitFor(); + await Promise.all([ + page.waitForURL(`${origin}/auth/link/confirm`), + page.getByRole('button', { name: 'Link this identity' }).click(), + ]); + } + await confirmInBrowser(); + expect(submissions).toEqual([{ origin: 'null', referrer: undefined, status: 400 }]); + expect( + await env.db + .select() + .from(platformLoginIdentities) + .where(eq(platformLoginIdentities.connection_id, 'company')), + ).toHaveLength(0); + suppressOrigin = false; + await confirmInBrowser(); + expect(submissions.at(-1)).toEqual({ origin, referrer: `${origin}/`, status: 200 }); + expect( + await env.db + .select() + .from(platformLoginIdentities) + .where(eq(platformLoginIdentities.connection_id, 'company')), + ).toMatchObject([{ user_id: human.user.id, subject: 'employee' }]); + } finally { + await browser.close(); } - await confirmInBrowser(); - expect(submissions).toEqual([{ origin: 'null', referrer: undefined, status: 400 }]); - expect( - await env.db - .select() - .from(platformLoginIdentities) - .where(eq(platformLoginIdentities.connection_id, 'company')), - ).toHaveLength(0); - suppressOrigin = false; - await confirmInBrowser(); - expect(submissions.at(-1)).toEqual({ origin, referrer: `${origin}/`, status: 200 }); - expect( - await env.db - .select() - .from(platformLoginIdentities) - .where(eq(platformLoginIdentities.connection_id, 'company')), - ).toMatchObject([{ user_id: human.user.id, subject: 'employee' }]); - } finally { - await browser.close(); - } - }); + }, + // Launching Chromium alone can take longer than the default under a full parallel run. + 30_000, + ); it('links an ordinary account only after recent login, provider proof, and explicit browser confirmation', async () => { const human = await ordinarySession(); const launch = await launchLink(human.token);