185 lines
5.2 KiB
TypeScript
185 lines
5.2 KiB
TypeScript
import {
|
|
ACCESS_TOKEN,
|
|
AUTH_USER_ID,
|
|
CLAIM_NAME,
|
|
COOKIE_DOMAIN,
|
|
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,
|
|
};
|
|
const deviceId = request.headers.get("X-Device-Id");
|
|
if (deviceId) {
|
|
headers["X-Device-Id"] = deviceId;
|
|
}
|
|
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 {
|
|
// GET 요청이 아닐 때만 body 파싱
|
|
if (method !== "GET" && !isRefreshHandler) {
|
|
try {
|
|
body = await request.json();
|
|
console.log(`[Proxy] Request body:`, body);
|
|
} catch {
|
|
// body가 없을 수 있음
|
|
body = {};
|
|
}
|
|
}
|
|
|
|
const targetUrl = `${SERVER_API_URL}/${params.url.join("/")}`;
|
|
console.log(`[Proxy] Target URL: ${targetUrl}`);
|
|
|
|
const result = await fetch(targetUrl, {
|
|
method,
|
|
headers,
|
|
body: method !== "GET" ? JSON.stringify(body) : undefined,
|
|
});
|
|
|
|
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");
|
|
// 배너 API는 인증 없이 호출 가능
|
|
const isBannerEndpoint = params.url.includes("bnrs");
|
|
let responseData = {};
|
|
|
|
if (is2FAEndpoint || isBannerEndpoint) {
|
|
try {
|
|
responseData = await result.json();
|
|
console.log(
|
|
`[Proxy] ${is2FAEndpoint ? "2FA" : "Banner"} 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 || isBannerEndpoint)) {
|
|
console.log(
|
|
`[Proxy] ${is2FAEndpoint ? "2FA" : "Banner"} endpoint failed, returning actual error`,
|
|
);
|
|
return NextResponse.json(responseData, { status: result.status });
|
|
}
|
|
|
|
// 2FA 또는 배너 엔드포인트이고 성공한 경우 (토큰이 헤더에 없을 수 있음)
|
|
if (result.ok && (is2FAEndpoint || isBannerEndpoint)) {
|
|
console.log(
|
|
`[Proxy] ${is2FAEndpoint ? "2FA" : "Banner"} 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",
|
|
domain: COOKIE_DOMAIN,
|
|
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");
|
|
}
|
|
|
|
export async function GET(
|
|
request: Request,
|
|
props: { params: Promise<{ url: string[] }> },
|
|
) {
|
|
const params = await props.params;
|
|
return handler(request, params, "GET");
|
|
}
|