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:
@@ -14,8 +14,12 @@ import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { BrandedAuthShell } from '@/components/shared/branded-auth-shell';
|
||||
|
||||
// `identifier` accepts either an email address or a username (3–30 lowercase
|
||||
// letters / digits / dot / underscore / hyphen). The page resolves usernames
|
||||
// to the canonical Better-Auth email via /api/auth/resolve-identifier before
|
||||
// the actual sign-in call.
|
||||
const loginSchema = z.object({
|
||||
email: z.string().email('Please enter a valid email address'),
|
||||
identifier: z.string().min(1, 'Email or username is required'),
|
||||
password: z.string().min(1, 'Password is required'),
|
||||
});
|
||||
|
||||
@@ -36,13 +40,29 @@ export default function LoginPage() {
|
||||
async function onSubmit(data: LoginFormData) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// Resolve username → email when the input isn't already an email.
|
||||
// The endpoint always returns SOMETHING (the input itself on miss)
|
||||
// so the auth call below fails uniformly with "invalid credentials"
|
||||
// either way — no username enumeration.
|
||||
const identifier = data.identifier.trim();
|
||||
let email = identifier;
|
||||
if (!identifier.includes('@')) {
|
||||
const res = await fetch('/api/auth/resolve-identifier', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ identifier }),
|
||||
});
|
||||
const payload = (await res.json().catch(() => ({}))) as { email?: string };
|
||||
email = payload.email?.trim() || identifier;
|
||||
}
|
||||
|
||||
const result = await authClient.signIn.email({
|
||||
email: data.email,
|
||||
email,
|
||||
password: data.password,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
toast.error(result.error.message ?? 'Invalid email or password');
|
||||
toast.error(result.error.message ?? 'Invalid credentials');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,17 +83,21 @@ export default function LoginPage() {
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4" noValidate>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Label htmlFor="identifier">Email or username</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
placeholder="you@example.com"
|
||||
id="identifier"
|
||||
type="text"
|
||||
autoComplete="username"
|
||||
autoCapitalize="none"
|
||||
spellCheck={false}
|
||||
placeholder="you@example.com or yourname"
|
||||
disabled={isLoading}
|
||||
className={cn(errors.email && 'border-destructive focus-visible:ring-destructive')}
|
||||
{...register('email')}
|
||||
className={cn(errors.identifier && 'border-destructive focus-visible:ring-destructive')}
|
||||
{...register('identifier')}
|
||||
/>
|
||||
{errors.email && <p className="text-sm text-destructive">{errors.email.message}</p>}
|
||||
{errors.identifier && (
|
||||
<p className="text-sm text-destructive">{errors.identifier.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
|
||||
Reference in New Issue
Block a user