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:
@@ -1,4 +1,5 @@
|
||||
'use client';
|
||||
import { formatErrorBanner } from '@/lib/api/toast-error';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Plus, X } from 'lucide-react';
|
||||
@@ -149,7 +150,7 @@ export function CustomFieldForm({ open, onOpenChange, field, onSuccess }: Custom
|
||||
onSuccess();
|
||||
onOpenChange(false);
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : 'Something went wrong';
|
||||
const message = formatErrorBanner(err);
|
||||
setError(message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -308,7 +309,7 @@ export function CustomFieldForm({ open, onOpenChange, field, onSuccess }: Custom
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||
{error && <p className="whitespace-pre-line text-sm text-destructive">{error}</p>}
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
'use client';
|
||||
import { formatErrorBanner } from '@/lib/api/toast-error';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -102,7 +103,7 @@ export function TemplateForm({ open, onOpenChange, template, onSuccess }: Templa
|
||||
onSuccess();
|
||||
onOpenChange(false);
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : 'Something went wrong';
|
||||
const message = formatErrorBanner(err);
|
||||
setError(message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -186,7 +187,9 @@ export function TemplateForm({ open, onOpenChange, template, onSuccess }: Templa
|
||||
</details>
|
||||
|
||||
{error && (
|
||||
<p className="rounded bg-destructive/10 px-3 py-2 text-sm text-destructive">{error}</p>
|
||||
<p className="whitespace-pre-line rounded bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<SheetFooter>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
'use client';
|
||||
import { formatErrorBanner } from '@/lib/api/toast-error';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -89,7 +90,7 @@ export function PortForm({ open, onOpenChange, port, onSuccess }: PortFormProps)
|
||||
onSuccess();
|
||||
onOpenChange(false);
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : 'Something went wrong';
|
||||
const message = formatErrorBanner(err);
|
||||
setError(message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -187,7 +188,7 @@ export function PortForm({ open, onOpenChange, port, onSuccess }: PortFormProps)
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||
{error && <p className="whitespace-pre-line text-sm text-destructive">{error}</p>}
|
||||
|
||||
<SheetFooter>
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
'use client';
|
||||
import { formatErrorBanner } from '@/lib/api/toast-error';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -202,7 +203,7 @@ export function RoleForm({ open, onOpenChange, role, onSuccess }: RoleFormProps)
|
||||
onSuccess();
|
||||
onOpenChange(false);
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : 'Something went wrong';
|
||||
const message = formatErrorBanner(err);
|
||||
setError(message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -283,7 +284,7 @@ export function RoleForm({ open, onOpenChange, role, onSuccess }: RoleFormProps)
|
||||
</Accordion>
|
||||
</ScrollArea>
|
||||
|
||||
{error && <p className="mt-2 text-sm text-destructive">{error}</p>}
|
||||
{error && <p className="mt-2 whitespace-pre-line text-sm text-destructive">{error}</p>}
|
||||
|
||||
<SheetFooter className="mt-4">
|
||||
<Button
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
'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 {
|
||||
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';
|
||||
|
||||
interface TagFormProps {
|
||||
@@ -21,9 +16,16 @@ interface TagFormProps {
|
||||
}
|
||||
|
||||
const PRESET_COLORS = [
|
||||
'#6B7280', '#EF4444', '#F97316', '#EAB308',
|
||||
'#22C55E', '#14B8A6', '#3B82F6', '#8B5CF6',
|
||||
'#EC4899', '#F43F5E',
|
||||
'#6B7280',
|
||||
'#EF4444',
|
||||
'#F97316',
|
||||
'#EAB308',
|
||||
'#22C55E',
|
||||
'#14B8A6',
|
||||
'#3B82F6',
|
||||
'#8B5CF6',
|
||||
'#EC4899',
|
||||
'#F43F5E',
|
||||
];
|
||||
|
||||
export function TagForm({ open, onOpenChange, tag, onSuccess }: TagFormProps) {
|
||||
@@ -54,7 +56,7 @@ export function TagForm({ open, onOpenChange, tag, onSuccess }: TagFormProps) {
|
||||
onSuccess();
|
||||
onOpenChange(false);
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : 'Something went wrong';
|
||||
const message = formatErrorBanner(err);
|
||||
setError(message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -109,10 +111,7 @@ export function TagForm({ open, onOpenChange, tag, onSuccess }: TagFormProps) {
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<div
|
||||
className="h-7 w-7 rounded-full border"
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
<div className="h-7 w-7 rounded-full border" style={{ backgroundColor: color }} />
|
||||
<Input
|
||||
value={color}
|
||||
onChange={(e) => setColor(e.target.value)}
|
||||
@@ -123,12 +122,15 @@ export function TagForm({ open, onOpenChange, tag, onSuccess }: TagFormProps) {
|
||||
</div>
|
||||
</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={() => handleOpenChange(false)} disabled={loading}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => handleOpenChange(false)}
|
||||
disabled={loading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading || !name.trim()}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
'use client';
|
||||
import { formatErrorBanner } from '@/lib/api/toast-error';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -115,7 +116,7 @@ export function UserForm({ open, onOpenChange, user, onSuccess }: UserFormProps)
|
||||
onSuccess();
|
||||
onOpenChange(false);
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : 'Something went wrong';
|
||||
const message = formatErrorBanner(err);
|
||||
setError(message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -221,7 +222,7 @@ export function UserForm({ open, onOpenChange, user, onSuccess }: UserFormProps)
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||
{error && <p className="whitespace-pre-line text-sm text-destructive">{error}</p>}
|
||||
|
||||
<SheetFooter>
|
||||
<Button
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user