diff --git a/src/lib/posts.ts b/src/lib/posts.ts index 1c8e44a..2b346dd 100644 --- a/src/lib/posts.ts +++ b/src/lib/posts.ts @@ -76,3 +76,8 @@ export function listArchiveMonths(db: Database.Database): { month: string; count export function listByAuthor(db: Database.Database, authorId: number): Post[] { return db.prepare('SELECT * FROM posts WHERE author_id = ? ORDER BY updated_at DESC').all(authorId) as Post[]; } + +export function canModifyPost(role: string, userId: number, post: { author_id: number | null }): boolean { + if (role === 'admin' || role === 'superuser') return true; + return post.author_id === userId; +} diff --git a/src/pages/api/admin/posts/[id].ts b/src/pages/api/admin/posts/[id].ts index c7e8a33..8c7ef21 100644 --- a/src/pages/api/admin/posts/[id].ts +++ b/src/pages/api/admin/posts/[id].ts @@ -1,15 +1,17 @@ import type { APIRoute } from 'astro'; import { getDb } from '../../../../lib/db'; -import { getPostById, updatePost, deletePost } from '../../../../lib/posts'; +import { getPostById, updatePost, deletePost, canModifyPost } from '../../../../lib/posts'; import { parsePostBody } from '../../../../lib/post-body'; export const prerender = false; const json = (status: number, body: object) => new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } }); -export const PUT: APIRoute = async ({ params, request }) => { +export const PUT: APIRoute = async ({ params, request, locals }) => { const id = Number(params.id); - if (!getPostById(getDb(), id)) return json(404, { error: 'Articolo non trovato.' }); + const existing = getPostById(getDb(), id); + if (!existing) return json(404, { error: 'Articolo non trovato.' }); + if (!canModifyPost(locals.user!.role, locals.user!.id, existing)) return json(403, { error: 'Non puoi modificare questo articolo.' }); let data: Record; try { data = await request.json(); } catch { return json(400, { error: 'Dati non validi.' }); } const parsed = parsePostBody(data); @@ -23,7 +25,11 @@ export const PUT: APIRoute = async ({ params, request }) => { } }; -export const DELETE: APIRoute = ({ params }) => { - deletePost(getDb(), Number(params.id)); +export const DELETE: APIRoute = ({ params, locals }) => { + const id = Number(params.id); + const existing = getPostById(getDb(), id); + if (!existing) return json(404, { error: 'Articolo non trovato.' }); + if (!canModifyPost(locals.user!.role, locals.user!.id, existing)) return json(403, { error: 'Non puoi eliminare questo articolo.' }); + deletePost(getDb(), id); return json(200, { ok: true }); }; diff --git a/src/pages/api/admin/posts/index.ts b/src/pages/api/admin/posts/index.ts index 16305d7..6d95c5d 100644 --- a/src/pages/api/admin/posts/index.ts +++ b/src/pages/api/admin/posts/index.ts @@ -7,13 +7,13 @@ export const prerender = false; const json = (status: number, body: object) => new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } }); -export const POST: APIRoute = async ({ request }) => { +export const POST: APIRoute = async ({ request, locals }) => { let data: Record; try { data = await request.json(); } catch { return json(400, { error: 'Dati non validi.' }); } const parsed = parsePostBody(data); if ('error' in parsed) return json(400, { error: parsed.error }); try { - const id = createPost(getDb(), parsed.value); + const id = createPost(getDb(), { ...parsed.value, author_id: locals.user!.id }); return json(201, { id, slug: parsed.value.slug }); } catch (err: any) { if (String(err?.message).includes('UNIQUE')) return json(400, { error: 'Slug giĆ  esistente.' }); diff --git a/tests/posts-ownership.test.ts b/tests/posts-ownership.test.ts new file mode 100644 index 0000000..88f4a40 --- /dev/null +++ b/tests/posts-ownership.test.ts @@ -0,0 +1,21 @@ +import { describe, it, expect } from 'vitest'; +import { canModifyPost } from '../src/lib/posts'; + +describe('canModifyPost', () => { + const own = { author_id: 7 }; + const other = { author_id: 8 }; + const orphan = { author_id: null }; + + it('admin e superuser modificano qualsiasi post', () => { + for (const role of ['admin', 'superuser']) { + expect(canModifyPost(role, 7, other)).toBe(true); + expect(canModifyPost(role, 7, orphan)).toBe(true); + } + }); + + it('user modifica solo i propri', () => { + expect(canModifyPost('user', 7, own)).toBe(true); + expect(canModifyPost('user', 7, other)).toBe(false); + expect(canModifyPost('user', 7, orphan)).toBe(false); + }); +});