From e85d1bf72afaaddeeb94e98a0e536fb9e877390b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Nov 2025 20:23:39 +0000 Subject: [PATCH] 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 --- .../core/047_add_tls_reject_unauthorized.js | 18 +++++++++ backend/src/routes/adminEmail.js | 11 +++++- backend/src/services/emailProcessor.js | 8 +++- frontend/src/i18n/locales/de.json | 29 +++++++++++++- frontend/src/i18n/locales/en.json | 4 +- frontend/src/pages/admin/EmailConfigPage.tsx | 38 ++++++++++++++++--- frontend/src/services/email.service.ts | 1 + 7 files changed, 97 insertions(+), 12 deletions(-) create mode 100644 backend/migrations/core/047_add_tls_reject_unauthorized.js diff --git a/backend/migrations/core/047_add_tls_reject_unauthorized.js b/backend/migrations/core/047_add_tls_reject_unauthorized.js new file mode 100644 index 0000000..0cf86af --- /dev/null +++ b/backend/migrations/core/047_add_tls_reject_unauthorized.js @@ -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'); + }); + } +}; diff --git a/backend/src/routes/adminEmail.js b/backend/src/routes/adminEmail.js index 02b0057..d7c95e8 100644 --- a/backend/src/routes/adminEmail.js +++ b/backend/src/routes/adminEmail.js @@ -18,7 +18,8 @@ router.get('/config', adminAuth, async (req, res) => { smtp_user: '', smtp_pass: '', // Don't send actual password from_email: '', - from_name: '' + from_name: '', + tls_reject_unauthorized: true }); } @@ -53,7 +54,8 @@ router.post('/config', [ smtp_user, smtp_pass, from_email, - from_name + from_name, + tls_reject_unauthorized } = req.body; // Check if config exists @@ -66,6 +68,7 @@ router.post('/config', [ smtp_user: smtp_user || '', from_email, from_name: from_name || 'Photo Sharing', + tls_reject_unauthorized: tls_reject_unauthorized !== false, // Default to true updated_at: new Date() }; @@ -137,6 +140,10 @@ router.post('/test', adminAuth, async (req, res) => { user: config.smtp_user, pass: config.smtp_pass } : 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', debug: process.env.NODE_ENV === 'development' }; diff --git a/backend/src/services/emailProcessor.js b/backend/src/services/emailProcessor.js index 0942637..552e6a6 100644 --- a/backend/src/services/emailProcessor.js +++ b/backend/src/services/emailProcessor.js @@ -9,7 +9,7 @@ let lastConfigHash = null; // Generate hash from config for change detection function generateConfigHash(config) { 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'); } @@ -40,7 +40,11 @@ async function initializeTransporter(forceReinit = false) { auth: config.smtp_user ? { user: config.smtp_user, 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 diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index f404549..a4caea7 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -1327,7 +1327,34 @@ "saveConfiguration": "Konfiguration speichern", "emailTemplates": "E-Mail-Vorlagen", "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": { "title": "CMS-Seiten", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 03ad1a4..ad21e1f 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -1072,7 +1072,9 @@ "enterPassword": "Enter password", "fromEmail": "From Email", "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": { "title": "CMS Pages", diff --git a/frontend/src/pages/admin/EmailConfigPage.tsx b/frontend/src/pages/admin/EmailConfigPage.tsx index 25bd319..f080e4c 100644 --- a/frontend/src/pages/admin/EmailConfigPage.tsx +++ b/frontend/src/pages/admin/EmailConfigPage.tsx @@ -1,8 +1,8 @@ import React, { useState } from 'react'; -import { - Mail, - Save, - Send, +import { + Mail, + Save, + Send, Server, Lock, User, @@ -10,6 +10,7 @@ import { CheckCircle, Eye, EyeOff, + ShieldAlert, } from 'lucide-react'; import { toast } from 'react-toastify'; @@ -107,7 +108,8 @@ export const EmailConfigPage: React.FC = () => { smtp_user: '', smtp_pass: '', from_email: '', - from_name: 'Photo Sharing' + from_name: 'Photo Sharing', + tls_reject_unauthorized: true }); // Fetch SMTP config @@ -342,7 +344,7 @@ export const EmailConfigPage: React.FC = () => { placeholder="587" /> - +
+ {/* Ignore SSL Certificate Errors */} +
+ + {!smtpConfig.tls_reject_unauthorized && ( +
+
+ +

+ {t('email.ignoreSslWarning')} +

+
+
+ )}
+