fix(gemini): floor-plan API 에 세션 인증·레이트리밋 추가

[작업내용] :
- iron-session 세션 검사 추가 — 페이지용 layout redirect 가 API 라우트를 보호하지 않아
  익명 요청으로 Gemini 과금이 가능하던 문제 차단(401)
- 사용자별 분당 5회 in-memory 레이트리밋(429) — 연타 업로드 과금 폭주 방지

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
sangwook yoo 2026-06-11 10:07:01 +09:00
parent 7b15b76054
commit 08ef577260

View File

@ -1,13 +1,33 @@
import { NextResponse } from 'next/server'
import { cookies } from 'next/headers'
import { getIronSession } from 'iron-session'
import { GoogleGenerativeAI, SchemaType } from '@google/generative-ai'
import { GoogleAIFileManager, FileState } from '@google/generative-ai/server'
import { sessionOptions } from '@/lib/session'
import { logger } from '@/util/logger'
const MAX_FILE_BYTES = 20 * 1024 * 1024 // 20MB
const MAX_VERTICES = 500
const DEFAULT_MODEL = 'gemini-3.1-pro-preview'
// Gemini 호출은 건당 과금이므로 사용자별 호출 빈도를 제한한다(프로세스 단위 in-memory).
const RATE_LIMIT_WINDOW_MS = 60 * 1000
const RATE_LIMIT_MAX_REQUESTS = 5
const rateLimitMap = new Map()
const isRateLimited = (key) => {
const now = Date.now()
const recent = (rateLimitMap.get(key) || []).filter((t) => now - t < RATE_LIMIT_WINDOW_MS)
if (recent.length >= RATE_LIMIT_MAX_REQUESTS) {
rateLimitMap.set(key, recent)
return true
}
recent.push(now)
rateLimitMap.set(key, recent)
return false
}
const FLOOR_PLAN_SCHEMA = {
type: SchemaType.OBJECT,
properties: {
@ -133,6 +153,18 @@ export async function POST(req) {
const apiKey = process.env.GEMINI_API_KEY
const modelName = process.env.GEMINI_MODEL || DEFAULT_MODEL
// 페이지 인증(layout redirect)은 API 라우트에 적용되지 않으므로 세션을 직접 검사한다 — 익명 Gemini 과금 차단.
const session = await getIronSession(cookies(), sessionOptions)
if (!session?.isLoggedIn) {
return NextResponse.json({ error: { code: 'UNAUTHORIZED', message: '로그인이 필요합니다.' } }, { status: 401 })
}
if (isRateLimited(session.userId || 'unknown')) {
return NextResponse.json(
{ error: { code: 'RATE_LIMITED', message: '분석 요청이 너무 잦습니다. 잠시 후 다시 시도해주세요.' } },
{ status: 429 },
)
}
if (!apiKey) {
return NextResponse.json({ error: { code: 'NO_API_KEY', message: 'GEMINI_API_KEY 가 설정되지 않았습니다.' } }, { status: 500 })
}