./knowledge-base/lambda/edge-auth/cf-function.js

/**
 * CloudFront Function: /era-assist/* 認証ガード + パスリライト
 *
 * 処理フロー:
 *   1. Cookie "okta_access_token" の存在・有効期限をチェック
 *   2. 無効 → /?redirect_to=/era-assist/ へ 302 リダイレクト(React SPA でログイン)
 *   3. 有効 → /era-assist/xxx を /xxx にリライトして App Runner へ転送
 *
 * NOTE: JWT 署名検証は行わない(Lambda@Edge が不要なライトウェイト実装)
 *       署名の真正性は Okta が発行元であることと、exp クレームの確認のみで担保
 *
 * Runtime: cloudfront-js-2.0
 */
function handler(event) {
  var request = event.request;
  var uri = request.uri;

  // ── 1. Cookie から okta_access_token を取得 ──────────────────────────────
  var cookies = request.cookies || {};
  var tokenCookie = cookies["okta_access_token"];
  var token = tokenCookie ? tokenCookie.value : null;

  // ── 2. JWT の exp クレームで有効期限チェック ────────────────────────────
  var isValid = false;
  if (token) {
    try {
      var parts = token.split(".");
      if (parts.length === 3) {
        // base64url → 標準 base64 変換(+/パディング追加)
        var b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
        var pad = b64.length % 4;
        if (pad === 2) b64 += "==";
        else if (pad === 3) b64 += "=";

        var payload = JSON.parse(atob(b64));
        var nowSec = Math.floor(Date.now() / 1000);
        if (payload.exp && payload.exp > nowSec) {
          isValid = true;
        }
      }
    } catch (_) {
      isValid = false;
    }
  }

  // ── 3. 未認証 → React SPA ログイン画面へリダイレクト ──────────────────
  if (!isValid) {
    // redirect_to に元のパスを渡す(LoginCallbackPage がログイン後にここへ戻す)
    var dest = "/?redirect_to=" + encodeURIComponent("/era-assist/");
    return {
      statusCode: 302,
      statusDescription: "Found",
      headers: {
        location: { value: dest },
        "cache-control": { value: "no-store, no-cache" },
      },
    };
  }

  // ── 4. 認証済み → /era-assist/xxx を /xxx にリライト ─────────────────
  //   /era-assist        → /
  //   /era-assist/       → /
  //   /era-assist/foo    → /foo
  //   /era-assist/foo/   → /foo/
  if (uri.startsWith("/era-assist")) {
    var rewritten = uri.slice(11); // "/era-assist" は 11 文字
    request.uri = rewritten === "" ? "/" : rewritten;
  }

  return request;
}