diff --git a/.gitignore b/.gitignore index 0f112df..9eda0e5 100644 --- a/.gitignore +++ b/.gitignore @@ -39,8 +39,8 @@ dist/ downloads/ eggs/ .eggs/ -lib/ -lib64/ +/lib/ +/lib64/ parts/ sdist/ var/ diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..0a4c217 --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,6 @@ +import NextAuth from 'next-auth'; +import { authOptions } from '@/lib/auth'; + +const handler = NextAuth(authOptions); + +export { handler as GET, handler as POST }; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 70fd968..40160ff 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -6,6 +6,7 @@ import { WebVitals } from "@/components/analytics/web-vitals"; import { OrganizationSchema, WebsiteSchema } from "@/components/seo/structured-data"; import { MobileTabBar } from "@/components/layout/mobile-tab-bar"; import { ErrorBoundary } from "@/components/ui/error-boundary"; +import { SessionProvider } from "@/providers/session-provider"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -136,11 +137,13 @@ export default function RootLayout({ style={{ fontFamily: "'Noto Sans SC', 'Geist', -apple-system, BlinkMacSystemFont, sans-serif" }} > - - - {children} - - + + + + {children} + + + diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..1a9b72f --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,106 @@ +import { NextAuthOptions } from 'next-auth'; +import CredentialsProvider from 'next-auth/providers/credentials'; +import EmailProvider from 'next-auth/providers/email'; +import { Resend } from 'resend'; +import { db } from '@/db'; +import { users } from '@/db/schema'; +import { eq } from 'drizzle-orm'; +import bcrypt from 'bcryptjs'; + +const resend = new Resend(process.env.RESEND_API_KEY); + +export const authOptions: NextAuthOptions = { + providers: [ + CredentialsProvider({ + name: '邮箱密码', + credentials: { + email: { label: '邮箱', type: 'email' }, + password: { label: '密码', type: 'password' }, + }, + async authorize(credentials) { + if (!credentials?.email || !credentials?.password) { + return null; + } + + const user = await db + .select() + .from(users) + .where(eq(users.email, credentials.email)) + .limit(1); + + if (user.length === 0) { + return null; + } + + const isValid = await bcrypt.compare( + credentials.password, + user[0].passwordHash || '' + ); + + if (!isValid) { + return null; + } + + return { + id: user[0].id, + email: user[0].email, + name: user[0].name, + role: user[0].role, + }; + }, + }), + EmailProvider({ + server: {}, + from: process.env.EMAIL_FROM || 'noreply@novalon.cn', + sendVerificationRequest: async ({ identifier: email, url }) => { + try { + await resend.emails.send({ + from: process.env.EMAIL_FROM || 'noreply@novalon.cn', + to: email, + subject: '睿新致遠 - 登录验证链接', + html: ` +
+

睿新致遠管理后台登录

+

您好!

+

您收到这封邮件是因为您请求登录睿新致遠管理后台。

+

请点击下方按钮完成登录:

+ + 立即登录 + +

如果您没有请求此链接,请忽略此邮件。

+
+

四川睿新致远科技有限公司

+
+ `, + }); + } catch (error) { + console.error('发送邮件失败:', error); + throw new Error('发送邮件失败'); + } + }, + }), + ], + callbacks: { + async jwt({ token, user }) { + if (user) { + token.id = user.id; + token.role = user.role; + } + return token; + }, + async session({ session, token }) { + if (session.user) { + session.user.id = token.id as string; + session.user.role = token.role as string; + } + return session; + }, + }, + pages: { + signIn: '/admin/login', + error: '/admin/login', + }, + session: { + strategy: 'jwt', + }, +}; diff --git a/src/providers/session-provider.tsx b/src/providers/session-provider.tsx new file mode 100644 index 0000000..11ed121 --- /dev/null +++ b/src/providers/session-provider.tsx @@ -0,0 +1,8 @@ +'use client'; + +import { SessionProvider as NextAuthSessionProvider } from 'next-auth/react'; +import { ReactNode } from 'react'; + +export function SessionProvider({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/src/types/next-auth.d.ts b/src/types/next-auth.d.ts new file mode 100644 index 0000000..7c488ea --- /dev/null +++ b/src/types/next-auth.d.ts @@ -0,0 +1,21 @@ +import { DefaultSession } from 'next-auth'; + +declare module 'next-auth' { + interface Session { + user: { + id: string; + role: string; + } & DefaultSession['user']; + } + + interface User { + role: string; + } +} + +declare module 'next-auth/jwt' { + interface JWT { + id: string; + role: string; + } +}