Commit d07b4bc6 authored by root's avatar root
Browse files

Final

parent 06f3cdb3
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
"lint": "eslint" "lint": "eslint"
}, },
"dependencies": { "dependencies": {
"@auth/prisma-adapter": "^2.10.0",
"@heroicons/react": "^2.2.0", "@heroicons/react": "^2.2.0",
"@next-auth/prisma-adapter": "^1.0.7", "@next-auth/prisma-adapter": "^1.0.7",
"@prisma/client": "^6.14.0", "@prisma/client": "^6.14.0",
...@@ -23,6 +24,7 @@ ...@@ -23,6 +24,7 @@
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/bcrypt": "^6.0.0",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
......
...@@ -8,6 +8,9 @@ importers: ...@@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@auth/prisma-adapter':
specifier: ^2.10.0
version: 2.10.0(@prisma/client@6.14.0(prisma@6.14.0(typescript@5.9.2))(typescript@5.9.2))
'@heroicons/react': '@heroicons/react':
specifier: ^2.2.0 specifier: ^2.2.0
version: 2.2.0(react@19.1.0) version: 2.2.0(react@19.1.0)
...@@ -45,6 +48,9 @@ importers: ...@@ -45,6 +48,9 @@ importers:
'@tailwindcss/postcss': '@tailwindcss/postcss':
specifier: ^4 specifier: ^4
version: 4.1.12 version: 4.1.12
'@types/bcrypt':
specifier: ^6.0.0
version: 6.0.0
'@types/node': '@types/node':
specifier: ^20 specifier: ^20
version: 20.19.11 version: 20.19.11
...@@ -73,6 +79,25 @@ packages: ...@@ -73,6 +79,25 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'} engines: {node: '>=10'}
'@auth/core@0.40.0':
resolution: {integrity: sha512-n53uJE0RH5SqZ7N1xZoMKekbHfQgjd0sAEyUbE+IYJnmuQkbvuZnXItCU7d+i7Fj8VGOgqvNO7Mw4YfBTlZeQw==}
peerDependencies:
'@simplewebauthn/browser': ^9.0.1
'@simplewebauthn/server': ^9.0.2
nodemailer: ^6.8.0
peerDependenciesMeta:
'@simplewebauthn/browser':
optional: true
'@simplewebauthn/server':
optional: true
nodemailer:
optional: true
'@auth/prisma-adapter@2.10.0':
resolution: {integrity: sha512-EliOQoTjGK87jWWqnJvlQjbR4PjQZQqtwRwPAe108WwT9ubuuJJIrL68aNnQr4hFESz6P7SEX2bZy+y2yL37Gw==}
peerDependencies:
'@prisma/client': '>=2.26.0 || >=3 || >=4 || >=5 || >=6'
'@babel/runtime@7.28.3': '@babel/runtime@7.28.3':
resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==} resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
...@@ -506,6 +531,9 @@ packages: ...@@ -506,6 +531,9 @@ packages:
'@tybys/wasm-util@0.10.0': '@tybys/wasm-util@0.10.0':
resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==}
'@types/bcrypt@6.0.0':
resolution: {integrity: sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==}
'@types/estree@1.0.8': '@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
...@@ -1375,6 +1403,9 @@ packages: ...@@ -1375,6 +1403,9 @@ packages:
jose@4.15.9: jose@4.15.9:
resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==}
jose@6.1.0:
resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==}
js-tokens@4.0.0: js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
...@@ -1597,6 +1628,9 @@ packages: ...@@ -1597,6 +1628,9 @@ packages:
engines: {node: ^14.16.0 || >=16.10.0} engines: {node: ^14.16.0 || >=16.10.0}
hasBin: true hasBin: true
oauth4webapi@3.8.1:
resolution: {integrity: sha512-olkZDELNycOWQf9LrsELFq8n05LwJgV8UkrS0cburk6FOwf8GvLam+YB+Uj5Qvryee+vwWOfQVeI5Vm0MVg7SA==}
oauth@0.9.15: oauth@0.9.15:
resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==} resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==}
...@@ -1714,6 +1748,14 @@ packages: ...@@ -1714,6 +1748,14 @@ packages:
peerDependencies: peerDependencies:
preact: '>=10' preact: '>=10'
preact-render-to-string@6.5.11:
resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==}
peerDependencies:
preact: '>=10'
preact@10.24.3:
resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==}
preact@10.27.1: preact@10.27.1:
resolution: {integrity: sha512-V79raXEWch/rbqoNc7nT9E4ep7lu+mI3+sBmfRD4i1M73R3WLYcCtdI0ibxGVf4eQL8ZIz2nFacqEC+rmnOORQ==} resolution: {integrity: sha512-V79raXEWch/rbqoNc7nT9E4ep7lu+mI3+sBmfRD4i1M73R3WLYcCtdI0ibxGVf4eQL8ZIz2nFacqEC+rmnOORQ==}
...@@ -2053,6 +2095,23 @@ snapshots: ...@@ -2053,6 +2095,23 @@ snapshots:
'@alloc/quick-lru@5.2.0': {} '@alloc/quick-lru@5.2.0': {}
'@auth/core@0.40.0':
dependencies:
'@panva/hkdf': 1.2.1
jose: 6.1.0
oauth4webapi: 3.8.1
preact: 10.24.3
preact-render-to-string: 6.5.11(preact@10.24.3)
'@auth/prisma-adapter@2.10.0(@prisma/client@6.14.0(prisma@6.14.0(typescript@5.9.2))(typescript@5.9.2))':
dependencies:
'@auth/core': 0.40.0
'@prisma/client': 6.14.0(prisma@6.14.0(typescript@5.9.2))(typescript@5.9.2)
transitivePeerDependencies:
- '@simplewebauthn/browser'
- '@simplewebauthn/server'
- nodemailer
'@babel/runtime@7.28.3': {} '@babel/runtime@7.28.3': {}
'@emnapi/core@1.4.5': '@emnapi/core@1.4.5':
...@@ -2421,6 +2480,10 @@ snapshots: ...@@ -2421,6 +2480,10 @@ snapshots:
tslib: 2.8.1 tslib: 2.8.1
optional: true optional: true
'@types/bcrypt@6.0.0':
dependencies:
'@types/node': 20.19.11
'@types/estree@1.0.8': {} '@types/estree@1.0.8': {}
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
...@@ -3477,6 +3540,8 @@ snapshots: ...@@ -3477,6 +3540,8 @@ snapshots:
jose@4.15.9: {} jose@4.15.9: {}
jose@6.1.0: {}
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
js-yaml@4.1.0: js-yaml@4.1.0:
...@@ -3665,6 +3730,8 @@ snapshots: ...@@ -3665,6 +3730,8 @@ snapshots:
pkg-types: 2.3.0 pkg-types: 2.3.0
tinyexec: 1.0.1 tinyexec: 1.0.1
oauth4webapi@3.8.1: {}
oauth@0.9.15: {} oauth@0.9.15: {}
object-assign@4.1.1: {} object-assign@4.1.1: {}
...@@ -3790,6 +3857,12 @@ snapshots: ...@@ -3790,6 +3857,12 @@ snapshots:
preact: 10.27.1 preact: 10.27.1
pretty-format: 3.8.0 pretty-format: 3.8.0
preact-render-to-string@6.5.11(preact@10.24.3):
dependencies:
preact: 10.24.3
preact@10.24.3: {}
preact@10.27.1: {} preact@10.27.1: {}
prelude-ls@1.2.1: {} prelude-ls@1.2.1: {}
......
/*
Warnings:
- Added the required column `password` to the `User` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "public"."User" ADD COLUMN "password" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "public"."User" ADD COLUMN "email" TEXT;
...@@ -10,6 +10,8 @@ datasource db { ...@@ -10,6 +10,8 @@ datasource db {
model User { model User {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
username String @unique username String @unique
password String
email String? // 添加这行
createdAt DateTime @default(now()) createdAt DateTime @default(now())
messages Message[] messages Message[]
} }
......
"use client";
import { SessionProvider } from "next-auth/react";
export default function ClientLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<SessionProvider>
{children}
</SessionProvider>
);
}
\ No newline at end of file
import { authOptions } from "@/lib/auth";
import NextAuth from "next-auth";
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
\ No newline at end of file
...@@ -44,7 +44,12 @@ export async function POST(request: Request) { ...@@ -44,7 +44,12 @@ export async function POST(request: Request) {
// 查找或创建用户 // 查找或创建用户
let user = await prisma.user.findUnique({ where: { username: sender } }); let user = await prisma.user.findUnique({ where: { username: sender } });
if (!user) { if (!user) {
user = await prisma.user.create({ data: { username: sender } }); user = await prisma.user.create({
data: {
username: sender,
password: "default_password" // 添加默认密码
}
});
} }
// 创建消息 // 创建消息
......
import prisma from "@/lib/prisma";
import bcrypt from "bcrypt";
export async function POST(request: Request) {
try {
const { username, password } = await request.json();
if (!username || !password) {
return new Response("用户名和密码不能为空", { status: 400 });
}
const existingUser = await prisma.user.findUnique({
where: { username }
});
if (existingUser) {
return new Response("用户名已存在", { status: 400 });
}
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = await prisma.user.create({
data: {
username,
password: hashedPassword,
email: `${username}@example.com`, // 添加 email 字段
}
});
return new Response(JSON.stringify({
id: newUser.id,
username: newUser.username
}), { status: 201 });
} catch (error) {
console.error("注册失败:", error);
return new Response("服务器错误", { status: 500 });
}
}
\ No newline at end of file
"use client"; "use client";
import { useRouter } from "next/navigation";
import { useState, useEffect } from "react"; import { signOut } from "next-auth/react";
import { useState} from "react";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
import { PlusCircleIcon } from "@heroicons/react/24/outline"; import { PlusCircleIcon } from "@heroicons/react/24/outline";
import { useSession } from "next-auth/react";
type Room = { type Room = {
id: string; id: string;
...@@ -20,19 +22,13 @@ type Message = { ...@@ -20,19 +22,13 @@ type Message = {
const fetcher = (url: string) => fetch(url).then((res) => res.json()); const fetcher = (url: string) => fetch(url).then((res) => res.json());
export default function ChatRoom() { export default function ChatRoom() {
const { data: session } = useSession();
const [activeRoom, setActiveRoom] = useState("1"); const [activeRoom, setActiveRoom] = useState("1");
const [newMessage, setNewMessage] = useState(""); const [newMessage, setNewMessage] = useState("");
const [username, setUsername] = useState("Guest");
const [isCreatingRoom, setIsCreatingRoom] = useState(false); const [isCreatingRoom, setIsCreatingRoom] = useState(false);
const [newRoomName, setNewRoomName] = useState(""); const [newRoomName, setNewRoomName] = useState("");
const router = useRouter();
// 从localStorage获取用户名
useEffect(() => {
const storedUsername = localStorage.getItem("chat-username");
if (storedUsername) {
setUsername(storedUsername);
}
}, []);
// 使用SWR获取房间列表 // 使用SWR获取房间列表
const { data: rooms, error: roomsError, mutate: mutateRooms } = useSWR<Room[]>( const { data: rooms, error: roomsError, mutate: mutateRooms } = useSWR<Room[]>(
...@@ -48,16 +44,15 @@ export default function ChatRoom() { ...@@ -48,16 +44,15 @@ export default function ChatRoom() {
); );
const handleSendMessage = async () => { const handleSendMessage = async () => {
if (newMessage.trim()) { if (newMessage.trim() && session?.user?.name) {
try { try {
// 发送消息到后端
await fetch("/api/messages", { await fetch("/api/messages", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({
content: newMessage.trim(), content: newMessage.trim(),
roomId: activeRoom, roomId: activeRoom,
sender: username, sender: session.user.name, // 使用登录用户名
}), }),
}); });
...@@ -111,6 +106,15 @@ export default function ChatRoom() { ...@@ -111,6 +106,15 @@ export default function ChatRoom() {
> >
<PlusCircleIcon className="h-6 w-6" /> <PlusCircleIcon className="h-6 w-6" />
</button> </button>
<button
onClick={() => signOut({ callbackUrl: '/login' })}
className="p-1 text-red-500 hover:text-red-700"
title="退出登录"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
</button>
</div> </div>
{/* 创建房间表单 */} {/* 创建房间表单 */}
......
...@@ -22,5 +22,7 @@ ...@@ -22,5 +22,7 @@
body { body {
background: var(--background); background: var(--background);
color: var(--foreground); color: var(--foreground);
font-family: Arial, Helvetica, sans-serif; font-family: var(--font-sans), Arial, Helvetica, sans-serif;
} margin: 0;
padding: 0;
}
\ No newline at end of file
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google"; import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css"; import "./globals.css";
import { SessionProvider } from "next-auth/react";
import ClientLayout from "./ClientLayout";
const geistSans = Geist({ const geistSans = Geist({
variable: "--font-geist-sans", variable: "--font-geist-sans",
...@@ -13,8 +16,8 @@ const geistMono = Geist_Mono({ ...@@ -13,8 +16,8 @@ const geistMono = Geist_Mono({
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: "聊天室",
description: "Generated by create next app", description: "一个简单的聊天室应用",
}; };
export default function RootLayout({ export default function RootLayout({
...@@ -23,12 +26,15 @@ export default function RootLayout({ ...@@ -23,12 +26,15 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html lang="en"> <html lang="zh-CN">
<body <body
className={`${geistSans.variable} ${geistMono.variable} antialiased`} className={`${geistSans.variable} ${geistMono.variable} antialiased`}
> >
{children} {/* 将 SessionProvider 移回客户端组件 */}
<ClientLayout>
{children}
</ClientLayout>
</body> </body>
</html> </html>
); );
} }
\ No newline at end of file
"use client";
import { signIn } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useState } from "react";
export default function LoginPage() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const router = useRouter();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const result = await signIn("credentials", {
redirect: false,
username,
password
});
if (result?.error) {
setError("用户名或密码错误");
} else {
router.push("/chat");
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<form onSubmit={handleSubmit} className="bg-white p-8 rounded shadow-md w-96">
<h1 className="text-2xl font-bold mb-6 text-center">登录聊天室</h1>
{error && (
<div className="mb-4 p-2 bg-red-100 text-red-700 rounded">
{error}
</div>
)}
<div className="mb-4">
<label className="block text-gray-700 mb-2" htmlFor="username">
用户名
</label>
<input
id="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-6">
<label className="block text-gray-700 mb-2" htmlFor="password">
密码
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600"
>
登录
</button>
<p className="mt-4 text-center">
还没有账号?{" "}
<a href="/register" className="text-blue-500 hover:underline">
注册
</a>
</p>
</form>
</div>
);
}
\ No newline at end of file
"use client"; "use client";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useState } from "react";
export default function SetName() { export default function Home() {
const [name, setName] = useState(""); const { data: session, status } = useSession();
const router = useRouter(); const router = useRouter();
const handleSubmit = (e: React.FormEvent) => { if (status === "loading") {
e.preventDefault(); return <div>加载中...</div>;
if (name.trim()) { }
localStorage.setItem("chat-username", name.trim());
router.push("/chat");
}
};
return ( if (session) {
<div className="min-h-screen flex items-center justify-center bg-gray-100"> router.push("/chat");
<form onSubmit={handleSubmit} className="bg-white p-8 rounded shadow-md"> return null;
<h1 className="text-2xl font-bold mb-4">Set Your Nickname</h1> }
<input
type="text" router.push("/login");
value={name} return null;
onChange={(e) => setName(e.target.value)}
placeholder="Enter your nickname"
className="w-full px-4 py-2 border border-gray-300 rounded mb-4"
required
/>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600"
>
Submit
</button>
</form>
</div>
);
} }
\ No newline at end of file
"use client";
import { useRouter } from "next/navigation";
import { useState } from "react";
export default function RegisterPage() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const router = useRouter();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const response = await fetch("/api/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password })
});
if (response.ok) {
router.push("/login");
} else {
const data = await response.json();
setError(data.error || "注册失败");
}
} catch (err) {
setError("网络错误,请重试");
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<form onSubmit={handleSubmit} className="bg-white p-8 rounded shadow-md w-96">
<h1 className="text-2xl font-bold mb-6 text-center">注册新账号</h1>
{error && (
<div className="mb-4 p-2 bg-red-100 text-red-700 rounded">
{error}
</div>
)}
<div className="mb-4">
<label className="block text-gray-700 mb-2" htmlFor="username">
用户名
</label>
<input
id="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-6">
<label className="block text-gray-700 mb-2" htmlFor="password">
密码
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600"
>
注册
</button>
<p className="mt-4 text-center">
已有账号?{" "}
<a href="/login" className="text-blue-500 hover:underline">
登录
</a>
</p>
</form>
</div>
);
}
\ No newline at end of file
import NextAuth, { Session, User, SessionStrategy } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { PrismaAdapter } from "@auth/prisma-adapter";
import prisma from "@/lib/prisma";
import bcrypt from "bcrypt";
import { JWT } from "next-auth/jwt";
declare module "next-auth" {
interface Session {
user: {
id: string;
name?: string | null;
email?: string | null;
image?: string | null;
};
}
}
export const authOptions = {
adapter: PrismaAdapter(prisma),
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
if (!credentials?.username || !credentials?.password) {
return null;
}
const user = await prisma.user.findUnique({
where: { username: credentials.username },
select: {
id: true,
username: true,
password: true
}
});
if (!user) return null;
const passwordMatch = await bcrypt.compare(
credentials.password,
user.password
);
if (!passwordMatch) return null;
return {
id: user.id.toString(), // 确保转换为字符串
name: user.username,
email: `${user.username}@example.com`, // 提供必需的 email 字段
};
},
})
],
session: {
strategy: "jwt" as SessionStrategy,
},
pages: {
signIn: "/login",
},
callbacks: {
async jwt({ token, user }: { token: JWT; user?: User }) {
if (user) {
token.id = user.id;
token.name = user.name;
}
return token;
},
async session({ session, token }: { session: Session; token: JWT }) {
if (session.user) {
session.user.id = token.id as string; // 确保使用字符串
session.user.name = token.name as string; // 添加 name 字段
}
return session;
}
}
};
export default NextAuth(authOptions);
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment