From e9d3d8a2fb3def1b30a73ffe5f73aa9d3b7f345d Mon Sep 17 00:00:00 2001 From: becarta Date: Tue, 4 Mar 2025 00:32:39 +0100 Subject: [PATCH] Contact form logic --- .env.example | 12 + check-email-delivery.cjs | 137 +++++++ email-test.cjs | 101 +++++ package-lock.json | 239 +++++++++++- package.json | 5 + protonmail-test.cjs | 69 ++++ protonmail-test2.cjs | 97 +++++ src/components/common/BasicScripts.astro | 208 +++++++++++ src/components/ui/Form.astro | 32 +- src/email-templates/README.md | 72 ++++ src/email-templates/admin-notification.ts | 111 ++++++ src/email-templates/user-confirmation.ts | 120 ++++++ src/pages/api/contact.ts | 259 +++++++++++++ src/pages/contact.astro | 2 +- src/test-email.ts | 31 ++ src/types.d.ts | 1 + src/utils/email-handler.ts | 426 ++++++++++++++++++++++ test-contact-curl.sh | 50 +++ test-contact-form.cjs | 133 +++++++ test-email.ts | 31 ++ test-smtp.js | 88 +++++ 21 files changed, 2210 insertions(+), 14 deletions(-) create mode 100644 .env.example create mode 100644 check-email-delivery.cjs create mode 100644 email-test.cjs create mode 100644 protonmail-test.cjs create mode 100644 protonmail-test2.cjs create mode 100644 src/email-templates/README.md create mode 100644 src/email-templates/admin-notification.ts create mode 100644 src/email-templates/user-confirmation.ts create mode 100644 src/pages/api/contact.ts create mode 100644 src/test-email.ts create mode 100644 src/utils/email-handler.ts create mode 100755 test-contact-curl.sh create mode 100644 test-contact-form.cjs create mode 100644 test-email.ts create mode 100644 test-smtp.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..565ba5e --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +# SMTP Configuration +SMTP_HOST=smtp.example.com +SMTP_PORT=587 +SMTP_USER=your-email@example.com +SMTP_PASS=your-password + +# Email Settings +ADMIN_EMAIL=admin@example.com +WEBSITE_NAME=Your Website Name + +# Environment +NODE_ENV=development \ No newline at end of file diff --git a/check-email-delivery.cjs b/check-email-delivery.cjs new file mode 100644 index 0000000..fa86721 --- /dev/null +++ b/check-email-delivery.cjs @@ -0,0 +1,137 @@ +/** + * Email Delivery Test Script + * + * This script tests email delivery by sending test emails to different addresses + * and with different configurations to help diagnose delivery issues. + */ + +const nodemailer = require('nodemailer'); +require('dotenv').config(); + +// Environment variables +const { + SMTP_HOST = 'smtp.protonmail.ch', + SMTP_PORT = '587', + SMTP_USER = '', + SMTP_PASS = '', + ADMIN_EMAIL = 'richard@bergsma.it', + WEBSITE_NAME = 'bergsma.it', + NODE_ENV = 'development' +} = process.env; + +// Set to production mode for this test +const isProduction = true; + +console.log('Email Delivery Test'); +console.log('------------------'); +console.log('Configuration:'); +console.log(`SMTP Host: ${SMTP_HOST}`); +console.log(`SMTP Port: ${SMTP_PORT}`); +console.log(`SMTP User: ${SMTP_USER}`); +console.log(`Admin Email: ${ADMIN_EMAIL}`); +console.log(`Website Name: ${WEBSITE_NAME}`); +console.log(`Mode: ${isProduction ? 'production' : 'development'}`); +console.log('------------------'); + +// Create a transporter +const transporter = nodemailer.createTransport({ + host: SMTP_HOST, + port: parseInt(SMTP_PORT, 10), + secure: parseInt(SMTP_PORT, 10) === 465, + auth: { + user: SMTP_USER, + pass: SMTP_PASS, + }, + tls: { + rejectUnauthorized: false, + ciphers: 'SSLv3' + }, + debug: true +}); + +// Verify connection +console.log('Testing SMTP connection...'); +transporter.verify(function(error, success) { + if (error) { + console.error('SMTP Connection Error:', error); + process.exit(1); + } + + console.log('SMTP Connection Successful!'); + runTests(); +}); + +// Run a series of email delivery tests +async function runTests() { + try { + // Test 1: Basic email to admin + console.log('\nTest 1: Sending basic email to admin...'); + await sendTestEmail( + ADMIN_EMAIL, + 'Email Delivery Test 1 - Basic', + 'This is a basic test email to the admin address.', + `

This is a basic test email to the admin address.

Time: ${new Date().toISOString()}

