o2o-infinith-demo/api/_origin.js
Haewon Kam 0be730f0f9 fix: 데모 화면의 DB 조회·쓰기를 서버 함수로 옮긴다
브라우저가 공개 키로 테이블을 직접 읽고 쓰던 구조를 없앤다.
- api/db.js: 정해진 작업만 서비스 키로 실행 (임의 쿼리·열 지정 불가)
- api/_origin.js: 서버 함수 공용 출처 검사, 공개 배포에서는 404
- src/lib/db.ts: 화면용 호출 함수, 반환 형태 { data, error } 유지
- 빌드 큐에 바로 넣던 예전 진단 신청 모달 삭제

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 16:09:29 +09:00

60 lines
2.2 KiB
JavaScript

/**
* 서버 함수 공용 출처 검사 (Vercel 서버 함수 전용, '_' 로 시작해 주소로 열리지 않는다).
*
* infinith-demo 와 그 미리보기, 로컬 개발에서 온 요청만 받는다. 데모는 Vercel 배포 보호(팀원 로그인) 뒤에 있어
* 서버 함수도 로그인한 팀원만 부를 수 있다. 공개 배포(webforai.kr, VITE_SITE=discovery)에서는 내부 함수를 열지 않는다.
*/
const ALLOWED_ORIGINS = [
'https://infinith-demo.vercel.app',
'http://localhost:3000',
'http://127.0.0.1:3000',
];
/** 이 프로젝트의 미리보기 배포 주소. 미리보기는 Vercel 로그인(배포 보호) 뒤에 있어 팀원만 연다. */
const PREVIEW_ORIGIN = /^https:\/\/infinith-demo-[a-z0-9]+-o2odev-5530s-projects\.vercel\.app$/;
const isAllowedOrigin = (o) => ALLOWED_ORIGINS.includes(o) || PREVIEW_ORIGIN.test(o);
/**
* CORS·메서드·출처를 확인한다. 통과하면 null, 아니면 이미 응답을 보낸 뒤 true 를 돌려준다.
* @param {string[]} methods 허용 메서드
*/
export function guard(req, res, methods) {
if (process.env.VITE_SITE === 'discovery') {
res.status(404).json({ ok: false, error: 'not found' });
return true;
}
const origin = req.headers.origin || '';
const allowed = isAllowedOrigin(origin);
if (allowed) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
res.setHeader('Access-Control-Allow-Methods', [...methods, 'OPTIONS'].join(', '));
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
}
if (req.method === 'OPTIONS') {
res.status(allowed ? 204 : 403).end();
return true;
}
if (!methods.includes(req.method)) {
res.status(405).json({ ok: false, error: 'method not allowed' });
return true;
}
// 같은 출처 GET 은 브라우저가 Origin 을 붙이지 않을 수 있어 Referer 로도 확인한다.
const referer = req.headers.referer || '';
const refOrigin = (referer.match(/^https?:\/\/[^/]+/) || [''])[0];
if (!allowed && !isAllowedOrigin(refOrigin)) {
res.status(403).json({ ok: false, error: 'forbidden origin' });
return true;
}
return null;
}
export function safeParse(s) {
try {
return JSON.parse(s);
} catch {
return null;
}
}