task(H3): library admin role guard - requireLibraryAdmin on all 8 global library write routes, read/export stays open for authenticated users
This commit is contained in:
@@ -44,3 +44,20 @@ export function requireAdmin(request: FastifyRequest, reply: FastifyReply, authS
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Require library-admin role (Task H3: global library write guard).
|
||||
* Library-admin is bound to the existing admin role: only admins may
|
||||
* write (create/update/delete/import) the GLOBAL block library, while
|
||||
* every authenticated user may read/export it. Own drawing-scoped
|
||||
* blocks are unaffected (per-user ownership there).
|
||||
*/
|
||||
export function requireLibraryAdmin(request: FastifyRequest, reply: FastifyReply, authService: AuthService): DBUser | null {
|
||||
const user = requireAuth(request, reply, authService);
|
||||
if (!user) return null;
|
||||
if (user.role !== 'admin') {
|
||||
reply.code(403).send({ error: 'Library admin access required' });
|
||||
return null;
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import type { DatabaseInterface } from '../database/DatabaseInterface.js';
|
||||
import { requireAuth } from '../auth/authMiddleware.js';
|
||||
import { requireAuth, requireLibraryAdmin } from '../auth/authMiddleware.js';
|
||||
import type { AuthService } from '../auth/AuthService.js';
|
||||
import { validateName, validateIdParam } from '../utils/validation.js';
|
||||
import JSZip from 'jszip';
|
||||
@@ -36,7 +36,7 @@ export function registerGlobalBlockRoutes(fastify: FastifyInstance, db: Database
|
||||
|
||||
// Create a new folder
|
||||
fastify.post('/api/global-folders', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
if (!requireLibraryAdmin(request, reply, authService)) return;
|
||||
const body = request.body as { name?: string; parent_id?: string | null };
|
||||
const nameErr = validateName(body.name, 'name');
|
||||
if (nameErr) return reply.code(400).send({ error: nameErr });
|
||||
@@ -49,7 +49,7 @@ export function registerGlobalBlockRoutes(fastify: FastifyInstance, db: Database
|
||||
|
||||
// Rename / move a folder
|
||||
fastify.patch('/api/global-folders/:id', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
if (!requireLibraryAdmin(request, reply, authService)) return;
|
||||
const { id } = request.params as { id: string };
|
||||
const idErr = validateIdParam(id, 'id');
|
||||
if (idErr) return reply.code(400).send({ error: idErr });
|
||||
@@ -71,7 +71,7 @@ export function registerGlobalBlockRoutes(fastify: FastifyInstance, db: Database
|
||||
|
||||
// Delete a folder (cascades to children, blocks get folder_id = NULL)
|
||||
fastify.delete('/api/global-folders/:id', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
if (!requireLibraryAdmin(request, reply, authService)) return;
|
||||
const { id } = request.params as { id: string };
|
||||
const idErr = validateIdParam(id, 'id');
|
||||
if (idErr) return reply.code(400).send({ error: idErr });
|
||||
@@ -107,7 +107,7 @@ export function registerGlobalBlockRoutes(fastify: FastifyInstance, db: Database
|
||||
|
||||
// Create a new global block
|
||||
fastify.post('/api/global-blocks', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
if (!requireLibraryAdmin(request, reply, authService)) return;
|
||||
const body = request.body as { name?: string; folder_id?: string | null; block_data?: string; svg_data?: string };
|
||||
const nameErr = validateName(body.name, 'name');
|
||||
if (nameErr) return reply.code(400).send({ error: nameErr });
|
||||
@@ -131,7 +131,7 @@ export function registerGlobalBlockRoutes(fastify: FastifyInstance, db: Database
|
||||
|
||||
// Update a global block (rename, move, update data)
|
||||
fastify.patch('/api/global-blocks/:id', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
if (!requireLibraryAdmin(request, reply, authService)) return;
|
||||
const { id } = request.params as { id: string };
|
||||
const idErr = validateIdParam(id, 'id');
|
||||
if (idErr) return reply.code(400).send({ error: idErr });
|
||||
@@ -160,7 +160,7 @@ export function registerGlobalBlockRoutes(fastify: FastifyInstance, db: Database
|
||||
|
||||
// Delete a global block
|
||||
fastify.delete('/api/global-blocks/:id', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
if (!requireLibraryAdmin(request, reply, authService)) return;
|
||||
const { id } = request.params as { id: string };
|
||||
const idErr = validateIdParam(id, 'id');
|
||||
if (idErr) return reply.code(400).send({ error: idErr });
|
||||
@@ -192,7 +192,7 @@ export function registerGlobalBlockRoutes(fastify: FastifyInstance, db: Database
|
||||
|
||||
// Import: .wcadlib-Paket einspielen (IDs neu, Ordner-Hierarchie über Mapping erhalten)
|
||||
fastify.post('/api/global-blocks/import', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
if (!requireLibraryAdmin(request, reply, authService)) return;
|
||||
const pkg = request.body as {
|
||||
format?: string;
|
||||
folders?: Array<{ id: string; name: string; parent_id: string | null }>;
|
||||
@@ -280,7 +280,7 @@ export function registerGlobalBlockRoutes(fastify: FastifyInstance, db: Database
|
||||
|
||||
// ZIP-Import: .wcadlib-ZIP einspielen (Ordner-Mapping, Blöcke, Thumbnails)
|
||||
fastify.post('/api/global-blocks/import-zip', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
if (!requireLibraryAdmin(request, reply, authService)) return;
|
||||
const body = request.body;
|
||||
if (!Buffer.isBuffer(body) || body.length === 0) {
|
||||
return reply.code(400).send({ error: 'ZIP body fehlt oder leer' });
|
||||
|
||||
Reference in New Issue
Block a user