![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/lavocat.ca/public_html/src/lib/ |
import { NextAuthOptions } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { compare } from 'bcryptjs';
import { prisma } from '@/lib/prisma';
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null;
}
try {
// Find user by email
const user = await prisma.user.findUnique({
where: { email: credentials.email },
select: {
id: true,
email: true,
password: true,
role: true,
name: true,
isVerified: true,
verificationStatus: true,
},
});
if (!user) {
return null;
}
// Verify password
const isValid = await compare(credentials.password, user.password);
if (!isValid) {
return null;
}
// Return user without password, ensuring name is never null
const { password: _, ...userWithoutPassword } = user;
return {
...userWithoutPassword,
name: userWithoutPassword.name || userWithoutPassword.email, // Use email as fallback if name is null
};
} catch (error) {
console.error('Auth error:', error);
return null;
}
}
})
],
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
cookies: {
sessionToken: {
name: process.env.NODE_ENV === 'development' ? 'next-auth.session-token' : '__Secure-next-auth.session-token',
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: process.env.NODE_ENV === 'production',
// Don't set domain in development to allow network access
domain: process.env.NODE_ENV === 'production' ? process.env.COOKIE_DOMAIN : undefined
}
}
},
pages: {
signIn: '/auth/login',
signOut: '/auth/login',
error: '/auth/error',
},
callbacks: {
async jwt({ token, user, trigger }) {
try {
console.log('JWT Callback - trigger:', trigger);
console.log('JWT Callback - user:', user ? { id: user.id, email: user.email, role: user.role } : null);
// Handle fresh login
if (user) {
// Store user data in token
token.id = user.id;
token.role = user.role;
token.email = user.email;
token.name = user.name || user.email;
token.profilePicture = user.profilePicture || undefined;
token.username = user.username || undefined;
token.isVerified = (user as any).isVerified || false;
token.verificationStatus = (user as any).verificationStatus || 'PENDING';
token.isImpersonating = user.isImpersonating || false;
token.originalUser = user.originalUser || undefined;
console.log('JWT Callback - Updated token from user:', {
id: token.id,
role: token.role,
isImpersonating: token.isImpersonating
});
} else if (token.id) {
// Check for impersonation sessions on ALL requests (including 'update' triggers)
// This ensures impersonation state is always current
try {
console.log('JWT Callback - Checking for impersonation sessions...');
const impersonationSession = await prisma.impersonationSession.findFirst({
where: {
originalUserId: token.id as string,
isActive: true,
expiresAt: { gt: new Date() } // Only get non-expired sessions
},
include: {
impersonatedUser: {
select: {
id: true,
email: true,
name: true,
role: true,
profilePicture: true,
username: true
}
},
originalUser: {
select: {
id: true,
email: true,
name: true,
role: true,
profilePicture: true,
username: true
}
}
}
});
if (impersonationSession) {
// Apply impersonation
const impersonatedUser = impersonationSession.impersonatedUser;
token.id = impersonatedUser.id;
token.email = impersonatedUser.email;
token.name = impersonatedUser.name || impersonatedUser.email;
token.role = impersonatedUser.role;
token.profilePicture = impersonatedUser.profilePicture || undefined;
token.username = impersonatedUser.username || undefined;
token.isImpersonating = true;
token.originalUser = {
id: impersonationSession.originalUser.id,
email: impersonationSession.originalUser.email,
name: impersonationSession.originalUser.name || impersonationSession.originalUser.email,
role: impersonationSession.originalUser.role,
profilePicture: impersonationSession.originalUser.profilePicture || undefined,
username: impersonationSession.originalUser.username || undefined
};
console.log('JWT Callback - Applied impersonation:', {
originalId: token.originalUser.id,
impersonatedId: token.id,
originalRole: token.originalUser.role,
impersonatedRole: token.role
});
} else if (token.isImpersonating) {
// No active impersonation found, restore original user
if (token.originalUser) {
console.log('JWT Callback - Restoring original user from token');
token.id = token.originalUser.id;
token.email = token.originalUser.email;
token.name = token.originalUser.name || token.originalUser.email;
token.role = token.originalUser.role;
token.profilePicture = token.originalUser.profilePicture || undefined;
token.username = token.originalUser.username || undefined;
}
token.isImpersonating = false;
token.originalUser = undefined;
console.log('JWT Callback - Cleared impersonation state');
}
} catch (error) {
console.error('JWT Callback - Database error:', error);
// On error, clear impersonation state
if (token.isImpersonating && token.originalUser) {
console.log('JWT Callback - Error occurred, restoring original user');
token.id = token.originalUser.id;
token.email = token.originalUser.email;
token.name = token.originalUser.name || token.originalUser.email;
token.role = token.originalUser.role;
token.profilePicture = token.originalUser.profilePicture || undefined;
token.username = token.originalUser.username || undefined;
token.isImpersonating = false;
token.originalUser = undefined;
}
}
}
console.log('JWT Callback - Final token:', {
id: token.id,
email: token.email,
role: token.role,
isImpersonating: token.isImpersonating
});
return token;
} catch (err) {
console.error('JWT Callback - Error:', err);
return token;
}
},
async session({ session, token }) {
try {
console.log('Session Callback - token:', { id: token.id, email: token.email, role: token.role, isImpersonating: token.isImpersonating });
console.log('Session Callback - session before:', session);
if (token && session.user) {
session.user.id = token.id as string;
session.user.role = token.role as string;
session.user.email = token.email as string;
session.user.name = token.name as string;
session.user.profilePicture = token.profilePicture as string;
session.user.username = token.username as string;
session.user.isVerified = token.isVerified as boolean;
session.user.verificationStatus = token.verificationStatus as string;
session.user.isImpersonating = token.isImpersonating as boolean;
session.user.originalUser = token.originalUser as any;
}
console.log('Session Callback - session after:', session);
console.log('Session Callback - Final user ID:', session.user?.id);
return session;
} catch (err) {
console.error('Session Callback - Error:', err);
return session;
}
},
async redirect({ url, baseUrl }) {
// Only force home/dashboard for login page or base
if (url === baseUrl || url === `${baseUrl}/` || url.endsWith('/auth/login')) {
return baseUrl;
}
// If URL is relative, make it absolute
if (url.startsWith('/')) {
return `${baseUrl}${url}`;
}
// If URL is on the same domain, allow it
if (url.startsWith(baseUrl)) {
return url;
}
// Default to base URL for external URLs (security)
return baseUrl;
}
},
secret: process.env.NEXTAUTH_SECRET || '3560f921b7bbf968e64fbc2835960840d184fcb95977e960a2124de6bbbed2d3',
debug: process.env.NODE_ENV === 'development',
};