import { createLogger } from "../../../utils/logger/index.js";
import { init_logger } from "../../../utils/logger.js";
import { safeEqualSecret } from "../../security/secret-equal.js";
import { resolveClientIpFromRequest } from "../../client-ip.js";
import { authPolicyConfig, buckets, resolveAuthRateLimit } from "../../rate-limit/buckets.js";
import { getClientIpFromHeaders } from "../../security/loopback.js";
import { resolveAuthTracking } from "../../rate-limit/auth-policy.js";
import { isAuthRateLimitGloballyDisabled } from "../../rate-limit/env-flags.js";
import "../../rate-limit/index.js";
import { authorizeTrustedProxy } from "../../trusted-proxy.js";
import { createMiddleware } from "hono/factory";
import { getConnInfo } from "@hono/node-server/conninfo";
//#region src/gateway/hono/middleware/auth.ts
init_logger();
const log = createLogger("Hono:Auth");
function validateToken(providedToken, expectedToken) {
	if (!providedToken) return false;
	return safeEqualSecret(providedToken, expectedToken);
}
function extractTokenFromHeader(authHeader) {
	if (!authHeader) return null;
	const parts = authHeader.split(" ");
	if (parts.length === 2 && parts[0].toLowerCase() === "bearer") return parts[1];
	return authHeader;
}
/**
* SECURITY: query-string tokens leak into server logs, Referer headers, and
* browser history. We accept them only for SSE/WebSocket connections where
* the `Authorization` header cannot be set by `EventSource`. For normal REST
* requests prefer the `Authorization: Bearer <token>` header.
*/
function extractTokenFromQuery(url) {
	return new URL(url).searchParams.get("token");
}
const QUERY_TOKEN_ALLOWED_PATHS = new Set(["/api/events", "/api/ws"]);
function isQueryTokenAllowedPath(path) {
	return QUERY_TOKEN_ALLOWED_PATHS.has(path) || path.startsWith("/api/events");
}
function resolveRemoteAddress(c) {
	try {
		return getConnInfo(c).remote.address;
	} catch {
		return;
	}
}
function resolveMiddlewareClientIp(c, trustedProxies, allowRealIpFallback) {
	if (trustedProxies?.length) return resolveClientIpFromRequest({
		remoteAddress: resolveRemoteAddress(c),
		getHeader: (name) => c.req.header(name),
		trustedProxies,
		allowRealIpFallback
	});
	return getClientIpFromHeaders({ get: (name) => c.req.header(name) ?? void 0 });
}
function buildRateLimitContext(getGatewayAuth, clientIp, origin) {
	const cfg = resolveAuthRateLimit(getGatewayAuth?.()?.rateLimit);
	if (!(cfg.enabled && !isAuthRateLimitGloballyDisabled())) return {
		active: false,
		cfg,
		trackingKey: void 0
	};
	const tracking = resolveAuthTracking({
		clientIp,
		origin,
		cfg: authPolicyConfig(cfg)
	});
	return {
		active: true,
		cfg,
		trackingKey: tracking.exempt ? void 0 : tracking.key
	};
}
function checkBlocked(rl) {
	if (!rl.active || rl.trackingKey === void 0) return { blocked: false };
	return buckets.authFailure(rl.cfg).check(rl.trackingKey);
}
function recordFailure(rl) {
	if (!rl.active || rl.trackingKey === void 0) return;
	buckets.authFailure(rl.cfg).fail(rl.trackingKey);
}
function recordSuccess(rl) {
	if (!rl.active || rl.trackingKey === void 0) return;
	buckets.authFailure(rl.cfg).succeed(rl.trackingKey);
}
function blockedResponse(c, retryAfterSec) {
	c.header("Retry-After", String(retryAfterSec));
	return c.json({
		error: "Too Many Requests",
		code: "auth_blocked",
		message: "Too many authentication attempts",
		retryAfter: retryAfterSec
	}, 429);
}
function auth(config) {
	const { token, getGatewayAuth, getResolvedAuth, getTrustedProxyContext } = config || {};
	return createMiddleware(async (c, next) => {
		const resolvedAuth = getResolvedAuth?.();
		const authMode = resolvedAuth?.mode ?? (token ? "token" : "none");
		if (authMode === "trusted-proxy") {
			const proxyContext = getTrustedProxyContext?.();
			const trustedProxies = proxyContext?.trustedProxies;
			const trustedProxyConfig = resolvedAuth?.trustedProxy;
			const clientIp = resolveMiddlewareClientIp(c, trustedProxies, proxyContext?.allowRealIpFallback);
			const origin = c.req.header("origin");
			const rl = buildRateLimitContext(getGatewayAuth, clientIp, origin);
			if (!trustedProxyConfig) {
				log.warn({
					path: c.req.path,
					method: c.req.method,
					clientIp,
					reason: "trusted_proxy_config_missing"
				}, "HTTP auth rejected: trusted-proxy config missing");
				return c.json({
					error: "Unauthorized",
					code: "auth_unconfigured",
					message: "Trusted-proxy auth is not configured"
				}, 401);
			}
			const blocked = checkBlocked(rl);
			if (blocked.blocked) {
				log.warn({
					clientIp,
					origin: origin ?? void 0,
					path: c.req.path,
					method: c.req.method,
					retryAfterSec: blocked.retryAfterSec,
					reason: "auth_blocked"
				}, "Auth rate limit blocked");
				return blockedResponse(c, blocked.retryAfterSec);
			}
			const result = authorizeTrustedProxy({
				remoteAddress: resolveRemoteAddress(c),
				getHeader: (name) => c.req.header(name),
				trustedProxies,
				trustedProxyConfig
			});
			if (result.ok === false) {
				recordFailure(rl);
				log.warn({
					path: c.req.path,
					method: c.req.method,
					clientIp,
					reason: result.reason
				}, `HTTP auth rejected: trusted-proxy validation failed (${result.reason})`);
				return c.json({
					error: "Unauthorized",
					code: "invalid_proxy_credentials",
					message: "Trusted-proxy authentication failed"
				}, 401);
			}
			recordSuccess(rl);
			await next();
			return;
		}
		if (authMode === "none" || !token) return next();
		const proxyContext = getTrustedProxyContext?.();
		const clientIp = resolveMiddlewareClientIp(c, proxyContext?.trustedProxies, proxyContext?.allowRealIpFallback);
		const origin = c.req.header("origin");
		const rl = buildRateLimitContext(getGatewayAuth, clientIp, origin);
		const authHeader = extractTokenFromHeader(c.req.header("authorization"));
		const requestPath = new URL(c.req.url).pathname;
		const queryToken = isQueryTokenAllowedPath(requestPath) ? extractTokenFromQuery(c.req.url) : null;
		if (!authHeader && queryToken === null && new URL(c.req.url).searchParams.has("token")) log.warn({
			path: requestPath,
			method: c.req.method,
			clientIp
		}, "Token in query string rejected: use Authorization header for this endpoint");
		const providedToken = authHeader || queryToken;
		if (providedToken && validateToken(providedToken, token)) {
			recordSuccess(rl);
			await next();
			return;
		}
		const blocked = checkBlocked(rl);
		if (blocked.blocked) {
			log.warn({
				clientIp,
				origin: origin ?? void 0,
				path: requestPath,
				method: c.req.method,
				retryAfterSec: blocked.retryAfterSec,
				reason: "auth_blocked"
			}, "Auth rate limit blocked");
			return blockedResponse(c, blocked.retryAfterSec);
		}
		if (!providedToken) {
			log.warn({
				path: c.req.path,
				method: c.req.method,
				clientIp,
				reason: "missing_token"
			}, "HTTP auth rejected: no Bearer or ?token=");
			return c.json({
				error: "Unauthorized",
				code: "missing_token",
				message: "Missing authentication token"
			}, 401);
		}
		recordFailure(rl);
		log.warn({
			path: c.req.path,
			method: c.req.method,
			clientIp,
			reason: "invalid_token"
		}, "HTTP auth rejected: token mismatch");
		return c.json({
			error: "Unauthorized",
			code: "invalid_token",
			message: "Invalid authentication token"
		}, 401);
	});
}
//#endregion
export { auth };

//# sourceMappingURL=auth.js.map