diff --git a/apps/super-admin/src/components/EmailDnsModal.tsx b/apps/super-admin/src/components/EmailDnsModal.tsx index e884d12..1319398 100644 --- a/apps/super-admin/src/components/EmailDnsModal.tsx +++ b/apps/super-admin/src/components/EmailDnsModal.tsx @@ -14,28 +14,27 @@ interface EmailDnsModalProps { onClose: () => void } +const DEFAULT_ED25519_SELECTOR = 'v1-ed25519-20260901' +const DEFAULT_RSA_SELECTOR = 'v1-rsa-20260901' + function pasteIncludesDomainkey(paste: string) { return /[a-z0-9][a-z0-9-]*\._domainkey/i.test(paste) } -function keyReady(paste: string, selector: string) { - const trimmed = paste.trim() - if (!trimmed) return false - return pasteIncludesDomainkey(trimmed) || Boolean(selector.trim()) -} - -/** Empty = skip Ed25519; otherwise same rules as RSA. */ -function optionalKeyReady(paste: string, selector: string) { - if (!paste.trim()) return true - return keyReady(paste, selector) +/** Prefer typed selector; otherwise default when the paste has no …._domainkey. */ +function resolveSelector(paste: string, selector: string, fallback: string) { + const typed = selector.trim() + if (typed) return typed + if (pasteIncludesDomainkey(paste)) return '' + return fallback } export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) { const { showToast } = useToast() const [dnsProvider, setDnsProvider] = useState('arvan') - const [ed25519Selector, setEd25519Selector] = useState('') + const [ed25519Selector, setEd25519Selector] = useState(DEFAULT_ED25519_SELECTOR) const [ed25519Key, setEd25519Key] = useState('') - const [rsaSelector, setRsaSelector] = useState('') + const [rsaSelector, setRsaSelector] = useState(DEFAULT_RSA_SELECTOR) const [rsaKey, setRsaKey] = useState('') const [submitting, setSubmitting] = useState(false) const [error, setError] = useState('') @@ -43,9 +42,9 @@ export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) { useEffect(() => { if (!domain) return setDnsProvider('arvan') - setEd25519Selector('') + setEd25519Selector(DEFAULT_ED25519_SELECTOR) setEd25519Key('') - setRsaSelector('') + setRsaSelector(DEFAULT_RSA_SELECTOR) setRsaKey('') setError('') setSubmitting(false) @@ -53,23 +52,50 @@ export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) { if (!domain) return null const target = domain - const canSubmit = - keyReady(rsaKey, rsaSelector) && - optionalKeyReady(ed25519Key, ed25519Selector) + const canSubmit = Boolean(rsaKey.trim()) async function submit() { + if (!rsaKey.trim()) { + const message = 'Paste the RSA DKIM TXT value from Stalwart.' + setError(message) + showToast(message, 'error') + return + } + + const ed25519Paste = ed25519Key.trim() + const rsaPaste = rsaKey.trim() + const resolvedRsaSelector = resolveSelector( + rsaPaste, + rsaSelector, + DEFAULT_RSA_SELECTOR, + ) + const resolvedEd25519Selector = ed25519Paste + ? resolveSelector( + ed25519Paste, + ed25519Selector, + DEFAULT_ED25519_SELECTOR, + ) + : '' + + // Show resolved defaults in the fields if they were cleared. + if (!rsaSelector.trim() && resolvedRsaSelector) { + setRsaSelector(resolvedRsaSelector) + } + if (ed25519Paste && !ed25519Selector.trim() && resolvedEd25519Selector) { + setEd25519Selector(resolvedEd25519Selector) + } + setSubmitting(true) setError('') try { - const ed25519Paste = ed25519Key.trim() const result = await applyMailDns(target.id, { dnsProvider, - rsaKey, + rsaKey: rsaPaste, + ...(resolvedRsaSelector ? { rsaSelector: resolvedRsaSelector } : {}), ...(ed25519Paste ? { ed25519Key: ed25519Paste } : {}), - ...(ed25519Paste && ed25519Selector.trim() - ? { ed25519Selector: ed25519Selector.trim() } + ...(ed25519Paste && resolvedEd25519Selector + ? { ed25519Selector: resolvedEd25519Selector } : {}), - ...(rsaSelector.trim() ? { rsaSelector: rsaSelector.trim() } : {}), }) showToast(result.message, 'success') onClose() @@ -95,14 +121,14 @@ export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) {

Paste the RSA DKIM TXT value (v=DKIM1; …) from Stalwart, or the full zone line. Ed25519 is optional — leave it empty to - skip. For value-only pastes, fill the selector (for example{' '} - v1-rsa-20260901). We upsert common mail records on{' '} - {dnsProvider === 'cloudflare' ? 'Cloudflare' : 'Arvan'} (DNS-only / not - proxied): mail A → 185.214.101.41, MX → mail.meshkee.com - (priority 10), SPF v=spf1 mx -all, DKIM (RSA required, - Ed25519 if provided), autoconfig / autodiscover → mail.meshkee.com. Extra - MX, dkim._domainkey, and _dmarc are - removed. + skip. Selectors default to {DEFAULT_RSA_SELECTOR} /{' '} + {DEFAULT_ED25519_SELECTOR} when empty. We upsert common mail + records on {dnsProvider === 'cloudflare' ? 'Cloudflare' : 'Arvan'}{' '} + (DNS-only / not proxied): mail A → 185.214.101.41, MX → + mail.meshkee.com (priority 10), SPF v=spf1 mx -all, DKIM + (RSA required, Ed25519 if provided), autoconfig / autodiscover → + mail.meshkee.com. Extra MX, dkim._domainkey, and{' '} + _dmarc are removed.

{error ? (

@@ -134,14 +160,14 @@ export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) { type="text" value={ed25519Selector} onChange={(e) => setEd25519Selector(e.target.value)} - placeholder="v1-ed25519-20260901" + placeholder={DEFAULT_ED25519_SELECTOR} disabled={submitting} spellCheck={false} dir="ltr" autoComplete="off" />

- Only needed with an Ed25519 paste that has no …._domainkey. + Auto-filled when empty. Change it if Stalwart uses a different selector.

@@ -170,15 +196,14 @@ export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) { type="text" value={rsaSelector} onChange={(e) => setRsaSelector(e.target.value)} - placeholder="v1-rsa-20260901" + placeholder={DEFAULT_RSA_SELECTOR} disabled={submitting} spellCheck={false} dir="ltr" autoComplete="off" />

- Needed when pasting only the TXT value. Optional if the paste includes{' '} - …._domainkey. + Auto-filled when empty. Change it if Stalwart uses a different selector.