/**
 * 共有トークンの検証（P1 の簡易トークン認証）。
 *
 * ★比較は必ず定数時間で行う★
 *   `a === b` は先頭から比較して違えば即 false を返すため、応答時間の差から
 *   1文字ずつ推測される（タイミング攻撃）。`node:crypto` の `timingSafeEqual` を使い、
 *   ★長さが違う場合も同じ時間で false を返す★。
 *   `proxy` やAPIルートの中で `===` を書かず、必ずこの関数を通すこと。
 */
import { timingSafeEqual } from "node:crypto";

/**
 * 入力トークンが期待値と一致するかを定数時間で判定する。
 *
 * ★`expected` が未設定・空なら常に false★
 *   `AUTH_TOKEN` の設定漏れが「認証を無効化して全部通す」になってはいけない。
 *   ★未設定なら誰も入れない★が正しい挙動（設定漏れをそのまま全公開にしない）。
 *
 * @param input 利用者が入力した値、または cookie に入っていた値
 * @param expected `AUTH_TOKEN`（`readAuthToken()` の戻り値）
 */
export function verifyToken(
  input: string | null | undefined,
  expected: string | null | undefined
): boolean {
  if (!input || !expected) return false;

  const inputBuffer = Buffer.from(input, "utf8");
  const expectedBuffer = Buffer.from(expected, "utf8");

  // timingSafeEqual は長さが違うと throw するので、先に長さを揃えた比較を行う。
  // 長さが違う時点で結果は false と決まっているが、★早期 return せずに★
  // 同じだけの比較処理を通してから false を返し、応答時間の差を作らない。
  if (inputBuffer.length !== expectedBuffer.length) {
    // 自分自身と比較して同じ計算量を消費する（結果は捨てる）
    timingSafeEqual(expectedBuffer, expectedBuffer);
    return false;
  }

  return timingSafeEqual(inputBuffer, expectedBuffer);
}

/**
 * `AUTH_TOKEN` を読む薄い関数（`lib/logger/index.ts` の `resolveLevel()` と同じ形）。
 * ★戻り値をログに出さないこと★
 */
export function readAuthToken(): string | undefined {
  return process.env.AUTH_TOKEN;
}