` + ); + + // Test 2: Email with different From address + console.log('\nTest 2: Sending email with different From address...'); + await sendTestEmail( + ADMIN_EMAIL, + 'Email Delivery Test 2 - Different From', + 'This email uses a different From address.', + `

This email uses a different From address.

Time: ${new Date().toISOString()}

`, + `"Test Sender" <${SMTP_USER}>` + ); + + // Test 3: Email with contact form format + console.log('\nTest 3: Sending email in contact form format...'); + const contactFormHtml = ` +
+

New Contact Form Submission

+

From: Test User (test@example.com)

+

Submitted on: ${new Date().toLocaleString()}

+
+

Message:

+

This is a test message simulating a contact form submission.

+
+
+

This is an automated email from your website contact form.

+
+
+ `; + await sendTestEmail( + ADMIN_EMAIL, + 'New Contact Form Submission (Test)', + 'This is a test message simulating a contact form submission.', + contactFormHtml + ); + + console.log('\nAll tests completed successfully!'); + console.log('\nPlease check your inbox (and spam folder) for the test emails.'); + console.log('If you received some emails but not others, this can help identify the issue.'); + + } catch (error) { + console.error('Error running tests:', error); + } +} + +// Helper function to send a test email +async function sendTestEmail(to, subject, text, html, from = `"${WEBSITE_NAME}" <${ADMIN_EMAIL}>`) { + try { + const info = await transporter.sendMail({ + from, + to, + subject, + text, + html + }); + + console.log(`Email sent successfully!`); + console.log(`Message ID: ${info.messageId}`); + console.log(`Response: ${info.response}`); + return info; + } catch (error) { + console.error('Error sending email:', error); + throw error; + } +} \ No newline at end of file diff --git a/email-test.cjs b/email-test.cjs new file mode 100644 index 0000000..8e04555 --- /dev/null +++ b/email-test.cjs @@ -0,0 +1,101 @@ +/** + * ProtonMail SMTP Test Script + * + * This script tests the SMTP configuration for sending emails through ProtonMail. + * Run with: node email-test.cjs + */ + +const nodemailer = require('nodemailer'); +require('dotenv').config(); + +// Get SMTP settings from environment variables +const { + SMTP_HOST = 'smtp.protonmail.ch', + SMTP_PORT = '587', + SMTP_USER, + SMTP_PASS, + ADMIN_EMAIL, + WEBSITE_NAME = 'Website' +} = process.env; + +console.log('Email Configuration Test'); +console.log('----------------------'); +console.log(`SMTP Host: ${SMTP_HOST}`); +console.log(`SMTP Port: ${SMTP_PORT}`); +console.log(`SMTP User: ${SMTP_USER}`); +console.log(`Admin Email: ${ADMIN_EMAIL}`); +console.log(`Environment: ${process.env.NODE_ENV || 'development'}`); +console.log('----------------------'); + +// Create a transporter with ProtonMail-specific settings +const transporter = nodemailer.createTransport({ + host: SMTP_HOST, + port: parseInt(SMTP_PORT, 10), + secure: parseInt(SMTP_PORT, 10) === 465, // true for 465, false for other ports + auth: { + user: SMTP_USER, + pass: SMTP_PASS, + }, + tls: { + // Do not fail on invalid certs + rejectUnauthorized: false, + // Specific ciphers for ProtonMail + ciphers: 'SSLv3' + }, + debug: true // Enable debug output +}); + +// Test the connection +console.log('Testing SMTP connection...'); +transporter.verify((error, success) => { + if (error) { + console.error('SMTP Connection Error:', error); + console.error('\nTroubleshooting Tips:'); + console.error('1. Check if your SMTP credentials are correct'); + console.error('2. For ProtonMail, ensure you\'re using an app-specific password'); + console.error('3. If using ProtonMail Bridge, make sure it\'s running'); + console.error('4. Verify your server allows outgoing connections on the SMTP port'); + process.exit(1); + } else { + console.log('SMTP Connection Successful!'); + + // Send a test email + console.log('\nSending a test email...'); + const mailOptions = { + from: `"${WEBSITE_NAME}" <${SMTP_USER}>`, + to: ADMIN_EMAIL, + subject: 'Email Configuration Test', + text: 'This is a test email to verify your website\'s email configuration is working correctly.', + html: ` +
+

Email Configuration Test

+

This is a test email to verify your website's email configuration is working correctly.

+

Configuration Details:

+ +

+ This is an automated test email. If you received this, your email configuration is working correctly. +

