fix(api): tighter public lot endpoint, SSO callback from config, mapped 4xx errors

- Public lot lookup: 30 requests/min per IP and only well-formed lot
  numbers (1-64 chars) reach the database.
- SSO callback URL is built from OIDC_REDIRECT_URI instead of the Host
  header; SSO cookies are cleared with the attributes they were set with.
- Logout is rate-limited like the other auth endpoints.
- Middleware errors with a 4xx status (body too large, malformed JSON)
  answer with that status instead of a 500; error log lines carry the
  path without query string.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-01 07:20:33 +02:00
1 parent eafe925d60
commit 0899576262
4 files changed
+49 -9

No files matched your search

@@ -1,16 +1,26 @@
import { Controller, Get, Param } from '@nestjs/common';
import { Controller, Get, NotFoundException, Param } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import { ApiTags } from '@nestjs/swagger';
import { Public } from '../auth/decorators/public.decorator';
import { PublicBatch, PublicService } from './public.service';
// What a printed lot number can look like - anything else is not a lot.
const LOT_NUMBER_PATTERN = /^[A-Za-z0-9._\/-]{1,64}$/;
@ApiTags('public')
@Public()
@Controller('public/batches')
export class PublicController {
constructor(private readonly publicService: PublicService) {}
// Anonymous and enumerable by design (lot numbers are printed on jars),
// so it gets a tighter per-IP limit than logged-in traffic.
@Throttle({ default: { limit: 30, ttl: 60000 } })
@Get(':lotNumber')
findByLot(@Param('lotNumber') lotNumber: string): Promise<PublicBatch> {
if (!LOT_NUMBER_PATTERN.test(lotNumber)) {
throw new NotFoundException();
}
return this.publicService.findBatchByLot(lotNumber);
}
}