// src/auth/keycloak.ts
var KEYCLOAK_DEFAULT_ROLES = [
  "offline_access",
  "uma_authorization"
];
function parseKeycloakRoles(accessToken, additionalDefaultRoles = []) {
  if (!accessToken) return [];
  try {
    const parts = accessToken.split(".");
    if (parts.length !== 3) return [];
    const payload = parts[1];
    const decoded = JSON.parse(atob(payload));
    const realmRoles = decoded.realm_roles ?? decoded.realm_access?.roles;
    if (!Array.isArray(realmRoles)) return [];
    const filterSet = /* @__PURE__ */ new Set([
      ...KEYCLOAK_DEFAULT_ROLES,
      ...additionalDefaultRoles
    ]);
    return realmRoles.filter(
      (role) => typeof role === "string" && !filterSet.has(role)
    );
  } catch {
    return [];
  }
}
function hasRole(roles, role) {
  return roles?.includes(role) ?? false;
}
function hasAnyRole(roles, requiredRoles) {
  if (!roles || roles.length === 0) return false;
  return requiredRoles.some((role) => roles.includes(role));
}
function hasAllRoles(roles, requiredRoles) {
  if (!roles || roles.length === 0) return false;
  return requiredRoles.every((role) => roles.includes(role));
}
function isTokenExpired(expiresAt, bufferMs = 6e4) {
  if (!expiresAt) return true;
  return Date.now() >= expiresAt - bufferMs;
}
function buildTokenRefreshParams(config, refreshToken) {
  return new URLSearchParams({
    grant_type: "refresh_token",
    client_id: config.clientId,
    client_secret: config.clientSecret,
    refresh_token: refreshToken
  });
}
function getTokenEndpoint(issuer) {
  const base = issuer.endsWith("/") ? issuer.slice(0, -1) : issuer;
  return `${base}/protocol/openid-connect/token`;
}
function getEndSessionEndpoint(issuer) {
  const base = issuer.endsWith("/") ? issuer.slice(0, -1) : issuer;
  return `${base}/protocol/openid-connect/logout`;
}
async function refreshKeycloakToken(config, refreshToken, additionalDefaultRoles) {
  try {
    const endpoint = getTokenEndpoint(config.issuer);
    const params = buildTokenRefreshParams(config, refreshToken);
    const response = await fetch(endpoint, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: params
    });
    if (!response.ok) {
      const body = await response.text().catch(() => "");
      return {
        ok: false,
        error: `Token refresh failed: HTTP ${response.status} - ${body}`
      };
    }
    const data = await response.json();
    return {
      ok: true,
      tokens: {
        accessToken: data.access_token,
        refreshToken: data.refresh_token ?? refreshToken,
        idToken: data.id_token,
        expiresAt: Date.now() + data.expires_in * 1e3,
        roles: parseKeycloakRoles(data.access_token, additionalDefaultRoles)
      }
    };
  } catch (error) {
    return {
      ok: false,
      error: error instanceof Error ? error.message : "Token refresh failed"
    };
  }
}

