auth.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import NextAuth from "next-auth"
  2. import GoogleProvider from "next-auth/providers/google"
  3. import GitHubProvider from "next-auth/providers/github"
  4. import CredentialsProvider from "next-auth/providers/credentials"
  5. import { db } from "./db"
  6. import { users, userActivities } from "./schema"
  7. import bcrypt from "bcryptjs"
  8. import { eq } from "drizzle-orm"
  9. import { CREDIT_CONFIG } from '@/lib/constants';
  10. export const { auth, handlers, signIn, signOut } = NextAuth({
  11. providers: [
  12. GoogleProvider({
  13. clientId: process.env.GOOGLE_CLIENT_ID!,
  14. clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  15. }),
  16. GitHubProvider({
  17. clientId: process.env.GITHUB_CLIENT_ID!,
  18. clientSecret: process.env.GITHUB_CLIENT_SECRET!,
  19. }),
  20. CredentialsProvider({
  21. name: "credentials",
  22. credentials: {
  23. email: { label: "Email", type: "email" },
  24. password: { label: "Password", type: "password" }
  25. },
  26. async authorize(credentials) {
  27. if (!credentials?.email || !credentials?.password) {
  28. return null
  29. }
  30. try {
  31. const user = await db.query.users.findFirst({
  32. where: eq(users.email, credentials.email as string),
  33. })
  34. if (!user || !user.password) {
  35. return null
  36. }
  37. const passwordsMatch = await bcrypt.compare(
  38. credentials.password as string,
  39. user.password
  40. )
  41. if (!passwordsMatch) {
  42. return null
  43. }
  44. return {
  45. id: user.id,
  46. email: user.email,
  47. name: user.username,
  48. image: user.image,
  49. }
  50. } catch (error) {
  51. console.error('Auth error:', error)
  52. return null
  53. }
  54. },
  55. }),
  56. ],
  57. session: {
  58. strategy: "jwt",
  59. maxAge: 7 * 24 * 60 * 60, // 7 days
  60. },
  61. cookies: {
  62. sessionToken: {
  63. name: `next-auth.session-token`,
  64. options: {
  65. httpOnly: true,
  66. sameSite: 'lax',
  67. path: '/',
  68. secure: process.env.NODE_ENV === 'production',
  69. },
  70. },
  71. },
  72. callbacks: {
  73. async jwt({ token, user, account }) {
  74. if (user) {
  75. token.id = user.id
  76. token.email = user.email
  77. token.name = user.name
  78. token.image = user.image
  79. }
  80. return token
  81. },
  82. async session({ session, token }) {
  83. if (token) {
  84. session.user.id = token.id as string
  85. session.user.email = token.email as string
  86. session.user.name = token.name as string
  87. session.user.image = token.image as string
  88. }
  89. return session
  90. },
  91. async signIn({ user, account, profile }) {
  92. try {
  93. // 如果是OAuth登录,确保用户邮箱已验证
  94. if (account?.provider === "google" || account?.provider === "github") {
  95. if (user.email) {
  96. // 检查用户是否已存在
  97. const existingUser = await db.query.users.findFirst({
  98. where: eq(users.email, user.email),
  99. })
  100. if (existingUser) {
  101. // 更新用户的邮箱验证状态和信息
  102. await db.update(users)
  103. .set({
  104. isEmailVerified: true,
  105. emailVerified: new Date(),
  106. username: user.name || existingUser.username,
  107. image: user.image || existingUser.image,
  108. })
  109. .where(eq(users.email, user.email))
  110. // 更新user对象的id
  111. user.id = existingUser.id
  112. } else {
  113. // 创建新用户,注册赠送积分
  114. const [newUser] = await db.insert(users).values({
  115. email: user.email,
  116. username: user.name,
  117. image: user.image,
  118. isEmailVerified: true,
  119. emailVerified: new Date(),
  120. credits: CREDIT_CONFIG.REGISTRATION_BONUS,
  121. }).returning()
  122. // 记录注册赠送积分的活动
  123. try {
  124. await db.insert(userActivities).values({
  125. userId: newUser.id,
  126. type: 'registration_bonus',
  127. description: 'credit_description.registration_bonus',
  128. creditAmount: CREDIT_CONFIG.REGISTRATION_BONUS,
  129. metadata: JSON.stringify({
  130. source: 'registration_bonus',
  131. provider: account?.provider,
  132. email: newUser.email,
  133. type: 'registration_bonus',
  134. })
  135. });
  136. } catch (error) {
  137. console.error('记录OAuth注册积分活动失败:', error);
  138. }
  139. // 更新user对象的id
  140. user.id = newUser.id
  141. }
  142. }
  143. }
  144. return true
  145. } catch (error) {
  146. console.error('SignIn callback error:', error)
  147. return false
  148. }
  149. },
  150. },
  151. pages: {
  152. signIn: "/auth/login",
  153. },
  154. debug: process.env.NODE_ENV === 'development',
  155. })
  156. // 导出类型
  157. export type { Session } from "next-auth"