Refs gitea:Adriano/AI-OS#310 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
4.9 KiB
Task 15 — Area admin: editor Tiptap, API CRUD, upload immagini
Data: 2026-07-02
Commit: f0ad162f742179e5154dd0e011345b35f093f119
Riepilogo
Il Task 15 introduce l'area di amministrazione del sito InsanityLab: layout admin dedicato, pagine di login/logout, lista articoli con eliminazione, form di creazione e modifica con editor Tiptap, API REST protette per il CRUD dei post, endpoint di upload immagini con validazione e route di serving dei file caricati con protezione contro il path traversal. La sanitizzazione dell'HTML in ingresso è centralizzata in src/lib/sanitize.ts (basata su sanitize-html) ed è coperta da test scritti prima dell'implementazione, secondo il flusso TDD richiesto.
File creati
tests/sanitize.test.tssrc/lib/sanitize.tssrc/lib/post-body.tssrc/layouts/Admin.astrosrc/pages/admin/login.astro,src/pages/admin/logout.ts,src/pages/admin/index.astro,src/pages/admin/new.astro,src/pages/admin/edit/[id].astrosrc/components/admin/PostForm.astrosrc/pages/api/admin/posts/index.ts,src/pages/api/admin/posts/[id].ts,src/pages/api/admin/upload.tssrc/pages/uploads/[...path].ts
Esito test (npm test)
Test Files 6 passed (6)
Tests 28 passed (28)
28/28 test superati: 25 preesistenti (auth, contact, posts, rate-limit, slug) più i 3 nuovi test di sanitize. Il ciclo TDD è stato rispettato: il test è stato creato per primo, la sua esecuzione è fallita con Cannot find module '../src/lib/sanitize', quindi è stata scritta l'implementazione e i test sono passati al primo tentativo.
Verifica end-to-end (dev server su porta 4321)
| # | Check | Atteso | Ottenuto | Esito |
|---|---|---|---|---|
| 1 | GET /admin senza autenticazione |
302 | 302 | ✓ |
| 2 | POST /api/admin/posts senza autenticazione |
401 | 401 | ✓ |
| 3 | Login POST /admin/login (utente e2e dedicato) |
302 + cookie | 302, cookie session impostato |
✓ |
| 4 | POST nuovo articolo | 201 | 201, {"id":16,"slug":"test-e2e-task-15"} |
✓ |
| 5 | GET /blog/test-e2e-task-15 |
200 | 200 | ✓ |
| 6 | PUT aggiornamento articolo | 200 | 200, {"ok":true} |
✓ |
| 7 | Upload PNG (10x10, generato via Python) | 200 | 200, {"url":"/uploads/1782949642171-10ccb41e.png"} |
✓ |
| 8 | GET file caricato | 200 | 200, Content-Type: image/png |
✓ |
| 9 | Path traversal /uploads/..%2f..%2fpackage.json |
404 | 404 | ✓ |
| 10 | DELETE articolo | 200 | 200, {"ok":true} |
✓ |
Nota sul check 3: il dev server risponde 403 ai POST privi di header Origin per via della protezione CSRF integrata di Astro (security.checkOrigin). Con Origin: http://localhost:4321 il login funziona correttamente. Un browser reale invia sempre l'header, quindi il comportamento è quello atteso.
Nota sul check 9: la variante --path-as-is /uploads/../package.json restituiva 200 in modalità dev, ma la risposta proveniva dal middleware statico di Vite (headers ETag e Cache-Control: no-cache, assenti nella nostra route), che in sviluppo espone i file di progetto dopo la normalizzazione del path. La verifica è stata ripetuta sul server di produzione (npm run build + node --env-file=.env ./dist/server/entry.mjs): tutte le varianti di traversal (../, %2e%2e/, ..%2f) e l'accesso diretto a /package.json restituiscono 404. La route [...path].ts è quindi sicura; l'esposizione riguarda esclusivamente l'ambiente di sviluppo locale e non il deploy.
Build
npm run build completata senza errori: 54 asset generati, sitemap creata, server bundle prodotto in dist/server.
Adattamenti al test sanitize
Nessuno. sanitize-html 2.17.5 preserva l'HTML consentito esattamente com'è, quindi l'asserzione toBe(html) del primo test è rimasta invariata. Le asserzioni su <script>, handler inline e schema javascript: non sono state toccate.
Concern e note
- Esposizione file in dev: come descritto sopra, il dev server Vite serve i file della root del progetto (incluso
package.json) dopo normalizzazione del path. Non riguarda la produzione, ma è bene non esporre mai il dev server su rete pubblica. - Utente e2e: per i check è stato creato un utente temporaneo
e2etesttramitescripts/create-user.mjs, poi rimosso dal database (sessioni incluse) al termine. L'utenteadminpreesistente non è stato toccato. - Pulizia: il PNG di test caricato in
uploads/è stato eliminato; l'articolo di prova è stato rimosso tramite l'endpoint DELETE (check 10). - Dev server Astro 7 come demone:
astro devin questa versione gira come demone (astro dev stopper fermarlo). Durante la verifica è stato trovato un demone preesistente sulla porta 4323, fermato e riavviato sulla 4321; al termine dei check il demone è stato fermato. getEnv('UPLOADS_DIR', 'uploads'): il fallback relativouploadsdipende dalla working directory del processo; il.envdefinisce giàUPLOADS_DIR=uploads. In produzione conviene valutare un path assoluto.