Files
frontend/apps/cm/app/api/proxy/[...url]/route.ts
T

146 lines
4.2 KiB
TypeScript

import {
ACCESS_TOKEN,
AUTH_USER_ID,
CLAIM_NAME,
REFRESH_TOKEN,
SERVER_API_URL,
SITE_ID,
} from "@/shared/config";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
async function handler(
request: Request,
params: { url: string[] },
method: "GET" | "POST" | "PUT" | "DELETE",
) {
const cookieStore = await cookies();
const headers: Record<string, string> = {
"Content-Type": "application/json",
"X-Site-Id": SITE_ID,
};
let body = {};
const isRefreshHandler = params.url.includes("refresh");
console.log(`[Proxy] ${method} ${params.url.join("/")}`);
console.log(`[Proxy] Server API URL: ${SERVER_API_URL}`);
// silent refresh
if (isRefreshHandler) {
const refreshToken = cookieStore.get(REFRESH_TOKEN);
headers[CLAIM_NAME] = refreshToken?.value ?? "";
if (!headers[CLAIM_NAME]) {
return NextResponse.json({ message: "Can't refresh" }, { status: 401 });
}
}
try {
if (!isRefreshHandler) {
body = await request.json();
console.log(`[Proxy] Request body:`, body);
}
const targetUrl = `${SERVER_API_URL}/${params.url.join("/")}`;
console.log(`[Proxy] Target URL: ${targetUrl}`);
const result = await fetch(targetUrl, {
method,
headers,
body: JSON.stringify(body),
});
console.log(`[Proxy] Response status: ${result.status}`);
console.log(`[Proxy] Response headers:`, Object.fromEntries(result.headers.entries()));
// 2FA 관련 엔드포인트인 경우 응답 본문도 확인
const is2FAEndpoint = params.url.includes("2fa") || params.url.includes("auth");
let responseData = {};
if (is2FAEndpoint) {
try {
responseData = await result.json();
console.log(`[Proxy] 2FA Response data:`, responseData);
} catch {
// JSON 파싱 실패 시 빈 객체 사용
console.log(`[Proxy] Failed to parse JSON response`);
}
}
const accessToken = result.headers.get(ACCESS_TOKEN) || result.headers.get('ss-token');
const refreshToken = result.headers.get(REFRESH_TOKEN);
const userId = result.headers.get(AUTH_USER_ID);
console.log(`[Proxy] Tokens - accessToken: ${!!accessToken}, userId: ${!!userId}, refreshToken: ${!!refreshToken}`);
console.log(`[Proxy] Available headers:`, Array.from(result.headers.keys()));
// 2FA 엔드포인트이고 실패한 경우 실제 오류 메시지 반환
if (!result.ok && is2FAEndpoint) {
console.log(`[Proxy] 2FA endpoint failed, returning actual error`);
return NextResponse.json(responseData, { status: result.status });
}
// 2FA 엔드포인트이고 성공한 경우 (토큰이 헤더에 없을 수 있음)
if (result.ok && is2FAEndpoint) {
console.log(`[Proxy] 2FA endpoint succeeded, returning response data`);
return NextResponse.json(responseData, { status: result.status });
}
// 일반적인 실패 처리
if (!result.ok) {
console.log(`[Proxy] General endpoint failed`);
return NextResponse.json(
{ message: "Invalid Credentials" },
{ status: 401 },
);
}
if (accessToken && userId) {
const response = NextResponse.json({
[ACCESS_TOKEN]: accessToken,
[AUTH_USER_ID]: userId,
...responseData, // 2FA 관련 추가 데이터 포함
});
if (refreshToken) {
cookieStore.set(REFRESH_TOKEN, refreshToken, {
httpOnly: true,
sameSite: "lax",
path: "/",
});
}
console.log(`[Proxy] Success response sent`);
return response;
}
console.log(`[Proxy] Missing tokens in response`);
return NextResponse.json(
{ message: "Invalid Credentials" },
{ status: 401 },
);
} catch (error) {
console.error(`[Proxy] Error:`, error);
return NextResponse.json(
{ message: "Server Error", error },
{ status: 500 },
);
}
}
export async function POST(
request: Request,
props: { params: Promise<{ url: string[] }> },
) {
const params = await props.params;
return handler(request, params, "POST");
}
export async function PUT(
request: Request,
props: { params: Promise<{ url: string[] }> },
) {
const params = await props.params;
return handler(request, params, "PUT");
}