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:
parent
7b15b76054
commit
08ef577260
@ -1,13 +1,33 @@
|
|||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
|
import { cookies } from 'next/headers'
|
||||||
|
import { getIronSession } from 'iron-session'
|
||||||
import { GoogleGenerativeAI, SchemaType } from '@google/generative-ai'
|
import { GoogleGenerativeAI, SchemaType } from '@google/generative-ai'
|
||||||
import { GoogleAIFileManager, FileState } from '@google/generative-ai/server'
|
import { GoogleAIFileManager, FileState } from '@google/generative-ai/server'
|
||||||
|
|
||||||
|
import { sessionOptions } from '@/lib/session'
|
||||||
import { logger } from '@/util/logger'
|
import { logger } from '@/util/logger'
|
||||||
|
|
||||||
const MAX_FILE_BYTES = 20 * 1024 * 1024 // 20MB
|
const MAX_FILE_BYTES = 20 * 1024 * 1024 // 20MB
|
||||||
const MAX_VERTICES = 500
|
const MAX_VERTICES = 500
|
||||||
const DEFAULT_MODEL = 'gemini-3.1-pro-preview'
|
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 = {
|
const FLOOR_PLAN_SCHEMA = {
|
||||||
type: SchemaType.OBJECT,
|
type: SchemaType.OBJECT,
|
||||||
properties: {
|
properties: {
|
||||||
@ -133,6 +153,18 @@ export async function POST(req) {
|
|||||||
const apiKey = process.env.GEMINI_API_KEY
|
const apiKey = process.env.GEMINI_API_KEY
|
||||||
const modelName = process.env.GEMINI_MODEL || DEFAULT_MODEL
|
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) {
|
if (!apiKey) {
|
||||||
return NextResponse.json({ error: { code: 'NO_API_KEY', message: 'GEMINI_API_KEY 가 설정되지 않았습니다.' } }, { status: 500 })
|
return NextResponse.json({ error: { code: 'NO_API_KEY', message: 'GEMINI_API_KEY 가 설정되지 않았습니다.' } }, { status: 500 })
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user