import { n as getPublicOrigin } from "../../../../public-url-Danv47cS.mjs";
import { n as VALID_SCOPES } from "../../../../api-tokens-DsvvCtdh.mjs";
import { t as escapeHtml } from "../../../../escape-DqD0aCKu.mjs";
import { c as validateRedirectUri, o as lookupOAuthClient, s as validateClientRedirectUri } from "../../../../oauth-clients-BmTI5rsl.mjs";
import "../../../../oauth-user-lookup-Bsc_GsX-.mjs";
import { n as handleAuthorizationApproval, t as buildDeniedRedirect } from "../../../../oauth-authorization-CXWkIGT4.mjs";

//#region src/astro/routes/api/oauth/authorize.ts
const prerender = false;
const CSRF_COOKIE_NAME = "emdash_oauth_csrf";
/** Generate a 32-byte random token as hex. */
function generateCsrfToken() {
	const bytes = new Uint8Array(32);
	crypto.getRandomValues(bytes);
	return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
}
/** Build the Set-Cookie header value for the CSRF token. */
function csrfCookieHeader(token, request, siteUrl) {
	return `${CSRF_COOKIE_NAME}=${token}; Path=/_emdash/oauth/authorize; HttpOnly; SameSite=Strict${(siteUrl ? siteUrl.startsWith("https:") : new URL(request.url).protocol === "https:") ? "; Secure" : ""}`;
}
/** Extract the CSRF token from the request's cookies. */
function getCsrfCookie(request) {
	const cookieHeader = request.headers.get("Cookie");
	if (!cookieHeader) return null;
	return cookieHeader.match(new RegExp(`(?:^|;\\s*)${CSRF_COOKIE_NAME}=([^;]+)`))?.[1] ?? null;
}
const SCOPE_LABELS = {
	"content:read": "Read content (posts, pages, etc.)",
	"content:write": "Create, edit, and delete content",
	"media:read": "View media files",
	"media:write": "Upload and manage media files",
	"schema:read": "View collection schemas",
	"schema:write": "Create and modify collection schemas",
	admin: "Full administrative access"
};
const GET = async ({ url, request, locals }) => {
	const { emdash, user } = locals;
	const clientId = url.searchParams.get("client_id");
	const redirectUri = url.searchParams.get("redirect_uri");
	const responseType = url.searchParams.get("response_type");
	const codeChallenge = url.searchParams.get("code_challenge");
	const codeChallengeMethod = url.searchParams.get("code_challenge_method");
	const scope = url.searchParams.get("scope");
	const state = url.searchParams.get("state");
	if (!clientId || !redirectUri || responseType !== "code" || !codeChallenge) return new Response(renderErrorPage("Invalid authorization request. Missing required parameters."), {
		status: 400,
		headers: { "Content-Type": "text/html; charset=utf-8" }
	});
	if (codeChallengeMethod && codeChallengeMethod !== "S256") return new Response(renderErrorPage("Only S256 code challenge method is supported."), {
		status: 400,
		headers: { "Content-Type": "text/html; charset=utf-8" }
	});
	if (emdash?.db) {
		const client = await lookupOAuthClient(emdash.db, clientId);
		if (!client) return new Response(renderErrorPage("Unknown client application."), {
			status: 400,
			headers: { "Content-Type": "text/html; charset=utf-8" }
		});
		if (validateClientRedirectUri(redirectUri, client.redirectUris)) return new Response(renderErrorPage("The redirect URI is not registered for this client."), {
			status: 400,
			headers: { "Content-Type": "text/html; charset=utf-8" }
		});
	}
	if (!user) {
		const loginUrl = new URL("/_emdash/admin/login", getPublicOrigin(url, emdash?.config));
		loginUrl.searchParams.set("redirect", url.pathname + url.search);
		return Response.redirect(loginUrl.toString(), 302);
	}
	const validSet = new Set(VALID_SCOPES);
	const requestedScopes = (scope ?? "").split(" ").filter(Boolean).filter((s) => validSet.has(s));
	if (requestedScopes.length === 0) return new Response(renderErrorPage("No valid scopes requested."), {
		status: 400,
		headers: { "Content-Type": "text/html; charset=utf-8" }
	});
	const csrfToken = generateCsrfToken();
	const html = renderConsentPage({
		clientId,
		scopes: requestedScopes,
		redirectUri,
		responseType,
		codeChallenge,
		codeChallengeMethod: codeChallengeMethod ?? "S256",
		state: state ?? "",
		resource: url.searchParams.get("resource") ?? "",
		userName: user.name ?? user.email,
		csrfToken
	});
	return new Response(html, { headers: {
		"Content-Type": "text/html; charset=utf-8",
		"Set-Cookie": csrfCookieHeader(csrfToken, request, getPublicOrigin(url, emdash?.config))
	} });
};
const POST = async ({ request, locals }) => {
	const { emdash, user } = locals;
	if (!emdash?.db) return new Response(renderErrorPage("EmDash is not initialized."), {
		status: 500,
		headers: { "Content-Type": "text/html; charset=utf-8" }
	});
	if (!user) return new Response(renderErrorPage("Authentication required."), {
		status: 401,
		headers: { "Content-Type": "text/html; charset=utf-8" }
	});
	const formData = await request.formData();
	const field = (name, fallback = "") => {
		const v = formData.get(name);
		return typeof v === "string" ? v : fallback;
	};
	const formCsrf = field("csrf_token");
	const cookieCsrf = getCsrfCookie(request);
	const csrfError = new Response(renderErrorPage("Invalid or missing CSRF token. Please try again."), {
		status: 403,
		headers: { "Content-Type": "text/html; charset=utf-8" }
	});
	if (!formCsrf || !cookieCsrf) return csrfError;
	const csrfEncoder = new TextEncoder();
	const [csrfHashA, csrfHashB] = await Promise.all([crypto.subtle.digest("SHA-256", csrfEncoder.encode(formCsrf)), crypto.subtle.digest("SHA-256", csrfEncoder.encode(cookieCsrf))]);
	const a = new Uint8Array(csrfHashA);
	const b = new Uint8Array(csrfHashB);
	let diff = 0;
	for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
	if (diff !== 0) return csrfError;
	const action = field("action");
	const redirectUri = field("redirect_uri");
	const state = field("state") || void 0;
	if (!redirectUri) return new Response(renderErrorPage("Missing redirect_uri."), {
		status: 400,
		headers: { "Content-Type": "text/html; charset=utf-8" }
	});
	const uriError = validateRedirectUri(redirectUri);
	if (uriError) return new Response(renderErrorPage(escapeHtml(uriError)), {
		status: 400,
		headers: { "Content-Type": "text/html; charset=utf-8" }
	});
	if (action === "deny") {
		const clientId = field("client_id");
		if (!clientId) return new Response(renderErrorPage("Missing client_id."), {
			status: 400,
			headers: { "Content-Type": "text/html; charset=utf-8" }
		});
		const client = await lookupOAuthClient(emdash.db, clientId);
		if (!client) return new Response(renderErrorPage("Unknown client application."), {
			status: 400,
			headers: { "Content-Type": "text/html; charset=utf-8" }
		});
		if (validateClientRedirectUri(redirectUri, client.redirectUris)) return new Response(renderErrorPage("The redirect URI is not registered for this client."), {
			status: 400,
			headers: { "Content-Type": "text/html; charset=utf-8" }
		});
		const denyUrl = buildDeniedRedirect(redirectUri, state);
		return Response.redirect(denyUrl, 302);
	}
	const result = await handleAuthorizationApproval(emdash.db, user.id, user.role, {
		response_type: field("response_type", "code"),
		client_id: field("client_id"),
		redirect_uri: redirectUri,
		scope: field("scope"),
		state,
		code_challenge: field("code_challenge"),
		code_challenge_method: field("code_challenge_method", "S256"),
		resource: field("resource") || void 0
	});
	if (!result.success) {
		const errMsg = result.error?.message ?? "Authorization failed";
		try {
			const errorUrl = new URL(redirectUri);
			errorUrl.searchParams.set("error", "server_error");
			errorUrl.searchParams.set("error_description", "Authorization failed");
			if (state) errorUrl.searchParams.set("state", state);
			return Response.redirect(errorUrl.toString(), 302);
		} catch {
			return new Response(renderErrorPage(escapeHtml(errMsg)), {
				status: 400,
				headers: { "Content-Type": "text/html; charset=utf-8" }
			});
		}
	}
	return Response.redirect(result.data.redirect_url, 302);
};
function renderConsentPage(params) {
	const scopeList = params.scopes.map((s) => {
		return `<li>${escapeHtml(SCOPE_LABELS[s] ?? s)}</li>`;
	}).join("\n");
	return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Authorize Application — EmDash</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body { font-family: system-ui, -apple-system, sans-serif; background: #0a0a0a; color: #e5e5e5; display: flex; justify-content: center; align-items: center; min-height: 100vh; padding: 1rem; }
  .card { background: #171717; border: 1px solid #262626; border-radius: 12px; max-width: 420px; width: 100%; padding: 2rem; }
  h1 { font-size: 1.25rem; font-weight: 600; margin-bottom: 0.5rem; }
  .client-id { color: #a3a3a3; font-size: 0.875rem; word-break: break-all; margin-bottom: 1.5rem; }
  .user { color: #a3a3a3; font-size: 0.875rem; margin-bottom: 1rem; }
  h2 { font-size: 0.875rem; font-weight: 500; color: #a3a3a3; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.75rem; }
  ul { list-style: none; margin-bottom: 1.5rem; }
  li { padding: 0.5rem 0; border-bottom: 1px solid #262626; font-size: 0.875rem; }
  li:last-child { border-bottom: none; }
  .actions { display: flex; gap: 0.75rem; }
  button { flex: 1; padding: 0.625rem 1rem; border-radius: 8px; border: none; font-size: 0.875rem; font-weight: 500; cursor: pointer; }
  .approve { background: #2563eb; color: white; }
  .approve:hover { background: #1d4ed8; }
  .deny { background: #262626; color: #e5e5e5; }
  .deny:hover { background: #333; }
</style>
</head>
<body>
<div class="card">
  <h1>Authorize Application</h1>
  <p class="client-id">${escapeHtml(params.clientId)}</p>
  <p class="user">Signed in as <strong>${escapeHtml(params.userName)}</strong></p>
  <h2>Permissions requested</h2>
  <ul>${scopeList}</ul>
  <form method="POST">
    <input type="hidden" name="csrf_token" value="${escapeHtml(params.csrfToken)}">
    <input type="hidden" name="response_type" value="${escapeHtml(params.responseType)}">
    <input type="hidden" name="client_id" value="${escapeHtml(params.clientId)}">
    <input type="hidden" name="redirect_uri" value="${escapeHtml(params.redirectUri)}">
    <input type="hidden" name="scope" value="${escapeHtml(params.scopes.join(" "))}">
    <input type="hidden" name="state" value="${escapeHtml(params.state)}">
    <input type="hidden" name="code_challenge" value="${escapeHtml(params.codeChallenge)}">
    <input type="hidden" name="code_challenge_method" value="${escapeHtml(params.codeChallengeMethod)}">
    <input type="hidden" name="resource" value="${escapeHtml(params.resource)}">
    <div class="actions">
      <button type="submit" name="action" value="deny" class="deny">Deny</button>
      <button type="submit" name="action" value="approve" class="approve">Approve</button>
    </div>
  </form>
</div>
</body>
</html>`;
}
function renderErrorPage(message) {
	return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Authorization Error — EmDash</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body { font-family: system-ui, -apple-system, sans-serif; background: #0a0a0a; color: #e5e5e5; display: flex; justify-content: center; align-items: center; min-height: 100vh; padding: 1rem; }
  .card { background: #171717; border: 1px solid #262626; border-radius: 12px; max-width: 420px; width: 100%; padding: 2rem; }
  h1 { font-size: 1.25rem; font-weight: 600; margin-bottom: 1rem; color: #ef4444; }
  p { font-size: 0.875rem; color: #a3a3a3; }
</style>
</head>
<body>
<div class="card">
  <h1>Authorization Error</h1>
  <p>${escapeHtml(message)}</p>
</div>
</body>
</html>`;
}

//#endregion
export { GET, POST, prerender };
//# sourceMappingURL=authorize.mjs.map