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 <noreply@anthropic.com>
This commit is contained in:
@@ -137,9 +137,22 @@ function isValidIpCidr(v) {
|
||||
return [a, b, c, d].every(n => n >= 0 && n <= 255) && p >= 0 && p <= 32;
|
||||
}
|
||||
|
||||
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 = {} }) {
|
||||
<Field label="DHCP Range" hint="e.g. 10.0.0.100,10.0.0.200,12h" error={errors.dhcp_range}>
|
||||
<TextInput value={data.dhcp_range} onChange={(v) => onChange({ ...data, dhcp_range: v })} placeholder="10.0.0.100,10.0.0.200,12h" />
|
||||
</Field>
|
||||
<Field label="NTP Servers">
|
||||
<Field label="NTP Servers" hint="Hostnames or IPs" error={errors.ntp_servers}>
|
||||
<TagList value={data.ntp_servers || []} onChange={(v) => onChange({ ...data, ntp_servers: v })} placeholder="0.pool.ntp.org" />
|
||||
</Field>
|
||||
</div>
|
||||
@@ -317,7 +333,7 @@ function WireguardForm({ data, onChange, errors = {} }) {
|
||||
function EmailForm({ data, onChange, errors = {} }) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<Field label="Mail Domain">
|
||||
<Field label="Mail Domain" error={errors.domain}>
|
||||
<TextInput value={data.domain} onChange={(v) => onChange({ ...data, domain: v })} placeholder="mail.example.com" />
|
||||
</Field>
|
||||
<Field label="SMTP Port" hint="MTA-to-MTA (default 25)" error={errors.smtp_port}>
|
||||
|
||||
Reference in New Issue
Block a user