fix(auth): stop class guards shadowing route permission keys

Nest runs class and method guards together, so a class-level view key
ANDs with every action key below it. Staff granted only an action were
denied before their key was ever checked: OCC could not fulfil wagon
transfers, dispatchers could not create a yard, and track staff could not
assign first/last-mile vehicles. Reads now carry the view key themselves,
and the warehouses baseline lists every key its routes use.
This commit is contained in:
Nathnael
2026-08-07 07:40:50 +00:00
parent 2a0810e734
commit 87e2edcbde
4 changed files with 26 additions and 5 deletions

View File

@@ -34,7 +34,9 @@ import { LastMileInvoiceService } from './last-mile-invoice.service';
@ApiTags('last-mile')
@ApiBearerAuth()
@Controller('last-mile')
@BookingStaff(FREIGHT_PERMS.lastMile.view)
// No class-level key: Nest stacks class and method guards, so a class-level
// `view` would AND with every action key below and lock out staff granted only
// an action (e.g. assign_vehicles). Each route carries its own key instead.
export class LastMileController {
constructor(
private readonly lastMileService: LastMileService,
@@ -42,6 +44,7 @@ export class LastMileController {
) {}
@Get()
@BookingStaff(FREIGHT_PERMS.lastMile.view)
@ApiOperation({ summary: 'List last-mile legs' })
findAll(
@Query('status') status?: string,
@@ -64,18 +67,21 @@ export class LastMileController {
}
@Get(':id')
@BookingStaff(FREIGHT_PERMS.lastMile.view)
@ApiOperation({ summary: 'Get a last-mile leg by ID' })
findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.lastMileService.findById(id);
}
@Get('booking/:bookingId/arrival-trucks')
@BookingStaff(FREIGHT_PERMS.lastMile.view)
@ApiOperation({ summary: "Assigned EDR last-mile trucks for a booking (arrival/exit weighing prefill)" })
arrivalTrucks(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
return this.lastMileService.arrivalTrucksForBooking(bookingId);
}
@Get('booking/:bookingId/remaining-tons')
@BookingStaff(FREIGHT_PERMS.lastMile.view)
@ApiOperation({ summary: 'Bulk drawdown: tonnage still to be hauled (total departed trucks)' })
remainingTons(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
return this.lastMileService.remainingTonsForBooking(bookingId);