import type { AuthOptions } from "next-auth" import CredentialsProvider from "next-auth/providers/credentials" import type { User } from "next-auth" import bcrypt from 'bcryptjs' import { db } from '@/lib/db' import crypto from 'crypto' type WeChatTokens = { access_token?: string refresh_token?: string expires_in?: number openid?: string unionid?: string scope?: string [key: string]: unknown } function WeChatProvider(): any { const appId = process.env.WECHAT_APP_ID const appSecret = process.env.WECHAT_APP_SECRET if (!appId || !appSecret) { console.error('❌ 微信登录配置缺失: WECHAT_APP_ID 或 WECHAT_APP_SECRET 未设置') } else { console.log('✅ 微信登录配置已加载:', { appId: appId.substring(0, 8) + '...' }) } return { id: 'wechat', name: 'WeChat', type: 'oauth', version: '2.0', checks: ['pkce', 'state'], authorization: { url: 'https://open.weixin.qq.com/connect/qrconnect', params: { appid: appId, scope: 'snsapi_login', response_type: 'code', // redirect_uri 由 NextAuth 自动注入 }, }, token: { url: 'https://api.weixin.qq.com/sns/oauth2/access_token', params: { appid: appId, secret: appSecret, grant_type: 'authorization_code', }, async request({ params, provider, }: { params: Record provider: any }) { const url = `${provider.token!.url}?${new URLSearchParams( params as Record ).toString()}` const res = await fetch(url, { method: 'GET' }) if (!res.ok) { throw new Error(`WeChat token request failed: ${res.status}`) } const tokens = (await res.json()) as WeChatTokens return { tokens } }, }, userinfo: { // 需要 access_token + openid async request({ tokens }: { tokens: WeChatTokens }) { const accessToken = tokens.access_token as string const openid = (tokens as any).openid as string if (!accessToken || !openid) { throw new Error('Missing access_token or openid from WeChat') } const url = `https://api.weixin.qq.com/sns/userinfo?${new URLSearchParams({ access_token: accessToken, openid, lang: 'zh_CN', }).toString()}` const res = await fetch(url) if (!res.ok) { throw new Error(`WeChat userinfo failed: ${res.status}`) } return res.json() }, }, profile(profile: any) { return { id: profile.unionid || profile.openid, name: profile.nickname || '微信用户', email: profile.email || `${profile.openid}@wechat.flnxi.com`, image: profile.headimgurl, } }, clientId: appId, clientSecret: appSecret, } } export const authOptions: AuthOptions = { providers: [ // 邮箱密码登录 CredentialsProvider({ name: 'credentials', credentials: { email: { label: "邮箱", type: "email", placeholder: "user@example.com" }, password: { label: "密码", type: "password" } }, async authorize(credentials): Promise { try { if (!credentials?.email || !credentials?.password) { console.log('❌ 登录失败: 缺少邮箱或密码') return null } // 从数据库查询用户 const user = await db.findUserByEmail(credentials.email) if (!user) { console.log('❌ 登录失败: 用户不存在 -', credentials.email) return null } // 使用 bcrypt 验证密码 const isValidPassword = await bcrypt.compare( credentials.password, user.password ) if (!isValidPassword) { console.log('❌ 登录失败: 密码错误 -', credentials.email) return null } console.log('✅ 登录成功:', { id: user.id, name: user.name, email: user.email, }) // 返回用户信息(不包含密码) return { id: user.id.toString(), name: user.name, email: user.email, } } catch (error) { console.error('登录错误:', error) return null } } }), // 微信扫码登录 WeChatProvider(), ], // 在 signIn 阶段将微信用户写入数据库 callbacks: { async signIn({ user, account, profile }) { if (account?.provider === 'wechat') { const unionid = (profile as any)?.unionid || (profile as any)?.id const openid = (profile as any)?.openid const name = (profile as any)?.nickname || user.name || '微信用户' const email = (profile as any)?.email || user.email || `${openid || unionid || crypto.randomUUID()}@wechat.flnxi.com` const randomPassword = crypto.randomBytes(16).toString('hex') const hashedPassword = await bcrypt.hash(randomPassword, 10) const dbUser = await db.upsertWechatUser({ name, email, unionid: unionid ?? null, openid: openid ?? null, hashedPassword, }) // 将数据库中的 id/email/name 赋回 user,后续 jwt/session 使用 user.id = dbUser.id.toString() user.email = dbUser.email user.name = dbUser.name } return true }, // 自定义页面 pages: { signIn: '/auth/signin', }, async jwt({ token, user }) { if (user) { token.id = user.id token.email = user.email token.name = user.name } return token }, async session({ session, token }) { if (session.user) { session.user.id = token.id as string session.user.email = token.email as string session.user.name = token.name as string } return session }, }, // 会话策略 session: { strategy: "jwt", maxAge: 30 * 24 * 60 * 60, // 30 天 }, // 密钥 secret: process.env.NEXTAUTH_SECRET, // 调试模式(生产环境设为false) debug: process.env.NODE_ENV === 'development', }