Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 30 additions & 30 deletions app/api/auth/[...nextauth]/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,12 @@ export const authOptions: AuthOptions = {
},
}),
],

pages: {
signIn: '/auth/signin',
error: '/auth/error',
},

callbacks: {
async signIn({ user, account }) {
// Skip if not Google sign in or if email is missing
Expand All @@ -107,17 +109,17 @@ export const authOptions: AuthOptions = {

// Check if user already exists
const existingUser = (await User.findOne({
email:
typeof user.email === 'string' ? user.email.toLowerCase() : undefined,
email: user.email.toLowerCase(),
}).select('+passwordSet +roles')) as IUser | null

let dbUser: IUser | null = null
let isNewUser = false

if (existingUser) {
// Update Google ID if needed
dbUser = existingUser

if (!existingUser.googleId && user.id) {
// Use findOneAndUpdate to ensure atomic update
const updatedUser = await User.findOneAndUpdate(
dbUser = await User.findOneAndUpdate(
{ email: existingUser.email },
{
$set: {
Expand All @@ -128,41 +130,35 @@ export const authOptions: AuthOptions = {
},
{ new: true, runValidators: true }
)

// Ensure we have the latest user data with roles
if (updatedUser) {
user.roles = updatedUser.roles
}
} else {
// If user already has Google ID, ensure roles are passed
user.roles = existingUser.roles
}
// If user exists and has a password set, allow sign in
if (existingUser.passwordSet) {
return true
}
} else if (user.email) {
// Create new user from Google auth
await User.create({
} else {
dbUser = await User.create({
name: user.name,
email: user.email,
image: user.image || undefined, // Convert null to undefined
image: user.image || undefined,
googleId: user.id,
passwordSet: false,
emailVerified: true, // Google OAuth automatically verifies email
roles: ['user'], // Default role for new users
emailVerified: true,
roles: ['user'],
})
isNewUser = true
}

// Only redirect to set-password for new users or existing users without a password
if (isNewUser || (existingUser && !existingUser.passwordSet)) {
const email = user.email || ''
return `/auth/set-password?email=${encodeURIComponent(email)}${isNewUser ? '&new=true' : ''}`
// 🔑 NORMALIZE IDENTITY (THE FIX)
if (dbUser && dbUser._id) {
;(user as any).id = dbUser._id.toString()
;(user as any).roles = dbUser.roles
}

if (isNewUser || (dbUser && !dbUser.passwordSet)) {
return `/auth/set-password?email=${encodeURIComponent(user.email)}${
isNewUser ? '&new=true' : ''
}`
}

return true
},

async session({ session, token }) {
if (token.sub && session.user) {
session.user.id = token.sub
Expand All @@ -173,6 +169,7 @@ export const authOptions: AuthOptions = {
}
return session
},

async jwt({ token, user }) {
if (user) {
token.sub = user.id
Expand All @@ -182,13 +179,16 @@ export const authOptions: AuthOptions = {
return token
},
},

session: {
strategy: 'jwt',
maxAge: 24 * 60 * 60, // 24 hours
updateAge: 60 * 60, // 1 hour
maxAge: 24 * 60 * 60,
updateAge: 60 * 60,
},

jwt: {
maxAge: 24 * 60 * 60, // 24 hours
maxAge: 24 * 60 * 60,
},

secret: process.env.NEXTAUTH_SECRET,
}