+
+ ` + }; + + transporter.sendMail(mailOptions, (error, info) => { + if (error) { + console.error('Error sending test email:', error); + process.exit(1); + } else { + console.log('Test email sent successfully!'); + console.log('Message ID:', info.messageId); + console.log('Response:', info.response); + console.log('\nIf you received the test email, your email configuration is working correctly.'); + process.exit(0); + } + }); + } +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 60a00d8..7b08ebc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,9 +20,14 @@ "astro": "^5.2.3", "astro-embed": "^0.9.0", "astro-icon": "^1.1.5", + "csrf": "^3.1.0", + "dotenv": "^16.4.7", + "form-data": "^4.0.2", "limax": "4.1.0", "lodash.merge": "^4.6.2", "node-fetch": "^3.3.2", + "nodemailer": "^6.10.0", + "rate-limiter-flexible": "^5.0.5", "react": "^19.0.0", "react-dom": "^19.0.0", "unpic": "^3.22.0" @@ -3959,6 +3964,19 @@ "version": "0.0.1", "license": "MIT" }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4458,6 +4476,20 @@ "uncrypto": "^0.1.3" } }, + "node_modules/csrf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz", + "integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==", + "license": "MIT", + "dependencies": { + "rndm": "1.2.0", + "tsscmp": "1.0.6", + "uid-safe": "2.1.5" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/css-select": { "version": "5.1.0", "license": "BSD-2-Clause", @@ -4733,7 +4765,7 @@ "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -4750,6 +4782,20 @@ "node": ">=4" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "license": "MIT" @@ -4815,11 +4861,56 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==" }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esast-util-from-estree": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", @@ -5608,12 +5699,14 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" }, "engines": { @@ -5691,7 +5784,6 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5726,6 +5818,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/github-slugger": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", @@ -5799,6 +5928,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -5826,9 +5967,35 @@ "unenv": "^1.10.0" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -6977,6 +7144,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdast-util-definitions": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", @@ -8206,6 +8382,15 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" }, + "node_modules/nodemailer": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", + "integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nopt": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", @@ -8984,6 +9169,21 @@ "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==" }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rate-limiter-flexible": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-5.0.5.tgz", + "integrity": "sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ==", + "license": "ISC" + }, "node_modules/react": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", @@ -9429,6 +9629,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rndm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", + "integrity": "sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==", + "license": "MIT" + }, "node_modules/rollup": { "version": "4.31.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.31.0.tgz", @@ -10213,6 +10419,15 @@ "dev": true, "license": "0BSD" }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "license": "MIT", + "engines": { + "node": ">=0.6.x" + } + }, "node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -10295,6 +10510,18 @@ "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", "license": "ISC" }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "license": "MIT", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/uint8arrays": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", diff --git a/package.json b/package.json index 8fb5c1b..42ab082 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,14 @@ "astro": "^5.2.3", "astro-embed": "^0.9.0", "astro-icon": "^1.1.5", + "csrf": "^3.1.0", + "dotenv": "^16.4.7", + "form-data": "^4.0.2", "limax": "4.1.0", "lodash.merge": "^4.6.2", "node-fetch": "^3.3.2", + "nodemailer": "^6.10.0", + "rate-limiter-flexible": "^5.0.5", "react": "^19.0.0", "react-dom": "^19.0.0", "unpic": "^3.22.0" diff --git a/protonmail-test.cjs b/protonmail-test.cjs new file mode 100644 index 0000000..beb9c35 --- /dev/null +++ b/protonmail-test.cjs @@ -0,0 +1,69 @@ +// Simple ProtonMail SMTP test +const nodemailer = require('nodemailer'); +require('dotenv').config(); + +// Get SMTP settings from environment variables +const { + SMTP_HOST = 'smtp.protonmail.ch', + SMTP_PORT = '587', + SMTP_USER, + SMTP_PASS, + ADMIN_EMAIL +} = process.env; + +console.log('ProtonMail SMTP Test'); +console.log('-------------------'); +console.log(`SMTP Host: ${SMTP_HOST}`); +console.log(`SMTP Port: ${SMTP_PORT}`); +console.log(`SMTP User: ${SMTP_USER}`); +console.log(`Admin Email: ${ADMIN_EMAIL}`); + +// ProtonMail specific configuration +const transporter = nodemailer.createTransport({ + host: SMTP_HOST, + port: parseInt(SMTP_PORT, 10), + secure: false, // For ProtonMail, use false for port 587 + auth: { + user: SMTP_USER, + pass: SMTP_PASS, + }, + tls: { + // Do not fail on invalid certs + rejectUnauthorized: false, + // Specific ciphers for ProtonMail + ciphers: 'SSLv3' + }, + logger: true, + debug: true // Include SMTP traffic in the logs +}); + +// Verify connection +console.log('\nTesting connection to ProtonMail SMTP server...'); +transporter.verify(function(error, _success) { + if (error) { + console.error('Connection failed:', error); + process.exit(1); + } else { + console.log('Server is ready to take our messages'); + + // Send test email + console.log('\nSending test email...'); + transporter.sendMail({ + from: SMTP_USER, + to: ADMIN_EMAIL, + subject: 'ProtonMail SMTP Test', + text: 'This is a test email from your website contact form.', + html: '

This is a test email from your website contact form.

' + }, (err, info) => { + if (err) { + console.error('Error sending email:', err); + process.exit(1); + } else { + console.log('Message sent successfully!'); + console.log('Message ID:', info.messageId); + console.log('Response:', info.response); + process.exit(0); + } + }); + } +}); \ No newline at end of file diff --git a/protonmail-test2.cjs b/protonmail-test2.cjs new file mode 100644 index 0000000..026e51c --- /dev/null +++ b/protonmail-test2.cjs @@ -0,0 +1,97 @@ +// ProtonMail SMTP test with alternative configuration +const nodemailer = require('nodemailer'); +require('dotenv').config(); + +// Get SMTP settings from environment variables +const { + SMTP_USER, + SMTP_PASS, + ADMIN_EMAIL +} = process.env; + +console.log('ProtonMail SMTP Test (Alternative Configuration)'); +console.log('----------------------------------------------'); +console.log(`SMTP User: ${SMTP_USER}`); +console.log(`Admin Email: ${ADMIN_EMAIL}`); + +// Try alternative ProtonMail configuration +// ProtonMail Bridge typically uses localhost:1025 or localhost:1143 +const transporterOptions = { + host: 'mail.protonmail.ch', + port: 443, + secure: true, + auth: { + user: SMTP_USER, + pass: SMTP_PASS, + }, + tls: { + rejectUnauthorized: false + }, + logger: true, + debug: true +}; + +console.log('\nUsing configuration:'); +console.log(`Host: ${transporterOptions.host}`); +console.log(`Port: ${transporterOptions.port}`); +console.log(`Secure: ${transporterOptions.secure}`); + +const transporter = nodemailer.createTransport(transporterOptions); + +// Verify connection +console.log('\nTesting connection to ProtonMail SMTP server...'); +transporter.verify(function(error, _success) { + if (error) { + console.error('Connection failed:', error); + console.log('\nTrying alternative port (25)...'); + + // Try port 25 + const transporter2 = nodemailer.createTransport({ + ...transporterOptions, + port: 25, + secure: false + }); + + transporter2.verify(function(error2, _success2) { + if (error2) { + console.error('Connection with port 25 also failed:', error2); + + console.log('\nImportant ProtonMail SMTP Notes:'); + console.log('1. ProtonMail requires the Bridge application for SMTP access from third-party apps'); + console.log('2. The Bridge runs locally and provides SMTP access via localhost:1025 or similar'); + console.log('3. You may need to install and configure ProtonMail Bridge on your server'); + console.log('4. Or use ProtonMail\'s API instead of SMTP for sending emails'); + + process.exit(1); + } else { + console.log('Connection successful with port 25!'); + sendTestEmail(transporter2); + } + }); + } else { + console.log('Server is ready to take our messages'); + sendTestEmail(transporter); + } +}); + +function sendTestEmail(transport) { + // Send test email + console.log('\nSending test email...'); + transport.sendMail({ + from: SMTP_USER, + to: ADMIN_EMAIL, + subject: 'ProtonMail SMTP Test (Alternative Config)', + text: 'This is a test email from your website contact form.', + html: '

This is a test email from your website contact form.

' + }, (err, info) => { + if (err) { + console.error('Error sending email:', err); + process.exit(1); + } else { + console.log('Message sent successfully!'); + console.log('Message ID:', info.messageId); + console.log('Response:', info.response); + process.exit(0); + } + }); +} \ No newline at end of file diff --git a/src/components/common/BasicScripts.astro b/src/components/common/BasicScripts.astro index d7ab890..0249017 100644 --- a/src/components/common/BasicScripts.astro +++ b/src/components/common/BasicScripts.astro @@ -452,3 +452,211 @@ import { UI } from 'astrowind:config'; Observer.start(); }); + + + diff --git a/src/components/ui/Form.astro b/src/components/ui/Form.astro index 276b39f..fd3c3ad 100644 --- a/src/components/ui/Form.astro +++ b/src/components/ui/Form.astro @@ -5,16 +5,28 @@ import Button from '~/components/ui/Button.astro'; const { inputs, textarea, disclaimer, button = 'Contact us', description = '' } = Astro.props; --- -
+ + + + + + + + + { inputs && inputs.map( - ({ type = 'text', name, label = '', autocomplete = 'on', placeholder = '' }) => + ({ type = 'text', name, label = '', autocomplete = 'on', placeholder = '', required = true }) => name && (
{label && ( )} +
) ) @@ -32,9 +46,9 @@ const { inputs, textarea, disclaimer, button = 'Contact us', description = '' } { textarea && ( -
+