fix(audit-tier-2-routes): manual NextResponse.json error sweep + admin form banners

Two final waves of error-surface hygiene closing the audit's MED §12 +
HIGH §15 + HIGH §17 findings:

* 50 route files swept (61 sites): manual NextResponse.json({error,
  status: 4xx|5xx}) early-returns replaced by typed throws +
  errorResponse(err) at the catch.
  - Super-admin gates (13 sites) use new requireSuperAdmin(ctx, action)
    helper from src/lib/api/helpers.ts so denials hit the audit log.
  - Path-param + body validation 400s become ValidationError throws.
  - 404s become NotFoundError or CodedError('NOT_FOUND') for AI
    feature-flag paths.
  - 11 manual 5xx returns now re-throw so error_events captures the
    request-id (the admin error inspector becomes usable from real
    incidents).
  - website-analytics 200-with-error anti-pattern flipped to 409 +
    UMAMI_NOT_CONFIGURED. 502 upstream paths use UMAMI_UPSTREAM_ERROR.
  - 11 sites intentionally preserved: storage/[token] anti-enumeration
    token-failure paths, webhook-secret 401, "Unknown port" 400 in
    public intake.

* 7 admin forms (roles, users, ports, webhooks, custom-fields,
  document-templates, tags) gain a formatErrorBanner() helper from
  src/lib/api/toast-error.ts that builds a multi-line "Error code / Reference ID"
  banner — the rep can copy the request id when reporting a failed
  save.  Banners get whitespace-pre-line so newlines render.

Test status: 1168/1168 vitest, tsc clean.

Refs: docs/audit-comprehensive-2026-05-05.md MED §12 (auditor-F Issue 1)
+ HIGH §15 (auditor-F Issue 2) + HIGH §17 (auditor-H Issue 2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-05 20:36:59 +02:00
parent fc7595faf8
commit d3a6a9beef
58 changed files with 529 additions and 558 deletions

View File

@@ -1,17 +1,12 @@
'use client';
import { formatErrorBanner } from '@/lib/api/toast-error';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetFooter,
} from '@/components/ui/sheet';
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetFooter } from '@/components/ui/sheet';
import { apiFetch } from '@/lib/api/client';
import { WebhookEventSelector } from './webhook-event-selector';
import { WebhookSecretDisplay } from './webhook-secret-display';
@@ -64,7 +59,7 @@ export function WebhookForm({ open, onOpenChange, webhook, onSuccess }: WebhookF
onSuccess();
}
} catch (err: unknown) {
const message = err instanceof Error ? err.message : 'Something went wrong';
const message = formatErrorBanner(err);
setError(message);
} finally {
setLoading(false);
@@ -82,7 +77,12 @@ export function WebhookForm({ open, onOpenChange, webhook, onSuccess }: WebhookF
}
return (
<Sheet open={open} onOpenChange={(o) => { if (!o) handleClose(); }}>
<Sheet
open={open}
onOpenChange={(o) => {
if (!o) handleClose();
}}
>
<SheetContent className="w-full sm:max-w-xl overflow-y-auto">
<SheetHeader>
<SheetTitle>{isEdit ? 'Edit Webhook' : 'New Webhook'}</SheetTitle>
@@ -92,7 +92,9 @@ export function WebhookForm({ open, onOpenChange, webhook, onSuccess }: WebhookF
<div className="mt-6 space-y-4">
<p className="text-sm">Webhook created successfully.</p>
<WebhookSecretDisplay plaintext={createdSecret} masked="" />
<Button onClick={handleClose} className="w-full">Done</Button>
<Button onClick={handleClose} className="w-full">
Done
</Button>
</div>
) : (
<form onSubmit={handleSubmit} className="mt-6 space-y-6">
@@ -126,21 +128,20 @@ export function WebhookForm({ open, onOpenChange, webhook, onSuccess }: WebhookF
</div>
<div className="flex items-center gap-3">
<Switch
id="webhook-active"
checked={isActive}
onCheckedChange={setIsActive}
/>
<Switch id="webhook-active" checked={isActive} onCheckedChange={setIsActive} />
<Label htmlFor="webhook-active">Active</Label>
</div>
{error && <p className="text-sm text-destructive">{error}</p>}
{error && <p className="whitespace-pre-line text-sm text-destructive">{error}</p>}
<SheetFooter>
<Button type="button" variant="outline" onClick={handleClose} disabled={loading}>
Cancel
</Button>
<Button type="submit" disabled={loading || !name.trim() || !url.trim() || events.length === 0}>
<Button
type="submit"
disabled={loading || !name.trim() || !url.trim() || events.length === 0}
>
{loading ? 'Saving...' : isEdit ? 'Save Changes' : 'Create Webhook'}
</Button>
</SheetFooter>