Files
feuerwerk/tools/lib/auth.mjs
T

66 lines
2.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Signing the check tools in. Since #177 every API route but `auth/*` needs a session, so every
* seed, smoke and e2e run logs in first – as `e2e`, the account that owns the four test projects.
*
* The password is never in the repo: it comes from `GAME_MAKER_E2E_PASSWORD` or from
* `data/e2e-password.txt`, which is git-ignored and written once per server (CI writes its own).
*/
import { readFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
export const E2E_USER = 'e2e';
const PASSWORD_FILE = resolve(dirname(fileURLToPath(import.meta.url)), '../../data/e2e-password.txt');
const COOKIE = 'game_maker_session';
/** One session per server for the whole process; logging in per request would be pointless load. */
const cookies = new Map();
/** The password of the `e2e` account; the step that drives the sign-in screen types it in. */
export async function e2ePassword() {
const fromEnv = process.env.GAME_MAKER_E2E_PASSWORD;
if (fromEnv) return fromEnv;
try {
return (await readFile(PASSWORD_FILE, 'utf8')).trim();
} catch {
throw new Error(
`no password for the "${E2E_USER}" account: set GAME_MAKER_E2E_PASSWORD or put it in ${PASSWORD_FILE}`,
);
}
}
/** `game_maker_session=…` for `Cookie` headers, logging in on first use. */
export async function sessionCookie(baseUrl) {
const cached = cookies.get(baseUrl);
if (cached) return cached;
const res = await fetch(`${baseUrl}api/index.php?r=auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: E2E_USER, password: await e2ePassword() }),
signal: AbortSignal.timeout(20_000),
});
if (!res.ok) {
throw new Error(`could not sign in as "${E2E_USER}" at ${baseUrl}: HTTP ${res.status}`);
}
// `getSetCookie` keeps the headers apart; a joined string cannot be split on commas, because a
// cookie's own Expires date carries one.
const set = res.headers.getSetCookie().find((c) => c.startsWith(`${COOKIE}=`));
if (!set) throw new Error(`signing in at ${baseUrl} returned no ${COOKIE} cookie`);
const cookie = set.split(';')[0];
cookies.set(baseUrl, cookie);
return cookie;
}
/** The headers a tool's `fetch` needs on top of its own. */
export async function authHeaders(baseUrl) {
return { Cookie: await sessionCookie(baseUrl) };
}
/** The session token alone – what the browser gets handed over CDP. */
export async function sessionToken(baseUrl) {
return (await sessionCookie(baseUrl)).slice(COOKIE.length + 1);
}
export const SESSION_COOKIE = COOKIE;