refactor(WEB): Server Action 공통화 및 보안 강화

- executeServerAction 공통 유틸 도입으로 actions.ts 대폭 간소화 (50+개 파일)
- sanitize 유틸 추가 (XSS 방지)
- middleware CSP 헤더 추가 및 Open Redirect 방지
- 프록시 라우트 로깅 개발환경 한정으로 변경
- 프로덕션 불필요 console.log 제거

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-09 16:14:06 +09:00
parent d014227e9c
commit 55e0791e16
85 changed files with 7211 additions and 17638 deletions

View File

@@ -180,7 +180,9 @@ export async function POST(request: NextRequest) {
`Max-Age=${data.expires_in || 7200}`,
].join('; ');
console.log('✅ Login successful - Access & Refresh tokens stored in HttpOnly cookies');
if (process.env.NODE_ENV === 'development') {
console.log('✅ Login successful - tokens stored in HttpOnly cookies');
}
const response = NextResponse.json(responseData, { status: 200 });

View File

@@ -112,10 +112,10 @@ async function proxyRequest(
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(method)) {
if (contentType.includes('application/json')) {
body = await request.text();
console.log('🔵 [PROXY] Request:', method, url.toString());
console.log('🔵 [PROXY] Request Body:', body);
if (process.env.NODE_ENV === 'development') {
console.log('🔵 [PROXY] Request:', method, url.pathname);
}
} else if (contentType.includes('multipart/form-data')) {
console.log('📎 [PROXY] Processing multipart/form-data request');
isFormData = true;
const originalFormData = await request.formData();
@@ -124,18 +124,20 @@ async function proxyRequest(
for (const [key, value] of originalFormData.entries()) {
if (value instanceof File) {
newFormData.append(key, value, value.name);
console.log(`📎 [PROXY] File field: ${key} = ${value.name} (${value.size} bytes)`);
} else {
newFormData.append(key, value);
console.log(`📎 [PROXY] Form field: ${key} = ${value}`);
}
}
body = newFormData;
console.log('🔵 [PROXY] Request:', method, url.toString());
if (process.env.NODE_ENV === 'development') {
console.log('🔵 [PROXY] Request:', method, url.pathname);
}
}
} else {
console.log('🔵 [PROXY] Request:', method, url.toString());
if (process.env.NODE_ENV === 'development') {
console.log('🔵 [PROXY] Request:', method, url.pathname);
}
}
// 4. 헤더 구성
@@ -159,7 +161,9 @@ async function proxyRequest(
// 6. 인증 실패 → 쿠키 삭제 + 401 반환
if (authFailed) {
console.warn('🔴 [PROXY] Auth failed, clearing cookies...');
if (process.env.NODE_ENV === 'development') {
console.warn('🔴 [PROXY] Auth failed, clearing cookies...');
}
const clearResponse = NextResponse.json(
{ error: 'Authentication failed', needsReauth: true },
{ status: 401 }
@@ -171,7 +175,9 @@ async function proxyRequest(
}
// 7. 응답 처리 (바이너리 vs 텍스트/JSON)
console.log('🔵 [PROXY] Response status:', backendResponse.status);
if (process.env.NODE_ENV === 'development') {
console.log('🔵 [PROXY] Response status:', backendResponse.status);
}
const responseContentType = backendResponse.headers.get('content-type') || 'application/json';
const isBinaryResponse =
@@ -186,7 +192,9 @@ async function proxyRequest(
let clientResponse: NextResponse;
if (isBinaryResponse) {
console.log('📄 [PROXY] Binary response detected:', responseContentType);
if (process.env.NODE_ENV === 'development') {
console.log('📄 [PROXY] Binary response detected:', responseContentType);
}
const binaryData = await backendResponse.arrayBuffer();
clientResponse = new NextResponse(binaryData, {
@@ -213,7 +221,9 @@ async function proxyRequest(
createTokenCookies(newTokens).forEach(cookie => {
clientResponse.headers.append('Set-Cookie', cookie);
});
console.log('🍪 [PROXY] New tokens set in cookies');
if (process.env.NODE_ENV === 'development') {
console.log('🍪 [PROXY] New tokens set in cookies');
}
}
return clientResponse;