// src/auth/nextjs-keycloak.ts
function buildAuthCookies(config = {}) {
  const { domain, sessionToken = true, callbackUrl = true } = config;
  const isProduction = process.env.NODE_ENV === "production";
  const cookieDomain = isProduction ? domain : void 0;
  const baseOptions = {
    httpOnly: true,
    sameSite: "lax",
    path: "/",
    secure: isProduction,
    domain: cookieDomain
  };
  const cookies = {
    pkceCodeVerifier: {
      name: "authjs.pkce.code_verifier",
      options: { ...baseOptions }
    },
    state: {
      name: "authjs.state",
      options: { ...baseOptions }
    }
  };
  if (sessionToken) {
    cookies.sessionToken = {
      name: isProduction ? "__Secure-authjs.session-token" : "authjs.session-token",
      options: { ...baseOptions }
    };
  }
  if (callbackUrl) {
    cookies.callbackUrl = {
      name: isProduction ? "__Secure-authjs.callback-url" : "authjs.callback-url",
      options: { ...baseOptions }
    };
  }
  return cookies;
}
function buildRedirectCallback(config = {}) {
  const { allowWwwVariant = false } = config;
  return async ({ url, baseUrl }) => {
    if (url.startsWith("/")) return `${baseUrl}${url}`;
    try {
      if (new URL(url).origin === baseUrl) return url;
    } catch {
      return baseUrl;
    }
    if (allowWwwVariant) {
      try {
        const urlHost = new URL(url).hostname;
        const baseHost = new URL(baseUrl).hostname;
        if (urlHost === `www.${baseHost}` || baseHost === `www.${urlHost}`) {
          return url;
        }
      } catch {
      }
    }
    return baseUrl;
  };
}
function buildKeycloakCallbacks(config) {
  const {
    issuer,
    clientId,
    clientSecret,
    defaultRoles = [],
    debug = process.env.NODE_ENV === "development"
  } = config;
  const kcConfig = { issuer, clientId, clientSecret };
  function log(message, meta) {
    if (debug) {
      console.log(`[Auth] ${message}`, meta ? JSON.stringify(meta) : "");
    }
  }
  return {
    /**
     * JWT callback — stores Keycloak tokens and handles refresh.
     *
     * Compatible with Auth.js v5 JWT callback signature.
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    async jwt({
      token,
      user,
      account
    }) {
      if (user) {
        token.id = token.sub ?? user.id;
      }
      if (account?.provider === "keycloak") {
        token.accessToken = account.access_token;
        token.refreshToken = account.refresh_token;
        token.idToken = account.id_token;
        token.roles = parseKeycloakRoles(
          account.access_token,
          defaultRoles
        );
        token.accessTokenExpires = account.expires_at ? account.expires_at * 1e3 : Date.now() + 3e5;
        return token;
      }
      if (!isTokenExpired(token.accessTokenExpires)) {
        return token;
      }
      if (token.refreshToken) {
        log("Token expired, attempting refresh...");
        const result = await refreshKeycloakToken(
          kcConfig,
          token.refreshToken,
          defaultRoles
        );
        if (result.ok) {
          token.accessToken = result.tokens.accessToken;
          token.idToken = result.tokens.idToken ?? token.idToken;
          token.refreshToken = result.tokens.refreshToken ?? token.refreshToken;
          token.accessTokenExpires = result.tokens.expiresAt;
          token.roles = result.tokens.roles;
          delete token.error;
          log("Token refreshed OK");
          return token;
        }
        log("Token refresh failed", { error: result.error });
        return { ...token, error: "RefreshTokenError" };
      }
      log("Token expired but no refresh token available");
      return { ...token, error: "RefreshTokenError" };
    },
    /**
     * Session callback — maps JWT fields to the session object.
     *
     * Compatible with Auth.js v5 session callback signature.
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    async session({ session, token }) {
      const user = session.user;
      if (user) {
        user.id = token.id || token.sub;
        user.roles = token.roles || [];
      }
      session.idToken = token.idToken;
      session.accessToken = token.accessToken;
      if (token.error) {
        session.error = token.error;
      }
      return session;
    }
  };
}

// src/auth/api-security.ts
var StandardRateLimitPresets = {
  /** General API: 100/min, 200/min authenticated */
  apiGeneral: {
    limit: 100,
    windowSeconds: 60,
    authenticatedLimit: 200
  },
  /** Admin operations: 100/min (admins are trusted) */
  adminAction: {
    limit: 100,
    windowSeconds: 60
  },
  /** AI/expensive operations: 20/hour, 50/hour authenticated */
  aiRequest: {
    limit: 20,
    windowSeconds: 3600,
    authenticatedLimit: 50
  },
  /** Auth attempts: 5/15min with 15min block */
  authAttempt: {
    limit: 5,
    windowSeconds: 900,
    blockDurationSeconds: 900
  },
  /** Contact/public forms: 10/hour */
  publicForm: {
    limit: 10,
    windowSeconds: 3600,
    blockDurationSeconds: 1800
  },
  /** Checkout/billing: 10/hour with 1hr block */
  checkout: {
    limit: 10,
    windowSeconds: 3600,
    blockDurationSeconds: 3600
  }
};
function resolveRateLimitIdentifier(session, clientIp) {
  if (session?.user?.id) {
    return { identifier: `user:${session.user.id}`, isAuthenticated: true };
  }
  if (session?.user?.email) {
    return {
      identifier: `email:${session.user.email}`,
      isAuthenticated: true
    };
  }
  return { identifier: `ip:${clientIp}`, isAuthenticated: false };
}
function extractClientIp(getHeader) {
  return getHeader("cf-connecting-ip") || getHeader("x-real-ip") || getHeader("x-forwarded-for")?.split(",")[0]?.trim() || "unknown";
}
function buildRateLimitHeaders(limit, remaining, resetAtMs) {
  return {
    "X-RateLimit-Limit": String(limit),
    "X-RateLimit-Remaining": String(Math.max(0, remaining)),
    "X-RateLimit-Reset": String(Math.ceil(resetAtMs / 1e3))
  };
}
function buildErrorBody(error, extra) {
  return { error, ...extra };
}
var WrapperPresets = {
  /** Public route: no auth, rate limited */
  public: {
    requireAuth: false,
    requireAdmin: false,
    rateLimit: "apiGeneral"
  },
  /** Authenticated route: requires session */
  authenticated: {
    requireAuth: true,
    requireAdmin: false,
    rateLimit: "apiGeneral"
  },
  /** Admin route: requires session with admin role */
  admin: {
    requireAuth: true,
    requireAdmin: true,
    rateLimit: "adminAction"
  },
  /** Legacy admin: accepts session OR bearer token */
  legacyAdmin: {
    requireAuth: true,
    requireAdmin: true,
    allowBearerToken: true,
    rateLimit: "adminAction"
  },
  /** AI/expensive: requires auth, strict rate limit */
  ai: {
    requireAuth: true,
    requireAdmin: false,
    rateLimit: "aiRequest"
  },
  /** Cron: no rate limit, admin-level access */
  cron: {
    requireAuth: true,
    requireAdmin: false,
    skipRateLimit: true,
    skipAudit: false
  }
};

// src/auth/schemas.ts
import { z } from "zod";

// src/security.ts
import { timingSafeEqual } from "crypto";
var URL_PROTOCOL_PATTERN = /(https?:\/\/|ftp:\/\/|www\.)\S+/i;
var URL_DOMAIN_PATTERN = /\b[\w.-]+\.(com|net|org|io|co|dev|app|xyz|info|biz|me|us|uk|edu|gov)\b/i;
var HTML_TAG_PATTERN = /<[^>]*>/;
function escapeHtml(str) {
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
}
function containsUrls(str) {
  return URL_PROTOCOL_PATTERN.test(str) || URL_DOMAIN_PATTERN.test(str);
}
function containsHtml(str) {
  return HTML_TAG_PATTERN.test(str);
}
function stripHtml(str) {
  return str.replace(/<[^>]*>/g, "");
}
function stripNullBytes(value) {
  if (value === null || value === void 0) return value;
  return value.replace(/\u0000/g, "");
}
function stripNullBytesDeep(value, depth = 8) {
  if (depth <= 0) {
    throw new Error("stripNullBytesDeep: max depth exceeded (circular?)");
  }
  if (typeof value === "string") {
    return value.replace(/\u0000/g, "");
  }
  if (Array.isArray(value)) {
    return value.map((v) => stripNullBytesDeep(v, depth - 1));
  }
  if (value !== null && typeof value === "object") {
    const out = {};
    for (const [k, v] of Object.entries(value)) {
      out[k] = stripNullBytesDeep(v, depth - 1);
    }
    return out;
  }
  return value;
}
function constantTimeEqual(a, b) {
  try {
    const aBuf = Buffer.from(a, "utf-8");
    const bBuf = Buffer.from(b, "utf-8");
    if (aBuf.length !== bBuf.length) return false;
    return timingSafeEqual(aBuf, bBuf);
  } catch {
    return false;
  }
}
function sanitizeApiError(error, statusCode, isDevelopment = false) {
  if (statusCode >= 400 && statusCode < 500) {
    const message = error instanceof Error ? error.message : String(error || "Bad request");
    return { message };
  }
  const result = {
    message: "An internal error occurred. Please try again later.",
    code: "INTERNAL_ERROR"
  };
  if (isDevelopment && error instanceof Error) {
    result.stack = error.stack;
  }
  return result;
}
function getCorrelationId(headers) {
  const get = typeof headers === "function" ? headers : (name) => {
    const val = headers[name] ?? headers[name.toLowerCase()];
    return Array.isArray(val) ? val[0] : val;
  };
  return get("x-request-id") || get("X-Request-ID") || get("x-correlation-id") || get("X-Correlation-ID") || crypto.randomUUID();
}

// src/auth/schemas.ts
var EmailSchema = z.string().trim().toLowerCase().email("Invalid email address");
var PasswordSchema = z.string().min(8, "Password must be at least 8 characters").max(100, "Password must be less than 100 characters");
var SlugSchema = z.string().min(1, "Slug is required").max(100, "Slug must be less than 100 characters").regex(
  /^[a-z0-9-]+$/,
  "Slug can only contain lowercase letters, numbers, and hyphens"
);
var PhoneSchema = z.string().regex(/^[\d\s()+.\-]{7,20}$/, "Invalid phone number format");
var PersonNameSchema = z.string().min(2, "Name must be at least 2 characters").max(100, "Name must be less than 100 characters").regex(
  /^[a-zA-Z\s\-']+$/,
  "Name can only contain letters, spaces, hyphens and apostrophes"
);
function createSafeTextSchema(options) {
  const {
    min,
    max,
    allowHtml = false,
    allowUrls = false,
    fieldName = "Text"
  } = options ?? {};
  let schema = z.string();
  if (min !== void 0)
    schema = schema.min(min, `${fieldName} must be at least ${min} characters`);
  if (max !== void 0)
    schema = schema.max(
      max,
      `${fieldName} must be less than ${max} characters`
    );
  if (!allowHtml && !allowUrls) {
    return schema.refine((val) => !HTML_TAG_PATTERN.test(val), "HTML tags are not allowed").refine(
      (val) => !URL_PROTOCOL_PATTERN.test(val),
      "Links are not allowed for security reasons"
    ).refine(
      (val) => !URL_DOMAIN_PATTERN.test(val),
      "Links are not allowed for security reasons"
    );
  }
  if (!allowHtml) {
    return schema.refine(
      (val) => !HTML_TAG_PATTERN.test(val),
      "HTML tags are not allowed"
    );
  }
  if (!allowUrls) {
    return schema.refine(
      (val) => !URL_PROTOCOL_PATTERN.test(val),
      "Links are not allowed for security reasons"
    ).refine(
      (val) => !URL_DOMAIN_PATTERN.test(val),
      "Links are not allowed for security reasons"
    );
  }
  return schema;
}
var PaginationSchema = z.object({
  page: z.coerce.number().int().positive().default(1),
  limit: z.coerce.number().int().positive().max(100).default(20),
  sortBy: z.string().optional(),
  sortOrder: z.enum(["asc", "desc"]).default("desc")
});
var DateRangeSchema = z.object({
  startDate: z.string().datetime(),
  endDate: z.string().datetime()
}).refine((data) => new Date(data.startDate) <= new Date(data.endDate), {
  message: "Start date must be before end date"
});
var SearchQuerySchema = z.object({
  query: z.string().min(1).max(200).trim(),
  page: z.coerce.number().int().positive().default(1),
  limit: z.coerce.number().int().positive().max(50).default(10)
});
var LoginSchema = z.object({
  email: EmailSchema,
  password: PasswordSchema
});
var SignupSchema = z.object({
  email: EmailSchema,
  password: PasswordSchema,
  name: z.string().min(2).max(100).optional()
});

// src/auth/feature-flags.ts
function detectStage() {
  const stage = process.env.DEPLOYMENT_STAGE;
  if (stage === "staging" || stage === "preview") return stage;
  if (process.env.NODE_ENV === "production") return "production";
  return "development";
}
function resolveFlagValue(value) {
  if (typeof value === "boolean") return value;
  const envValue = process.env[value.envVar];
  if (envValue === void 0 || envValue === "") {
    return value.default ?? false;
  }
  return envValue === "true" || envValue === "1";
}
function createFeatureFlags(definitions) {
  return {
    /**
     * Resolve all flags for the current environment.
     * Call this once at startup or per-request for dynamic flags.
     */
    resolve(stage) {
      const currentStage = stage ?? detectStage();
      const resolved = {};
      for (const [key, def] of Object.entries(definitions)) {
        const stageKey = currentStage === "preview" ? "staging" : currentStage;
        resolved[key] = resolveFlagValue(def[stageKey]);
      }
      return resolved;
    },
    /**
     * Check if a single flag is enabled.
     */
    isEnabled(flag, stage) {
      const currentStage = stage ?? detectStage();
      const def = definitions[flag];
      const stageKey = currentStage === "preview" ? "staging" : currentStage;
      return resolveFlagValue(def[stageKey]);
    },
    /**
     * Get the flag definitions (for introspection/admin UI).
     */
    definitions
  };
}
function buildAllowlist(config) {
  const fromEnv = config.envVar ? process.env[config.envVar]?.split(",").map((e) => e.trim().toLowerCase()).filter(Boolean) ?? [] : [];
  const fallback = config.fallback?.map((e) => e.toLowerCase()) ?? [];
  return [.../* @__PURE__ */ new Set([...fromEnv, ...fallback])];
}
function isAllowlisted(email, allowlist) {
  return allowlist.includes(email.toLowerCase());
}

// src/auth/rate-limiter.ts
var CommonRateLimits = {
  /** General API: 100/min, 200/min authenticated */
  apiGeneral: {
    limit: 100,
    windowSeconds: 60,
    authenticatedLimit: 200
  },
  /** Admin actions: 100/min */
  adminAction: {
    limit: 100,
    windowSeconds: 60
  },
  /** Auth attempts: 10/15min with 30min block */
  authAttempt: {
    limit: 10,
    windowSeconds: 900,
    blockDurationSeconds: 1800
  },
  /** AI/expensive requests: 20/hour, 50/hour authenticated */
  aiRequest: {
    limit: 20,
    windowSeconds: 3600,
    authenticatedLimit: 50
  },
  /** Public form submissions: 5/hour with 1hr block */
  publicForm: {
    limit: 5,
    windowSeconds: 3600,
    blockDurationSeconds: 3600
  },
  /** Checkout/billing: 10/hour with 1hr block */
  checkout: {
    limit: 10,
    windowSeconds: 3600,
    blockDurationSeconds: 3600
  },
  /** Beta code validation: 5/min with 5min block (prevents brute force guessing) */
  betaValidation: {
    limit: 5,
    windowSeconds: 60,
    blockDurationSeconds: 300
  }
};
function createMemoryRateLimitStore() {
  const windows = /* @__PURE__ */ new Map();
  const blocks = /* @__PURE__ */ new Map();
  const cleanupInterval = setInterval(() => {
    const now = Date.now();
    for (const [key, entry] of windows) {
      if (entry.expiresAt < now) windows.delete(key);
    }
    for (const [key, expiry] of blocks) {
      if (expiry < now) blocks.delete(key);
    }
  }, 60 * 1e3);
  if (cleanupInterval.unref) {
    cleanupInterval.unref();
  }
  return {
    async increment(key, windowMs, now) {
      const windowStart = now - windowMs;
      let entry = windows.get(key);
      if (!entry) {
        entry = { timestamps: [], expiresAt: now + windowMs + 6e4 };
        windows.set(key, entry);
      }
      entry.timestamps = entry.timestamps.filter((t) => t > windowStart);
      entry.timestamps.push(now);
      entry.expiresAt = now + windowMs + 6e4;
      return { count: entry.timestamps.length };
    },
    async isBlocked(key) {
      const expiry = blocks.get(key);
      if (!expiry || expiry < Date.now()) {
        blocks.delete(key);
        return { blocked: false, ttlMs: 0 };
      }
      return { blocked: true, ttlMs: expiry - Date.now() };
    },
    async setBlock(key, durationSeconds) {
      blocks.set(key, Date.now() + durationSeconds * 1e3);
    },
    async reset(key) {
      windows.delete(key);
      blocks.delete(`block:${key}`);
      blocks.delete(key);
    }
  };
}
var defaultStore;
function getDefaultStore() {
  if (!defaultStore) {
    defaultStore = createMemoryRateLimitStore();
  }
  return defaultStore;
}
async function checkRateLimit(operation, identifier, rule, options = {}) {
  const store = options.store ?? getDefaultStore();
  const limit = options.isAuthenticated && rule.authenticatedLimit ? rule.authenticatedLimit : rule.limit;
  const now = Date.now();
  const windowMs = rule.windowSeconds * 1e3;
  const resetAt = now + windowMs;
  const key = `ratelimit:${operation}:${identifier}`;
  const blockKey = `ratelimit:block:${operation}:${identifier}`;
  try {
    if (rule.blockDurationSeconds) {
      const blockStatus = await store.isBlocked(blockKey);
      if (blockStatus.blocked) {
        return {
          allowed: false,
          remaining: 0,
          resetAt: now + blockStatus.ttlMs,
          current: limit + 1,
          limit,
          retryAfterSeconds: Math.ceil(blockStatus.ttlMs / 1e3)
        };
      }
    }
    const { count } = await store.increment(key, windowMs, now);
    if (count > limit) {
      options.logger?.warn("Rate limit exceeded", {
        operation,
        identifier,
        current: count,
        limit
      });
      if (rule.blockDurationSeconds) {
        await store.setBlock(blockKey, rule.blockDurationSeconds);
      }
      return {
        allowed: false,
        remaining: 0,
        resetAt,
        current: count,
        limit,
        retryAfterSeconds: Math.ceil(windowMs / 1e3)
      };
    }
    return {
      allowed: true,
      remaining: limit - count,
      resetAt,
      current: count,
      limit,
      retryAfterSeconds: 0
    };
  } catch (error) {
    options.logger?.error("Rate limit check failed, allowing request", {
      error: error instanceof Error ? error.message : String(error),
      operation,
      identifier
    });
    return {
      allowed: true,
      remaining: limit,
      resetAt,
      current: 0,
      limit,
      retryAfterSeconds: 0
    };
  }
}
async function getRateLimitStatus(operation, identifier, rule, store) {
  const s = store ?? getDefaultStore();
  const key = `ratelimit:${operation}:${identifier}`;
  const now = Date.now();
  const windowMs = rule.windowSeconds * 1e3;
  try {
    const { count } = await s.increment(key, windowMs, now);
    return {
      allowed: count <= rule.limit,
      remaining: Math.max(0, rule.limit - count),
      resetAt: now + windowMs,
      current: count,
      limit: rule.limit,
      retryAfterSeconds: count > rule.limit ? Math.ceil(windowMs / 1e3) : 0
    };
  } catch {
    return null;
  }
}
async function resetRateLimitForKey(operation, identifier, store) {
  const s = store ?? getDefaultStore();
  const key = `ratelimit:${operation}:${identifier}`;
  const blockKey = `ratelimit:block:${operation}:${identifier}`;
  await s.reset(key);
  await s.reset(blockKey);
}
function buildRateLimitResponseHeaders(result) {
  const headers = {
    "X-RateLimit-Limit": String(result.limit),
    "X-RateLimit-Remaining": String(result.remaining),
    "X-RateLimit-Reset": String(Math.ceil(result.resetAt / 1e3))
  };
  if (!result.allowed) {
    headers["Retry-After"] = String(result.retryAfterSeconds);
  }
  return headers;
}
function resolveIdentifier(session, clientIp) {
  if (session?.user?.id) {
    return { identifier: `user:${session.user.id}`, isAuthenticated: true };
  }
  if (session?.user?.email) {
    return {
      identifier: `email:${session.user.email}`,
      isAuthenticated: true
    };
  }
  return { identifier: `ip:${clientIp ?? "unknown"}`, isAuthenticated: false };
}

// src/auth/rate-limit-store-redis.ts
function createRedisRateLimitStore(redis, options = {}) {
  const prefix = options.keyPrefix ?? "";
  return {
    async increment(key, windowMs, now) {
      const fullKey = `${prefix}${key}`;
      const windowStart = now - windowMs;
      const windowSeconds = Math.ceil(windowMs / 1e3) + 60;
      await redis.zremrangebyscore(fullKey, 0, windowStart);
      const current = await redis.zcard(fullKey);
      const member = `${now}:${Math.random().toString(36).slice(2, 10)}`;
      await redis.zadd(fullKey, now, member);
      await redis.expire(fullKey, windowSeconds);
      return { count: current + 1 };
    },
    async isBlocked(key) {
      const fullKey = `${prefix}${key}`;
      const value = await redis.get(fullKey);
      if (!value) {
        return { blocked: false, ttlMs: 0 };
      }
      const ttlSeconds = await redis.ttl(fullKey);
      if (ttlSeconds <= 0) {
        return { blocked: false, ttlMs: 0 };
      }
      return { blocked: true, ttlMs: ttlSeconds * 1e3 };
    },
    async setBlock(key, durationSeconds) {
      const fullKey = `${prefix}${key}`;
      await redis.setex(fullKey, durationSeconds, "1");
    },
    async reset(key) {
      const fullKey = `${prefix}${key}`;
      await redis.del(fullKey);
    }
  };
}

// src/auth/audit.ts
var StandardAuditActions = {
  // Authentication
  LOGIN_SUCCESS: "auth.login.success",
  LOGIN_FAILURE: "auth.login.failure",
  LOGOUT: "auth.logout",
  SESSION_REFRESH: "auth.session.refresh",
  PASSWORD_CHANGE: "auth.password.change",
  PASSWORD_RESET: "auth.password.reset",
  // Billing
  CHECKOUT_START: "billing.checkout.start",
  CHECKOUT_COMPLETE: "billing.checkout.complete",
  SUBSCRIPTION_CREATE: "billing.subscription.create",
  SUBSCRIPTION_CANCEL: "billing.subscription.cancel",
  SUBSCRIPTION_UPDATE: "billing.subscription.update",
  PAYMENT_FAILED: "billing.payment.failed",
  // Admin
  ADMIN_LOGIN: "admin.login",
  ADMIN_USER_VIEW: "admin.user.view",
  ADMIN_USER_UPDATE: "admin.user.update",
  ADMIN_CONFIG_CHANGE: "admin.config.change",
  // Security Events
  RATE_LIMIT_EXCEEDED: "security.rate_limit.exceeded",
  INVALID_INPUT: "security.input.invalid",
  UNAUTHORIZED_ACCESS: "security.access.unauthorized",
  OWNERSHIP_VIOLATION: "security.ownership.violation",
  WEBHOOK_SIGNATURE_INVALID: "security.webhook.signature_invalid",
  // Data
  DATA_EXPORT: "data.export",
  DATA_DELETE: "data.delete",
  DATA_UPDATE: "data.update"
};
function extractAuditIp(request) {
  if (!request) return void 0;
  const headers = [
    "cf-connecting-ip",
    // Cloudflare
    "x-real-ip",
    // Nginx
    "x-forwarded-for",
    // Standard proxy
    "x-client-ip"
    // Apache
  ];
  for (const header of headers) {
    const value = request.headers.get(header);
    if (value) {
      return value.split(",")[0]?.trim();
    }
  }
  return void 0;
}
function extractAuditUserAgent(request) {
  return request?.headers.get("user-agent") ?? void 0;
}
function extractAuditRequestId(request) {
  return request?.headers.get("x-request-id") ?? crypto.randomUUID();
}
function createAuditActor(session) {
  if (!session?.user) {
    return { id: "anonymous", type: "anonymous" };
  }
  return {
    id: session.user.id ?? session.user.email ?? "unknown",
    email: session.user.email ?? void 0,
    type: "user"
  };
}
var defaultLogger = {
  info: (msg, meta) => console.log(msg, meta ? JSON.stringify(meta) : ""),
  warn: (msg, meta) => console.warn(msg, meta ? JSON.stringify(meta) : ""),
  error: (msg, meta) => console.error(msg, meta ? JSON.stringify(meta) : "")
};
function createAuditLogger(options = {}) {
  const { persist, logger = defaultLogger } = options;
  async function log(event, request) {
    const record = {
      id: crypto.randomUUID(),
      timestamp: (/* @__PURE__ */ new Date()).toISOString(),
      ip: extractAuditIp(request),
      userAgent: extractAuditUserAgent(request),
      requestId: extractAuditRequestId(request),
      ...event
    };
    const logFn = event.outcome === "failure" || event.outcome === "blocked" ? logger.warn : logger.info;
    logFn(`[AUDIT] ${event.action}`, {
      auditId: record.id,
      actor: record.actor,
      action: record.action,
      resource: record.resource,
      outcome: record.outcome,
      ip: record.ip,
      metadata: record.metadata,
      reason: record.reason
    });
    if (persist) {
      try {
        await persist(record);
      } catch (error) {
        logger.error("Failed to persist audit log", {
          error: error instanceof Error ? error.message : String(error),
          auditId: record.id
        });
      }
    }
    return record;
  }
  function createTimedAudit(event, request) {
    const startTime = Date.now();
    return {
      success: async (metadata) => {
        return log(
          {
            ...event,
            outcome: "success",
            metadata: {
              ...event.metadata,
              ...metadata,
              durationMs: Date.now() - startTime
            }
          },
          request
        );
      },
      failure: async (reason, metadata) => {
        return log(
          {
            ...event,
            outcome: "failure",
            reason,
            metadata: {
              ...event.metadata,
              ...metadata,
              durationMs: Date.now() - startTime
            }
          },
          request
        );
      },
      blocked: async (reason, metadata) => {
        return log(
          {
            ...event,
            outcome: "blocked",
            reason,
            metadata: {
              ...event.metadata,
              ...metadata,
              durationMs: Date.now() - startTime
            }
          },
          request
        );
      }
    };
  }
  return { log, createTimedAudit };
}

// src/api.ts
var ApiErrorCode = {
  VALIDATION_ERROR: "VALIDATION_ERROR",
  UNAUTHORIZED: "UNAUTHORIZED",
  FORBIDDEN: "FORBIDDEN",
  NOT_FOUND: "NOT_FOUND",
  CONFLICT: "CONFLICT",
  RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED",
  INTERNAL_ERROR: "INTERNAL_ERROR",
  DATABASE_ERROR: "DATABASE_ERROR",
  EXTERNAL_SERVICE_ERROR: "EXTERNAL_SERVICE_ERROR",
  CONFIGURATION_ERROR: "CONFIGURATION_ERROR"
};
var ApiError = class extends Error {
  statusCode;
  code;
  details;
  constructor(statusCode, message, code = ApiErrorCode.INTERNAL_ERROR, details) {
    super(message);
    this.name = "ApiError";
    this.statusCode = statusCode;
    this.code = code;
    this.details = details;
  }
};
function isApiError(error) {
  return error instanceof ApiError;
}
var CommonApiErrors = {
  unauthorized: (msg = "Unauthorized") => new ApiError(401, msg, ApiErrorCode.UNAUTHORIZED),
  forbidden: (msg = "Forbidden") => new ApiError(403, msg, ApiErrorCode.FORBIDDEN),
  notFound: (resource = "Resource") => new ApiError(404, `${resource} not found`, ApiErrorCode.NOT_FOUND),
  conflict: (msg = "Resource already exists") => new ApiError(409, msg, ApiErrorCode.CONFLICT),
  rateLimitExceeded: (msg = "Rate limit exceeded") => new ApiError(429, msg, ApiErrorCode.RATE_LIMIT_EXCEEDED),
  validationError: (details) => new ApiError(
    400,
    "Validation failed",
    ApiErrorCode.VALIDATION_ERROR,
    details
  ),
  internalError: (msg = "Internal server error") => new ApiError(500, msg, ApiErrorCode.INTERNAL_ERROR)
};
var PG_ERROR_MAP = {
  "23505": {
    status: 409,
    code: ApiErrorCode.CONFLICT,
    message: "Resource already exists"
  },
  "23503": {
    status: 404,
    code: ApiErrorCode.NOT_FOUND,
    message: "Referenced resource not found"
  },
  PGRST116: {
    status: 404,
    code: ApiErrorCode.NOT_FOUND,
    message: "Resource not found"
  }
};
function classifyError(error, isDev = false) {
  if (isApiError(error)) {
    return {
      status: error.statusCode,
      body: { error: error.message, code: error.code, details: error.details }
    };
  }
  if (error && typeof error === "object" && "issues" in error && Array.isArray(error.issues)) {
    return {
      status: 400,
      body: {
        error: "Validation failed",
        code: ApiErrorCode.VALIDATION_ERROR,
        details: error.issues.map((i) => ({
          field: i.path?.join("."),
          message: i.message
        }))
      }
    };
  }
  if (error && typeof error === "object" && "code" in error && typeof error.code === "string") {
    const pgCode = error.code;
    const mapped = PG_ERROR_MAP[pgCode];
    if (mapped) {
      return {
        status: mapped.status,
        body: { error: mapped.message, code: mapped.code }
      };
    }
    return {
      status: 500,
      body: {
        error: "Database error",
        code: ApiErrorCode.DATABASE_ERROR,
        details: isDev ? error.message : void 0
      }
    };
  }
  if (error instanceof Error) {
    return {
      status: 500,
      body: {
        error: isDev ? error.message : "Internal server error",
        code: ApiErrorCode.INTERNAL_ERROR,
        details: isDev ? error.stack : void 0
      }
    };
  }
  return {
    status: 500,
    body: {
      error: "An unexpected error occurred",
      code: ApiErrorCode.INTERNAL_ERROR
    }
  };
}
function buildPagination(page, limit, total) {
  return {
    page,
    limit,
    total,
    totalPages: Math.ceil(total / limit),
    hasMore: page * limit < total
  };
}

// src/auth/nextjs-api.ts
async function enforceRateLimit(request, operation, rule, options) {
  const identifier = options?.identifier ?? (options?.userId ? `user:${options.userId}` : void 0) ?? `ip:${extractClientIp((name) => request.headers.get(name))}`;
  const isAuthenticated = !!options?.userId;
  const result = await checkRateLimit(operation, identifier, rule, {
    ...options?.rateLimitOptions,
    isAuthenticated
  });
  if (!result.allowed) {
    const headers = buildRateLimitResponseHeaders(result);
    return new Response(
      JSON.stringify({
        error: "Rate limit exceeded. Please try again later.",
        retryAfter: result.retryAfterSeconds
      }),
      {
        status: 429,
        headers: { "Content-Type": "application/json", ...headers }
      }
    );
  }
  return null;
}
function errorResponse(error, options) {
  const isDev = options?.isDevelopment ?? process.env.NODE_ENV === "development";
  const { status, body } = classifyError(error, isDev);
  return new Response(JSON.stringify(body), {
    status,
    headers: { "Content-Type": "application/json" }
  });
}
function zodErrorResponse(error) {
  const firstIssue = error.issues[0];
  const message = firstIssue ? `${firstIssue.path.join(".") || "input"}: ${firstIssue.message}` : "Validation error";
  return new Response(JSON.stringify({ error: message }), {
    status: 400,
    headers: { "Content-Type": "application/json" }
  });
}
function extractBearerToken(request) {
  const auth = request.headers.get("authorization");
  if (!auth?.startsWith("Bearer ")) return null;
  return auth.slice(7).trim() || null;
}
function isValidBearerToken(request, secret) {
  if (!secret) return false;
  const token = extractBearerToken(request);
  if (!token) return false;
  return constantTimeEqual(token, secret);
}
function verifyCronAuth(request, secret) {
  const authHeader = request.headers.get("authorization");
  if (!authHeader?.startsWith("Bearer ")) {
    return new Response(JSON.stringify({ error: "Missing authorization" }), {
      status: 401,
      headers: { "Content-Type": "application/json" }
    });
  }
  const token = authHeader.slice(7);
  const expectedSecret = secret ?? process.env.CRON_SECRET;
  if (!expectedSecret) {
    console.error("[verifyCronAuth] CRON_SECRET not configured");
    return new Response(
      JSON.stringify({ error: "Server configuration error" }),
      { status: 500, headers: { "Content-Type": "application/json" } }
    );
  }
  if (!constantTimeEqual(token, expectedSecret)) {
    return new Response(JSON.stringify({ error: "Invalid authorization" }), {
      status: 401,
      headers: { "Content-Type": "application/json" }
    });
  }
  return null;
}
function getClientIp(request) {
  return request.headers.get("cf-connecting-ip") || request.headers.get("x-real-ip") || request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "unknown";
}
function rateLimitResponse(result) {
  const retryAfter = Math.ceil(result.resetMs / 1e3);
  const resetTimestamp = Math.ceil(Date.now() / 1e3 + result.resetMs / 1e3);
  return new Response(
    JSON.stringify({ error: "Too many requests", retryAfter }),
    {
      status: 429,
      headers: {
        "Content-Type": "application/json",
        "X-RateLimit-Limit": String(result.limit),
        "X-RateLimit-Remaining": "0",
        "X-RateLimit-Reset": String(resetTimestamp),
        "Retry-After": String(retryAfter)
      }
    }
  );
}
function addRateLimitHeaders(response, result) {
  const resetTimestamp = Math.ceil(Date.now() / 1e3 + result.resetMs / 1e3);
  response.headers.set("X-RateLimit-Limit", String(result.limit));
  response.headers.set("X-RateLimit-Remaining", String(result.remaining));
  response.headers.set("X-RateLimit-Reset", String(resetTimestamp));
  return response;
}
function safeValidate(schema, data) {
  const result = schema.safeParse(data);
  if (result.success) {
    return { success: true, data: result.data };
  }
  return {
    success: false,
    errors: (result.error?.issues || []).map((issue) => ({
      field: issue.path.join("."),
      message: issue.message
    }))
  };
}

// src/auth/beta-client.ts
var DEFAULT_CONFIG = {
  baseUrl: "",
  settingsEndpoint: "/api/beta-settings",
  validateEndpoint: "/api/validate-beta-code",
  storageKey: "beta_code",
  failSafeDefaults: {
    betaMode: true,
    requireInviteCode: true,
    betaMessage: ""
  }
};
function createBetaClient(config = {}) {
  const cfg = {
    ...DEFAULT_CONFIG,
    ...config,
    failSafeDefaults: {
      ...DEFAULT_CONFIG.failSafeDefaults,
      ...config.failSafeDefaults
    }
  };
  return {
    fetchSettings: () => fetchBetaSettings(cfg),
    validateCode: (code) => validateBetaCode(code, cfg),
    storeCode: (code) => storeBetaCode(code, cfg),
    getStoredCode: () => getStoredBetaCode(cfg),
    clearStoredCode: () => clearStoredBetaCode(cfg)
  };
}
async function fetchBetaSettings(config = {}) {
  const cfg = { ...DEFAULT_CONFIG, ...config };
  try {
    const response = await fetch(`${cfg.baseUrl}${cfg.settingsEndpoint}`, {
      method: "GET",
      headers: { "Content-Type": "application/json" },
      cache: "no-store"
    });
    if (!response.ok) {
      throw new Error(`Failed to fetch beta settings: ${response.status}`);
    }
    const data = await response.json();
    return {
      betaMode: data.betaMode ?? cfg.failSafeDefaults.betaMode ?? true,
      requireInviteCode: data.requireInviteCode ?? cfg.failSafeDefaults.requireInviteCode ?? true,
      betaMessage: data.betaMessage ?? cfg.failSafeDefaults.betaMessage ?? ""
    };
  } catch (error) {
    console.error("Error fetching beta settings:", error);
    return {
      betaMode: cfg.failSafeDefaults.betaMode ?? true,
      requireInviteCode: cfg.failSafeDefaults.requireInviteCode ?? true,
      betaMessage: cfg.failSafeDefaults.betaMessage ?? ""
    };
  }
}
async function validateBetaCode(code, config = {}) {
  const cfg = { ...DEFAULT_CONFIG, ...config };
  if (!code || code.trim().length < 3) {
    return {
      valid: false,
      message: "Please enter a valid invite code."
    };
  }
  try {
    const response = await fetch(`${cfg.baseUrl}${cfg.validateEndpoint}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ code: code.trim().toUpperCase() })
    });
    if (response.status === 429) {
      return {
        valid: false,
        message: "Too many attempts. Please try again later."
      };
    }
    if (!response.ok) {
      throw new Error(`Validation request failed: ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error("Error validating invite code:", error);
    return {
      valid: false,
      message: "Unable to validate code. Please try again."
    };
  }
}
function storeBetaCode(code, config = {}) {
  const key = config.storageKey ?? DEFAULT_CONFIG.storageKey;
  if (typeof window !== "undefined") {
    sessionStorage.setItem(key, code.trim().toUpperCase());
  }
}
function getStoredBetaCode(config = {}) {
  const key = config.storageKey ?? DEFAULT_CONFIG.storageKey;
  if (typeof window !== "undefined") {
    return sessionStorage.getItem(key);
  }
  return null;
}
function clearStoredBetaCode(config = {}) {
  const key = config.storageKey ?? DEFAULT_CONFIG.storageKey;
  if (typeof window !== "undefined") {
    sessionStorage.removeItem(key);
  }
}

// src/auth/federated-logout.ts
function expireCookie(name, options) {
  const parts = [
    `${name}=`,
    "Path=/",
    "Expires=Thu, 01 Jan 1970 00:00:00 GMT",
    "Max-Age=0",
    "SameSite=Lax"
  ];
  if (options?.hostPrefix) {
    parts.push("Secure");
  } else {
    parts.push("HttpOnly");
    if (options?.domain) parts.push(`Domain=${options.domain}`);
    if (options?.secure) parts.push("Secure");
  }
  return parts.join("; ");
}
function isAllowedCallbackUrl(url, baseUrl) {
  if (url.startsWith("/") && !url.startsWith("//")) return true;
  try {
    const parsed = new URL(url);
    const base = new URL(baseUrl);
    const allowedHosts = [base.hostname, `www.${base.hostname}`];
    if (base.hostname.startsWith("www.")) {
      allowedHosts.push(base.hostname.slice(4));
    }
    return allowedHosts.includes(parsed.hostname);
  } catch {
    return false;
  }
}
var SESSION_TOKEN_BASE_NAMES_AUTHJS = [
  "authjs.session-token",
  "__Secure-authjs.session-token"
];
var SESSION_TOKEN_BASE_NAMES_LEGACY = [
  "next-auth.session-token",
  "__Secure-next-auth.session-token"
];
var NON_SESSION_COOKIE_NAMES_AUTHJS = [
  "authjs.callback-url",
  "__Secure-authjs.callback-url",
  "authjs.csrf-token",
  "__Secure-authjs.csrf-token",
  "authjs.pkce.code_verifier",
  "__Secure-authjs.pkce.code_verifier",
  "authjs.state",
  "__Secure-authjs.state"
];
var NON_SESSION_COOKIE_NAMES_LEGACY = [
  "next-auth.callback-url",
  "__Secure-next-auth.callback-url",
  "next-auth.csrf-token",
  "__Secure-next-auth.csrf-token"
];
var HOST_COOKIES_AUTHJS = ["__Host-authjs.csrf-token"];
var HOST_COOKIES_LEGACY = ["__Host-next-auth.csrf-token"];
function buildCookieNames(chunkCount, includeLegacy, extraCookies) {
  const sessionBases = includeLegacy ? [...SESSION_TOKEN_BASE_NAMES_AUTHJS, ...SESSION_TOKEN_BASE_NAMES_LEGACY] : SESSION_TOKEN_BASE_NAMES_AUTHJS;
  const sessionExpanded = [];
  for (const base of sessionBases) {
    sessionExpanded.push(base);
    for (let i = 0; i < chunkCount; i++) {
      sessionExpanded.push(`${base}.${i}`);
    }
  }
  const nonSession = includeLegacy ? [...NON_SESSION_COOKIE_NAMES_AUTHJS, ...NON_SESSION_COOKIE_NAMES_LEGACY] : NON_SESSION_COOKIE_NAMES_AUTHJS;
  const hostPrefixed = includeLegacy ? [...HOST_COOKIES_AUTHJS, ...HOST_COOKIES_LEGACY] : HOST_COOKIES_AUTHJS;
  return {
    scoped: [...sessionExpanded, ...nonSession, ...extraCookies],
    hostPrefixed
  };
}
function buildFederatedLogoutHandler(config) {
  const {
    auth,
    domain,
    baseUrlFallback,
    extraCookies = [],
    onError,
    chunkCount = 3,
    includeLegacyNextAuthCookies = true
  } = config;
  return async function GET(request) {
    const session = await auth();
    const url = new URL(request.url);
    const rawCallbackUrl = url.searchParams.get("callbackUrl") || "/";
    const queryIdToken = url.searchParams.get("id_token_hint");
    const baseUrl = process.env.NEXTAUTH_URL || process.env.AUTH_URL || baseUrlFallback;
    const callbackUrl = isAllowedCallbackUrl(rawCallbackUrl, baseUrl) ? rawCallbackUrl : "/";
    const postLogoutRedirectUri = callbackUrl.startsWith("http") ? callbackUrl : `${baseUrl}${callbackUrl}`;
    const keycloakIssuer = process.env.AUTH_KEYCLOAK_ISSUER;
    if (!keycloakIssuer) {
      onError?.("Missing AUTH_KEYCLOAK_ISSUER");
      return Response.redirect(
        new URL(
          "/api/auth/signout?callbackUrl=" + encodeURIComponent(callbackUrl),
          request.url
        ).toString()
      );
    }
    const refreshToken = session?.refreshToken;
    if (refreshToken) {
      try {
        const revokeUrl = `${keycloakIssuer}/protocol/openid-connect/revoke`;
        const clientId2 = process.env.AUTH_KEYCLOAK_ID;
        const clientSecret = process.env.AUTH_KEYCLOAK_SECRET;
        await fetch(revokeUrl, {
          method: "POST",
          headers: { "Content-Type": "application/x-www-form-urlencoded" },
          body: new URLSearchParams({
            token: refreshToken,
            token_type_hint: "refresh_token",
            ...clientId2 && { client_id: clientId2 },
            ...clientSecret && { client_secret: clientSecret }
          })
        });
      } catch (err) {
        onError?.("Token revocation failed", err);
      }
    }
    const keycloakLogoutUrl = new URL(
      `${keycloakIssuer}/protocol/openid-connect/logout`
    );
    keycloakLogoutUrl.searchParams.set(
      "post_logout_redirect_uri",
      postLogoutRedirectUri
    );
    const clientId = process.env.AUTH_KEYCLOAK_ID;
    if (clientId) keycloakLogoutUrl.searchParams.set("client_id", clientId);
    const idToken = session?.idToken || queryIdToken;
    if (idToken) keycloakLogoutUrl.searchParams.set("id_token_hint", idToken);
    const escapedUrl = keycloakLogoutUrl.toString().replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
    const html = `<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="0;url=${escapedUrl}">
<title>Signing out...</title>
<style>body{display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;font-family:system-ui,sans-serif;background:#0a0a0a;color:#fff}p{font-size:1.1rem;opacity:0.7}</style>
</head><body>
<p>Signing out&hellip;</p>
<script>window.location.replace(${JSON.stringify(keycloakLogoutUrl.toString()).replace(/</g, "\\u003c")});</script>
</body></html>`;
    const response = new Response(html, {
      status: 200,
      headers: {
        "Content-Type": "text/html; charset=utf-8",
        "Cache-Control": "no-store, no-cache, must-revalidate",
        Pragma: "no-cache"
      }
    });
    const isProduction = process.env.NODE_ENV === "production";
    const isSecure = isProduction;
    const { scoped, hostPrefixed } = buildCookieNames(
      chunkCount,
      includeLegacyNextAuthCookies,
      extraCookies
    );
    for (const name of scoped) {
      const needsSecure = isSecure || name.startsWith("__Secure-");
      response.headers.append(
        "Set-Cookie",
        expireCookie(name, { secure: needsSecure })
      );
      if (isProduction) {
        response.headers.append(
          "Set-Cookie",
          expireCookie(name, { domain, secure: needsSecure })
        );
      }
    }
    for (const name of hostPrefixed) {
      response.headers.append(
        "Set-Cookie",
        expireCookie(name, { hostPrefix: true })
      );
    }
    return response;
  };
}

// src/auth/lazy-rate-limit-store.ts
function createLazyRateLimitStore(getRedis, options = {}) {
  const { keyPrefix = "rl:" } = options;
  let store;
  let initialized = false;
  return function getRateLimitStore() {
    if (initialized) return store;
    initialized = true;
    const redis = getRedis();
    if (!redis) return void 0;
    store = createRedisRateLimitStore(redis, { keyPrefix });
    return store;
  };
}

// src/auth/secure-handler.ts
function defaultLogger2(_request, operation) {
  const prefix = `[${operation}]`;
  return {
    info: (msg, meta) => console.log(prefix, msg, meta ? JSON.stringify(meta) : ""),
    warn: (msg, meta) => console.warn(prefix, msg, meta ? JSON.stringify(meta) : ""),
    error: (msg, meta) => console.error(prefix, msg, meta ? JSON.stringify(meta) : "")
  };
}
function createSecureHandlerFactory(factoryConfig) {
  const isDev = factoryConfig.isDevelopment ?? process.env.NODE_ENV === "development";
  const classify = factoryConfig.classifyError ?? classifyError;
  const makeLogger = factoryConfig.createLogger ?? defaultLogger2;
  function getRequestId(request) {
    const headers = request.headers;
    return headers.get("x-request-id") || headers.get("x-correlation-id") || crypto.randomUUID();
  }
  function checkLegacyToken(request) {
    if (!factoryConfig.adminSecret) return false;
    const auth = request.headers.get("authorization");
    if (!auth?.startsWith("Bearer ")) return false;
    return constantTimeEqual(auth.slice(7).trim(), factoryConfig.adminSecret);
  }
  function createSecureHandler(routeConfig, handler) {
    return async (request, routeContext) => {
      const requestId = getRequestId(request);
      const operation = routeConfig.operation || `${request.method} ${new URL(request.url).pathname}`;
      const log = makeLogger(request, operation);
      const params = await routeContext.params;
      let session = null;
      let isAdmin = false;
      let isLegacyToken = false;
      let timedAudit;
      const auditCfg = routeConfig.audit;
      const resolvedAuditAction = typeof auditCfg === "object" && auditCfg !== null ? auditCfg.action : auditCfg;
      const resolvedAuditResource = typeof auditCfg === "object" && auditCfg !== null ? auditCfg.resource : routeConfig.auditResource;
      const resolvedGetResourceId = typeof auditCfg === "object" && auditCfg !== null ? auditCfg.getResourceId : routeConfig.getResourceId;
      try {
        if (routeConfig.requireAuth || routeConfig.requireAdmin || routeConfig.requireRoles?.length) {
          session = await factoryConfig.getSession();
          if (session?.user) {
            isAdmin = factoryConfig.isAdmin(session);
          } else if (routeConfig.allowLegacyToken && checkLegacyToken(request)) {
            isLegacyToken = true;
            isAdmin = true;
          } else {
            log.warn("Unauthorized access attempt");
            return Response.json(
              { error: "Unauthorized" },
              { status: 401, headers: { "X-Request-ID": requestId } }
            );
          }
        }
        if (routeConfig.requireAdmin && !isLegacyToken && !isAdmin) {
          log.warn("Admin access denied");
          return Response.json(
            { error: "Forbidden: Admin access required" },
            { status: 403, headers: { "X-Request-ID": requestId } }
          );
        }
        if (routeConfig.requireRoles?.length && !isLegacyToken) {
          const hasRole2 = factoryConfig.hasAnyRole ? factoryConfig.hasAnyRole(session, routeConfig.requireRoles) : routeConfig.requireRoles.some(
            (r) => session?.user?.roles?.includes(r)
          );
          if (!hasRole2) {
            log.warn("Role-based access denied");
            return Response.json(
              { error: "Forbidden: Insufficient permissions" },
              { status: 403, headers: { "X-Request-ID": requestId } }
            );
          }
        }
        if (!routeConfig.skipRateLimit && routeConfig.rateLimit && factoryConfig.rateLimiter) {
          const userId = session?.user?.id ?? void 0;
          const blocked = await factoryConfig.rateLimiter.enforce(
            request,
            operation,
            routeConfig.rateLimit,
            userId
          );
          if (blocked) return blocked;
        }
        let validated = {};
        if (routeConfig.validate) {
          let input;
          if (request.method === "GET") {
            const url = new URL(request.url);
            const qs = {};
            url.searchParams.forEach((v, k) => qs[k] = v);
            input = qs;
          } else {
            const ct = request.headers.get("content-type");
            input = ct?.includes("application/json") ? await request.json() : {};
          }
          if (factoryConfig.sanitizeNullBytes !== false) {
            input = stripNullBytesDeep(input);
          }
          const result = routeConfig.validate.safeParse(input);
          if (!result.success) {
            const fieldErrors = result.error?.flatten ? result.error.flatten().fieldErrors : void 0;
            return Response.json(
              { error: "Validation error", errors: fieldErrors },
              { status: 400, headers: { "X-Request-ID": requestId } }
            );
          }
          validated = result.data;
        }
        const actorId = isLegacyToken ? "admin_token" : session?.user?.id || "anonymous";
        const actorType = isLegacyToken ? "admin" : "user";
        const actorEmail = session?.user?.email ?? void 0;
        const resourceId = resolvedGetResourceId ? resolvedGetResourceId(
          request,
          params,
          validated
        ) : void 0;
        if (resolvedAuditAction && factoryConfig.createTimedAudit) {
          timedAudit = factoryConfig.createTimedAudit(
            {
              action: resolvedAuditAction,
              resource: resolvedAuditResource ? {
                type: resolvedAuditResource,
                id: resourceId
              } : void 0,
              actor: {
                id: actorId,
                type: actorType,
                email: actorEmail
              }
            },
            request
          );
        }
        const authMethod = isLegacyToken ? "legacy_token" : session?.user ? "session" : "none";
        const ctx = {
          session,
          isLegacyToken,
          authMethod,
          isAdmin,
          validated,
          logger: log,
          requestId,
          params,
          timedAudit
        };
        const response = await handler(request, ctx);
        response.headers.set("X-Request-ID", requestId);
        if (resolvedAuditAction && factoryConfig.auditLog && !timedAudit) {
          await factoryConfig.auditLog({
            actor: {
              id: actorId,
              type: actorType,
              email: actorEmail
            },
            action: resolvedAuditAction,
            resource: resolvedAuditResource ? {
              type: resolvedAuditResource,
              id: resourceId ?? "unknown"
            } : void 0,
            outcome: "success"
          }).catch(() => {
          });
        }
        return response;
      } catch (error) {
        log.error("Request handler error", {
          error: error instanceof Error ? error.message : String(error)
        });
        const errReason = error instanceof Error ? error.message : "Unknown error";
        if (timedAudit) {
          await timedAudit.failure(errReason).catch(() => {
          });
        } else if (resolvedAuditAction && factoryConfig.auditLog) {
          await factoryConfig.auditLog({
            actor: {
              id: session?.user?.id || "unknown",
              type: "user"
            },
            action: resolvedAuditAction,
            outcome: "failure",
            reason: errReason
          }).catch(() => {
          });
        }
        const { status, body } = classify(error, isDev);
        return Response.json(
          { error: body.error, code: body.code },
          { status, headers: { "X-Request-ID": requestId } }
        );
      }
    };
  }
  function withPublicApi(config, handler) {
    return createSecureHandler(
      {
        rateLimit: factoryConfig.rateLimiter?.publicDefault,
        ...config,
        requireAuth: false,
        requireAdmin: false
      },
      handler
    );
  }
  function withAuthenticatedApi(config, handler) {
    return createSecureHandler(
      {
        rateLimit: factoryConfig.rateLimiter?.authDefault,
        ...config,
        requireAuth: true,
        requireAdmin: false
      },
      handler
    );
  }
  function withAdminApi(config, handler) {
    return createSecureHandler(
      {
        rateLimit: factoryConfig.rateLimiter?.adminDefault,
        ...config,
        requireAuth: true,
        requireAdmin: true
      },
      handler
    );
  }
  function withLegacyAdminApi(config, handler) {
    return createSecureHandler(
      {
        rateLimit: factoryConfig.rateLimiter?.adminDefault,
        ...config,
        requireAuth: true,
        requireAdmin: true,
        allowLegacyToken: true
      },
      handler
    );
  }
  return {
    createSecureHandler,
    withPublicApi,
    withAuthenticatedApi,
    withAdminApi,
    withLegacyAdminApi
  };
}

// src/auth/ensure-user.ts
function createEnsureUser(config) {
  return async function ensureUser(keycloakId, email, name) {
    if (!keycloakId) {
      throw new Error(
        "createEnsureUser: keycloakId is required (got empty string / null / undefined)"
      );
    }
    const normalizedEmail = normalizeEmail(email);
    const normalizedName = normalizeName(name);
    const byKeycloak = await config.findByKeycloakId(keycloakId);
    if (byKeycloak) {
      if (config.onClaimRefresh) {
        const delta = computeClaimDelta(
          byKeycloak,
          normalizedEmail,
          normalizedName
        );
        if (delta) {
          await config.onClaimRefresh(byKeycloak.id, delta);
        }
      }
      return byKeycloak.id;
    }
    if (normalizedEmail) {
      const byEmail = await config.findByEmailLower(
        normalizedEmail.toLowerCase()
      );
      if (byEmail) {
        await config.rebindKeycloakId(byEmail.id, keycloakId, normalizedName);
        return byEmail.id;
      }
    }
    const inserted = await config.insertNewUser({
      keycloakId,
      email: normalizedEmail,
      name: normalizedName
    });
    if (config.onNewUser) {
      try {
        await config.onNewUser(inserted.id, normalizedEmail);
      } catch {
      }
    }
    return inserted.id;
  };
}
function normalizeEmail(email) {
  if (email === void 0 || email === null) return null;
  const trimmed = email.trim();
  return trimmed === "" ? null : trimmed;
}
function normalizeName(name) {
  if (name === void 0 || name === null) return void 0;
  const trimmed = name.trim();
  return trimmed === "" ? void 0 : trimmed;
}
function computeClaimDelta(current, newEmail, newName) {
  const delta = {};
  let changed = false;
  if (current.email !== void 0) {
    const currentLower = current.email == null ? null : current.email.toLowerCase();
    const newLower = newEmail == null ? null : newEmail.toLowerCase();
    if (currentLower !== newLower) {
      delta.email = newEmail;
      changed = true;
    }
  }
  if (newName !== void 0 && current.displayName !== void 0) {
    if (current.displayName !== newName) {
      delta.displayName = newName;
      changed = true;
    }
  }
  return changed ? delta : null;
}

// src/env.ts
function getRequiredEnv(key) {
  const value = process.env[key];
  if (!value) {
    throw new Error(`Missing required environment variable: ${key}`);
  }
  return value;
}
function getOptionalEnv(key, defaultValue) {
  return process.env[key] || defaultValue;
}
function getBoolEnv(key, defaultValue = false) {
  const value = process.env[key];
  if (value === void 0 || value === "") return defaultValue;
  return value === "true" || value === "1";
}
function getIntEnv(key, defaultValue) {
  const value = process.env[key];
  if (value === void 0 || value === "") return defaultValue;
  const parsed = parseInt(value, 10);
  return isNaN(parsed) ? defaultValue : parsed;
}
function validateEnvVars(config) {
  const result = checkEnvVars(config);
  if (!result.valid) {
    const lines = [];
    if (result.missing.length > 0) {
      lines.push(
        "Missing required environment variables:",
        ...result.missing.map((v) => `  - ${v}`)
      );
    }
    if (result.missingOneOf.length > 0) {
      for (const group of result.missingOneOf) {
        lines.push(`Missing one of: ${group.join(" | ")}`);
      }
    }
    if (result.invalid.length > 0) {
      lines.push(
        "Invalid environment variables:",
        ...result.invalid.map((v) => `  - ${v.key}: ${v.reason}`)
      );
    }
    throw new Error(lines.join("\n"));
  }
}
function checkEnvVars(config) {
  const missing = [];
  const invalid = [];
  const missingOneOf = [];
  if (config.required) {
    for (const key of config.required) {
      if (!process.env[key]) {
        missing.push(key);
      }
    }
  }
  if (config.requireOneOf) {
    for (const group of config.requireOneOf) {
      const hasAny = group.some((key) => !!process.env[key]);
      if (!hasAny) {
        missingOneOf.push(group);
      }
    }
  }
  if (config.validators) {
    for (const [key, validator] of Object.entries(config.validators)) {
      const value = process.env[key];
      if (value) {
        const result = validator(value);
        if (result !== true) {
          invalid.push({ key, reason: result });
        }
      }
    }
  }
  return {
    valid: missing.length === 0 && invalid.length === 0 && missingOneOf.length === 0,
    missing,
    invalid,
    missingOneOf
  };
}
function getEnvSummary(keys) {
  const summary = {};
  for (const key of keys) {
    summary[key] = !!process.env[key];
  }
  return summary;
}
export {
  ApiError,
  ApiErrorCode,
  CommonApiErrors,
  CommonRateLimits,
  DateRangeSchema,
  EmailSchema,
  KEYCLOAK_DEFAULT_ROLES,
  LoginSchema,
  PaginationSchema,
  PasswordSchema,
  PersonNameSchema,
  PhoneSchema,
  SearchQuerySchema,
  SignupSchema,
  SlugSchema,
  StandardAuditActions,
  StandardRateLimitPresets,
  WrapperPresets,
  addRateLimitHeaders,
  buildAllowlist,
  buildAuthCookies,
  buildErrorBody,
  buildFederatedLogoutHandler,
  buildKeycloakCallbacks,
  buildPagination,
  buildRateLimitHeaders,
  buildRateLimitResponseHeaders,
  buildRedirectCallback,
  buildTokenRefreshParams,
  checkEnvVars,
  checkRateLimit,
  classifyError,
  clearStoredBetaCode,
  constantTimeEqual,
  containsHtml,
  containsUrls,
  createAuditActor,
  createAuditLogger,
  createBetaClient,
  createEnsureUser,
  createFeatureFlags,
  createLazyRateLimitStore,
  createMemoryRateLimitStore,
  createRedisRateLimitStore,
  createSafeTextSchema,
  createSecureHandlerFactory,
  detectStage,
  enforceRateLimit,
  errorResponse,
  escapeHtml,
  extractAuditIp,
  extractAuditRequestId,
  extractAuditUserAgent,
  extractBearerToken,
  extractClientIp,
  fetchBetaSettings,
  getBoolEnv,
  getClientIp,
  getCorrelationId,
  getEndSessionEndpoint,
  getEnvSummary,
  getIntEnv,
  getOptionalEnv,
  getRateLimitStatus,
  getRequiredEnv,
  getStoredBetaCode,
  getTokenEndpoint,
  hasAllRoles,
  hasAnyRole,
  hasRole,
  isAllowlisted,
  isApiError,
  isTokenExpired,
  isValidBearerToken,
  parseKeycloakRoles,
  rateLimitResponse,
  refreshKeycloakToken,
  resetRateLimitForKey,
  resolveIdentifier,
  resolveRateLimitIdentifier,
  safeValidate,
  sanitizeApiError,
  storeBetaCode,
  stripHtml,
  stripNullBytes,
  stripNullBytesDeep,
  validateBetaCode,
  validateEnvVars,
  verifyCronAuth,
  zodErrorResponse
};
//# sourceMappingURL=auth.mjs.map