globalThis.process ??= {};
globalThis.process.env ??= {};
import { r as timingSafeEqual } from "./crypto_CY2zBtG3.mjs";
import { d as getSession, f as requireRole, t as REFRESH_TOKEN_TTL_SECS, u as InvalidSessionError } from "./engine-custom_BJVTPCgT.mjs";
import { i as buildServiceTokenResolver, n as SESSION_COOKIE, r as buildAuthEngine, t as ROLE_HIERARCHY } from "./admin-engine_Cqzo5vQB.mjs";
//#region ../../packages/util/dist/csrf.js
/**
* Double-submit verify: the token stored in a cookie vs the token echoed back in a request
* header/body field. Constant-time compare (timingSafeEqual). Fail-closed: either side
* null/undefined/empty → `false` (before the compare — a present-vs-absent check is not a
* secret-timing leak). Equal-length, equal-value → `true`.
*/
function verifyDoubleSubmit(cookieToken, submittedToken) {
	if (!cookieToken || !submittedToken) return false;
	return timingSafeEqual(cookieToken, submittedToken);
}
/**
* Origin-based CSRF defense for state-changing requests. Returns `true` IFF same-origin:
*   - `Origin` header present → must be ∈ `allowedOrigins` (exact string match)
*   - `Origin` absent         → `Sec-Fetch-Site` ∈ {'same-origin','none'}
*   - neither present         → `false` (fail-closed)
*
* Bearer-token requests have no ambient cookie credential — CSRF is N/A; the host SHOULD exempt
* them before calling this.
*/
function isSameOrigin(headers, allowedOrigins) {
	const h = headers instanceof Headers ? headers : new Headers(headers);
	const origin = h.get("origin");
	if (origin !== null) return allowedOrigins.includes(origin);
	const site = h.get("sec-fetch-site");
	return site === "same-origin" || site === "none";
}
//#endregion
//#region src/lib/csrf.ts
var CsrfError = class extends Error {
	name = "CsrfError";
};
function assertCsrf(request, { authMode, siteOrigin }) {
	if (authMode === "token") return;
	if (!isSameOrigin(request.headers, [siteOrigin])) throw new CsrfError("cross-origin or missing Origin on session mutation");
}
//#endregion
//#region src/lib/auth.ts
var REFRESH_COOKIE = "mod_refresh";
var ACCESS_OPTS = {
	maxAge: 900,
	httpOnly: true,
	secure: true,
	sameSite: "lax",
	path: "/"
};
var REFRESH_OPTS = {
	maxAge: REFRESH_TOKEN_TTL_SECS,
	httpOnly: true,
	secure: true,
	sameSite: "lax",
	path: "/"
};
var DELETE_OPTS = { path: "/" };
function readCookie(header, name) {
	if (!header) return null;
	for (const part of header.split(";")) {
		const eq = part.indexOf("=");
		if (eq === -1) continue;
		if (part.slice(0, eq).trim() === name) return part.slice(eq + 1).trim();
	}
	return null;
}
function setSessionCookies(sink, accessToken, refreshToken) {
	sink.set(SESSION_COOKIE, accessToken, ACCESS_OPTS);
	if (refreshToken) sink.set(REFRESH_COOKIE, refreshToken, REFRESH_OPTS);
}
function clearSessionCookies(sink) {
	sink.delete(SESSION_COOKIE, DELETE_OPTS);
	sink.delete(REFRESH_COOKIE, DELETE_OPTS);
}
async function resolveSession(request, engine, sink, resolveToken) {
	const bearer = request.headers.get("authorization");
	if (bearer?.startsWith("Bearer ")) {
		const p = await getSession(new Headers({ authorization: bearer }), engine);
		if (p) return {
			principal: p,
			authMode: "token"
		};
		if (resolveToken) {
			const svc = await resolveToken(bearer.slice(7).trim());
			if (svc) return {
				principal: svc,
				authMode: "token"
			};
		}
	}
	const cookieHeader = request.headers.get("cookie");
	if (cookieHeader) {
		const p = await getSession(new Headers({ cookie: cookieHeader }), engine, { accessCookieName: SESSION_COOKIE });
		if (p) return {
			principal: p,
			authMode: "session"
		};
		const refreshToken = readCookie(cookieHeader, REFRESH_COOKIE);
		if (refreshToken) {
			const result = await engine.refresh(refreshToken);
			if (!result) {
				clearSessionCookies(sink);
				throw new InvalidSessionError("admin authentication required", "refresh_failed");
			}
			setSessionCookies(sink, result.accessToken, result.refreshToken);
			return {
				principal: result.principal,
				authMode: "session"
			};
		}
	}
	throw new InvalidSessionError("admin authentication required", "missing_principal");
}
async function resolveAdmin(request, engine, sink, resolveToken) {
	const { principal, authMode } = await resolveSession(request, engine, sink, resolveToken);
	requireRole("admin", ROLE_HIERARCHY)(principal);
	return {
		actor: actorFromPrincipal(principal),
		authMode,
		sessionId: principal.sessionId
	};
}
async function resolveEditor(request, engine, sink, resolveToken) {
	const { principal, authMode } = await resolveSession(request, engine, sink, resolveToken);
	requireRole("editor", ROLE_HIERARCHY)(principal);
	return {
		actor: actorFromPrincipal(principal),
		authMode,
		sessionId: principal.sessionId
	};
}
function requireAdmin(request, env, sink) {
	return resolveAdmin(request, buildAuthEngine(env), sink, buildServiceTokenResolver(env));
}
function requireEditor(request, env, sink) {
	return resolveEditor(request, buildAuthEngine(env), sink, buildServiceTokenResolver(env));
}
async function loginWithCredentials(engine, sink, creds) {
	const result = await engine.signIn(creds);
	setSessionCookies(sink, result.accessToken, result.refreshToken);
	return result.principal;
}
async function logout(engine, sink, request, siteOrigin) {
	try {
		const { principal, authMode } = await resolveSession(request, engine, sink);
		assertCsrf(request, {
			authMode,
			siteOrigin
		});
		await engine.signOut(principal.sessionId);
	} finally {
		clearSessionCookies(sink);
	}
}
function actorFromPrincipal(principal) {
	const roles = principal.roles ?? [];
	const isAdmin = roles.includes("admin");
	const isEditor = roles.includes("editor");
	return {
		id: principal.userId,
		canEditAny: isAdmin || isEditor,
		canPublish: isAdmin
	};
}
//#endregion
export { requireEditor as a, assertCsrf as c, requireAdmin as i, isSameOrigin as l, loginWithCredentials as n, resolveAdmin as o, logout as r, resolveSession as s, actorFromPrincipal as t, verifyDoubleSubmit as u };
