Backend/frontend hardening & consistency cleanups (#1113)

* refactor(auth): session token validation and password-change consistency

* refactor(journey): entry field allow-list and public share-link consistency

* refactor(mcp): align tool authorization with the REST permission checks

* chore: input validation and sanitisation touch-ups (uploads, pdf, maps, backup, csp)
This commit is contained in:
Maurice
2026-06-06 16:37:03 +02:00
committed by GitHub
parent 070ef01328
commit 093e069ccc
41 changed files with 653 additions and 74 deletions
+20
View File
@@ -1,4 +1,6 @@
import { broadcast } from '../../websocket';
import { db } from '../../db/database';
import { checkPermission } from '../../services/permissions';
export function safeBroadcast(tripId: number, event: string, payload: Record<string, unknown>): void {
try {
@@ -46,6 +48,24 @@ export function noAccess() {
return { content: [{ type: 'text' as const, text: 'Trip not found or access denied.' }], isError: true };
}
export function permissionDenied() {
return { content: [{ type: 'text' as const, text: 'You do not have permission to perform this action on this trip.' }], isError: true };
}
/**
* RBAC gate for MCP tools, mirroring the checkPermission() calls the REST/Nest
* routes run. Call this after canAccessTrip() with the same action key the
* matching REST route uses. Returns true when the user may perform `action`
* on `tripId`.
*/
export function hasTripPermission(action: string, tripId: number | string, userId: number): boolean {
const trip = db.prepare('SELECT user_id FROM trips WHERE id = ?').get(tripId) as { user_id?: number } | undefined;
if (!trip) return false;
const userRow = db.prepare('SELECT role FROM users WHERE id = ?').get(userId) as { role?: string } | undefined;
const tripOwnerId = typeof trip.user_id === 'number' ? trip.user_id : null;
return checkPermission(action, userRow?.role ?? 'user', tripOwnerId, userId, tripOwnerId !== userId);
}
export function ok(data: unknown) {
return { content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }] };
}