import { NextAuthOptions } from "next-auth" import GoogleProvider from "next-auth/providers/google" import GitHubProvider from "next-auth/providers/github" import CredentialsProvider from "next-auth/providers/credentials" import { env } from "./env" import { UserRole } from "@/types/database" import { getNextcloudUserProfile, getNextcloudUserGroups, determineUserRole } from "./nextcloud-client" export const authOptions: NextAuthOptions = { // Note: Database adapter will be configured via Supabase MCP // For now, using JWT strategy without database adapter providers: [ // Credentials provider for email/password login (admin fallback) and Nextcloud OAuth completion CredentialsProvider({ name: "credentials", credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" }, nextcloud_token: { label: "Nextcloud Token", type: "text" }, }, async authorize(credentials, req) { console.log("Authorize called with:", credentials) // Handle Nextcloud OAuth completion if (credentials?.nextcloud_token) { console.log("Nextcloud OAuth completion with token") // Get cookies from request const cookies = req.headers?.cookie if (!cookies) { console.error("No cookies found") return null } // Parse cookies manually const cookieMap = new Map( cookies.split(';').map(c => { const [key, ...values] = c.trim().split('=') return [key, values.join('=')] }) ) const storedToken = cookieMap.get('nextcloud_one_time_token') const userId = cookieMap.get('nextcloud_user_id') console.log("Stored token:", storedToken ? "present" : "missing") console.log("User ID:", userId ? userId : "missing") if (!storedToken || !userId || storedToken !== credentials.nextcloud_token) { console.error("Token validation failed") return null } // Fetch user from database const { getUserById } = await import('@/lib/db') const user = await getUserById(userId) if (!user) { console.error("User not found") return null } console.log("Nextcloud user authenticated:", user.email) return { id: user.id, email: user.email, name: user.name, role: user.role, } } // Handle regular credentials login if (!credentials?.email || !credentials?.password) { console.log("Missing email or password") return null } console.log("Email received:", credentials.email) console.log("Password received:", credentials.password ? "***" : "empty") // Seed admin user for nicholai@biohazardvfx.com if (credentials.email === "nicholai@biohazardvfx.com") { console.log("Admin user recognized!") return { id: "admin-nicholai", email: "nicholai@biohazardvfx.com", name: "Nicholai", role: UserRole.SUPER_ADMIN, } } // For development: Accept any other email/password combination console.log("Using fallback user creation") const user = { id: "dev-user-" + Date.now(), email: credentials.email, name: credentials.email.split("@")[0], role: UserRole.SUPER_ADMIN, // Give admin access for testing } console.log("Created user:", user) return user } }), // Google OAuth provider (optional) ...(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET ? [ GoogleProvider({ clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, }) ] : []), // GitHub OAuth provider (optional) ...(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET ? [ GitHubProvider({ clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET, }) ] : []), ], session: { strategy: "jwt", maxAge: 30 * 24 * 60 * 60, // 30 days }, callbacks: { async jwt({ token, user, account }) { // Add user role to JWT token if (user) { // Use the role from the user object (set in authorize function) token.role = (user as any).role || UserRole.CLIENT token.userId = user.id } return token }, async session({ session, token }) { // Add user role and ID to session if (token) { session.user.id = token.userId as string session.user.role = token.role as UserRole } return session }, async signIn({ user, account, profile }) { // Custom sign-in logic // Note: Nextcloud OAuth auto-provisioning happens in custom callback handler return true }, async redirect({ url, baseUrl }) { // Follows NextAuth.js best practices for redirect if (url.startsWith("/")) return `${baseUrl}${url}` else if (new URL(url).origin === baseUrl) return url return `${baseUrl}/admin` }, }, pages: { signIn: "/auth/signin", error: "/auth/error", }, events: { async signIn({ user, account, profile, isNewUser }) { // Log sign-in events console.log(`User ${user.email} signed in`) }, async signOut({ session, token }) { // Log sign-out events console.log(`User signed out`) }, }, debug: env.NODE_ENV === "development", } /** * Utility function to get server-side session */ export async function getServerSession() { const { getServerSession: getNextAuthServerSession } = await import("next-auth/next") return getNextAuthServerSession(authOptions) } /** * Route protection utility * @param requiredRole - Minimum role required to access the route */ export async function requireAuth(requiredRole?: UserRole) { const session = await getServerSession() if (!session) { throw new Error("Authentication required") } if (requiredRole && !hasRole(session.user.role, requiredRole)) { throw new Error("Insufficient permissions") } return session } /** * Check if user has required role or higher */ export function hasRole(userRole: UserRole, requiredRole: UserRole): boolean { const roleHierarchy = { [UserRole.CLIENT]: 0, [UserRole.ARTIST]: 1, [UserRole.SHOP_ADMIN]: 2, [UserRole.SUPER_ADMIN]: 3, } return roleHierarchy[userRole] >= roleHierarchy[requiredRole] } /** * Check if user is admin (SHOP_ADMIN or SUPER_ADMIN) */ export function isAdmin(role: UserRole): boolean { return role === UserRole.SHOP_ADMIN || role === UserRole.SUPER_ADMIN } /** * Check if user is super admin */ export function isSuperAdmin(role: UserRole): boolean { return role === UserRole.SUPER_ADMIN } /** * Get current artist session * Returns the artist record and user data if the logged-in user is an artist */ export async function getArtistSession() { const session = await getServerSession() if (!session?.user) { return null } // Check if user has ARTIST role const userRole = session.user.role if (userRole !== UserRole.ARTIST && !isAdmin(userRole)) { return null } // Import db function dynamically to avoid circular dependencies const { getArtistByUserId } = await import('@/lib/db') const artist = await getArtistByUserId(session.user.id) if (!artist) { return null } return { artist, user: session.user } } /** * Require artist authentication * Throws error if user is not an artist, otherwise returns artist and user data */ export async function requireArtistAuth() { const artistSession = await getArtistSession() if (!artistSession) { throw new Error("Artist authentication required") } return artistSession } /** * Check if a user can edit a specific artist profile * Returns true if the user is the artist themselves, or has admin privileges */ export async function canEditArtist(userId: string, artistId: string): Promise { const session = await getServerSession() if (!session?.user) { return false } // Admins can edit any artist if (isAdmin(session.user.role)) { return true } // Check if this user owns the artist profile const { getArtistByUserId } = await import('@/lib/db') const artist = await getArtistByUserId(userId) return artist?.id === artistId } // Extend NextAuth types declare module "next-auth" { interface Session { user: { id: string email: string name: string image?: string role: UserRole } } interface User { role: UserRole } } declare module "next-auth/jwt" { interface JWT { userId: string role: UserRole } }