87 lines
3.4 KiB
TypeScript
87 lines
3.4 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { API_BASE, COOKIE_SECURE, COOKIE_SAMESITE } from "@/lib/config";
|
|
import { resolveTenantFromRequest } from "@/lib/server-tenant";
|
|
import type { UpstreamRefreshResponse } from "@/types/auth";
|
|
|
|
const DEBUG_AUTH = (process.env.DEBUG_AUTH ?? "true").toLowerCase() === "true";
|
|
|
|
export async function POST(req: NextRequest) {
|
|
const tenantKey = resolveTenantFromRequest(req);
|
|
const url = `${API_BASE}/api/authentication/refresh`;
|
|
const refresh = req.cookies.get("refresh_token")?.value;
|
|
|
|
try {
|
|
const r = await fetch(url, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-Tenant": tenantKey,
|
|
...(refresh ? { Cookie: `refresh_token=${encodeURIComponent(refresh)}` } : {}),
|
|
},
|
|
body: JSON.stringify({}),
|
|
redirect: "manual",
|
|
});
|
|
|
|
const rawText = await r.text();
|
|
let data: UpstreamRefreshResponse | { _raw?: string };
|
|
try {
|
|
data = rawText ? (JSON.parse(rawText) as UpstreamRefreshResponse) : ({} as UpstreamRefreshResponse);
|
|
} catch {
|
|
data = { _raw: rawText };
|
|
}
|
|
|
|
if (DEBUG_AUTH) {
|
|
console.log("[auth/refresh] ← upstream", {
|
|
status: r.status,
|
|
hasAccessToken: Boolean((data as UpstreamRefreshResponse)?.access_token),
|
|
bodyPreview: rawText.slice(0, 160),
|
|
});
|
|
}
|
|
|
|
if (!r.ok) {
|
|
return NextResponse.json({ message: (data as { message?: string })?.message ?? "Refresh failed" }, { status: r.status });
|
|
}
|
|
|
|
const nextRes = NextResponse.json(
|
|
{ token_type: (data as UpstreamRefreshResponse).token_type ?? "Bearer", expires_at: (data as UpstreamRefreshResponse).expires_at },
|
|
{ status: 200 },
|
|
);
|
|
|
|
if ((data as UpstreamRefreshResponse)?.access_token && (data as UpstreamRefreshResponse)?.expires_at) {
|
|
nextRes.cookies.set("access_token", (data as UpstreamRefreshResponse).access_token!, {
|
|
httpOnly: false,
|
|
secure: COOKIE_SECURE,
|
|
sameSite: COOKIE_SAMESITE,
|
|
path: "/",
|
|
expires: new Date((data as UpstreamRefreshResponse).expires_at!),
|
|
});
|
|
}
|
|
|
|
const setCookieHeader = r.headers.get("set-cookie") ?? "";
|
|
const parts = setCookieHeader.split(/,(?=\s*[a-zA-Z0-9_\-]+=)/).map((s) => s.trim());
|
|
const refreshPart = parts.find((p) => p.toLowerCase().startsWith("refresh_token="));
|
|
if (refreshPart) {
|
|
const match = refreshPart.match(/^refresh_token=([^;]+)/i);
|
|
if (match) {
|
|
const refreshValue = decodeURIComponent(match[1]);
|
|
const expMatch = refreshPart.match(/expires=([^;]+)/i);
|
|
const expires = expMatch ? new Date(expMatch[1]) : undefined;
|
|
|
|
nextRes.cookies.set("refresh_token", refreshValue, {
|
|
httpOnly: true,
|
|
secure: COOKIE_SECURE,
|
|
sameSite: COOKIE_SAMESITE,
|
|
path: "/",
|
|
expires,
|
|
});
|
|
}
|
|
}
|
|
|
|
return nextRes;
|
|
} catch (e) {
|
|
const err = e as Error;
|
|
console.error("[auth/refresh] error:", err.message);
|
|
return NextResponse.json({ message: "Server error", detail: err.message }, { status: 500 });
|
|
}
|
|
}
|