fix: ( bookings ) require explicit tickets:generate to issue reservations

This commit is contained in:
Abubeker Yasin
2026-07-28 12:18:25 +03:00
parent 0d46f3715e
commit ed09bfd8b6
7 changed files with 79 additions and 12 deletions

View File

@@ -4,7 +4,7 @@ import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { seatsApi, schedulesApi, fleetApi, routeCoachTemplatesApi, bookingsApi } from '@/lib/api';
import { routesApi } from '@/lib/api/routes';
import { usePermission } from '@/lib/use-permission';
import { usePermissionStrict } from '@/lib/use-permission';
import { PERMS } from '@/lib/permissions';
import Modal from '@/components/ui/Modal';
import ActionButton from '@/components/ui/ActionButton'
@@ -44,9 +44,10 @@ export default function SeatsPage() {
const [issueBookingResult, setIssueBookingResult] = useState<{ payUrl?: string; bookingRef?: string } | null>(null);
const queryClient = useQueryClient();
// Issuing a booking off a reserved seat is admin-only (POST /bookings/reservations/:seatId/issue
// is guarded by @PassengerAdmin) — hide the ticket action for everyone else.
const canIssueBooking = usePermission(PERMS.admin);
// Issuing a booking off a reserved seat needs edr_passenger_app:tickets:generate
// (POST /bookings/reservations/:seatId/issue is guarded by @PassengerStaffStrict).
// Strict: being an admin is not enough, the permission has to be granted.
const canIssueBooking = usePermissionStrict(PERMS.tickets.generate);
const { data: schedulesData } = useQuery({
queryKey: ['schedules'],

View File

@@ -23,6 +23,7 @@ interface AuthState {
setUser: (user: AdminUser, token: string) => void;
initialize: () => void;
hasPermission: (key: string) => boolean;
hasPermissionStrict: (key: string) => boolean;
}
export const useAuthStore = create<AuthState>((set, get) => ({
@@ -123,4 +124,12 @@ export const useAuthStore = create<AuthState>((set, get) => ({
if (user.isSuperAdmin || user.isOrgAdmin) return true;
return user.permissions.includes(key);
},
// No super-admin / org-admin bypass — mirrors PassengerStaffStrict on the API,
// so we don't render actions that would 403.
hasPermissionStrict: (key: string) => {
const { user } = get();
if (!user) return false;
return user.permissions.includes(key);
},
}));

View File

@@ -13,3 +13,15 @@ import { useAuthStore } from './auth-store';
export function usePermission(key: string): boolean {
return useAuthStore((s) => s.hasPermission(key));
}
/**
* Same as usePermission but WITHOUT the super-admin / org-admin bypass — the
* permission must be explicitly granted. Use it wherever the API endpoint is
* guarded with PassengerStaffStrict, so the UI matches what the API allows.
*
* Usage:
* const canIssue = usePermissionStrict(PERMS.tickets.generate);
*/
export function usePermissionStrict(key: string): boolean {
return useAuthStore((s) => s.hasPermissionStrict(key));
}