Files
pn-new-crm/src/lib/validators/username.ts

53 lines
1.3 KiB
TypeScript
Raw Normal View History

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 230 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());
}