feat: autenticazione a sessioni, middleware admin e script create-user
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import type Database from 'better-sqlite3';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
|
||||
export const SESSION_COOKIE = 'session';
|
||||
const SESSION_DAYS = 7;
|
||||
|
||||
export function hashPassword(plain: string): string {
|
||||
return bcrypt.hashSync(plain, 12);
|
||||
}
|
||||
|
||||
export function verifyPassword(plain: string, hash: string): boolean {
|
||||
return bcrypt.compareSync(plain, hash);
|
||||
}
|
||||
|
||||
export function createUser(db: Database.Database, username: string, password: string): number {
|
||||
const res = db.prepare('INSERT INTO users (username, password_hash) VALUES (?, ?)')
|
||||
.run(username, hashPassword(password));
|
||||
return Number(res.lastInsertRowid);
|
||||
}
|
||||
|
||||
export function login(db: Database.Database, username: string, password: string): string | null {
|
||||
const user = db.prepare('SELECT * FROM users WHERE username = ?').get(username) as
|
||||
| { id: number; password_hash: string } | undefined;
|
||||
if (!user || !verifyPassword(password, user.password_hash)) return null;
|
||||
const token = randomBytes(32).toString('hex');
|
||||
db.prepare(
|
||||
`INSERT INTO sessions (token, user_id, expires_at) VALUES (?, ?, datetime('now', '+${SESSION_DAYS} days'))`
|
||||
).run(token, user.id);
|
||||
return token;
|
||||
}
|
||||
|
||||
export function getSessionUser(db: Database.Database, token: string): { id: number; username: string } | null {
|
||||
const row = db.prepare(
|
||||
`SELECT s.token, s.expires_at, u.id, u.username
|
||||
FROM sessions s JOIN users u ON u.id = s.user_id WHERE s.token = ?`
|
||||
).get(token) as { token: string; expires_at: string; id: number; username: string } | undefined;
|
||||
if (!row) return null;
|
||||
if (row.expires_at <= new Date().toISOString().slice(0, 19).replace('T', ' ')) {
|
||||
db.prepare('DELETE FROM sessions WHERE token = ?').run(token);
|
||||
return null;
|
||||
}
|
||||
return { id: row.id, username: row.username };
|
||||
}
|
||||
|
||||
export function logout(db: Database.Database, token: string): void {
|
||||
db.prepare('DELETE FROM sessions WHERE token = ?').run(token);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { defineMiddleware } from 'astro:middleware';
|
||||
import { getDb } from './lib/db';
|
||||
import { getSessionUser, SESSION_COOKIE } from './lib/auth';
|
||||
|
||||
export const onRequest = defineMiddleware((context, next) => {
|
||||
const { pathname } = context.url;
|
||||
const isProtected =
|
||||
(pathname.startsWith('/admin') && pathname !== '/admin/login') ||
|
||||
pathname.startsWith('/api/admin');
|
||||
if (!isProtected) return next();
|
||||
|
||||
const token = context.cookies.get(SESSION_COOKIE)?.value;
|
||||
const user = token ? getSessionUser(getDb(), token) : null;
|
||||
if (!user) {
|
||||
if (pathname.startsWith('/api/')) {
|
||||
return new Response(JSON.stringify({ error: 'Non autorizzato' }), {
|
||||
status: 401, headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
return context.redirect('/admin/login');
|
||||
}
|
||||
context.locals.user = user;
|
||||
return next();
|
||||
});
|
||||
Reference in New Issue
Block a user