inseriti log per verifica login

This commit is contained in:
AV77web 2026-05-27 15:24:29 +02:00
parent 71ed95251c
commit 2de9d601f5
8 changed files with 181 additions and 17 deletions

View file

@ -7,6 +7,8 @@ services:
restart: always restart: always
env_file: env_file:
- .env - .env
environment:
MYSQL_HOST: mysql
ports: ports:
- "127.0.0.1:3001:3000" - "127.0.0.1:3001:3000"
depends_on: depends_on:
@ -23,6 +25,8 @@ services:
MYSQL_PASSWORD: ${MYSQL_PASSWORD} MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes: volumes:
- dbdata:/var/lib/mysql - dbdata:/var/lib/mysql
ports:
- "127.0.0.1:3306:3306"
phpmyadmin: phpmyadmin:
image: phpmyadmin:latest image: phpmyadmin:latest

View file

@ -0,0 +1,22 @@
export default function ArticoliPage() {
return (
<div className="flex flex-1 flex-col bg-zinc-50 p-6 dark:bg-black">
<div className="mx-auto w-full max-w-6xl">
<header className="mb-8">
<h1 className="text-3xl font-semibold text-zinc-950 dark:text-zinc-50">
Articoli
</h1>
<p className="mt-2 text-zinc-600 dark:text-zinc-400">
Catalogo ricambi e componenti
</p>
</header>
<div className="rounded-lg border border-zinc-200 bg-white p-6 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
<p className="text-zinc-600 dark:text-zinc-400">
Contenuto della pagina articoli in arrivo...
</p>
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,22 @@
export default function MachinePage() {
return (
<div className="flex flex-1 flex-col bg-zinc-50 p-6 dark:bg-black">
<div className="mx-auto w-full max-w-6xl">
<header className="mb-8">
<h1 className="text-3xl font-semibold text-zinc-950 dark:text-zinc-50">
Macchine
</h1>
<p className="mt-2 text-zinc-600 dark:text-zinc-400">
Gestione del parco macchine e attrezzature
</p>
</header>
<div className="rounded-lg border border-zinc-200 bg-white p-6 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
<p className="text-zinc-600 dark:text-zinc-400">
Contenuto della pagina macchine in arrivo...
</p>
</div>
</div>
</div>
);
}

View file

@ -12,17 +12,31 @@ type LoginPageProps = {
async function login(formData: FormData) { async function login(formData: FormData) {
"use server"; "use server";
const identifier = String(formData.get("identifier") ?? "").trim();
console.log("[login-page] Submit login ricevuto", {
hasIdentifier: Boolean(identifier),
hasPassword: Boolean(formData.get("password")),
});
try { try {
await signIn("credentials", { await signIn("credentials", {
identifier: formData.get("identifier"), identifier,
password: formData.get("password"), password: formData.get("password"),
redirectTo: "/", redirectTo: "/",
}); });
} catch (error) { } catch (error) {
if (error instanceof AuthError) { if (error instanceof AuthError) {
console.log("[login-page] AuthError durante login", {
type: error.type,
cause: error.cause,
});
redirect("/login?error=CredentialsSignin"); redirect("/login?error=CredentialsSignin");
} }
console.error("[login-page] Errore inatteso durante login", {
error: error instanceof Error ? error.message : String(error),
});
throw error; throw error;
} }
} }

View file

@ -8,12 +8,41 @@ import type { DbUser, UserRole } from "@/src/types/user";
type DbUserRow = DbUser & RowDataPacket; type DbUserRow = DbUser & RowDataPacket;
function maskIdentifier(identifier: string): string {
if (identifier.includes("@")) {
const [name = "", domain = ""] = identifier.split("@");
const visibleName = name.slice(0, 2);
return `${visibleName}${"*".repeat(Math.max(name.length - 2, 1))}@${domain}`;
}
if (identifier.length <= 2) {
return "*".repeat(identifier.length);
}
return `${identifier.slice(0, 2)}${"*".repeat(identifier.length - 2)}`;
}
function authLog(message: string, details?: Record<string, unknown>): void {
console.log(`[auth] ${message}`, details ?? "");
}
function authError(message: string, error: unknown, details?: Record<string, unknown>): void {
console.error(`[auth] ${message}`, {
...details,
error: error instanceof Error ? error.message : String(error),
});
}
function isUserRole(value: string): value is UserRole { function isUserRole(value: string): value is UserRole {
return ["admin", "manager", "user", "sviluppo"].includes(value); return ["admin", "manager", "user", "sviluppo"].includes(value);
} }
async function findActiveUser(identifier: string): Promise<DbUser | null> { async function findActiveUser(identifier: string): Promise<DbUser | null> {
const [rows] = await db.execute<DbUserRow[]>( let rows: DbUserRow[];
try {
[rows] = await db.execute<DbUserRow[]>(
`SELECT id, utente, mail, password, ruolo, attivo, last_login `SELECT id, utente, mail, password, ruolo, attivo, last_login
FROM utenti FROM utenti
WHERE attivo = 1 WHERE attivo = 1
@ -21,21 +50,50 @@ async function findActiveUser(identifier: string): Promise<DbUser | null> {
LIMIT 1`, LIMIT 1`,
{ identifier }, { identifier },
); );
} catch (error) {
authError("Errore durante la query utente attivo", error, {
identifier: maskIdentifier(identifier),
});
throw error;
}
const user = rows[0]; const user = rows[0];
if (!user || !isUserRole(user.ruolo)) { if (!user) {
authLog("Nessun utente attivo trovato", {
identifier: maskIdentifier(identifier),
});
return null; return null;
} }
if (!isUserRole(user.ruolo)) {
authLog("Utente trovato con ruolo non valido", {
userId: user.id,
ruolo: user.ruolo,
});
return null;
}
authLog("Utente attivo trovato", {
userId: user.id,
utente: user.utente,
ruolo: user.ruolo,
});
return user; return user;
} }
async function updateLastLogin(userId: number): Promise<void> { async function updateLastLogin(userId: number): Promise<void> {
try {
await db.execute<ResultSetHeader>( await db.execute<ResultSetHeader>(
"UPDATE utenti SET last_login = NOW() WHERE id = :userId", "UPDATE utenti SET last_login = NOW() WHERE id = :userId",
{ userId }, { userId },
); );
authLog("last_login aggiornato", { userId });
} catch (error) {
authError("Errore durante aggiornamento last_login", error, { userId });
throw error;
}
} }
export const { handlers, auth, signIn, signOut } = NextAuth({ export const { handlers, auth, signIn, signOut } = NextAuth({
@ -53,27 +111,64 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
password: { label: "Password", type: "password" }, password: { label: "Password", type: "password" },
}, },
async authorize(credentials) { async authorize(credentials) {
const startedAt = Date.now();
const identifier = String(credentials?.identifier ?? "").trim(); const identifier = String(credentials?.identifier ?? "").trim();
const password = String(credentials?.password ?? ""); const password = String(credentials?.password ?? "");
const maskedIdentifier = maskIdentifier(identifier);
authLog("Tentativo login ricevuto", {
identifier: maskedIdentifier,
hasIdentifier: Boolean(identifier),
hasPassword: Boolean(password),
});
if (!identifier || !password) { if (!identifier || !password) {
authLog("Login respinto: campi mancanti", {
identifier: maskedIdentifier,
hasIdentifier: Boolean(identifier),
hasPassword: Boolean(password),
});
return null; return null;
} }
const user = await findActiveUser(identifier); const user = await findActiveUser(identifier);
if (!user) { if (!user) {
authLog("Login respinto: utente non trovato o non valido", {
identifier: maskedIdentifier,
durationMs: Date.now() - startedAt,
});
return null; return null;
} }
const passwordMatches = await bcrypt.compare(password, user.password); let passwordMatches = false;
try {
passwordMatches = await bcrypt.compare(password, user.password);
} catch (error) {
authError("Errore durante confronto password bcrypt", error, {
userId: user.id,
durationMs: Date.now() - startedAt,
});
throw error;
}
if (!passwordMatches) { if (!passwordMatches) {
authLog("Login respinto: password non valida", {
userId: user.id,
durationMs: Date.now() - startedAt,
});
return null; return null;
} }
await updateLastLogin(user.id); await updateLastLogin(user.id);
authLog("Login riuscito", {
userId: user.id,
ruolo: user.ruolo,
durationMs: Date.now() - startedAt,
});
return { return {
id: String(user.id), id: String(user.id),
name: user.utente, name: user.utente,

View file

@ -58,8 +58,15 @@ function getPool(): Pool {
} }
export const db = new Proxy({} as Pool, { export const db = new Proxy({} as Pool, {
get(_target, property, receiver) { get(_target, property) {
return Reflect.get(getPool(), property, receiver); const pool = getPool();
const value = Reflect.get(pool, property);
if (typeof value === "function") {
return value.bind(pool);
}
return value;
}, },
}); });