From 4b994a59642ae54ac6efaf6dd6bbc9b7162d82e0 Mon Sep 17 00:00:00 2001 From: Dmitrii Iurco Date: Fri, 24 Apr 2026 09:39:59 -0400 Subject: [PATCH] feat: domain validation for NTP servers and mail domain fields - isValidDomain / isValidDomainOrIp helpers (RFC-compliant label regex) - network.ntp_servers: each entry validated as hostname or IP; invalid entry shown in error message - email.domain: validated as a proper domain name; blocks autosave until fixed Co-Authored-By: Claude Sonnet 4.6 --- webui/src/pages/Settings.jsx | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/webui/src/pages/Settings.jsx b/webui/src/pages/Settings.jsx index 2302ccf..c83ab7d 100644 --- a/webui/src/pages/Settings.jsx +++ b/webui/src/pages/Settings.jsx @@ -137,9 +137,22 @@ function isValidIpCidr(v) { return [a, b, c, d].every(n => n >= 0 && n <= 255) && p >= 0 && p <= 32; } -const E_PORT = 'Must be 1–65535'; -const E_IP = 'Must be a valid IP address'; -const E_CIDR = 'Must be a valid IP/CIDR (e.g. 10.0.0.1/24)'; +function isValidDomain(v) { + if (!v || !v.trim()) return false; + const s = v.trim(); + if (s.length > 253) return false; + return /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(s); +} + +function isValidDomainOrIp(v) { + const s = (v || '').trim(); + return isValidIp(s) || isValidDomain(s); +} + +const E_PORT = 'Must be 1–65535'; +const E_IP = 'Must be a valid IP address'; +const E_CIDR = 'Must be a valid IP/CIDR (e.g. 10.0.0.1/24)'; +const E_DOMAIN = 'Must be a valid domain (e.g. mail.example.com)'; function validateServiceConfig(key, data) { const errors = {}; @@ -156,6 +169,8 @@ function validateServiceConfig(key, data) { else if (parts[1]?.trim() && !isValidIp(parts[1].trim())) errors.dhcp_range = `End IP is invalid`; } + const badNtp = (data.ntp_servers || []).find(s => !isValidDomainOrIp(s)); + if (badNtp !== undefined) errors.ntp_servers = `"${badNtp}" is not a valid hostname or IP`; } if (key === 'wireguard') { port('port'); @@ -163,6 +178,7 @@ function validateServiceConfig(key, data) { } if (key === 'email') { port('smtp_port'); port('submission_port'); port('imap_port'); port('webmail_port'); + if (data.domain && !isValidDomain(data.domain)) errors.domain = E_DOMAIN; } if (key === 'calendar') port('port'); if (key === 'files') { port('port'); port('manager_port'); } @@ -291,7 +307,7 @@ function NetworkForm({ data, onChange, errors = {} }) { onChange({ ...data, dhcp_range: v })} placeholder="10.0.0.100,10.0.0.200,12h" /> - + onChange({ ...data, ntp_servers: v })} placeholder="0.pool.ntp.org" /> @@ -317,7 +333,7 @@ function WireguardForm({ data, onChange, errors = {} }) { function EmailForm({ data, onChange, errors = {} }) { return (
- + onChange({ ...data, domain: v })} placeholder="mail.example.com" />