audit: 33-agent comprehensive audit + critical fixes

Full team audit run, all reports verbatim in docs/AUDIT-2026-05-12.md
(5900+ lines, 30+ critical findings). Already-fixed this commit:
- permission-overrides PUT: self-target block + RolePermissions allow-list + cross-tenant guard
- /api/auth/resolve-identifier: rate-limit + synthetic miss-email kill enumeration
- admin email-change: rotates account.accountId + revokes sessions
- middleware: token-gated email confirm/cancel routes whitelisted
- NAV_CATALOG: 10 dead-link sweeps to existing /admin/<x> targets

Feature work landing same commit: optional username sign-in
(migration 0054), per-user permission overrides (0055) with three-state
matrix tabbed inside UserForm, user disable button, role + outcome +
stage label normalisation across the platform, admin email-change
with auto-notification template.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 16:52:35 +02:00
parent 660553c074
commit 4b9743a594
31 changed files with 7042 additions and 81 deletions

View File

@@ -2,7 +2,6 @@
import { formatErrorBanner } from '@/lib/api/toast-error';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
@@ -15,6 +14,8 @@ import {
} from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetFooter } from '@/components/ui/sheet';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { UserPermissionMatrix } from './user-permission-matrix';
import {
AlertDialog,
AlertDialogAction,
@@ -26,7 +27,6 @@ import {
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { PhoneInput, type PhoneInputValue } from '@/components/shared/phone-input';
import { useUIStore } from '@/stores/ui-store';
import { apiFetch } from '@/lib/api/client';
import { formatRole } from '@/lib/constants';
@@ -69,7 +69,6 @@ export function UserForm({ open, onOpenChange, user, onSuccess }: UserFormProps)
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const portSlug = useUIStore((s) => s.currentPortSlug);
const isEdit = !!user;
const fullName = `${firstName} ${lastName}`.trim();
@@ -192,7 +191,28 @@ export function UserForm({ open, onOpenChange, user, onSuccess }: UserFormProps)
<SheetTitle>{isEdit ? 'Edit User' : 'New User'}</SheetTitle>
</SheetHeader>
<form onSubmit={handleSubmit} className="mt-6 space-y-4">
<Tabs defaultValue="profile" className="mt-6">
<TabsList className="w-full">
<TabsTrigger value="profile" className="flex-1">
Profile &amp; role
</TabsTrigger>
<TabsTrigger value="permissions" className="flex-1" disabled={!isEdit}>
Permissions
</TabsTrigger>
</TabsList>
<TabsContent value="permissions" className="mt-4">
{isEdit ? (
<UserPermissionMatrix userId={user.userId} />
) : (
<p className="text-sm text-muted-foreground">
Save the new user first, then return here to fine-tune their permissions.
</p>
)}
</TabsContent>
<TabsContent value="profile" className="mt-4">
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-2 gap-3">
<div className="space-y-2">
<Label htmlFor="user-first-name">First name</Label>
@@ -320,23 +340,6 @@ export function UserForm({ open, onOpenChange, user, onSuccess }: UserFormProps)
</div>
)}
{isEdit && portSlug && (
<div className="rounded-lg border bg-muted/30 p-3">
<p className="text-sm font-medium">Fine-tuned permissions</p>
<p className="text-xs text-muted-foreground">
The selected role grants a baseline. To add or remove a specific permission for
this user only, open the role &amp; permissions page.
</p>
<Link
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
href={`/${portSlug}/admin/roles?focusUser=${user.userId}` as any}
className="mt-2 inline-block text-xs font-medium text-primary hover:underline"
>
Manage permissions
</Link>
</div>
)}
{error && <p className="whitespace-pre-line text-sm text-destructive">{error}</p>}
<SheetFooter>
@@ -353,6 +356,8 @@ export function UserForm({ open, onOpenChange, user, onSuccess }: UserFormProps)
</Button>
</SheetFooter>
</form>
</TabsContent>
</Tabs>
<AlertDialog open={emailConfirmOpen} onOpenChange={setEmailConfirmOpen}>
<AlertDialogContent>