export interface AdmissionMessages { registrationMessage: string; groupAccessDeniedMessage: string; vpnDeniedMessage: string; scheduledAccessDeniedMessage: string; } export type AdmissionDenialReason = | "not_registered" | "group_access_disabled" | "schedule_disallowed" | "anonymized_network_disallowed"; export const DEFAULT_ADMISSION_MESSAGES: AdmissionMessages = { registrationMessage: "Please register your Minecraft account before joining.", groupAccessDeniedMessage: "Your account group does not currently have server access. Contact a host if you believe this is a mistake.", vpnDeniedMessage: "VPN, proxy, and Tor connections are not allowed. Contact a host to request an exception.", scheduledAccessDeniedMessage: "Your group is only allowed access from {next_start} to {next_end}.", } as const; export function admissionDenialMessage(reason: AdmissionDenialReason, messages: AdmissionMessages) { if (reason === "not_registered") return messages.registrationMessage; if (reason === "group_access_disabled") return messages.groupAccessDeniedMessage; if (reason === "schedule_disallowed") return messages.scheduledAccessDeniedMessage; return messages.vpnDeniedMessage; } const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/; const TEMPLATE_VARIABLE = /\{([a-z_]+)\}/g; const COMMON_VARIABLES = new Set(["player", "group"]); const SCHEDULE_VARIABLES = new Set(["player", "group", "next_start", "next_end"]); type MessageName = keyof AdmissionMessages; function messageValue(formData: FormData, name: MessageName, allowedVariables: Set) { const value = String(formData.get(name) ?? "").trim(); if (value.length < 10 || value.length > 500 || CONTROL_CHARACTERS.test(value)) return null; const withoutVariables = value.replace(TEMPLATE_VARIABLE, (match, variable: string) => allowedVariables.has(variable) ? "" : match); return /[{}]/.test(withoutVariables) ? null : value; } export function parseAdmissionMessages(formData: FormData) { const registrationMessage = messageValue(formData, "registrationMessage", COMMON_VARIABLES); const groupAccessDeniedMessage = messageValue(formData, "groupAccessDeniedMessage", COMMON_VARIABLES); const vpnDeniedMessage = messageValue(formData, "vpnDeniedMessage", COMMON_VARIABLES); const scheduledAccessDeniedMessage = messageValue(formData, "scheduledAccessDeniedMessage", SCHEDULE_VARIABLES); if (!registrationMessage || !groupAccessDeniedMessage || !vpnDeniedMessage || !scheduledAccessDeniedMessage) return null; return { registrationMessage, groupAccessDeniedMessage, vpnDeniedMessage, scheduledAccessDeniedMessage }; } export function renderAdmissionMessage(template: string, variables: Record) { return template.replace(TEMPLATE_VARIABLE, (match, variable: string) => variables[variable] ?? match); }