Files

97 lines
3.5 KiB
JavaScript

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' }),
};
}