Untitled

 avatar
unknown
javascript
16 days ago
4.7 kB
2
Indexable
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