Files
web-cad/backend/src/routes/users.ts
T
Leopoldadmin 707214447b merge: cherry-pick e1b96310 (security + type-safety + auth middleware) from web-cad
Brings the following 28.06 improvements into web-cad-neu:
- backend/src/auth/authMiddleware.ts: shared requireAuth/requireAdmin middleware
- backend/src/routes/users.ts: admin role checks (critical security fix)
- 6 routes (projects, drawings, elements, layers, blocks, settings): requireAuth
- server.ts: per-route auth (defense-in-depth alongside global hook)
- 6 test files: auth setup + 401 tests
- Frontend: type safety fixes (App.tsx, RenderEngine, SnapEngine, etc.)
- 10 components: SVG stroke-width/linecap/linejoin -> camelCase
- PropertiesPanel: formatPos/formatDim/parseNum helpers
- LayerPanel: aria-labels
- WORKLOG.md: historical worklog from 28.06

Conflicts resolved (4 blocks, all kept HEAD + e1b96310 improvements):
- LayerPanel.tsx: kept HEAD display:flex + e1b96310 aria-label
- PropertiesPanel.tsx: kept HEAD onDelete handler + e1b96310 formatPos helpers
- PropertiesPanel.tsx: kept HEAD delete button (feature) + full inline styles
- Topbar.tsx: kept HEAD onClick + e1b96310 camelCase SVG attrs

Refs: CODE_ANALYSIS.md (Issue #1 partially improved), e1b96310
2026-06-30 11:32:33 +02:00

74 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Users Routes Admin user management
*/
import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import type { DatabaseInterface, DBUser } from '../database/DatabaseInterface.js';
import type { AuthService } from '../auth/AuthService.js';
function extractToken(request: FastifyRequest): string | null {
const auth = request.headers?.authorization;
if (auth && auth.startsWith('Bearer ')) {
return auth.slice(7);
}
return null;
}
function requireAdmin(request: FastifyRequest, reply: FastifyReply, authService: AuthService): DBUser | null {
const token = extractToken(request);
if (!token) {
reply.code(401).send({ error: 'Authentication required' });
return null;
}
const user = authService.getUserFromSession(token);
if (!user) {
reply.code(401).send({ error: 'Invalid or expired session' });
return null;
}
if (user.role !== 'admin') {
reply.code(403).send({ error: 'Admin access required' });
return null;
}
return user;
}
export function registerUserRoutes(fastify: FastifyInstance, db: DatabaseInterface, authService: AuthService) {
// List all users (admin only)
fastify.get('/api/users', async (request, reply) => {
if (!requireAdmin(request, reply, authService)) return;
const users = db.listUsers();
return users.map(({ password_hash, ...safe }) => safe);
});
// Get single user (admin only)
fastify.get('/api/users/:id', async (request, reply) => {
if (!requireAdmin(request, reply, authService)) return;
const { id } = request.params as { id: string };
const user = db.getUser(id);
if (!user) return reply.code(404).send({ error: 'User not found' });
const { password_hash, ...safe } = user;
return safe;
});
// Update user (admin only)
fastify.patch('/api/users/:id', async (request, reply) => {
if (!requireAdmin(request, reply, authService)) return;
const { id } = request.params as { id: string };
const body = request.body as Partial<DBUser>;
// Prevent password update via this endpoint
delete body.password_hash;
const updated = db.updateUser(id, body);
if (!updated) return reply.code(404).send({ error: 'User not found' });
const { password_hash, ...safe } = updated;
return safe;
});
// Delete user (admin only)
fastify.delete('/api/users/:id', async (request, reply) => {
if (!requireAdmin(request, reply, authService)) return;
const { id } = request.params as { id: string };
const ok = db.deleteUser(id);
if (!ok) return reply.code(404).send({ error: 'User not found' });
return reply.code(204).send();
});
}