Add Login and Dashboard

This commit is contained in:
Thanakarn Klangkasame
2025-10-05 19:17:42 +07:00
parent 594e0d42d1
commit 7fb60ba0f5
26 changed files with 1750 additions and 243 deletions

View File

@@ -0,0 +1,86 @@
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 });
}
}