assessment-model/src/app/api/auth/[...nextauth]/route.ts
2024-09-10 18:44:03 +01:00

155 lines
4.5 KiB
TypeScript

import NextAuth, { NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import AzureADB2CProvider from "next-auth/providers/azure-ad-b2c";
import CredentialsProvider from "next-auth/providers/credentials";
import { db } from "@/app/db/db";
import { user as userTable, User } from "@/app/db/schema/users";
import { eq } from "drizzle-orm";
const { GOOGLE_CLIENT_ID = "", GOOGLE_CLIENT_SECRET = "" } = process.env;
const {
AZURE_AD_B2C_TENANT_NAME = "",
AZURE_AD_B2C_CLIENT_ID = "",
AZURE_AD_B2C_CLIENT_SECRET = "",
AZURE_AD_B2C_PRIMARY_USER_FLOW = "",
} = process.env;
type OauthProvider = "google";
// TODO: handle token expiration
// https://next-auth.js.org/v3/tutorials/refresh-token-rotation
// propertly set options too
// https://next-auth.js.org/configuration/options
export const AuthOptions: NextAuthOptions = {
providers: [
GoogleProvider({
clientId: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
authorization: {
params: {
access_type: "offline",
prompt: "consent",
response_type: "code",
},
},
}),
AzureADB2CProvider({
tenantId: AZURE_AD_B2C_TENANT_NAME,
clientId: AZURE_AD_B2C_CLIENT_ID,
clientSecret: AZURE_AD_B2C_CLIENT_SECRET,
primaryUserFlow: AZURE_AD_B2C_PRIMARY_USER_FLOW,
authorization: {
params: {
scope: "openid profile offline_access",
prompt: "login",
},
},
}),
CredentialsProvider({
name: "Email Login",
credentials: {
email: {
label: "Email",
type: "email",
},
},
async authorize(credentials, req) {
if (!credentials || !credentials.email) {
throw new Error("Email is required");
}
const { email } = credentials;
// Query the database to find the user by email
const dbUser = await db
.select()
.from(userTable)
.where(eq(userTable.email, email));
// If the email exists, return the user object (no password check)
if (dbUser.length === 1) {
return {
id: dbUser[0].id.toString(), // Convert bigint to string to avoid serialization issues
email: dbUser[0].email,
dbId: dbUser[0].id.toString(), // Ensure dbId is added and is a string
};
}
return null;
},
}),
],
pages: {
signIn: "/",
},
callbacks: {
async signIn({ user, account }) {
try {
if (user === null || user.email === null) {
return "/beta";
}
const dbUser: User[] = await db
.select()
.from(userTable)
.where(eq(userTable.email, String(user.email)));
if (dbUser.length > 1) {
console.error(`Multiple users found with email ${user.email}`);
return false;
}
if (dbUser.length === 0 || account === null) {
return "/beta";
}
if (!dbUser[0].oauthId) {
// We make a second query to populate the oauthId and oauthProvider
console.log("Updating user with oauthId and oauthProvider");
const provider = account.provider as OauthProvider;
await db
.update(userTable)
.set({ oauthId: user.id, oauthProvider: provider })
.where(eq(userTable.email, String(user.email)));
console.log("Updated oauthId and oauthProvider");
}
// Set the user's ID from your database
// Because bigint isn't serializable, we need to convert it to a string
user.dbId = dbUser[0].id.toString();
return true;
} catch (error) {
console.error("Error during sign-in: ", error);
return false;
}
},
async jwt({ token, user }) {
// This is executed whenever a JWT is created or refreshed.
// `user` is the object returned from `signIn` callback and
// is only available during sign in, which is why we need to
// store the id in the token and then read it back into the session.
if (user?.dbId) {
token.dbId = user.dbId;
}
return token;
},
async session({ session, token }) {
if (session?.user) {
session.user.dbId = token.dbId;
}
return session;
},
async redirect({ baseUrl }) {
const redirectUrl = baseUrl + "/home";
return redirectUrl;
},
},
};
const handler = NextAuth(AuthOptions);
export { handler as GET, handler as POST };