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 (
+
+ );
+}
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;
}
}