import { t as apiError } from "./error-CkAseSqZ.mjs";
import { sql } from "kysely";

//#region src/auth/rate-limit.ts
/** Loose validation for IPv4 and IPv6 addresses. */
const IP_PATTERN = /^[\da-fA-F.:]+$/;
/**
* Check (and increment) the rate limit for a given IP + endpoint.
*
* If `ip` is null (no trusted IP available), rate limiting is skipped
* and the request is allowed. There's no meaningful key to rate limit
* on when the IP is unknown.
*
* Returns whether the request is allowed. The counter is always
* incremented — even when the limit is exceeded — so that repeated
* abuse doesn't reset the window.
*
* Piggybacks cleanup of expired entries with a 1% probability
* to prevent unbounded table growth.
*/
async function checkRateLimit(db, ip, endpoint, maxRequests, windowSeconds) {
	if (!ip) return {
		allowed: true,
		count: 0,
		limit: maxRequests
	};
	const windowStart = (/* @__PURE__ */ new Date(Math.floor(Date.now() / (windowSeconds * 1e3)) * windowSeconds * 1e3)).toISOString();
	const count = (await sql`
		INSERT INTO _emdash_rate_limits (key, "window", count)
		VALUES (${`${ip}:${endpoint}`}, ${windowStart}, 1)
		ON CONFLICT (key, "window")
		DO UPDATE SET count = _emdash_rate_limits.count + 1
		RETURNING count
	`.execute(db)).rows[0]?.count ?? 1;
	if (Math.random() < .01) cleanupExpiredRateLimits(db).catch(() => {});
	return {
		allowed: count <= maxRequests,
		count,
		limit: maxRequests
	};
}
/**
* Build a 429 Too Many Requests response with standard headers.
*/
function rateLimitResponse(retryAfterSeconds) {
	const response = apiError("RATE_LIMITED", "Too many requests. Please try again later.", 429);
	response.headers.set("Retry-After", String(retryAfterSeconds));
	return response;
}
/**
* Extract client IP from a Request.
*
* Resolution order:
* 1. `CF-Connecting-IP` — trusted only when the Cloudflare `cf` object is
*    present. CF edge overwrites any client-supplied value, so this is the
*    cryptographically trustworthy path on Workers. Operator-declared
*    trusted headers cannot override it.
* 2. `X-Forwarded-For` (first entry) — trusted only when the `cf` object
*    is present (CF sets this reliably).
* 3. Operator-declared trusted proxy headers (ordered list) — used as a
*    fallback for non-CF deployments behind a reverse proxy the operator
*    controls. Also applies as a fill-in on CF when the CF headers are
*    absent (e.g. internal cron handlers).
* 4. `null` — no trusted IP available. Callers must handle this gracefully
*    (e.g. skip rate limiting).
*
* Pass `trustedHeaders` from `getTrustedProxyHeaders(emdash.config)` so
* self-hosted non-CF deployments can opt into reading a specific header.
*
* Aligned with `extractRequestMeta` in `plugins/request-meta.ts`.
*/
function getClientIp(request, trustedHeaders = []) {
	const headers = request.headers;
	if (request.cf) {
		const cfIp = headers.get("cf-connecting-ip")?.trim();
		if (cfIp && IP_PATTERN.test(cfIp)) return cfIp;
		const xff = headers.get("x-forwarded-for");
		if (xff) {
			const first = xff.split(",")[0]?.trim();
			if (first && IP_PATTERN.test(first)) return first;
		}
	}
	for (const name of trustedHeaders) {
		const value = readIpFromHeader(headers, name);
		if (value) return value;
	}
	return null;
}
/**
* Read an IP from an operator-declared trusted header. XFF-style headers
* are parsed as comma-separated lists and the first entry is used.
*/
function readIpFromHeader(headers, name) {
	const value = headers.get(name);
	if (!value) return null;
	if (name.toLowerCase().endsWith("forwarded-for")) {
		const first = value.split(",")[0]?.trim();
		if (!first) return null;
		return IP_PATTERN.test(first) ? first : null;
	}
	const trimmed = value.trim();
	if (!trimmed) return null;
	return IP_PATTERN.test(trimmed) ? trimmed : null;
}
/**
* Delete expired rate limit entries.
*
* Entries with a window timestamp older than `maxAgeSeconds` are removed.
* Safe to call periodically (e.g., from cron cleanup or on-request piggyback).
*/
async function cleanupExpiredRateLimits(db, maxAgeSeconds = 3600) {
	const result = await sql`
		DELETE FROM _emdash_rate_limits WHERE "window" < ${(/* @__PURE__ */ new Date(Date.now() - maxAgeSeconds * 1e3)).toISOString()}
	`.execute(db);
	return Number(result.numAffectedRows ?? 0);
}

//#endregion
export { getClientIp as n, rateLimitResponse as r, checkRateLimit as t };
//# sourceMappingURL=rate-limit-C_Lf63Dd.mjs.map