import "../../after-B1IIdH3Y.mjs";
import "../../base64-CmWvODNW.mjs";
import "../../types-CuW0zWDA.mjs";
import { t as apiError } from "../../error-CkAseSqZ.mjs";
import { n as getPublicOrigin } from "../../public-url-Danv47cS.mjs";
import { t as getAuthMode } from "../../mode-BM9QgEmt.mjs";
import { t as resolveSessionUser } from "../../session-user-Cascbax7.mjs";
import { i as hasScope } from "../../api-tokens-DsvvCtdh.mjs";
import { a as resolveApiToken, o as resolveOAuthToken } from "../../api-tokens-DvqeZmVq.mjs";
import { ulid } from "ulidx";
import { defineMiddleware } from "astro:middleware";
import virtualConfig from "virtual:emdash/config";
import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
import { authenticate } from "virtual:emdash/auth";

//#region src/api/csrf.ts
/**
* CSRF protection utilities.
*
* Two mechanisms:
* 1. Custom header check (X-EmDash-Request: 1) — used for authenticated API routes.
*    Browsers block cross-origin custom headers, so presence proves same-origin.
* 2. Origin check — used for public API routes that skip auth. Compares the Origin
*    header against the request origin. Same approach as Astro's `checkOrigin`.
*/
/**
* Origin-based CSRF check for public API routes that skip auth.
*
* State-changing requests (POST/PUT/DELETE) to public endpoints must either:
*   1. Include the X-EmDash-Request: 1 header (custom header blocked cross-origin), OR
*   2. Have an Origin header matching the request origin (or the configured public origin)
*
* This prevents cross-origin form submissions (which can't set custom headers)
* and cross-origin fetch (blocked by CORS unless allowed). Same-origin requests
* always include a matching Origin header.
*
* Returns a 403 Response if the check fails, or null if allowed.
*
* @param request  The incoming request
* @param url      The request URL (internal origin)
* @param publicOrigin  The public-facing origin from config.siteUrl. Must be
*        `undefined` when absent — never `null` or `""` (security invariant H-1a).
*/
function checkPublicCsrf(request, url, publicOrigin) {
	if (request.headers.get("X-EmDash-Request") === "1") return null;
	const origin = request.headers.get("Origin");
	if (origin) {
		try {
			const originUrl = new URL(origin);
			if (originUrl.origin === url.origin) return null;
			if (publicOrigin && originUrl.origin === publicOrigin) return null;
		} catch {}
		return apiError("CSRF_REJECTED", "Cross-origin request blocked", 403);
	}
	return null;
}

//#endregion
//#region src/astro/middleware/csp.ts
/** Entrypoint constant used by the `s3()` adapter (see `astro/storage/adapters.ts`). */
const S3_ADAPTER_ENTRYPOINT = "emdash/storage/s3";
/**
* Storage entrypoints are free to shape their config however they like, so
* `endpoint` isn't a known field on `StorageDescriptor["config"]` -- only
* S3-compatible adapters (R2, S3, Minio, ...) set it. Anything else (e.g.
* local filesystem storage) simply has no `endpoint` to allow.
*
* The `s3()` adapter resolves any field omitted from its config -- including
* `endpoint` -- from the matching `S3_*` env var at runtime (see
* `storage/s3.ts`'s `resolveS3Config`). A site configured as `s3({ ... })`
* with only `S3_ENDPOINT` set has no `endpoint` in the descriptor's config,
* so fall back to that env var for S3-adapter storage before giving up.
*/
function getConfiguredStorageEndpoint(storage) {
	const config = storage?.config;
	if (typeof config === "object" && config !== null && "endpoint" in config) {
		const endpoint = config.endpoint;
		if (typeof endpoint === "string") return endpoint;
	}
	if (storage?.entrypoint === S3_ADAPTER_ENTRYPOINT) {
		const envEndpoint = typeof process !== "undefined" && process.env ? process.env.S3_ENDPOINT : void 0;
		if (envEndpoint) return envEndpoint;
	}
}
function getRegistryAggregatorOrigin(registry) {
	const aggregatorUrl = typeof registry === "string" ? registry : registry?.aggregatorUrl;
	if (!aggregatorUrl) return void 0;
	try {
		const url = new URL(aggregatorUrl);
		if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
		return url.origin;
	} catch {
		return;
	}
}
function getHttpOrigin(rawUrl) {
	if (!rawUrl) return void 0;
	try {
		const url = new URL(rawUrl);
		if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
		return url.origin;
	} catch {
		return;
	}
}
function buildEmDashCsp(registry, storageEndpoint) {
	const connectSrc = ["connect-src 'self'"];
	const origins = /* @__PURE__ */ new Set();
	const registryAggregatorOrigin = getRegistryAggregatorOrigin(registry);
	if (registryAggregatorOrigin) origins.add(registryAggregatorOrigin);
	const storageOrigin = getHttpOrigin(storageEndpoint);
	if (storageOrigin) origins.add(storageOrigin);
	connectSrc.push(...origins);
	return [
		"default-src 'self'",
		"script-src 'self' 'unsafe-inline'",
		"style-src 'self' 'unsafe-inline'",
		connectSrc.join(" "),
		"form-action 'self'",
		"frame-ancestors 'none'",
		"img-src 'self' https: data: blob:",
		"object-src 'none'",
		"base-uri 'self'"
	].join("; ");
}

