Untitled
import { NextAuthOptions } from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import { signInSchema } from "./zod"; import { JWT } from "next-auth/jwt"; import { User } from "next-auth"; import axios, { AxiosError } from "axios"; import { Role } from "@/types/role"; const BASEURL = process.env.NEXT_PUBLIC_API_BASE_URL; interface ApiError { message: string; statusCode?: number; errors?: Record<string, string[]>; } interface AuthResponse { accessToken: string; refreshToken: string; user: { id: string; email: string; firstName: string; lastName: string; role: Omit<Role, "departments">; }; } const handleApiError = (error: unknown): never => { if (axios.isAxiosError(error)) { const axiosError = error as AxiosError<ApiError>; const errorMessage = axiosError.response?.data?.message || axiosError.response?.data?.errors?.general?.[0] || axiosError.message || "Authentication failed"; throw new Error(errorMessage); } throw error; }; export const refreshAccessToken = async (token: JWT): Promise<JWT> => { try { const { data } = await axios.post<AuthResponse>( `${BASEURL}/auth/refresh-token`, { refreshToken: token.refreshToken }, { headers: { "Content-Type": "application/json" } } ); return { ...token, accessToken: data.accessToken, refreshToken: data.refreshToken || token.refreshToken, accessTokenExpires: Date.now() + 60 * 60 * 1000, user: token.user, }; } catch (error) { return { ...token, error: "RefreshAccessTokenError", }; } }; export const authOptions: NextAuthOptions = { providers: [ CredentialsProvider({ name: "Credentials", type: "credentials", credentials: { email: { label: "Email", type: "email", placeholder: "johndoe@email.com", }, password: { label: "Password", type: "password", placeholder: "********", }, }, async authorize(credentials) { try { const { email, password } = signInSchema.parse(credentials); const res = await axios.post<AuthResponse>( `${BASEURL}/auth/login`, { email, password }, { headers: { "Content-Type": "application/json", Accept: "application/json", }, withCredentials: true, timeout: 10000, } ); const data = res.data; if (!data.user) { return null; } const user: User = { id: data.user.id, firstName: data.user.firstName, lastName: data.user.lastName, email: data.user.email, role: { id: data.user.role.id, createdAt: data.user.role.createdAt, description: data.user.role.description, name: data.user.role.name, }, accessToken: data.accessToken, }; return user; } catch (error) { handleApiError(error); return null; } }, }), ], callbacks: { async jwt({ token, user }) { if (user) { if ( !user.email || !user.id || !user.firstName || !user.lastName || !user.role ) { throw new Error("Missing required user fields"); } token.accessToken = user.accessToken!; token.user = { id: user.id, email: user.email, firstName: user.firstName, lastName: user.lastName, role: user.role, }; token.accessTokenExpires = Date.now() + 60 * 60 * 1000; return token; } if ( token.accessTokenExpires && typeof token.accessTokenExpires === "number" && Date.now() < token.accessTokenExpires ) { return token; } return await refreshAccessToken(token); }, async session({ session, token }) { session.user = { id: token.user.id, email: token.user.email, firstName: token.user.firstName, lastName: token.user.lastName, role: token.user.role, }; session.accessToken = token.accessToken; session.error = token.error || null; return session; }, }, pages: { signIn: "/", }, session: { strategy: "jwt", maxAge: 30 * 24 * 60 * 60, }, jwt: { secret: process.env.NEXTAUTH_SECRET, maxAge: 30 * 24 * 60 * 60, }, debug: process.env.NODE_ENV === "development", };
Leave a Comment