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: "一時的な問題が発生しました" };
}