//#endregion
//#region src/astro/middleware/auth.ts
/** Cache headers for middleware error responses (matches API_CACHE_HEADERS in api/error.ts) */
const MW_CACHE_HEADERS = { "Cache-Control": "private, no-store" };
const ROLE_ADMIN = 50;
const MCP_ENDPOINT_PATH = "/_emdash/api/mcp";
function isUnsafeMethod(method) {
	return method !== "GET" && method !== "HEAD" && method !== "OPTIONS";
}
function csrfRejectedResponse() {
	return new Response(JSON.stringify({ error: {
		code: "CSRF_REJECTED",
		message: "Missing required header"
	} }), {
		status: 403,
		headers: {
			"Content-Type": "application/json",
			...MW_CACHE_HEADERS
		}
	});
}
function mcpUnauthorizedResponse(url, config) {
	const origin = getPublicOrigin(url, config);
	return Response.json({ error: {
		code: "NOT_AUTHENTICATED",
		message: "Not authenticated"
	} }, {
		status: 401,
		headers: {
			"WWW-Authenticate": `Bearer resource_metadata="${origin}/.well-known/oauth-protected-resource"`,
			...MW_CACHE_HEADERS
		}
	});
}
/**
* API routes that skip auth — each handles its own access control.
*
* Prefix entries match any path starting with that prefix.
* Exact entries (no trailing slash or wildcard) match that path only.
*/
const PUBLIC_API_PREFIXES = [
	"/_emdash/api/setup",
	"/_emdash/api/auth/login",
	"/_emdash/api/auth/register",
	"/_emdash/api/auth/dev-bypass",
	"/_emdash/api/auth/signup/",
	"/_emdash/api/auth/magic-link/",
	"/_emdash/api/auth/invite/",
	"/_emdash/api/auth/oauth/",
	"/_emdash/api/oauth/device/token",
	"/_emdash/api/oauth/device/code",
	"/_emdash/api/oauth/token",
	"/_emdash/api/oauth/register",
	"/_emdash/api/comments/",
	"/_emdash/api/media/file/",
	"/_emdash/.well-known/"
];
const PUBLIC_API_EXACT = new Set([
	"/_emdash/api/auth/passkey/options",
	"/_emdash/api/auth/passkey/verify",
	"/_emdash/api/auth/mode",
	"/_emdash/api/oauth/token",
	"/_emdash/api/snapshot",
	"/_emdash/api/search",
	"/_emdash/api/search/suggest"
]);
const { exact: _providerExactRoutes, prefixes: _providerPrefixRoutes } = (() => {
	const exact = /* @__PURE__ */ new Set();
	const prefixes = [];
	if (!virtualConfig?.authProviders) return {
		exact,
		prefixes
	};
	for (const route of virtualConfig.authProviders.flatMap((p) => p.publicRoutes ?? [])) if (route.endsWith("/")) prefixes.push(route);
	else exact.add(route);
	return {
		exact,
		prefixes
	};
})();
/**
* OAuth protocol endpoints that are CSRF-exempt by design.
*
* These are RFC-defined endpoints (RFC 6749 §3.2, RFC 7591 §3, RFC 8628 §3.1/§3.4)
* specified to be called cross-origin by external clients (MCP clients, CLIs,
* native apps). They authenticate each request on its own merits:
*
* - /oauth/token: requires PKCE code_verifier, device_code, or refresh_token
* - /oauth/register: RFC 7591 dynamic client registration — anonymous by design
* - /oauth/device/code: RFC 8628 device flow initiation — anonymous by design
* - /oauth/device/token: requires device_code the client already holds
*
* None of these rely on ambient cookie credentials, so browser-based CSRF
* attacks have nothing to exploit. The endpoints themselves advertise
* `Access-Control-Allow-Origin: *`. Note: /oauth/device/authorize (the user
* consent step) is NOT in this list — it is session-authenticated.
*/
const CSRF_EXEMPT_PUBLIC_ROUTES = new Set([
	"/_emdash/api/oauth/token",
	"/_emdash/api/oauth/register",
	"/_emdash/api/oauth/device/code",
	"/_emdash/api/oauth/device/token"
]);
function isPublicEmDashRoute(pathname) {
	if (PUBLIC_API_EXACT.has(pathname)) return true;
	if (PUBLIC_API_PREFIXES.some((p) => pathname.startsWith(p))) return true;
	if (_providerExactRoutes.has(pathname)) return true;
	if (_providerPrefixRoutes.some((p) => pathname.startsWith(p))) return true;
	if (import.meta.env.DEV && pathname === "/_emdash/api/typegen") return true;
	return false;
}
function isCsrfExemptPublicRoute(pathname) {
	return CSRF_EXEMPT_PUBLIC_ROUTES.has(pathname);
}
const onRequest = defineMiddleware(async (context, next) => {
	const { url } = context;
	const isAdminRoute = url.pathname.startsWith("/_emdash/admin");
	const isSetupRoute = url.pathname.startsWith("/_emdash/admin/setup");
	const isApiRoute = url.pathname.startsWith("/_emdash/api");
	const isPublicApiRoute = isPublicEmDashRoute(url.pathname);
	const isPublicRoute = !isAdminRoute && !isApiRoute;
	if (isPublicApiRoute) {
		if (isUnsafeMethod(context.request.method.toUpperCase()) && !isCsrfExemptPublicRoute(url.pathname)) {
			const publicOrigin = getPublicOrigin(url, context.locals.emdash?.config);
			const csrfError = checkPublicCsrf(context.request, url, publicOrigin);
			if (csrfError) return csrfError;
		}
		return next();
	}
	if (url.pathname.startsWith("/_emdash/api/plugins/")) {
		const method = context.request.method.toUpperCase();
		if (method !== "GET" && method !== "HEAD" && method !== "OPTIONS") {
			const publicOrigin = getPublicOrigin(url, context.locals.emdash?.config);
			const csrfError = checkPublicCsrf(context.request, url, publicOrigin);
			if (csrfError) return csrfError;
		}
		return handlePluginRouteAuth(context, next);
	}
	if (isSetupRoute) {
		const method = context.request.method.toUpperCase();
		if (method !== "GET" && method !== "HEAD" && method !== "OPTIONS") {
			if (context.request.headers.get("X-EmDash-Request") !== "1") return new Response(JSON.stringify({ error: {
				code: "CSRF_REJECTED",
				message: "Missing required header"
			} }), {
				status: 403,
				headers: {
					"Content-Type": "application/json",
					...MW_CACHE_HEADERS
				}
			});
		}
		return next();
	}
	if (isPublicRoute) return handlePublicRouteAuth(context, next);
	const bearerResult = await handleBearerAuth(context);
	if (bearerResult === "invalid") {
		const headers = {
			"Content-Type": "application/json",
			...MW_CACHE_HEADERS
		};
		if (url.pathname === "/_emdash/api/mcp") headers["WWW-Authenticate"] = `Bearer resource_metadata="${getPublicOrigin(url, context.locals.emdash?.config)}/.well-known/oauth-protected-resource"`;
		return new Response(JSON.stringify({ error: {
			code: "INVALID_TOKEN",
			message: "Invalid or expired token"
		} }), {
			status: 401,
			headers
		});
	}
	const isTokenAuth = bearerResult === "authenticated";
	const method = context.request.method.toUpperCase();
	if (url.pathname === MCP_ENDPOINT_PATH && !isTokenAuth) return mcpUnauthorizedResponse(url, context.locals.emdash?.config);
	const isOAuthConsent = url.pathname.startsWith("/_emdash/oauth/authorize");
	if (isApiRoute && !isTokenAuth && !isOAuthConsent && isUnsafeMethod(method) && !isPublicApiRoute) {
		if (context.request.headers.get("X-EmDash-Request") !== "1") return csrfRejectedResponse();
	}
	if (isTokenAuth) {
		const scopeError = enforceTokenScope(url.pathname, method, context.locals.tokenScopes);
		if (scopeError) return scopeError;
		const response = await next();
		if (!import.meta.env.DEV) response.headers.set("Content-Security-Policy", buildEmDashCsp(context.locals.emdash?.config.experimental?.registry, getConfiguredStorageEndpoint(context.locals.emdash?.config.storage)));
		return response;
	}
	const response = await handleEmDashAuth(context, next);
	if (!import.meta.env.DEV) response.headers.set("Content-Security-Policy", buildEmDashCsp(context.locals.emdash?.config.experimental?.registry, getConfiguredStorageEndpoint(context.locals.emdash?.config.storage)));
	return response;
});
/**
* Auth handling for /_emdash routes. Returns a Response from either
* an auth error/redirect or the downstream route handler.
*/
async function handleEmDashAuth(context, next) {
	const { url, locals } = context;
	const { emdash } = locals;
	const isPublicAdminRoute = url.pathname.startsWith("/_emdash/admin/login") || url.pathname.startsWith("/_emdash/admin/invite/accept");
	const isApiRoute = url.pathname.startsWith("/_emdash/api");
	if (!emdash?.db) return next();
	const authMode = getAuthMode(emdash.config);
	if (authMode.type === "external") {
		if (import.meta.env.DEV) {
			if (isPublicAdminRoute) return next();
			return handlePasskeyAuth(context, next, isApiRoute);
		}
		return handleExternalAuth(context, next, authMode, isApiRoute);
	}
	if (isPublicAdminRoute) return next();
	return handlePasskeyAuth(context, next, isApiRoute);
}
/**
* Soft auth for plugin routes: resolve user from Bearer token or session if present,
* but never block unauthenticated requests. The catch-all handler checks route
* metadata to decide whether auth is required (public vs private routes).
*/
async function handlePluginRouteAuth(context, next) {
	const { locals } = context;
	const { emdash } = locals;
	try {
		const bearerResult = await handleBearerAuth(context);
		if (bearerResult === "authenticated") return next();
		if (bearerResult === "invalid") return new Response(JSON.stringify({ error: {
			code: "INVALID_TOKEN",
			message: "Invalid or expired token"
		} }), {
			status: 401,
			headers: {
				"Content-Type": "application/json",
				...MW_CACHE_HEADERS
			}
		});
	} catch (error) {
		console.error("Plugin route bearer auth error:", error);
	}
	try {
		const { session } = context;
		const sessionUser = await resolveSessionUser(session);
		if (sessionUser?.id && emdash?.db) {
			const user = await createKyselyAdapter(emdash.db).getUserById(sessionUser.id);
			if (user && !user.disabled) locals.user = user;
		}
	} catch (error) {
		console.error("Plugin route session auth error:", error);
	}
	return next();
}
/**
* Soft auth check for public routes with edit mode cookie.
* Checks the session and sets locals.user if valid, but never blocks the request.
*/
async function handlePublicRouteAuth(context, next) {
	const { locals, session } = context;
	const { emdash } = locals;
	try {
		const sessionUser = await resolveSessionUser(session);
		if (sessionUser?.id && emdash?.db) {
			const user = await createKyselyAdapter(emdash.db).getUserById(sessionUser.id);
			if (user && !user.disabled) locals.user = user;
		}
	} catch {}
	return next();
}
/**
* Handle external auth provider authentication (Cloudflare Access, etc.)
*/
async function handleExternalAuth(context, next, authMode, _isApiRoute) {
	const { locals, request } = context;
	const { emdash } = locals;
	try {
		if (typeof authenticate !== "function") throw new Error(`Auth provider ${authMode.entrypoint} does not export an authenticate function`);
		const authResult = await authenticate(request, authMode.config);
		const externalConfig = authMode.config;
		const adapter = createKyselyAdapter(emdash.db);
		let user = await adapter.getUserByEmail(authResult.email);
		if (!user) {
			if (externalConfig.autoProvision === false) return new Response("User not authorized", {
				status: 403,
				headers: {
					"Content-Type": "text/plain",
					...MW_CACHE_HEADERS
				}
			});
			const userCount = await emdash.db.selectFrom("users").select(emdash.db.fn.count("id").as("count")).executeTakeFirst();
			const isFirstUser = Number(userCount?.count ?? 0) === 0;
			const role = isFirstUser ? ROLE_ADMIN : authResult.role;
			const now = (/* @__PURE__ */ new Date()).toISOString();
			const newUser = {
				id: ulid(),
				email: authResult.email,
				name: authResult.name,
				role,
				email_verified: 1,
				created_at: now,
				updated_at: now
			};
			await emdash.db.insertInto("users").values(newUser).execute();
			user = await adapter.getUserByEmail(authResult.email);
			console.log(`[external-auth] Provisioned user: ${authResult.email} (role: ${role}, first: ${isFirstUser})`);
		} else {
			const updates = {};
			let newName;
			let newRole;
			if (authResult.name && user.name !== authResult.name) {
				newName = authResult.name;
				updates.name = newName;
			}
			if (externalConfig.syncRoles && user.role !== authResult.role) {
				newRole = authResult.role;
				updates.role = newRole;
			}
			if (Object.keys(updates).length > 0) {
				updates.updated_at = (/* @__PURE__ */ new Date()).toISOString();
				await emdash.db.updateTable("users").set(updates).where("id", "=", user.id).execute();
				user = {
					...user,
					...newName ? { name: newName } : {},
					...newRole ? { role: newRole } : {}
				};
				console.log(`[external-auth] Updated user ${authResult.email}:`, Object.keys(updates).filter((k) => k !== "updated_at"));
			}
		}
		if (!user) return new Response("Failed to provision user", {
			status: 500,
			headers: {
				"Content-Type": "text/plain",
				...MW_CACHE_HEADERS
			}
		});
		if (user.disabled) return new Response("Account disabled", {
			status: 403,
			headers: {
				"Content-Type": "text/plain",
				...MW_CACHE_HEADERS
			}
		});
		locals.user = user;
		const { session } = context;
		session?.set("user", { id: user.id });
		return next();
	} catch (error) {
		console.error("[external-auth] Auth error:", error);
		return new Response("Authentication failed", {
			status: 401,
			headers: {
				"Content-Type": "text/plain",
				...MW_CACHE_HEADERS
			}
		});
	}
}
/**
* Try to authenticate via Bearer token (API token or OAuth token).
*
* Returns:
* - "authenticated" if token is valid and user is resolved
* - "invalid" if a token was provided but is invalid/expired
* - "none" if no Bearer token was provided
*/
async function handleBearerAuth(context) {
	const authHeader = context.request.headers.get("Authorization");
	if (!authHeader?.startsWith("Bearer ")) return "none";
	const token = authHeader.slice(7);
	if (!token) return "none";
	const { locals } = context;
	const { emdash } = locals;
	if (!emdash?.db) return "none";
	let resolved = null;
	if (token.startsWith("ec_pat_")) resolved = await resolveApiToken(emdash.db, token);
	else if (token.startsWith("ec_oat_")) resolved = await resolveOAuthToken(emdash.db, token);
	else return "invalid";
	if (!resolved) return "invalid";
	const user = await createKyselyAdapter(emdash.db).getUserById(resolved.userId);
	if (!user || user.disabled) return "invalid";
	locals.user = user;
	locals.tokenScopes = resolved.scopes;
	return "authenticated";
}
/**
* Handle passkey (session-based) authentication
*/
async function handlePasskeyAuth(context, next, isApiRoute) {
	const { url, locals, session } = context;
	const { emdash } = locals;
	try {
		const sessionUser = await resolveSessionUser(session);
		if (!sessionUser?.id) {
			if (isApiRoute) return Response.json({ error: {
				code: "NOT_AUTHENTICATED",
				message: "Not authenticated"
			} }, {
				status: 401,
				headers: MW_CACHE_HEADERS
			});
			const loginUrl = new URL("/_emdash/admin/login", getPublicOrigin(url, emdash?.config));
			loginUrl.searchParams.set("redirect", url.pathname);
			return context.redirect(loginUrl.toString());
		}
		const user = await createKyselyAdapter(emdash.db).getUserById(sessionUser.id);
		if (!user) {
			session?.destroy();
			if (isApiRoute) return Response.json({ error: {
				code: "NOT_FOUND",
				message: "User not found"
			} }, {
				status: 401,
				headers: MW_CACHE_HEADERS
			});
			const loginUrl = new URL("/_emdash/admin/login", getPublicOrigin(url, emdash?.config));
			return context.redirect(loginUrl.toString());
		}
		if (user.disabled) {
			session?.destroy();
			if (isApiRoute) return apiError("ACCOUNT_DISABLED", "Account disabled", 403);
			const loginUrl = new URL("/_emdash/admin/login", getPublicOrigin(url, emdash?.config));
			loginUrl.searchParams.set("error", "account_disabled");
			return context.redirect(loginUrl.toString());
		}
		locals.user = user;
	} catch (error) {
		console.error("Auth middleware error:", error);
		return context.redirect("/_emdash/admin/login");
	}
	return next();
}
/**
* Scope rules: ordered list of (pathPrefix, method, requiredScope) tuples.
* First matching rule wins. Methods: "*" = any, "WRITE" = POST/PUT/PATCH/DELETE.
*
* Routes not matched by any rule default to "admin" scope (fail-closed).
*/
const SCOPE_RULES = [
	[
		"/_emdash/api/content",
		"GET",
		"content:read"
	],
	[
		"/_emdash/api/content",
		"WRITE",
		"content:write"
	],
	[
		"/_emdash/api/media/file",
		"*",
		"media:read"
	],
	[
		"/_emdash/api/media",
		"GET",
		"media:read"
	],
	[
		"/_emdash/api/media",
		"WRITE",
		"media:write"
	],
	[
		"/_emdash/api/schema",
		"GET",
		"schema:read"
	],
	[
		"/_emdash/api/schema",
		"WRITE",
		"schema:write"
	],
	[
		"/_emdash/api/taxonomies",
		"GET",
		"content:read"
	],
	[
		"/_emdash/api/taxonomies",
		"WRITE",
		"taxonomies:manage"
	],
	[
		"/_emdash/api/menus",
		"GET",
		"content:read"
	],
	[
		"/_emdash/api/menus",
		"WRITE",
		"menus:manage"
	],
	[
		"/_emdash/api/sections",
		"GET",
		"content:read"
	],
	[
		"/_emdash/api/sections",
		"WRITE",
		"content:write"
	],
	[
		"/_emdash/api/widget-areas",
		"GET",
		"content:read"
	],
	[
		"/_emdash/api/widget-areas",
		"WRITE",
		"content:write"
	],
	[
		"/_emdash/api/revisions",
		"GET",
		"content:read"
	],
	[
		"/_emdash/api/revisions",
		"WRITE",
		"content:write"
	],
	[
		"/_emdash/api/search",
		"GET",
		"content:read"
	],
	[
		"/_emdash/api/search",
		"WRITE",
		"admin"
	],
	[
		"/_emdash/api/import",
		"*",
		"admin"
	],
	[
		"/_emdash/api/admin",
		"*",
		"admin"
	],
	[
		"/_emdash/api/plugins",
		"*",
		"admin"
	],
	[
		"/_emdash/api/settings",
		"GET",
		"settings:read"
	],
	[
		"/_emdash/api/settings",
		"WRITE",
		"settings:manage"
	],
	[
		"/_emdash/api/mcp",
		"*",
		"content:read"
	]
];
const WRITE_METHODS = new Set([
	"POST",
	"PUT",
	"PATCH",
	"DELETE"
]);
/**
* Enforce API token scopes based on the request URL and HTTP method.
* Returns a 403 Response if the scope is insufficient, or null if allowed.
*
* Session-authenticated requests (tokenScopes === undefined) are never checked.
*/
function enforceTokenScope(pathname, method, tokenScopes) {
	if (!tokenScopes) return null;
	const isWrite = WRITE_METHODS.has(method);
	for (const [prefix, ruleMethod, scope] of SCOPE_RULES) {
		if (pathname !== prefix && !pathname.startsWith(prefix + "/")) continue;
		if (ruleMethod === "*" || ruleMethod === "WRITE" && isWrite || ruleMethod === method) {
			if (hasScope(tokenScopes, scope)) return null;
			return new Response(JSON.stringify({ error: {
				code: "INSUFFICIENT_SCOPE",
				message: `Token lacks required scope: ${scope}`
			} }), {
				status: 403,
				headers: {
					"Content-Type": "application/json",
					...MW_CACHE_HEADERS
				}
			});
		}
	}
	if (hasScope(tokenScopes, "admin")) return null;
	return new Response(JSON.stringify({ error: {
		code: "INSUFFICIENT_SCOPE",
		message: "Token lacks required scope: admin"
	} }), {
		status: 403,
		headers: {
			"Content-Type": "application/json",
			...MW_CACHE_HEADERS
		}
	});
}

//#endregion
export { onRequest };
//# sourceMappingURL=auth.mjs.map