### Task 2: Autorizzazione a matrice in `auth.ts` **Files:** - Modify: `src/lib/auth.ts` - Test: `tests/roles.test.ts` (riscrittura) **Interfaces:** - Produces: - `type Role = 'admin' | 'superuser' | 'user'` - `canAccessAdminPath(role: string, pathname: string): boolean` - `landingFor(role: string): string` - `createUser(db, username, password, role?: Role): number` (default invariato `'admin'`) - [ ] **Step 1: Riscrivi il test dei ruoli** Sostituisci l'intero `tests/roles.test.ts` con: ```typescript import { describe, it, expect, beforeEach } from 'vitest'; import type Database from 'better-sqlite3'; import { createDb } from '../src/lib/db'; import { createUser, login, getSessionUser, canAccessAdminPath, landingFor } from '../src/lib/auth'; let db: Database.Database; beforeEach(() => { db = createDb(':memory:'); }); describe('ruoli', () => { it('createUser accetta i tre ruoli e getSessionUser li ritorna', () => { createUser(db, 'u', 'segreta123', 'user'); createUser(db, 's', 'segreta123', 'superuser'); expect(getSessionUser(db, login(db, 'u', 'segreta123')!)).toMatchObject({ role: 'user' }); expect(getSessionUser(db, login(db, 's', 'segreta123')!)).toMatchObject({ role: 'superuser' }); }); it('createUser senza ruolo crea admin', () => { createUser(db, 'adriano', 'segreta123'); expect(getSessionUser(db, login(db, 'adriano', 'segreta123')!)).toMatchObject({ role: 'admin' }); }); }); describe('canAccessAdminPath', () => { it('admin accede a tutto', () => { for (const p of ['/admin', '/admin/users', '/admin/content', '/api/admin/posts', '/api/admin/users/1']) expect(canAccessAdminPath('admin', p)).toBe(true); }); it('superuser: contenuti + blog sì, utenti no', () => { expect(canAccessAdminPath('superuser', '/admin/content')).toBe(true); expect(canAccessAdminPath('superuser', '/admin/content/')).toBe(true); expect(canAccessAdminPath('superuser', '/api/admin/content/home.hero.title')).toBe(true); expect(canAccessAdminPath('superuser', '/admin')).toBe(true); expect(canAccessAdminPath('superuser', '/admin/new')).toBe(true); expect(canAccessAdminPath('superuser', '/api/admin/posts')).toBe(true); expect(canAccessAdminPath('superuser', '/admin/users')).toBe(false); expect(canAccessAdminPath('superuser', '/api/admin/users/1')).toBe(false); }); it('user: blog sì, contenuti e utenti no', () => { expect(canAccessAdminPath('user', '/admin')).toBe(true); expect(canAccessAdminPath('user', '/admin/new')).toBe(true); expect(canAccessAdminPath('user', '/api/admin/posts')).toBe(true); expect(canAccessAdminPath('user', '/api/admin/upload')).toBe(true); expect(canAccessAdminPath('user', '/admin/logout')).toBe(true); expect(canAccessAdminPath('user', '/admin/content')).toBe(false); expect(canAccessAdminPath('user', '/api/admin/content/x')).toBe(false); expect(canAccessAdminPath('user', '/admin/users')).toBe(false); }); it('la matrice non confonde prefissi simili', () => { expect(canAccessAdminPath('user', '/admin/contentious')).toBe(true); // NON è /admin/content expect(canAccessAdminPath('superuser', '/admin/users-x')).toBe(true); // NON è /admin/users }); }); describe('landingFor', () => { it('instrada ogni ruolo alla sua home', () => { expect(landingFor('superuser')).toBe('/admin/content'); expect(landingFor('user')).toBe('/admin'); expect(landingFor('admin')).toBe('/admin'); }); }); ``` - [ ] **Step 2: Esegui e verifica il fallimento** Run: `npm test -- tests/roles.test.ts` Expected: FAIL — `landingFor` non esportata, matrice non aggiornata. - [ ] **Step 3: Aggiorna `auth.ts`** In `src/lib/auth.ts`: 1. Cambia il tipo: `export type Role = 'admin' | 'superuser' | 'user';` 2. Sostituisci il blocco finale (da `// Percorsi admin consentiti anche al ruolo editor.` fino a fine file) con: ```typescript // Autorizzazione per prefisso di rotta. L'admin passa sempre; per gli altri, la prima regola // che matcha decide; se nessuna regola matcha la rotta è "blog/upload/logout" → consentita a // qualsiasi loggato (il middleware protegge solo /admin e /api/admin, quindi qui arrivano solo // utenti già autenticati). const RULES: [RegExp, Role[]][] = [ [/^\/admin\/users(\/|$)/, ['admin']], [/^\/api\/admin\/users(\/|$)/, ['admin']], [/^\/admin\/content(\/|$)/, ['admin', 'superuser']], [/^\/api\/admin\/content(\/|$)/, ['admin', 'superuser']], ]; export function canAccessAdminPath(role: string, pathname: string): boolean { if (role === 'admin') return true; for (const [re, roles] of RULES) { if (re.test(pathname)) return roles.includes(role as Role); } return true; // blog, upload, logout, showtags: tutti i loggati } export function landingFor(role: string): string { if (role === 'superuser') return '/admin/content'; return '/admin'; } ``` - [ ] **Step 4: Esegui e verifica il successo** Run: `npm test -- tests/roles.test.ts` Expected: PASS. - [ ] **Step 5: Commit** ```bash git add src/lib/auth.ts tests/roles.test.ts git commit -m "feat(auth): matrice permessi 3 ruoli + landingFor" ``` ---