Add option to ignore SSL/TLS certificate errors for email (Issue #53)
This feature allows users with non-standard SMTP setups (shared hosting, self-signed certificates) to bypass certificate validation when needed. Changes: - Add database migration for tls_reject_unauthorized column - Update emailProcessor.js to pass TLS option to nodemailer - Update adminEmail.js routes to handle the new field - Add checkbox UI with security warning in EmailConfigPage - Add English and German translations
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
const { addColumnIfNotExists } = require('../helpers');
|
||||||
|
|
||||||
|
exports.up = async function up(knex) {
|
||||||
|
// Add tls_reject_unauthorized column to email_configs table
|
||||||
|
// Default is true (validate certificates), false means ignore SSL/TLS certificate errors
|
||||||
|
await addColumnIfNotExists(knex, 'email_configs', 'tls_reject_unauthorized', (table) => {
|
||||||
|
table.boolean('tls_reject_unauthorized').defaultTo(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = async function down(knex) {
|
||||||
|
const hasColumn = await knex.schema.hasColumn('email_configs', 'tls_reject_unauthorized');
|
||||||
|
if (hasColumn) {
|
||||||
|
await knex.schema.alterTable('email_configs', (table) => {
|
||||||
|
table.dropColumn('tls_reject_unauthorized');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -18,7 +18,8 @@ router.get('/config', adminAuth, async (req, res) => {
|
|||||||
smtp_user: '',
|
smtp_user: '',
|
||||||
smtp_pass: '', // Don't send actual password
|
smtp_pass: '', // Don't send actual password
|
||||||
from_email: '',
|
from_email: '',
|
||||||
from_name: ''
|
from_name: '',
|
||||||
|
tls_reject_unauthorized: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +54,8 @@ router.post('/config', [
|
|||||||
smtp_user,
|
smtp_user,
|
||||||
smtp_pass,
|
smtp_pass,
|
||||||
from_email,
|
from_email,
|
||||||
from_name
|
from_name,
|
||||||
|
tls_reject_unauthorized
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
// Check if config exists
|
// Check if config exists
|
||||||
@@ -66,6 +68,7 @@ router.post('/config', [
|
|||||||
smtp_user: smtp_user || '',
|
smtp_user: smtp_user || '',
|
||||||
from_email,
|
from_email,
|
||||||
from_name: from_name || 'Photo Sharing',
|
from_name: from_name || 'Photo Sharing',
|
||||||
|
tls_reject_unauthorized: tls_reject_unauthorized !== false, // Default to true
|
||||||
updated_at: new Date()
|
updated_at: new Date()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -137,6 +140,10 @@ router.post('/test', adminAuth, async (req, res) => {
|
|||||||
user: config.smtp_user,
|
user: config.smtp_user,
|
||||||
pass: config.smtp_pass
|
pass: config.smtp_pass
|
||||||
} : undefined,
|
} : undefined,
|
||||||
|
tls: {
|
||||||
|
// Allow ignoring SSL certificate errors when tls_reject_unauthorized is false
|
||||||
|
rejectUnauthorized: config.tls_reject_unauthorized !== false
|
||||||
|
},
|
||||||
logger: process.env.NODE_ENV === 'development',
|
logger: process.env.NODE_ENV === 'development',
|
||||||
debug: process.env.NODE_ENV === 'development'
|
debug: process.env.NODE_ENV === 'development'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ let lastConfigHash = null;
|
|||||||
// Generate hash from config for change detection
|
// Generate hash from config for change detection
|
||||||
function generateConfigHash(config) {
|
function generateConfigHash(config) {
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const configString = `${config.smtp_host}:${config.smtp_port}:${config.smtp_user}:${config.smtp_pass}:${config.smtp_secure}`;
|
const configString = `${config.smtp_host}:${config.smtp_port}:${config.smtp_user}:${config.smtp_pass}:${config.smtp_secure}:${config.tls_reject_unauthorized}`;
|
||||||
return crypto.createHash('md5').update(configString).digest('hex');
|
return crypto.createHash('md5').update(configString).digest('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,11 @@ async function initializeTransporter(forceReinit = false) {
|
|||||||
auth: config.smtp_user ? {
|
auth: config.smtp_user ? {
|
||||||
user: config.smtp_user,
|
user: config.smtp_user,
|
||||||
pass: config.smtp_pass
|
pass: config.smtp_pass
|
||||||
} : undefined
|
} : undefined,
|
||||||
|
tls: {
|
||||||
|
// Allow ignoring SSL certificate errors when tls_reject_unauthorized is false
|
||||||
|
rejectUnauthorized: config.tls_reject_unauthorized !== false
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verify configuration
|
// Verify configuration
|
||||||
|
|||||||
@@ -1327,7 +1327,34 @@
|
|||||||
"saveConfiguration": "Konfiguration speichern",
|
"saveConfiguration": "Konfiguration speichern",
|
||||||
"emailTemplates": "E-Mail-Vorlagen",
|
"emailTemplates": "E-Mail-Vorlagen",
|
||||||
"templateVariables": "Verfügbare Variablen",
|
"templateVariables": "Verfügbare Variablen",
|
||||||
"previewTemplate": "Vorlage anzeigen"
|
"previewTemplate": "Vorlage anzeigen",
|
||||||
|
"smtpSettings": "SMTP-Einstellungen",
|
||||||
|
"testEmailSuccess": "Test-E-Mail erfolgreich gesendet",
|
||||||
|
"saveSmtpSettings": "SMTP-Einstellungen speichern",
|
||||||
|
"testEmailSection": "E-Mail testen",
|
||||||
|
"beforeTesting": "Vor dem Testen:",
|
||||||
|
"saveSmtpFirst": "Speichern Sie zuerst Ihre SMTP-Einstellungen",
|
||||||
|
"ensureFirewall": "Stellen Sie sicher, dass Ihre Firewall ausgehende SMTP-Verbindungen erlaubt",
|
||||||
|
"gmailAppPassword": "Für Gmail verwenden Sie ein App-spezifisches Passwort",
|
||||||
|
"testEmailAddressLabel": "Test-E-Mail-Adresse",
|
||||||
|
"sendTestEmailButton": "Test-E-Mail senden",
|
||||||
|
"commonSmtpSettings": "Häufige SMTP-Einstellungen:",
|
||||||
|
"editTemplate": "Vorlage bearbeiten",
|
||||||
|
"templateName": "Vorlagenname",
|
||||||
|
"subjectLine": "Betreffzeile",
|
||||||
|
"emailBody": "E-Mail-Text",
|
||||||
|
"preview": "Vorschau",
|
||||||
|
"saveChanges": "Änderungen speichern",
|
||||||
|
"templates": "Vorlagen",
|
||||||
|
"variableHelp": "Verwenden Sie diese Variablen in Ihrer Vorlage. Sie werden beim Senden durch tatsächliche Werte ersetzt.",
|
||||||
|
"port": "Port",
|
||||||
|
"security": "Sicherheit",
|
||||||
|
"username": "Benutzername",
|
||||||
|
"password": "Passwort",
|
||||||
|
"enterPassword": "Passwort eingeben",
|
||||||
|
"required": "erforderlich",
|
||||||
|
"ignoreSslErrors": "SSL/TLS-Zertifikatfehler ignorieren",
|
||||||
|
"ignoreSslWarning": "Warnung: Das Deaktivieren der Zertifikatüberprüfung macht die Verbindung anfällig für Man-in-the-Middle-Angriffe. Aktivieren Sie dies nur, wenn Sie dem SMTP-Server vertrauen und die Sicherheitsrisiken verstehen."
|
||||||
},
|
},
|
||||||
"cms": {
|
"cms": {
|
||||||
"title": "CMS-Seiten",
|
"title": "CMS-Seiten",
|
||||||
|
|||||||
@@ -1072,7 +1072,9 @@
|
|||||||
"enterPassword": "Enter password",
|
"enterPassword": "Enter password",
|
||||||
"fromEmail": "From Email",
|
"fromEmail": "From Email",
|
||||||
"fromName": "From Name",
|
"fromName": "From Name",
|
||||||
"required": "required"
|
"required": "required",
|
||||||
|
"ignoreSslErrors": "Ignore SSL/TLS certificate errors",
|
||||||
|
"ignoreSslWarning": "Warning: Disabling certificate verification makes the connection vulnerable to man-in-the-middle attacks. Only enable this if you trust the SMTP server and understand the security implications."
|
||||||
},
|
},
|
||||||
"cms": {
|
"cms": {
|
||||||
"title": "CMS Pages",
|
"title": "CMS Pages",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Mail,
|
Mail,
|
||||||
Save,
|
Save,
|
||||||
Send,
|
Send,
|
||||||
Server,
|
Server,
|
||||||
Lock,
|
Lock,
|
||||||
User,
|
User,
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
CheckCircle,
|
CheckCircle,
|
||||||
Eye,
|
Eye,
|
||||||
EyeOff,
|
EyeOff,
|
||||||
|
ShieldAlert,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
@@ -107,7 +108,8 @@ export const EmailConfigPage: React.FC = () => {
|
|||||||
smtp_user: '',
|
smtp_user: '',
|
||||||
smtp_pass: '',
|
smtp_pass: '',
|
||||||
from_email: '',
|
from_email: '',
|
||||||
from_name: 'Photo Sharing'
|
from_name: 'Photo Sharing',
|
||||||
|
tls_reject_unauthorized: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch SMTP config
|
// Fetch SMTP config
|
||||||
@@ -342,7 +344,7 @@ export const EmailConfigPage: React.FC = () => {
|
|||||||
placeholder="587"
|
placeholder="587"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-neutral-700 mb-1">
|
<label className="block text-sm font-medium text-neutral-700 mb-1">
|
||||||
{t('email.security')}
|
{t('email.security')}
|
||||||
@@ -358,6 +360,30 @@ export const EmailConfigPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Ignore SSL Certificate Errors */}
|
||||||
|
<div className="mt-2">
|
||||||
|
<label className="flex items-center gap-3 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={!smtpConfig.tls_reject_unauthorized}
|
||||||
|
onChange={(e) => setSmtpConfig(prev => ({ ...prev, tls_reject_unauthorized: !e.target.checked }))}
|
||||||
|
className="w-4 h-4 text-primary-600 border-neutral-300 rounded focus:ring-primary-500"
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-medium text-neutral-700">
|
||||||
|
{t('email.ignoreSslErrors')}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
{!smtpConfig.tls_reject_unauthorized && (
|
||||||
|
<div className="mt-2 p-3 bg-amber-50 border border-amber-200 rounded-lg">
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<ShieldAlert className="w-4 h-4 text-amber-600 flex-shrink-0 mt-0.5" />
|
||||||
|
<p className="text-xs text-amber-800">
|
||||||
|
{t('email.ignoreSslWarning')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-neutral-700 mb-1">
|
<label className="block text-sm font-medium text-neutral-700 mb-1">
|
||||||
{t('email.username')}
|
{t('email.username')}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export interface EmailConfig {
|
|||||||
smtp_pass: string;
|
smtp_pass: string;
|
||||||
from_email: string;
|
from_email: string;
|
||||||
from_name: string;
|
from_name: string;
|
||||||
|
tls_reject_unauthorized: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmailTemplate {
|
export interface EmailTemplate {
|
||||||
|
|||||||
Reference in New Issue
Block a user