L10b Betrieb & Aufräumen: /api/v1 über gemeinsamen Adapter, einheitliches Fehlerformat, Rate Limiting

Aufräumpunkt a: Die lane-lokalen API-Kontexte (imports/_context.ts, sync/api-context.ts,
reports/http.ts, work-orders/_http.ts mit moduleGuard) sind entfernt. Alle v1-Routen laufen über
requireApiContext (DB-autoritative Rechte, 401/403) und withApi/toErrorResponse (respond.ts):
- Fehlerformat überall { error: { code, message, details? } }; invalid und blocked → 422,
  conflict → 409, payload_too_large → 413, rate_limited → 429 + Retry-After.
- Same-Origin-Prüfung in withApi für jede Mutation vor der Anmeldung (vorher fehlte sie bei
  imports, reports und work-orders).
- Rate Limiting je Nutzer mit rate-limit.ts: api (API_RATE_LIMIT_PER_MINUTE, 300/min) und
  apiField für sync/uploads/field (API_FIELD_RATE_LIMIT_PER_MINUTE, 1200/min).
- Clients angepasst: Import-Uploader liest das neue Fehlerformat, Upload/Outbox werten 422 als
  endgültig ungültig (429 bleibt transient mit Backoff).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 18:19:19 +02:00
co-authored by Claude Opus 5
parent a7d4b02a13
commit fb993a7730
29 changed files with 333 additions and 483 deletions
+10
View File
@@ -40,6 +40,12 @@ function keyOf(scope: string, identifier: string): string {
export type RateLimitRule = { limit: number; windowMs: number };
/** Positive integer from the environment, otherwise the default (read once at module load). */
function perMinute(name: string, fallback: number): number {
const v = Number(process.env[name]);
return Number.isInteger(v) && v > 0 ? v : fallback;
}
/** Voreinstellungen der sicherheitskritischen Aktionen (SEC2 §6). */
export const RATE_LIMITS = {
/** Reset-Anfrage: 5 pro Stunde je IP und je Konto. */
@@ -50,6 +56,10 @@ export const RATE_LIMITS = {
passwordVerify: { limit: 10, windowMs: 15 * 60_000 },
/** Anforderung einer E-Mail-Änderung. */
emailChangeRequest: { limit: 5, windowMs: 60 * 60_000 },
/** L10b: /api/v1/** je Nutzer (Integrationen, Backoffice-Clients). */
api: { limit: perMinute("API_RATE_LIMIT_PER_MINUTE", 300), windowMs: 60_000 },
/** L10b: Einsatz-/Sync-Endpunkte (/sync, /uploads, /field/**) je Nutzer — großzügig (Outbox, Vorab-Download). */
apiField: { limit: perMinute("API_FIELD_RATE_LIMIT_PER_MINUTE", 1200), windowMs: 60_000 },
} as const satisfies Record<string, RateLimitRule>;
export type RateLimitScope = keyof typeof RATE_LIMITS;