/** * Makes the forms check the same limits on what users submit as the server * enforces, by holding them in one place. * * @module */ /** * Read by the validators and services, which refuse what breaks a limit, and * by the forms, which say so before sending. Lengths are JavaScript string * lengths, so an emoji may count for two. The category limits are checked on * the server only. */ export const VALIDATION = { USERNAME_MIN: 1, USERNAME_MAX: 32, PASSWORD_MIN: 8, PASSWORD_MAX: 128, DUMP_TITLE_MAX: 200, /** Applies to a dump's note, which the API calls its comment. */ DUMP_COMMENT_MAX: 5000, PLAYLIST_TITLE_MAX: 100, PLAYLIST_DESCRIPTION_MAX: 2000, COMMENT_BODY_MAX: 5000, USER_DESCRIPTION_MAX: 2000, CATEGORY_NAME_MAX: 50, CATEGORY_SLUG_MAX: 50, CHAT_MESSAGE_MAX: 2000, } as const; /** * Matches a valid username, made of letters, digits and underscores within the * `VALIDATION` limits. Sign-up and renaming check it on both sides. It uses the * same `\w` that a handle's pattern ({@link HANDLE_SOURCE}) reads a name with; * only a user from another instance has the `@host` part. */ export const USERNAME_RE: RegExp = new RegExp( `^[a-zA-Z0-9_]{${VALIDATION.USERNAME_MIN},${VALIDATION.USERNAME_MAX}}$`, ); /** * The pattern of an instance's host as a handle writes it: DNS labels, and a * port for instances on one machine (`localhost:8001`). It ends on a letter or * a digit, so the period that ends a sentence after a mention isn't taken in. * {@link HANDLE_SOURCE} builds on it. */ export const HOST_SOURCE: string = "[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)*(?::\\d{1,5})?"; /** * The pattern of a handle, the name a user goes by on this instance: a local * user's username, or `name@host` for a user whose account lives on another * gerbeur (`federation-service.ts` stores it so in `users.username`). Paths, * mentions and the profile links all read names by it, through * {@link HANDLE_RE}, `MENTION_RE` (shared/mention.ts) and `parsePagePath` * (shared/paths.ts). */ export const HANDLE_SOURCE: string = `\\w+(?:@${HOST_SOURCE})?`; /** Matches a whole handle ({@link HANDLE_SOURCE}). */ export const HANDLE_RE: RegExp = new RegExp(`^${HANDLE_SOURCE}$`); /** Matches a whole host ({@link HOST_SOURCE}). */ export const HOST_RE: RegExp = new RegExp(`^${HOST_SOURCE}$`); /** * Returns the name and, for a user from another instance, the host of * `handle`. `splitHandle("alice@music.example")` is * `{ name: "alice", host: "music.example" }`; a local name has no host. */ export function splitHandle(handle: string): { name: string; host?: string } { const at = handle.indexOf("@"); return at < 0 ? { name: handle } : { name: handle.slice(0, at), host: handle.slice(at + 1) }; } /** * How often a dump's or a playlist's slug may change, counted from the aliases * the changes left: 3 times a day. Moderators are exempt. slug-service refuses * a change past it with a 429, and the edit forms say what the limit is. */ export const RENAME_LIMITS = { SLUG: { count: 3, windowMs: 24 * 3_600_000 }, } as const; /** * Matches an id the server makes with `crypto.randomUUID()`. A route checks * one before it names a file on disk, and shared/attachment.ts finds the * attachments a text links by it. */ export const UUID_RE: RegExp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; /** * Matches an email address well enough to catch a typo; only the reset email * arriving proves it. */ export const EMAIL_RE: RegExp = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; /** * The largest file a file dump may hold, in bytes. dump-service refuses a * bigger one, and the create form before uploading it. */ export const DUMP_MAX_FILE_SIZE_BYTES: number = 50 * 1024 * 1024;