diff --git a/.env b/.env index 6b10c27..20ad4e3 100644 --- a/.env +++ b/.env @@ -8,3 +8,5 @@ POSTGRE_DATABASE="postgres" POSTGRE_USERNAME="postgres" # Postgre 密码 POSTGRE_PASSWORD="postgres" +# AUTH_SECRET npx auth secret +AUTH_SECRET="T7tQXKqqKjZrJhN62Z05AHY8qKGQKaG/4iqv0tKmEvw=" diff --git a/.env.production b/.env.production index 5da7cf3..898a80d 100644 --- a/.env.production +++ b/.env.production @@ -8,3 +8,5 @@ POSTGRE_DATABASE="postgres" POSTGRE_USERNAME="postgres" # Postgre 密码 POSTGRE_PASSWORD="postgres" +# AUTH_SECRET npx auth secret +AUTH_SECRET="T7tQXKqqKjZrJhN62Z05AHY8qKGQKaG/4iqv0tKmEvw=" diff --git a/app/(admin)/admin/page.tsx b/app/(admin)/admin/page.tsx index 6c402d4..7804cd9 100644 --- a/app/(admin)/admin/page.tsx +++ b/app/(admin)/admin/page.tsx @@ -1,7 +1,11 @@ -export default function Admin() { +import { auth } from '~/utils/lib/auth' + +export default async function Admin() { + const session = await auth() + return ( <> - 控制台 + 控制台 {session?.user} ) } \ No newline at end of file diff --git a/app/(admin)/layout.tsx b/app/(admin)/layout.tsx index eb1bbac..5732d9b 100644 --- a/app/(admin)/layout.tsx +++ b/app/(admin)/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next' import '~/style/globals.css' import { Providers } from '~/app/providers/providers' -import Header from '~/components/Header' +import DashHeader from '~/components/DashHeader' import { UserStoreProvider } from '~/app/providers/userStoreProvider' import { Toaster } from 'sonner' @@ -21,13 +21,20 @@ export default function RootLayout({ return ( - - - -
- {children} - - + + + + +
+ +
+ {children} +
+
+
+
); diff --git a/app/(none)/login/page.tsx b/app/(none)/login/page.tsx index 084a3f8..88645d3 100644 --- a/app/(none)/login/page.tsx +++ b/app/(none)/login/page.tsx @@ -1,16 +1,8 @@ -'use client' - import Image from "next/image" -import Link from "next/link" -import { Input } from '@nextui-org/input' -import { Button } from '@nextui-org/react' import fufu from '~/public/112962239_p0.jpg' -import { useRouter } from 'next/navigation' -import { toast } from 'sonner' +import { UserFrom } from '~/components/UserFrom' export default function Login() { - const router = useRouter() - return (
@@ -25,43 +17,7 @@ export default function Login() { />
-
-
-

登录

-

- 输入您的用户名和密码 -

-
-
-
-
用户名
- -
-
-
-
密码
- - 忘记密码? - -
- -
- - -
-
+
) diff --git a/app/actions.ts b/app/actions.ts new file mode 100644 index 0000000..e16ed69 --- /dev/null +++ b/app/actions.ts @@ -0,0 +1,27 @@ +'use server' + +import { AuthError } from 'next-auth' +import { signIn } from '~/utils/lib/auth' + +export async function authenticate( + email:string, password: string +) { + try { + await signIn('credentials', { + email: email, + password: password, + redirect: true, + redirectTo: '/admin' + }); + } catch (error) { + if (error instanceof AuthError) { + switch (error.type) { + case 'CredentialsSignin': + return 'Invalid credentials.'; + default: + return 'Something went wrong.'; + } + } + throw error; + } +} \ No newline at end of file diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..523f820 --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1 @@ +export { GET, POST } from '~/utils/lib/auth' \ No newline at end of file diff --git a/app/api/login/route.ts b/app/api/login/route.ts deleted file mode 100644 index b49f384..0000000 --- a/app/api/login/route.ts +++ /dev/null @@ -1,8 +0,0 @@ -import 'server-only' -import { NextRequest } from 'next/server' - -export async function POST(request: NextRequest) { - const res = await request.json() - console.log(res) - return Response.json(res) -} \ No newline at end of file diff --git a/components/DashDropMenu.tsx b/components/DashDropMenu.tsx new file mode 100644 index 0000000..f432536 --- /dev/null +++ b/components/DashDropMenu.tsx @@ -0,0 +1,36 @@ +'use client' + +import React from 'react' +import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Button } from '@nextui-org/react' +import { useRouter } from 'next/navigation' +import { getSession } from '~/utils/lib/session' + +export const DashDropMenu = async () => { + const router = useRouter() + const session = await getSession() + + return ( + <> + { + session ? + + + + 菜单 + + + + router.push('/admin')}>后台 + { + router.push('/') + }}>退出登录 + + + : +
router.push('/login')}> + 登录 +
+ } + + ) +} \ No newline at end of file diff --git a/components/DashHeader.tsx b/components/DashHeader.tsx new file mode 100644 index 0000000..6718c75 --- /dev/null +++ b/components/DashHeader.tsx @@ -0,0 +1,33 @@ +import { Link, Navbar, NavbarBrand, NavbarContent, NavbarItem } from '@nextui-org/react' +import Logo from '~/components/Logo' +import {GithubIcon} from '~/style/icons/GitHub' +import DashNavbar from '~/components/DashNavbar' +import React from 'react' + +export default function DashHeader() { + return ( + + + + + + 6 + + + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/components/DashNavbar.tsx b/components/DashNavbar.tsx new file mode 100644 index 0000000..326505c --- /dev/null +++ b/components/DashNavbar.tsx @@ -0,0 +1,25 @@ +'use client' + +import { useBreakpoints } from '~/utils/useBreakpoints' +import { ThemeSwitch } from '~/components/DarkToggle' +import DashVaulDrawer from '~/components/DashVaulDrawer' +import { useHydrated } from '~/composables/react' +import { DashDropMenu } from '~/components/DashDropMenu' +import React from "react"; + +export default function DashNavbar() { + const hydrated = useHydrated() + const { smAndLarger } = useBreakpoints() + + return ( + <> + + {/*{ hydrated ? smAndLarger ? : : null }*/} + {/*{ hydrated && smAndLarger && }*/} + + ) +} \ No newline at end of file diff --git a/components/DashVaulDrawer.tsx b/components/DashVaulDrawer.tsx new file mode 100644 index 0000000..6272d2d --- /dev/null +++ b/components/DashVaulDrawer.tsx @@ -0,0 +1,65 @@ +'use client' + +import { Drawer } from 'vaul' +import { useRouter } from 'next/navigation' +import React from 'react'; +import { Listbox, ListboxItem } from '@nextui-org/react' + +export default function DashVaulDrawer() { + const router = useRouter() + + return ( + + 菜单 + + + +
+
+
+
+ + 菜单1 + 菜单2 + 菜单3 + + 菜单4 + + +
+
+
+ + + + + ); +} \ No newline at end of file diff --git a/components/DropMenu.tsx b/components/DropMenu.tsx index 8532a11..ecab75e 100644 --- a/components/DropMenu.tsx +++ b/components/DropMenu.tsx @@ -20,9 +20,9 @@ export const DropMenu = () => { token ? - + router.push('/admin')}>后台 @@ -30,9 +30,9 @@ export const DropMenu = () => { : - +
} ) diff --git a/components/DynamicNavbar.tsx b/components/DynamicNavbar.tsx index 897e253..4a80303 100644 --- a/components/DynamicNavbar.tsx +++ b/components/DynamicNavbar.tsx @@ -13,7 +13,7 @@ export default function DynamicNavbar() { return ( <> { hydrated ? smAndLarger ? : : null } - + { hydrated && smAndLarger && } ) } \ No newline at end of file diff --git a/components/UserFrom.tsx b/components/UserFrom.tsx new file mode 100644 index 0000000..3c3048b --- /dev/null +++ b/components/UserFrom.tsx @@ -0,0 +1,80 @@ +'use client' + +import { useState } from 'react' +import { Input } from '@nextui-org/input' +import Link from 'next/link' +import { Button } from '@nextui-org/react' +import { useRouter } from 'next/navigation' +import { authenticate } from '~/app/actions' +// import { useFormState, useFormStatus } from 'react-dom' + +export const UserFrom = () => { + const router = useRouter() + + // const [errorMessage, dispatch] = useFormState(authenticate, undefined) + // const { pending } = useFormStatus() + + const [isLoading, setIsLoading] = useState(false) + + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + + return ( +
+
+

登录

+

+ 输入您的邮箱和密码 +

+
+
+
+
邮箱
+ setEmail(value)} + required + /> +
+
+
+
密码
+ + 忘记密码? + +
+ setPassword(value)} + required + /> +
+ + +
+
+ ) +} \ No newline at end of file diff --git a/components/VaulDrawer.tsx b/components/VaulDrawer.tsx index 14a450e..c5a9de2 100644 --- a/components/VaulDrawer.tsx +++ b/components/VaulDrawer.tsx @@ -2,6 +2,8 @@ import { Drawer } from 'vaul' import { useRouter } from 'next/navigation' +import { Listbox, ListboxItem } from '@nextui-org/react' +import React from "react"; export default function VaulDrawer() { const router = useRouter() @@ -14,7 +16,17 @@ export default function VaulDrawer() {
- 内容 +
+
+ + 菜单1 + 菜单2 + 菜单3 + +
+
diff --git a/doc/sql/schema.sql b/doc/sql/schema.sql index e3d2b5c..dc758dd 100644 --- a/doc/sql/schema.sql +++ b/doc/sql/schema.sql @@ -1,3 +1,52 @@ +CREATE TABLE verification_token +( + identifier TEXT NOT NULL, + expires TIMESTAMPTZ NOT NULL, + token TEXT NOT NULL, + + PRIMARY KEY (identifier, token) +); + +CREATE TABLE accounts +( + id SERIAL, + "userId" INTEGER NOT NULL, + type VARCHAR(255) NOT NULL, + provider VARCHAR(255) NOT NULL, + "providerAccountId" VARCHAR(255) NOT NULL, + refresh_token TEXT, + access_token TEXT, + expires_at BIGINT, + id_token TEXT, + scope TEXT, + session_state TEXT, + token_type TEXT, + + PRIMARY KEY (id) +); + +CREATE TABLE sessions +( + id SERIAL, + "userId" INTEGER NOT NULL, + expires TIMESTAMPTZ NOT NULL, + "sessionToken" VARCHAR(255) NOT NULL, + + PRIMARY KEY (id) +); + +CREATE TABLE users +( + id SERIAL, + name VARCHAR(255), + email VARCHAR(255), + password VARCHAR(255), + "emailVerified" TIMESTAMPTZ, + image TEXT, + + PRIMARY KEY (id) +); + -- ---------------------------- -- Table structure for picimpact_image -- ---------------------------- @@ -72,5 +121,3 @@ INSERT INTO "public"."picimpact_config" VALUES (12, 'cdn_url', '', 'CDN 域名 INSERT INTO "public"."picimpact_config" VALUES (5, 'auth_key', 'picimpact', '权限 key,jwt 和 hash 都需要用到它。如果您更改了它,请自行生成新密码,默认值:picimpact', '2023-12-25 16:45:46.12813', NULL); INSERT INTO "public"."picimpact_config" VALUES (4, 'alist_token', '', 'alist 令牌 ', '2023-12-25 16:45:08.661365', NULL); INSERT INTO "public"."picimpact_config" VALUES (3, 'alist_url', '', 'AList 地址,如:https://alist.besscroft.com', '2023-12-25 16:44:55.289006', NULL); -INSERT INTO "public"."picimpact_config" VALUES (1, 'username', 'admin', '系统用户账号,默认值:admin,单次登录有效期 24 小时。', '2023-12-25 16:44:29.653008', NULL); -INSERT INTO "public"."picimpact_config" VALUES (2, 'password', '2a2a3d2b5dcef92937839896bcf07dc62605ebe2ac428f57ea061c734d950d075667654a5e130a9fee5b85512a98eac8138100f32a40953b8678243dbfc97297', '系统用户密码,默认值(666666)在 `.env.local` 文件中可查,如果需要更改密码,可在登录后进入后台自行生成后替换。', '2023-12-25 16:44:43.899401', NULL); diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 0000000..f43c7ed --- /dev/null +++ b/middleware.ts @@ -0,0 +1,7 @@ +import { auth } from '~/utils/lib/auth' + +export default auth; + +export const config = { + matcher: ["/admin/:path*"], +} \ No newline at end of file diff --git a/package.json b/package.json index eccb483..979e4c3 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,13 @@ "lint": "next lint" }, "dependencies": { + "@auth/pg-adapter": "^0.7.2", "@nextui-org/react": "^2.2.10", "framer-motion": "^11.0.24", "next": "^14.1.4", + "next-auth": "5.0.0-beta.16", "next-themes": "^0.3.0", + "pg": "^8.11.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-responsive": "^10.0.0", @@ -20,10 +23,12 @@ "sonner": "^1.4.41", "swr": "^2.2.5", "vaul": "^0.9.0", + "zod": "^3.22.4", "zustand": "^4.5.2" }, "devDependencies": { "@types/node": "^20.12.2", + "@types/pg": "^8.11.4", "@types/react": "^18.2.73", "@types/react-dom": "^18.2.23", "autoprefixer": "^10.4.19", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff7abcb..96f1180 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@auth/pg-adapter': + specifier: ^0.7.2 + version: 0.7.2(pg@8.11.5) '@nextui-org/react': specifier: ^2.2.10 version: 2.2.10(@types/react@18.2.73)(framer-motion@11.0.24)(react-dom@18.2.0)(react@18.2.0)(tailwind-variants@0.2.1)(tailwindcss@3.4.3) @@ -14,9 +17,15 @@ dependencies: next: specifier: ^14.1.4 version: 14.1.4(react-dom@18.2.0)(react@18.2.0) + next-auth: + specifier: 5.0.0-beta.16 + version: 5.0.0-beta.16(next@14.1.4)(react@18.2.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0)(react@18.2.0) + pg: + specifier: ^8.11.5 + version: 8.11.5 react: specifier: ^18.2.0 version: 18.2.0 @@ -38,6 +47,9 @@ dependencies: vaul: specifier: ^0.9.0 version: 0.9.0(@types/react-dom@18.2.23)(@types/react@18.2.73)(react-dom@18.2.0)(react@18.2.0) + zod: + specifier: ^3.22.4 + version: 3.22.4 zustand: specifier: ^4.5.2 version: 4.5.2(@types/react@18.2.73)(react@18.2.0) @@ -46,6 +58,9 @@ devDependencies: '@types/node': specifier: ^20.12.2 version: 20.12.2 + '@types/pg': + specifier: ^8.11.4 + version: 8.11.4 '@types/react': specifier: ^18.2.73 version: 18.2.73 @@ -82,6 +97,65 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + /@auth/core@0.28.1: + resolution: {integrity: sha512-gvp74mypYZADpTlfGRp6HE0G3pIHWvtJpy+KZ+8FvY0cmlIpHog+jdMOdd29dQtLtN25kF2YbfHsesCFuGUQbg==} + 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 + dependencies: + '@panva/hkdf': 1.1.1 + '@types/cookie': 0.6.0 + cookie: 0.6.0 + jose: 5.2.3 + oauth4webapi: 2.10.4 + preact: 10.11.3 + preact-render-to-string: 5.2.3(preact@10.11.3) + dev: false + + /@auth/core@0.28.2: + resolution: {integrity: sha512-Rlvu6yKa4bKbhQESMaEm6jHOY5ncIrsrQkC8tcwVQmf+cBLk7ReI9DIJS2O/WkIDoOwvM9PHiXTi5b+b/eyXxw==} + 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 + dependencies: + '@panva/hkdf': 1.1.1 + '@types/cookie': 0.6.0 + cookie: 0.6.0 + jose: 5.2.3 + oauth4webapi: 2.10.4 + preact: 10.11.3 + preact-render-to-string: 5.2.3(preact@10.11.3) + dev: false + + /@auth/pg-adapter@0.7.2(pg@8.11.5): + resolution: {integrity: sha512-hhMflWXzkB1TVBZs/1zdgVHgnAkxL+xZrK5F3l44RQZ7ZAUoIvVXYKJva551wj0F11x04cpy+noeXg3a0jDnHw==} + peerDependencies: + pg: ^8 + dependencies: + '@auth/core': 0.28.2 + pg: 8.11.5 + transitivePeerDependencies: + - '@simplewebauthn/browser' + - '@simplewebauthn/server' + - nodemailer + dev: false + /@babel/runtime@7.24.1: resolution: {integrity: sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==} engines: {node: '>=6.9.0'} @@ -1643,6 +1717,10 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 + /@panva/hkdf@1.1.1: + resolution: {integrity: sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==} + dev: false + /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -2857,6 +2935,10 @@ packages: tslib: 2.6.2 dev: false + /@types/cookie@0.6.0: + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + dev: false + /@types/json5@0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true @@ -2867,6 +2949,14 @@ packages: undici-types: 5.26.5 dev: true + /@types/pg@8.11.4: + resolution: {integrity: sha512-yw3Bwbda6vO+NvI1Ue/YKOwtl31AYvvd/e73O3V4ZkNzuGpTDndLSyc0dQRB2xrQqDePd20pEGIfqSp/GH3pRw==} + dependencies: + '@types/node': 20.12.2 + pg-protocol: 1.6.1 + pg-types: 4.0.2 + dev: true + /@types/prop-types@15.7.12: resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} @@ -3302,6 +3392,11 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true + /cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -4408,6 +4503,10 @@ packages: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true + /jose@5.2.3: + resolution: {integrity: sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA==} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4599,6 +4698,27 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true + /next-auth@5.0.0-beta.16(next@14.1.4)(react@18.2.0): + resolution: {integrity: sha512-dX2snB+ezN23tFzSes3n3uosT9iBf0eILPYWH/R2fd9n3ZzdMQlRzq7JIOPeS1aLc84IuRlyuyXyx9XmmZB6og==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + next: ^14 + nodemailer: ^6.6.5 + react: ^18.2.0 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + dependencies: + '@auth/core': 0.28.1 + next: 14.1.4(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + dev: false + /next-themes@0.3.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==} peerDependencies: @@ -4661,6 +4781,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /oauth4webapi@2.10.4: + resolution: {integrity: sha512-DSoj8QoChzOCQlJkRmYxAJCIpnXFW32R0Uq7avyghIeB6iJq0XAblOD7pcq3mx4WEBDwMuKr0Y1qveCBleG2Xw==} + dev: false + /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -4734,6 +4858,10 @@ packages: es-object-atoms: 1.0.0 dev: true + /obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + dev: true + /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -4802,6 +4930,84 @@ packages: engines: {node: '>=8'} dev: true + /pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + requiresBuild: true + dev: false + optional: true + + /pg-connection-string@2.6.4: + resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==} + dev: false + + /pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + /pg-numeric@1.0.2: + resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} + engines: {node: '>=4'} + dev: true + + /pg-pool@3.6.2(pg@8.11.5): + resolution: {integrity: sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==} + peerDependencies: + pg: '>=8.0' + dependencies: + pg: 8.11.5 + dev: false + + /pg-protocol@1.6.1: + resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==} + + /pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + dev: false + + /pg-types@4.0.2: + resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} + engines: {node: '>=10'} + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: 3.0.2 + postgres-bytea: 3.0.0 + postgres-date: 2.1.0 + postgres-interval: 3.0.0 + postgres-range: 1.1.4 + dev: true + + /pg@8.11.5: + resolution: {integrity: sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + dependencies: + pg-connection-string: 2.6.4 + pg-pool: 3.6.2(pg@8.11.5) + pg-protocol: 1.6.1 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + dev: false + + /pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + dependencies: + split2: 4.2.0 + dev: false + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -4894,11 +5100,76 @@ packages: picocolors: 1.0.0 source-map-js: 1.2.0 + /postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + dev: false + + /postgres-array@3.0.2: + resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} + engines: {node: '>=12'} + dev: true + + /postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + dev: false + + /postgres-bytea@3.0.0: + resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} + engines: {node: '>= 6'} + dependencies: + obuf: 1.1.2 + dev: true + + /postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + dev: false + + /postgres-date@2.1.0: + resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} + engines: {node: '>=12'} + dev: true + + /postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + dependencies: + xtend: 4.0.2 + dev: false + + /postgres-interval@3.0.0: + resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} + engines: {node: '>=12'} + dev: true + + /postgres-range@1.1.4: + resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} + dev: true + + /preact-render-to-string@5.2.3(preact@10.11.3): + resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} + peerDependencies: + preact: '>=10' + dependencies: + preact: 10.11.3 + pretty-format: 3.8.0 + dev: false + + /preact@10.11.3: + resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} + dev: false + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} dev: true + /pretty-format@3.8.0: + resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + dev: false + /prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} dependencies: @@ -5234,6 +5505,11 @@ packages: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: false + /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -5743,6 +6019,11 @@ packages: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: false + /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true @@ -5757,6 +6038,10 @@ packages: engines: {node: '>=10'} dev: true + /zod@3.22.4: + resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + dev: false + /zustand@4.5.2(@types/react@18.2.73)(react@18.2.0): resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==} engines: {node: '>=12.7.0'} diff --git a/utils/lib/auth.ts b/utils/lib/auth.ts new file mode 100644 index 0000000..54db8d9 --- /dev/null +++ b/utils/lib/auth.ts @@ -0,0 +1,93 @@ +import NextAuth from 'next-auth' +import type { NextAuthConfig } from 'next-auth' +import CredentialsProvider from 'next-auth/providers/credentials' +import PostgresAdapter from '@auth/pg-adapter' +import pool from '~/utils/lib/db' +import { z } from 'zod' +// import { toast } from 'sonner' + +const authConfig = { + secret: 'T7tQXKqqKjZrJhN62Z05AHY8qKGQKaG/4iqv0tKmEvw=', + adapter: PostgresAdapter(pool), + session: { + strategy: "jwt", + }, + pages: { + signIn: "/login", + }, + providers: [ + CredentialsProvider({ + id: "credentials", + name: "Credentials", + type: "credentials", + credentials: { + email: { label: "Email", type: "email", placeholder: "Email" }, + password: { label: "Password", type: "password" } + }, + async authorize(credentials, req) { + const parsedCredentials = z + .object({ email: z.string().email(), password: z.string().min(6) }) + .safeParse(credentials); + + if (parsedCredentials.success) { + const { email, password } = parsedCredentials.data; + // TODO 邮件和密码校验 + console.log(email, password) + + const user = await pool.query('SELECT * FROM users WHERE email = $1::text LIMIT 1', [`${email}`]) + + console.log(user.rowCount) + console.log(user.rows[0]) + if (user.rowCount === 0) { + // toast.error('登录失败!', {duration: 1000}) + return null; + } + + // toast.success('登录成功!', {duration: 1000}) + return user.rows[0]; + } + + console.log('Invalid credentials'); + // toast.error('登录失败!', {duration: 1000}) + return null; + } + }) + ], + callbacks: { + async session({ token, session }) { + if (token) { + session.user = { + ...session.user, + id: session.user.id, + name: session.user.name, + email: session.user.email, + image: session.user.image, + } + } + + return session + }, + async jwt({ token, user }) { + const dbUser = await pool.query('SELECT * FROM users WHERE email = $1::text LIMIT 1', [`${user.email}`]) + + if (dbUser.rowCount === 0) { + token.id = Number(user?.id) + return token + } + + return { + ...token, + id: dbUser.rows[0]?.id, + name: dbUser.rows[0]?.name, + email: dbUser.rows[0]?.email, + picture: dbUser.rows[0]?.image, + } + }, + }, +} satisfies NextAuthConfig; + +export const { + handlers: { GET, POST }, + auth, + signIn, +} = NextAuth(authConfig) \ No newline at end of file diff --git a/utils/lib/db.ts b/utils/lib/db.ts new file mode 100644 index 0000000..a466675 --- /dev/null +++ b/utils/lib/db.ts @@ -0,0 +1,14 @@ +import { Pool } from 'pg' + +const pool = new Pool({ + host: process.env.POSTGRE_HOST, + port: process.env.POSTGRE_PORT, + database: process.env.POSTGRE_DATABASE, + user: process.env.POSTGRE_USERNAME, + password: process.env.POSTGRE_PASSWORD, + max: 20, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 2000, +}) + +export default pool; \ No newline at end of file diff --git a/utils/lib/session.ts b/utils/lib/session.ts new file mode 100644 index 0000000..b71d3c5 --- /dev/null +++ b/utils/lib/session.ts @@ -0,0 +1,7 @@ +import { auth } from '~/utils/lib/auth' + +export async function getSession() { + const session = await auth() + + return session +} \ No newline at end of file diff --git a/utils/lib/userContext.ts b/utils/lib/userContext.ts new file mode 100644 index 0000000..a9cce77 --- /dev/null +++ b/utils/lib/userContext.ts @@ -0,0 +1,7 @@ +import { auth } from '~/utils/lib/auth' + +export async function getCurrentUser() { + const session = await auth() + + return session?.user +} \ No newline at end of file