151 lines
3.8 KiB
TypeScript
151 lines
3.8 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,
|
|
};
|
|
|
|
// X-Device-Id 중계 로직 (헤더 또는 쿠키에서 획득)
|
|
const deviceId =
|
|
request.headers.get("x-device-id") ||
|
|
request.headers.get("X-Device-Id") ||
|
|
cookieStore.get("device-id")?.value;
|
|
if (deviceId) {
|
|
headers["X-Device-Id"] = deviceId;
|
|
}
|
|
|
|
let body = {};
|
|
const isRefreshHandler = params.url.includes("refresh");
|
|
|
|
// 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();
|
|
}
|
|
|
|
const result = await fetch(`${SERVER_API_URL}/${params.url.join("/")}`, {
|
|
method,
|
|
headers,
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
if (!result.ok) {
|
|
console.error(`[BFF Proxy Error] Backend returned status ${result.status} for ${params.url.join("/")}`);
|
|
let errorText = "";
|
|
try {
|
|
errorText = await result.text();
|
|
console.error(`[BFF Proxy Error Body] ${errorText}`);
|
|
} catch (err) {}
|
|
|
|
if (result.status === 503) {
|
|
const serviceName = params.url[0] || "user-service";
|
|
return NextResponse.json(
|
|
{ message: `${serviceName} 서비스와의 연결이 불안정 합니다. 관리자에게 연락해 주십시오.` },
|
|
{ status: 503 },
|
|
);
|
|
}
|
|
|
|
if (result.status !== 500 && errorText) {
|
|
try {
|
|
const errorJson = JSON.parse(errorText);
|
|
if (errorJson && errorJson.message) {
|
|
return NextResponse.json(
|
|
{ message: errorJson.message },
|
|
{ status: result.status },
|
|
);
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
|
|
return NextResponse.json(
|
|
{ message: "Invalid Credentials" },
|
|
{ status: 401 },
|
|
);
|
|
}
|
|
|
|
const accessToken = result.headers.get(ACCESS_TOKEN);
|
|
const refreshToken = result.headers.get(REFRESH_TOKEN);
|
|
const userId = result.headers.get(AUTH_USER_ID);
|
|
|
|
if (accessToken && userId) {
|
|
const response = NextResponse.json({
|
|
[ACCESS_TOKEN]: accessToken,
|
|
[AUTH_USER_ID]: userId,
|
|
});
|
|
|
|
if (refreshToken) {
|
|
response.cookies.set(REFRESH_TOKEN, refreshToken, {
|
|
httpOnly: true,
|
|
sameSite: "strict",
|
|
domain: getCookieDomain(),
|
|
path: "/",
|
|
});
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
return NextResponse.json(
|
|
{ message: "Invalid Credentials" },
|
|
{ status: 401 },
|
|
);
|
|
} catch (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");
|
|
}
|
|
|
|
function getCookieDomain(): string | undefined {
|
|
if (typeof window === "undefined") return undefined;
|
|
|
|
if (COOKIE_DOMAIN) return COOKIE_DOMAIN;
|
|
|
|
const { hostname } = window.location;
|
|
if (hostname === "localhost" || hostname === "127.0.0.1") return undefined;
|
|
|
|
return hostname;
|
|
}
|