From c5db715e4c431eb94879aeaa4eb35e65fe5e4399 Mon Sep 17 00:00:00 2001 From: AV77web Date: Wed, 27 May 2026 13:05:32 +0200 Subject: [PATCH] implementato modulo Auth.js e pagina Login --- src/app/(management)/layout.tsx | 38 ++++++--- src/app/api/auth/[...nextauth]/route.ts | 3 + src/app/login/page.tsx | 89 ++++++++++++++++++++ src/auth.ts | 105 ++++++++++++++++++++++++ src/components/auth/SignOutButton.tsx | 19 +++++ src/types/next-auth.d.ts | 10 ++- 6 files changed, 251 insertions(+), 13 deletions(-) create mode 100644 src/app/api/auth/[...nextauth]/route.ts create mode 100644 src/app/login/page.tsx create mode 100644 src/auth.ts create mode 100644 src/components/auth/SignOutButton.tsx diff --git a/src/app/(management)/layout.tsx b/src/app/(management)/layout.tsx index cca2ade..4aab574 100644 --- a/src/app/(management)/layout.tsx +++ b/src/app/(management)/layout.tsx @@ -1,15 +1,33 @@ -export default function MarketingLayout({ - children, +import { redirect } from "next/navigation"; +import { auth } from "@/src/auth"; +import SignOutButton from "@/src/components/auth/SignOutButton"; + +export default async function ManagementLayout({ + children, }: { - children: React.ReactNode; + children: React.ReactNode; }) { - return ( -
-

- Sono del layout (marketing) + const session = await auth(); + + if (!session?.user) { + redirect("/login"); + } + + return ( +

+
+
+
+

Area gestionale

+

+ {session.user.utente}

-
{children}
+
+
- ); -} \ No newline at end of file +
+
{children}
+
+ ); +} diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..122ac35 --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,3 @@ +import { handlers } from "@/src/auth"; + +export const { GET, POST } = handlers; diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx new file mode 100644 index 0000000..eebf47a --- /dev/null +++ b/src/app/login/page.tsx @@ -0,0 +1,89 @@ +import { AuthError } from "next-auth"; +import { redirect } from "next/navigation"; + +import { auth, signIn } from "@/src/auth"; + +type LoginPageProps = { + searchParams?: Promise<{ + error?: string; + }>; +}; + +async function login(formData: FormData) { + "use server"; + + try { + await signIn("credentials", { + identifier: formData.get("identifier"), + password: formData.get("password"), + redirectTo: "/", + }); + } catch (error) { + if (error instanceof AuthError) { + redirect("/login?error=CredentialsSignin"); + } + + throw error; + } +} + +export default async function LoginPage({ searchParams }: LoginPageProps) { + const session = await auth(); + + if (session?.user) { + redirect("/"); + } + + const params = await searchParams; + const hasError = params?.error === "CredentialsSignin"; + + return ( +
+
+
+

Accesso

+

+ Entra in MagRicambi con utente o email. +

+
+ + {hasError ? ( +

+ Credenziali non valide oppure utente non attivo. +

+ ) : null} + +
+ + + + + +
+
+
+ ); +} diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 0000000..701ca2f --- /dev/null +++ b/src/auth.ts @@ -0,0 +1,105 @@ +import bcrypt from "bcrypt"; +import NextAuth from "next-auth"; +import Credentials from "next-auth/providers/credentials"; +import type { ResultSetHeader, RowDataPacket } from "mysql2"; + +import { db } from "@/src/lib/db"; +import type { DbUser, UserRole } from "@/src/types/user"; + +type DbUserRow = DbUser & RowDataPacket; + +function isUserRole(value: string): value is UserRole { + return ["admin", "manager", "user", "sviluppo"].includes(value); +} + +async function findActiveUser(identifier: string): Promise { + const [rows] = await db.execute( + `SELECT id, utente, mail, password, ruolo, attivo, last_login + FROM utenti + WHERE attivo = 1 + AND (utente = :identifier OR mail = :identifier) + LIMIT 1`, + { identifier }, + ); + + const user = rows[0]; + + if (!user || !isUserRole(user.ruolo)) { + return null; + } + + return user; +} + +async function updateLastLogin(userId: number): Promise { + await db.execute( + "UPDATE utenti SET last_login = NOW() WHERE id = :userId", + { userId }, + ); +} + +export const { handlers, auth, signIn, signOut } = NextAuth({ + session: { + strategy: "jwt", + }, + pages: { + signIn: "/login", + }, + providers: [ + Credentials({ + name: "Credenziali", + credentials: { + identifier: { label: "Utente o email", type: "text" }, + password: { label: "Password", type: "password" }, + }, + async authorize(credentials) { + const identifier = String(credentials?.identifier ?? "").trim(); + const password = String(credentials?.password ?? ""); + + if (!identifier || !password) { + return null; + } + + const user = await findActiveUser(identifier); + + if (!user) { + return null; + } + + const passwordMatches = await bcrypt.compare(password, user.password); + + if (!passwordMatches) { + return null; + } + + await updateLastLogin(user.id); + + return { + id: String(user.id), + name: user.utente, + email: user.mail, + ruolo: user.ruolo, + utente: user.utente, + }; + }, + }), + ], + callbacks: { + jwt({ token, user }) { + if (user) { + token.id = user.id; + token.ruolo = user.ruolo; + token.utente = user.utente; + } + + return token; + }, + session({ session, token }) { + session.user.id = token.id ?? ""; + session.user.ruolo = token.ruolo ?? "user"; + session.user.utente = token.utente ?? session.user.name ?? ""; + + return session; + }, + }, +}); diff --git a/src/components/auth/SignOutButton.tsx b/src/components/auth/SignOutButton.tsx new file mode 100644 index 0000000..8989e93 --- /dev/null +++ b/src/components/auth/SignOutButton.tsx @@ -0,0 +1,19 @@ +import { signOut } from "@/src/auth"; + +export default function SignOutButton() { + return ( +
{ + "use server"; + await signOut({ redirectTo: "/login" }); + }} + > + +
+ ); +} diff --git a/src/types/next-auth.d.ts b/src/types/next-auth.d.ts index 8a397a9..c8bb039 100644 --- a/src/types/next-auth.d.ts +++ b/src/types/next-auth.d.ts @@ -4,19 +4,23 @@ import type { UserRole } from "./user"; declare module "next-auth" { interface Session { user: { - id: number; + id: string; ruolo: UserRole; + utente: string; } & DefaultSession["user"]; } interface User { + id: string; ruolo: UserRole; + utente: string; } } -declare module "next-auth/jwt" { +declare module "@auth/core/jwt" { interface JWT { - id?: number; + id?: string; ruolo?: UserRole; + utente?: string; } }