Files
lcbp3/frontend/lib/auth.ts
T
admin bac263c097
Build and Deploy / deploy (push) Successful in 6m43s
690320:2012 login #01
2026-03-20 20:12:03 +07:00

186 lines
5.2 KiB
TypeScript

// File: lib/auth.ts
import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { z } from "zod";
import type { User } from "next-auth";
import type { JWT } from "next-auth/jwt";
// Schema for input validation
const loginSchema = z.object({
username: z.string().min(1),
password: z.string().min(1),
});
// ✅ ใช้แบบ SSR-safe (ดีที่สุด)
const baseUrl =
(typeof window === "undefined" ? process.env.INTERNAL_API_URL : null) ||
process.env.NEXT_PUBLIC_API_URL ||
"http://localhost:3001/api";
// Helper to parse JWT expiry
function getJwtExpiry(token: string): number {
try {
const payload = JSON.parse(atob(token.split(".")[1]));
return payload.exp * 1000;
} catch {
return Date.now();
}
}
async function refreshAccessToken(token: JWT) {
try {
const response = await fetch(`${baseUrl}/auth/refresh`, {
method: "POST",
headers: {
Authorization: `Bearer ${token.refreshToken}`,
},
});
const refreshedTokens = await response.json();
if (!response.ok) {
throw refreshedTokens;
}
const data = refreshedTokens.data || refreshedTokens;
return {
...token,
accessToken: data.access_token,
accessTokenExpires: getJwtExpiry(data.access_token),
refreshToken: data.refresh_token ?? token.refreshToken,
};
} catch {
return {
...token,
error: "RefreshAccessTokenError",
};
}
}
export const {
handlers: { GET, POST },
auth,
signIn,
signOut,
} = NextAuth({
providers: [
Credentials({
name: "Credentials",
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" },
},
authorize: async (credentials) => {
if (!credentials?.username || !credentials?.password) return null;
try {
// ✅ sanitize payload
const payload = {
username: credentials.username as string,
password: credentials.password as string,
};
console.log(`[AUTH] Attempting login at: ${baseUrl}/auth/login`);
console.log(`[AUTH] INTERNAL_API_URL: ${process.env.INTERNAL_API_URL}`);
console.log(`[AUTH] NEXT_PUBLIC_API_URL: ${process.env.NEXT_PUBLIC_API_URL}`);
const res = await fetch(`${baseUrl}/auth/login`, {
method: "POST",
body: JSON.stringify(payload),
headers: {
"Content-Type": "application/json",
},
cache: "no-store",
});
if (!res.ok) {
console.error(`[AUTH] Login Failed: status ${res.status}`);
const errorBody = await res.text().catch(() => "No error body");
console.error(`[AUTH] Error details: ${errorBody}`);
return null;
}
const responseJson = await res.json();
console.log("[AUTH] Backend raw response:", JSON.stringify(responseJson));
const backendData = responseJson.data || responseJson;
if (!backendData || !backendData.access_token) {
console.error("[AUTH] Invalid backend response:", backendData);
return null;
}
console.log(
`[AUTH] Login Successful for user: ${
backendData.user?.username || "unknown"
}`
);
return {
id: backendData.user.user_id.toString(),
name: `${backendData.user.firstName} ${backendData.user.lastName}`,
email: backendData.user.email,
username: backendData.user.username,
role: backendData.user.role || "User",
organizationId: backendData.user.primaryOrganizationId,
accessToken: backendData.access_token,
refreshToken: backendData.refresh_token,
} as User;
} catch (error) {
console.error("[AUTH] Network Error:", error);
return null;
}
},
}),
],
pages: {
signIn: "/login",
error: "/login",
},
callbacks: {
async jwt({ token, user }) {
if (user) {
return {
...token,
id: user.id,
username: user.username,
role: user.role,
organizationId: user.organizationId,
accessToken: user.accessToken,
refreshToken: user.refreshToken,
accessTokenExpires: getJwtExpiry(user.accessToken!),
};
}
if (Date.now() < (token.accessTokenExpires as number) - 10000) {
return token;
}
if (token.error) {
return token;
}
return refreshAccessToken(token);
},
async session({ session, token }) {
if (token && session.user) {
session.user.id = token.id as string;
session.user.username = token.username as string;
session.user.role = token.role as string;
session.user.organizationId = token.organizationId as number;
session.accessToken = token.accessToken as string;
session.refreshToken = token.refreshToken as string;
session.error = token.error as string;
}
return session;
},
},
session: {
strategy: "jwt",
maxAge: 24 * 60 * 60,
},
secret: process.env.AUTH_SECRET,
debug: process.env.NODE_ENV === "development",
});