From ed9d5d1b9f0595374c266fa8bbb64d7db960f4bc Mon Sep 17 00:00:00 2001 From: Sergey Elpashev Date: Mon, 3 Feb 2025 11:54:45 +0300 Subject: [PATCH] Feat: authentication --- bun.lockb | Bin 160612 -> 165885 bytes package.json | 1 + src/app/admin/actions.ts | 9 --- src/app/admin/login/page.tsx | 80 ++++++++++++++++++++++++ src/app/admin/page.tsx | 57 +---------------- src/app/admin/{ => panel}/layout.tsx | 11 ++-- src/app/api/auth/[...nextauth]/route.ts | 33 ++++++++++ src/app/layout.tsx | 9 ++- src/app/providers.tsx | 6 ++ src/middleware.ts | 14 +---- 10 files changed, 139 insertions(+), 81 deletions(-) delete mode 100644 src/app/admin/actions.ts create mode 100644 src/app/admin/login/page.tsx rename src/app/admin/{ => panel}/layout.tsx (80%) create mode 100644 src/app/api/auth/[...nextauth]/route.ts create mode 100644 src/app/providers.tsx diff --git a/bun.lockb b/bun.lockb index 02a3f781bdfa1d5d09f8bcd8b2d517451e58e0c2..3a84d965ea52b6605d011a507cb0cb109fce7d1e 100755 GIT binary patch delta 33808 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 b*x_Ra4@=h(5$0?9Xeo2fs#OtY>j(cId3%=2 diff --git a/package.json b/package.json index 01bfa33..6e5e8ac 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@mantine/hooks": "^7.16.1", "@mantine/modals": "^7.16.1", "next": "15.1.5", + "next-auth": "^4.24.11", "react": "^19.0.0", "react-dom": "^19.0.0", "react-icons": "^5.4.0", diff --git a/src/app/admin/actions.ts b/src/app/admin/actions.ts deleted file mode 100644 index 0f9c94f..0000000 --- a/src/app/admin/actions.ts +++ /dev/null @@ -1,9 +0,0 @@ -"use server"; - -import { redirect } from "next/navigation"; - -export async function login(formData: { name: string; password: string }) { - console.log("data"); - console.log(formData); - redirect("/admin/panel"); -} diff --git a/src/app/admin/login/page.tsx b/src/app/admin/login/page.tsx new file mode 100644 index 0000000..6bbea86 --- /dev/null +++ b/src/app/admin/login/page.tsx @@ -0,0 +1,80 @@ +"use client"; +import { Button, Card, Flex, PasswordInput, Stack, Text, TextInput } from "@mantine/core"; +import { hasLength, useForm } from "@mantine/form"; +import { signIn } from "next-auth/react"; +import { redirect } from "next/navigation"; + +interface FormValues { + name: string; + password: string; +} + +export default function LoginPage() { + const form = useForm({ + mode: "uncontrolled", + initialValues: { + name: "", + password: "", + }, + validate: { + name: hasLength({ min: 5 }, "Too short"), + password: hasLength({ min: 5 }, "Too short"), + }, + }); + + const handleSubmit = async (formData: { name: string; password: string }) => { + const res = await signIn("credentials", { + username: formData.name, + password: formData.password, + redirect: false, + }); + if (res && res.error) { + if (res.status === 401) + form.setErrors({ + name: "Wrong password or username", + }); + else + form.setErrors({ + name: `Unknown error: ${res.status}`, + }); + } else { + redirect("/admin/panel"); + } + }; + + return ( + + +

+ + + Admin + + + + + +
+ + + ); +} diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 4da4321..e0bf75a 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -1,60 +1,9 @@ -"use client"; +"use server"; -import { Button, Card, Flex, PasswordInput, Text, TextInput } from "@mantine/core"; -import { hasLength, useForm } from "@mantine/form"; -import { login } from "./actions"; - -interface FormValues { - name: string; - password: string; -} +import { redirect } from "next/navigation"; const AdminPage = () => { - const form = useForm({ - mode: "uncontrolled", - initialValues: { - name: "", - password: "", - }, - validate: { - name: hasLength({ min: 5 }, "Too short"), - password: hasLength({ min: 5 }, "Too short"), - }, - }); - - return ( - - -
- - Admin - - - - - -
-
- ); + return redirect("/admin/panel"); }; export default AdminPage; diff --git a/src/app/admin/layout.tsx b/src/app/admin/panel/layout.tsx similarity index 80% rename from src/app/admin/layout.tsx rename to src/app/admin/panel/layout.tsx index 70f87d1..d38236a 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/panel/layout.tsx @@ -1,9 +1,9 @@ "use client"; -import { ActionIcon, AppShell, Burger, Flex, Group, Skeleton, useMantineColorScheme } from "@mantine/core"; +import { ActionIcon, AppShell, Burger, Button, Flex, Group, Skeleton, useMantineColorScheme } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; +import { signOut } from "next-auth/react"; import { LuMoon, LuSun } from "react-icons/lu"; - -const AdminLayout = ({ +const AdminPanelLayout = ({ children, }: Readonly<{ children: React.ReactNode; @@ -32,10 +32,13 @@ const AdminLayout = ({ .map((_, index) => ( ))} + {children} ); }; -export default AdminLayout; +export default AdminPanelLayout; diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..1540a6b --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,33 @@ +import NextAuth from "next-auth"; +import CredentialsProvider from "next-auth/providers/credentials"; + +const handler = NextAuth({ + jwt: { + maxAge: 60 * 60 * 24 * 30, + }, + session: { + strategy: "jwt", + }, + pages: { + signIn: "/admin/login", + }, + providers: [ + CredentialsProvider({ + name: "Credentials", + credentials: { + username: { label: "Username", type: "text", placeholder: "jsmith" }, + password: { label: "Password", type: "password" }, + }, + async authorize(credentials) { + const { username, password } = credentials as { username: string; password: string }; + if (username !== "admin" || password !== "admin") { + return null; + } + + return new Promise((resolve) => resolve({ id: "1", email: "example@example.org", name: "test" })); + }, + }), + ], +}); + +export { handler as GET, handler as POST }; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 911df11..a5f5b97 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,8 +1,10 @@ import { ColorSchemeScript, MantineProvider } from "@mantine/core"; import "@mantine/core/styles.css"; import type { Metadata } from "next"; +import { getSession } from "next-auth/react"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.scss"; +import Providers from "./providers"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -19,18 +21,21 @@ export const metadata: Metadata = { description: "Generated by create next app", }; -export default function RootLayout({ +export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { + const session = await getSession(); return ( - {children} + + {children} + ); diff --git a/src/app/providers.tsx b/src/app/providers.tsx new file mode 100644 index 0000000..eca4ed9 --- /dev/null +++ b/src/app/providers.tsx @@ -0,0 +1,6 @@ +"use client"; +import type { Session } from "next-auth"; +import { SessionProvider } from "next-auth/react"; +export default function Providers({ session, children }: { session: Session | null; children: React.ReactNode }) { + return {children}; +} diff --git a/src/middleware.ts b/src/middleware.ts index e387e2f..781617d 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,14 +1,4 @@ -import { isAuthenticated } from "@/lib/auth"; -import { NextRequest, NextResponse } from "next/server"; - +export { default } from "next-auth/middleware"; export const config = { - matcher: "/admin/:path*", + matcher: ["/admin/panel"], }; - -export function middleware(request: NextRequest) { - const { pathname } = request.nextUrl; - if (pathname === "/admin" || isAuthenticated()) { - return NextResponse.next(); - } - return NextResponse.redirect(new URL("/admin", request.url)); -}