Files
Adriano b913a2bf90 registri dei piani in git (AI-OS#309)
Refs gitea:Adriano/AI-OS#310

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 10:36:26 +02:00

5.0 KiB

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:

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:
// 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
git add src/lib/auth.ts tests/roles.test.ts
git commit -m "feat(auth): matrice permessi 3 ruoli + landingFor"