feat: 보안 강화 + 자동화 도구 3종 추가 (웹 크롤러·PPT·엑셀)
- lib/security.ts: escapeHtml, isValidEmail, sanitizeStr, checkRateLimit 유틸 추가 - next.config.ts: 보안 헤더 적용 (X-Frame-Options, HSTS, Permissions-Policy 등) - api/contact: XSS 방어, Rate Limit(5/min), 입력 길이 제한 - api/payment/confirm: 사용자 인증·소유권 검증, 타입 체크, 에러 메시지 정제 - api/admin/quotes: PUT 허용 필드 화이트리스트 적용 - api/saju/analyze: 로그인·결제 검증, 입력 크기 제한, gender 값 검증 - public/downloads/web_scraper_v1.0.py: requests+BS4+openpyxl 웹 크롤러 - public/downloads/ppt_automation_v1.0.py: python-pptx+openpyxl PPT 자동화 - app/services/automation/tools/scraper: 크롤러 상세 페이지 추가 - app/services/automation/tools/ppt: PPT 도구 상세 페이지 추가 - app/services/automation/page.tsx: scraper ready=true, email→PPT 교체 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -64,16 +64,49 @@ const MODELS = [
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { saju, daeun, daeunList, gender, engineData } = await request.json();
|
||||
// ── 결제 사용자 인증 (Gemini API 무단 호출 방지) ──────────
|
||||
const { createClient } = await import('@/lib/supabase/server');
|
||||
const supabase = await createClient();
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
|
||||
if (user) {
|
||||
// 로그인된 경우: saju_detail 결제 여부 확인
|
||||
const { data: paidOrder } = await supabase
|
||||
.from('orders')
|
||||
.select('id')
|
||||
.eq('user_id', user.id)
|
||||
.eq('product_id', 'saju_detail')
|
||||
.eq('status', 'paid')
|
||||
.maybeSingle();
|
||||
|
||||
if (!paidOrder) {
|
||||
return NextResponse.json({ error: '사주 리포트를 구매한 사용자만 이용할 수 있습니다' }, { status: 403 });
|
||||
}
|
||||
} else {
|
||||
// 비로그인 사용자는 AI 호출 불가
|
||||
return NextResponse.json({ error: '로그인이 필요합니다' }, { status: 401 });
|
||||
}
|
||||
|
||||
// ── 입력 길이 검증 (DoS / 프롬프트 인젝션 기초 방어) ──────
|
||||
const raw = await request.json();
|
||||
if (JSON.stringify(raw).length > 50_000) {
|
||||
return NextResponse.json({ error: '요청 데이터가 너무 큽니다' }, { status: 400 });
|
||||
}
|
||||
const { saju, daeun, daeunList, gender, engineData } = raw;
|
||||
|
||||
// gender 값 제한
|
||||
if (gender !== 'male' && gender !== 'female') {
|
||||
return NextResponse.json({ error: '잘못된 성별 값' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 종합 분석 수행
|
||||
let analysis;
|
||||
try {
|
||||
analysis = performFullAnalysis(saju);
|
||||
} catch (analysisError: any) {
|
||||
console.error('[사주] 분석 계산 오류:', analysisError.message);
|
||||
console.error('[사주] 분석 계산 오류');
|
||||
return NextResponse.json(
|
||||
{ error: '사주 분석 계산 중 오류: ' + analysisError.message },
|
||||
{ error: '사주 분석 중 오류가 발생했습니다' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user