← GPT-AI-LP
CODE · 2.3 KB

api/security.js

Workspace snapshot · 08/31 23:51

import crypto from "node:crypto";

const attempts = new Map();
const WINDOW_MS = 60_000;
const MAX_REQUESTS = 12;

const equal = (left, right) => {
  const a = Buffer.from(String(left || ""));
  const b = Buffer.from(String(right || ""));
  return a.length === b.length && crypto.timingSafeEqual(a, b);
};

export function authorizeRequest(req, env = process.env, now = Date.now(), options = {}) {
  const configured = env.AI_LP_ACCESS_TOKEN;
  if (!configured) {
    if (env.NODE_ENV === "production") return { ok: false, status: 503, error: "生成機能は準備中です" };
    return { ok: true };
  }
  const supplied = req.headers?.["x-ai-lp-access-token"];
  if (!equal(supplied, configured)) return { ok: false, status: 401, error: "アクセスキーが必要です" };
  if (options.rateLimit === false) return { ok: true };
  const forwarded = String(req.headers?.["x-forwarded-for"] || "local").split(",")[0].trim();
  const sessionId = String(req.headers?.["x-ai-lp-session"] || "");
  if (!/^[a-zA-Z0-9:_-]{16,120}$/u.test(sessionId))
    return { ok: false, status: 400, error: "セッションを確認できません" };
  const key = crypto.createHash("sha256").update(`${forwarded}:${sessionId}:${supplied}`).digest("hex");
  const record = attempts.get(key);
  if (!record || now - record.startedAt >= WINDOW_MS) {
    attempts.set(key, { startedAt: now, count: 1 });
    return { ok: true, remaining: MAX_REQUESTS - 1 };
  }
  if (record.count >= MAX_REQUESTS) return { ok: false, status: 429, error: "短時間の生成回数が上限に達しました" };
  record.count += 1;
  return { ok: true, remaining: MAX_REQUESTS - record.count };
}

export function publicError(error) {
  if (String(error?.message || "") === "SOURCE_TOO_LARGE")
    return { status: 413, error: "資料が長すぎます。6万字以内に分けてアップロードしてください" };
  if (String(error?.message || "").includes("LLM_DAILY_JOB_LIMIT"))
    return { status: 429, error: "本日の生成枠が上限に達しました" };
  if ([400, 404, 409, 422, 429, 503].includes(error?.status))
    return { status: error.status, error: "生成処理を完了できませんでした。入力内容を保持したまま再試行できます" };
  return { status: 500, error: "一時的な問題が発生しました" };
}