mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-26 14:58:55 +08:00
fix(server): normalize mail server name (#14564)
fix #14562 fix #14226 fix #14192 #### PR Dependency Tree * **PR #14564** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * SMTP and fallback SMTP name now default to empty and will use the system hostname when not set. * HELO hostname resolution includes stricter normalization/validation for more reliable mail handshakes. * **Documentation** * Updated admin and config descriptions to explain hostname/HELO behavior and fallback. * **Tests** * Added tests covering hostname normalization and rejection of invalid HELO values. * **Chores** * Updated example env and ignore rules. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -31,8 +31,8 @@ declare global {
|
||||
|
||||
defineModuleConfig('mailer', {
|
||||
'SMTP.name': {
|
||||
desc: 'Name of the email server (e.g. your domain name)',
|
||||
default: 'AFFiNE Server',
|
||||
desc: 'Hostname used for SMTP HELO/EHLO (e.g. mail.example.com). Leave empty to use the system hostname.',
|
||||
default: '',
|
||||
env: 'MAILER_SERVERNAME',
|
||||
},
|
||||
'SMTP.host': {
|
||||
@@ -72,8 +72,8 @@ defineModuleConfig('mailer', {
|
||||
shape: z.array(z.string()),
|
||||
},
|
||||
'fallbackSMTP.name': {
|
||||
desc: 'Name of the fallback email server (e.g. your domain name)',
|
||||
default: 'AFFiNE Server',
|
||||
desc: 'Hostname used for fallback SMTP HELO/EHLO (e.g. mail.example.com). Leave empty to use the system hostname.',
|
||||
default: '',
|
||||
},
|
||||
'fallbackSMTP.host': {
|
||||
desc: 'Host of the email server (e.g. smtp.gmail.com)',
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import SMTPTransport from 'nodemailer/lib/smtp-transport';
|
||||
|
||||
import { Config, metrics, OnEvent } from '../../base';
|
||||
import { resolveSMTPHeloHostname } from './utils';
|
||||
|
||||
export type SendOptions = Omit<SendMailOptions, 'to' | 'subject' | 'html'> & {
|
||||
to: string;
|
||||
@@ -19,8 +20,10 @@ export type SendOptions = Omit<SendMailOptions, 'to' | 'subject' | 'html'> & {
|
||||
function configToSMTPOptions(
|
||||
config: AppConfig['mailer']['SMTP']
|
||||
): SMTPTransport.Options {
|
||||
const name = resolveSMTPHeloHostname(config.name);
|
||||
|
||||
return {
|
||||
name: config.name,
|
||||
...(name ? { name } : {}),
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
tls: {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { isIP } from 'node:net';
|
||||
import { hostname as getHostname } from 'node:os';
|
||||
|
||||
const hostnameLabelRegexp = /^[A-Za-z0-9-]+$/;
|
||||
|
||||
function isValidSMTPAddressLiteral(hostname: string) {
|
||||
if (!hostname.startsWith('[') || !hostname.endsWith(']')) return false;
|
||||
|
||||
const literal = hostname.slice(1, -1);
|
||||
if (!literal || literal.includes(' ')) return false;
|
||||
if (isIP(literal) === 4) return true;
|
||||
|
||||
if (literal.startsWith('IPv6:')) {
|
||||
return isIP(literal.slice('IPv6:'.length)) === 6;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function normalizeSMTPHeloHostname(hostname: string) {
|
||||
const normalized = hostname.trim().replace(/\.$/, '');
|
||||
if (!normalized) return undefined;
|
||||
if (isValidSMTPAddressLiteral(normalized)) return normalized;
|
||||
if (normalized.length > 253) return undefined;
|
||||
|
||||
const labels = normalized.split('.');
|
||||
for (const label of labels) {
|
||||
if (!label || label.length > 63) return undefined;
|
||||
if (
|
||||
!hostnameLabelRegexp.test(label) ||
|
||||
label.startsWith('-') ||
|
||||
label.endsWith('-')
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function readSystemHostname() {
|
||||
try {
|
||||
return getHostname();
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveSMTPHeloHostname(configuredName: string) {
|
||||
const normalizedConfiguredName = normalizeSMTPHeloHostname(configuredName);
|
||||
if (normalizedConfiguredName) return normalizedConfiguredName;
|
||||
return normalizeSMTPHeloHostname(readSystemHostname());
|
||||
}
|
||||
Reference in New Issue
Block a user