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:
52
src/lib/validators/username.ts
Normal file
52
src/lib/validators/username.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Canonical username shape.
|
||||
*
|
||||
* - 2..30 characters (yes, 2 — initials like "dm" are real and the
|
||||
* director uses them)
|
||||
* - lowercase letters, digits, `.`, `_`, `-`
|
||||
* - case-insensitive uniqueness is enforced by a partial unique index on
|
||||
* LOWER(username) in migration 0054.
|
||||
*
|
||||
* The same regex lives on the DB CHECK constraint, so any insert that
|
||||
* slips past the API still gets rejected at the DB layer.
|
||||
*/
|
||||
export const USERNAME_REGEX = /^[a-z0-9._-]{2,30}$/;
|
||||
|
||||
export const usernameSchema = z
|
||||
.string()
|
||||
.transform((s) => s.trim().toLowerCase())
|
||||
.refine(
|
||||
(s) => USERNAME_REGEX.test(s),
|
||||
'Use 2–30 lowercase letters, digits, dot, underscore, or hyphen.',
|
||||
);
|
||||
|
||||
/** Reserved names that the API rejects even if the regex would accept them.
|
||||
* Keeps obvious confusables out of customer-facing URLs / mentions. */
|
||||
export const RESERVED_USERNAMES = new Set([
|
||||
'admin',
|
||||
'administrator',
|
||||
'root',
|
||||
'system',
|
||||
'support',
|
||||
'noreply',
|
||||
'no-reply',
|
||||
'help',
|
||||
'security',
|
||||
'me',
|
||||
'self',
|
||||
'api',
|
||||
'auth',
|
||||
'login',
|
||||
'logout',
|
||||
'signin',
|
||||
'signup',
|
||||
'register',
|
||||
'undefined',
|
||||
'null',
|
||||
]);
|
||||
|
||||
export function isReservedUsername(username: string): boolean {
|
||||
return RESERVED_USERNAMES.has(username.trim().toLowerCase());
|
||||
}
|
||||
Reference in New Issue
Block a user