import { PrismaAdapter } from "@next-auth/prisma-adapter"; import CredentialsProvider from "next-auth/providers/credentials"; import { prisma } from "./prisma"; import HuggingFaceProvider from "./providers/huggingface"; export const authOptions = { adapter: PrismaAdapter(prisma), session: { strategy: "jwt" }, providers: [ HuggingFaceProvider({ clientId: process.env.HF_CLIENT_ID, clientSecret: process.env.HF_CLIENT_SECRET, }), CredentialsProvider({ id: "credentials", name: "API Key / HF Token", credentials: { apiKey: { label: "API Key or HF Token", type: "password" }, }, async authorize(credentials) { if (!credentials?.apiKey) throw new Error("API Key is required"); const apiKey = credentials.apiKey.trim(); if (apiKey.length < 5) throw new Error("Invalid API key format"); let hfUser = null; if (apiKey.startsWith("hf_")) { try { const r = await fetch("https://huggingface.co/api/whoami-v2", { headers: { Authorization: `Bearer ${apiKey}` }, }); if (r.ok) { const j = await r.json(); hfUser = { name: j.name || j.fullname || "HF User", username: j.name, email: j.email || `${j.name}@users.huggingface.co`, }; } } catch (e) { console.warn("[HF_WHOAMI_FAILED]", e.message); } } const dummyEmail = hfUser?.email || `apikey_${apiKey.slice(-8)}@local.invalid`; let dbUser = await prisma.user.findFirst({ where: { OR: [{ customApiKey: apiKey }, { email: dummyEmail }] }, }); if (!dbUser) { dbUser = await prisma.user.create({ data: { name: hfUser?.name || "API Key User", email: dummyEmail, customApiKey: apiKey, hfUsername: hfUser?.username || null, credits: 0, }, }); } else if (!dbUser.customApiKey) { dbUser = await prisma.user.update({ where: { id: dbUser.id }, data: { customApiKey: apiKey }, }); } return { id: dbUser.id, name: dbUser.name, email: dbUser.email, image: dbUser.image || null, credits: dbUser.credits, customApiKey: dbUser.customApiKey || apiKey, isApiKeyUser: true, }; }, }), ], callbacks: { async jwt({ token, user, account, trigger, session }) { if (account?.access_token) { token.hfAccessToken = account.access_token; token.hfRefreshToken = account.refresh_token; } if (user) { token.id = user.id; token.credits = user.credits; token.customApiKey = user.customApiKey; token.isApiKeyUser = user.isApiKeyUser || false; if (user.hfUsername) token.hfUsername = user.hfUsername; } if (trigger === "update" && session) { if (session.customApiKey !== undefined) token.customApiKey = session.customApiKey; if (session.credits !== undefined) token.credits = session.credits; } const userId = token.id || token.sub; if (userId) { token.id = userId; try { const dbUser = await prisma.user.findUnique({ where: { id: userId }, select: { credits: true, customApiKey: true, hfUsername: true, accounts: { where: { provider: "huggingface" }, select: { access_token: true }, take: 1, }, }, }); if (dbUser) { token.credits = dbUser.credits; token.customApiKey = dbUser.customApiKey; if (dbUser.hfUsername) token.hfUsername = dbUser.hfUsername; if (dbUser.accounts?.[0]?.access_token) { token.hfAccessToken = dbUser.accounts[0].access_token; } } } catch (err) {} } return token; }, async session({ session, token }) { if (session.user && token) { session.user.id = token.id || token.sub; session.user.credits = token.credits; session.user.customApiKey = token.customApiKey; session.user.isApiKeyUser = Boolean(token.customApiKey); session.user.hfUsername = token.hfUsername || null; session.user.hfAccessToken = token.hfAccessToken || null; } return session; }, }, secret: process.env.NEXTAUTH_SECRET, pages: { signIn: "/login" }, };