import { ownerAccounts } from './owner-accounts.mjs'; // Q284 / #385: real OIDC sessions for writer-route tests. The provider is the existing // in-process fake and is shared only within one test-file process; each test gets its own // createSso instance and completes the real login, code and callback flow. import { after } from 'node:test'; import assert from 'node:assert/strict'; import { request } from 'node:http'; import { createSso, SESSION_COOKIE } from '../../src/sso.mjs'; import { fakeOidc } from './fake-oidc.mjs'; export const SSO_HOST = 'console.example.test'; export const SSO_ORIGIN = `https://${SSO_HOST}`; let providerPromise = null; async function provider() { if (!providerPromise) providerPromise = (async () => { const op = await fakeOidc(); return { op, issuer: op.issuer('writer') }; })(); return providerPromise; } after(async () => { if (providerPromise) await (await providerPromise).op.close(); }); function call(port, { host, path, headers = {} }) { return new Promise((resolve, reject) => { const req = request({ host: '127.0.0.1', port, path, headers: { host, ...headers } }, res => { let body = ''; res.on('data', chunk => { body += chunk; }); res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body })); }); req.on('error', reject); req.end(); }); } const cookies = response => [response.headers['set-cookie'] ?? []].flat(); export async function createSsoSession(t, { host = SSO_HOST, owner = { sub: 'owner-sub', name: 'jason' }, subjects = null, users = null, log = () => {}, } = {}) { const { op, issuer } = await provider(); const publicOrigin = `https://${host}`; const configuredSubjects = subjects ?? [owner]; const sso = await createSso({ publicOrigin, providers: [{ id: issuer.name, issuer: issuer.issuer, clientId: issuer.clientId, clientSecret: { slot: issuer.name }, algs: ['RS256'], subjects: configuredSubjects, }], }, { accounts: ownerAccounts({ providers: [{ id: issuer.name, subjects: configuredSubjects }] }), secrets: new Map([[issuer.name, issuer.clientSecret]]), fetch: op.fetch, log, ...(users ? { users } : {}), }); t.after(() => sso.close()); async function signIn(port, { sub = owner.sub } = {}) { const begin = await call(port, { host, path: '/auth/login' }); assert.equal(begin.status, 303, `OIDC login start: ${begin.status} ${begin.body}`); const loginCookie = cookies(begin)[0]?.split(';')[0]; assert.ok(loginCookie, 'OIDC login did not set its transaction cookie'); const grant = op.grant(issuer, begin.headers.location, { sub }); const state = new URL(begin.headers.location).searchParams.get('state'); const callback = await call(port, { host, path: `/auth/callback?${new URLSearchParams({ code: grant.code, state })}`, headers: { cookie: loginCookie }, }); assert.equal(callback.status, 303, `OIDC callback: ${callback.status} ${callback.body}`); const session = cookies(callback).find(value => value.startsWith(`${SESSION_COOKIE}=`) && !value.includes('Max-Age=0'))?.split(';')[0]; assert.ok(session, 'OIDC callback did not set a session cookie'); return session; } return { sso, host, publicOrigin, signIn, readHeaders: session => ({ host, ...(session ? { cookie: session } : {}) }), writeHeaders: session => ({ host, ...(session ? { cookie: session } : {}), origin: publicOrigin, 'sec-fetch-site': 'same-origin', 'content-type': 'application/json' }), }; }