/**
 * `@platform-modules/util/csrf` — CSRF defense primitives (pure, zero-dep).
 *
 * Two independent, composable mechanisms for protecting cookie-session state-changing requests:
 *   - double-submit token (issue + constant-time verify)
 *   - Origin / Sec-Fetch-Site same-origin check
 *
 * Bearer-token requests carry no ambient cookie credential → CSRF is N/A for them; the host
 * SHOULD exempt token-authenticated requests BEFORE calling these guards.
 *
 * Predicates return `boolean` (util convention); fail-closed — any uncertainty yields `false`.
 * The host responds 403 on `false`.
 */
import { generateOpaqueToken } from './tokens'
import { timingSafeEqual } from './crypto'

/** Issue an opaque CSRF token for the double-submit pattern (32 random bytes, base64url). */
export function issueCsrfToken(): string {
  return generateOpaqueToken(32)
}

/**
 * Double-submit verify: the token stored in a cookie vs the token echoed back in a request
 * header/body field. Constant-time compare (timingSafeEqual). Fail-closed: either side
 * null/undefined/empty → `false` (before the compare — a present-vs-absent check is not a
 * secret-timing leak). Equal-length, equal-value → `true`.
 */
export function verifyDoubleSubmit(
  cookieToken: string | null | undefined,
  submittedToken: string | null | undefined,
): boolean {
  if (!cookieToken || !submittedToken) return false
  return timingSafeEqual(cookieToken, submittedToken)
}

/**
 * Origin-based CSRF defense for state-changing requests. Returns `true` IFF same-origin:
 *   - `Origin` header present → must be ∈ `allowedOrigins` (exact string match)
 *   - `Origin` absent         → `Sec-Fetch-Site` ∈ {'same-origin','none'}
 *   - neither present         → `false` (fail-closed)
 *
 * Bearer-token requests have no ambient cookie credential — CSRF is N/A; the host SHOULD exempt
 * them before calling this.
 */
export function isSameOrigin(headers: HeadersInit, allowedOrigins: readonly string[]): boolean {
  const h = headers instanceof Headers ? headers : new Headers(headers)
  const origin = h.get('origin')
  if (origin !== null) {
    return allowedOrigins.includes(origin)
  }
  const site = h.get('sec-fetch-site')
  return site === 'same-origin' || site === 'none'
}
