690320:2026 login #02
Build and Deploy / deploy (push) Successful in 6m50s

This commit is contained in:
2026-03-20 20:26:03 +07:00
parent bac263c097
commit e3859c8349
+75 -19
View File
@@ -5,6 +5,28 @@ import { z } from "zod";
import type { User } from "next-auth";
import type { JWT } from "next-auth/jwt";
type ApiEnvelope<T> = {
data?: ApiEnvelope<T> | T;
message?: string;
statusCode?: number;
};
const authResponseSchema = z.object({
access_token: z.string().min(1),
refresh_token: z.string().min(1).optional(),
user: z
.object({
user_id: z.number(),
username: z.string().min(1),
email: z.string(),
firstName: z.string().min(1),
lastName: z.string().min(1),
role: z.string().min(1).optional(),
primaryOrganizationId: z.number().nullable().optional(),
})
.optional(),
});
// Schema for input validation
const loginSchema = z.object({
username: z.string().min(1),
@@ -27,6 +49,21 @@ function getJwtExpiry(token: string): number {
}
}
function unwrapApiData<T>(payload: ApiEnvelope<T> | T): ApiEnvelope<T> | T | null {
let current: ApiEnvelope<T> | T | null = payload;
while (
current &&
typeof current === "object" &&
"data" in current &&
current.data !== undefined
) {
current = current.data;
}
return current;
}
async function refreshAccessToken(token: JWT) {
try {
const response = await fetch(`${baseUrl}/auth/refresh`, {
@@ -42,13 +79,20 @@ async function refreshAccessToken(token: JWT) {
throw refreshedTokens;
}
const data = refreshedTokens.data || refreshedTokens;
const parsedAuthResponse = authResponseSchema.safeParse(
unwrapApiData(refreshedTokens)
);
if (!parsedAuthResponse.success) {
throw new Error("Invalid refresh token response");
}
return {
...token,
accessToken: data.access_token,
accessTokenExpires: getJwtExpiry(data.access_token),
refreshToken: data.refresh_token ?? token.refreshToken,
accessToken: parsedAuthResponse.data.access_token,
accessTokenExpires: getJwtExpiry(parsedAuthResponse.data.access_token),
refreshToken:
parsedAuthResponse.data.refresh_token ?? token.refreshToken,
};
} catch {
return {
@@ -75,11 +119,10 @@ export const {
if (!credentials?.username || !credentials?.password) return null;
try {
// ✅ sanitize payload
const payload = {
username: credentials.username as string,
password: credentials.password as string,
};
const payload = loginSchema.parse({
username: credentials.username,
password: credentials.password,
});
console.log(`[AUTH] Attempting login at: ${baseUrl}/auth/login`);
console.log(`[AUTH] INTERNAL_API_URL: ${process.env.INTERNAL_API_URL}`);
@@ -104,26 +147,39 @@ export const {
const responseJson = await res.json();
console.log("[AUTH] Backend raw response:", JSON.stringify(responseJson));
const backendData = responseJson.data || responseJson;
const parsedAuthResponse = authResponseSchema.safeParse(
unwrapApiData(responseJson)
);
if (!backendData || !backendData.access_token) {
if (!parsedAuthResponse.success || !parsedAuthResponse.data.user) {
console.error(
"[AUTH] Invalid backend response:",
unwrapApiData(responseJson)
);
return null;
}
const backendData = parsedAuthResponse.data;
const user = backendData.user;
if (!user) {
console.error("[AUTH] Invalid backend response:", backendData);
return null;
}
console.log(
`[AUTH] Login Successful for user: ${
backendData.user?.username || "unknown"
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,
id: user.user_id.toString(),
name: `${user.firstName} ${user.lastName}`,
email: user.email,
username: user.username,
role: user.role || "User",
organizationId: user.primaryOrganizationId,
accessToken: backendData.access_token,
refreshToken: backendData.refresh_token,
} as User;
@@ -183,4 +239,4 @@ export const {
},
secret: process.env.AUTH_SECRET,
debug: process.env.NODE_ENV === "development",
});
});