97 lines
3.5 KiB
JavaScript
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' }),
|
|
};
|
|
}
|