| 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" }, |
| }; |
|
|