first commit
This commit is contained in:
518
utils/email.js
Normal file
518
utils/email.js
Normal file
@@ -0,0 +1,518 @@
|
||||
import nodemailer from "nodemailer";
|
||||
import { config } from "../config/index.js";
|
||||
|
||||
/**
|
||||
* Email Service for TurboTrades
|
||||
* Handles sending verification emails, 2FA codes, etc.
|
||||
*/
|
||||
|
||||
// Create transporter
|
||||
let transporter = null;
|
||||
|
||||
const initializeTransporter = () => {
|
||||
if (transporter) return transporter;
|
||||
|
||||
// In development, use Ethereal email (fake SMTP service for testing)
|
||||
if (config.isDevelopment && (!config.email.host || !config.email.user)) {
|
||||
console.log(
|
||||
"⚠️ No email credentials found. Email will be logged to console only."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
transporter = nodemailer.createTransport({
|
||||
host: config.email.host,
|
||||
port: config.email.port,
|
||||
secure: config.email.port === 465, // true for 465, false for other ports
|
||||
auth: {
|
||||
user: config.email.user,
|
||||
pass: config.email.pass,
|
||||
},
|
||||
});
|
||||
|
||||
console.log("✅ Email transporter initialized");
|
||||
return transporter;
|
||||
} catch (error) {
|
||||
console.error("❌ Failed to initialize email transporter:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send email
|
||||
* @param {Object} options - Email options
|
||||
* @param {string} options.to - Recipient email
|
||||
* @param {string} options.subject - Email subject
|
||||
* @param {string} options.html - HTML content
|
||||
* @param {string} options.text - Plain text content
|
||||
*/
|
||||
export const sendEmail = async ({ to, subject, html, text }) => {
|
||||
const transport = initializeTransporter();
|
||||
|
||||
// If no transporter, log to console (development mode)
|
||||
if (!transport) {
|
||||
console.log("\n📧 ===== EMAIL (Console Mode) =====");
|
||||
console.log(`To: ${to}`);
|
||||
console.log(`Subject: ${subject}`);
|
||||
console.log(`Text: ${text || "N/A"}`);
|
||||
console.log(`HTML: ${html ? "Yes" : "No"}`);
|
||||
console.log("===================================\n");
|
||||
return { success: true, mode: "console" };
|
||||
}
|
||||
|
||||
try {
|
||||
const info = await transport.sendMail({
|
||||
from: config.email.from,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
});
|
||||
|
||||
console.log(`✅ Email sent to ${to}: ${info.messageId}`);
|
||||
return { success: true, messageId: info.messageId };
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to send email to ${to}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send email verification email
|
||||
* @param {string} email - User email
|
||||
* @param {string} username - User username
|
||||
* @param {string} token - Verification token
|
||||
*/
|
||||
export const sendVerificationEmail = async (email, username, token) => {
|
||||
const verificationUrl = `${config.cors.origin}/verify-email/${token}`;
|
||||
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background-color: #0f1923;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 40px auto;
|
||||
background: linear-gradient(135deg, #1a2332 0%, #0f1923 100%);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #2d3748;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
color: #0f1923;
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.content {
|
||||
padding: 40px 30px;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
.content h2 {
|
||||
color: #fbbf24;
|
||||
margin-top: 0;
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
|
||||
color: #0f1923;
|
||||
padding: 14px 32px;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
margin: 20px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
.footer {
|
||||
background: #0a0f16;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #94a3b8;
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #2d3748;
|
||||
}
|
||||
.code-box {
|
||||
background: #0a0f16;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #2d3748;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
font-family: monospace;
|
||||
font-size: 18px;
|
||||
color: #fbbf24;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🚀 TurboTrades</h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2>Welcome, ${username}!</h2>
|
||||
<p>Thank you for joining TurboTrades. To complete your registration and secure your account, please verify your email address.</p>
|
||||
|
||||
<p style="text-align: center;">
|
||||
<a href="${verificationUrl}" class="button">Verify Email Address</a>
|
||||
</p>
|
||||
|
||||
<p style="color: #94a3b8; font-size: 14px; margin-top: 30px;">
|
||||
If the button doesn't work, copy and paste this link into your browser:
|
||||
</p>
|
||||
<div class="code-box">${verificationUrl}</div>
|
||||
|
||||
<p style="color: #94a3b8; font-size: 13px; margin-top: 30px;">
|
||||
This link will expire in 24 hours. If you didn't create an account on TurboTrades, please ignore this email.
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>© ${new Date().getFullYear()} TurboTrades. All rights reserved.</p>
|
||||
<p>Premium CS2 & Rust Skin Marketplace</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const text = `
|
||||
Welcome to TurboTrades, ${username}!
|
||||
|
||||
Please verify your email address by visiting this link:
|
||||
${verificationUrl}
|
||||
|
||||
This link will expire in 24 hours.
|
||||
|
||||
If you didn't create an account on TurboTrades, please ignore this email.
|
||||
|
||||
© ${new Date().getFullYear()} TurboTrades
|
||||
`.trim();
|
||||
|
||||
return sendEmail({
|
||||
to: email,
|
||||
subject: "Verify your TurboTrades email address",
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Send 2FA setup email
|
||||
* @param {string} email - User email
|
||||
* @param {string} username - User username
|
||||
* @param {string} revocationCode - Revocation code for 2FA recovery
|
||||
*/
|
||||
export const send2FASetupEmail = async (email, username) => {
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background-color: #0f1923;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 40px auto;
|
||||
background: linear-gradient(135deg, #1a2332 0%, #0f1923 100%);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #2d3748;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
color: #ffffff;
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.content {
|
||||
padding: 40px 30px;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
.content h2 {
|
||||
color: #10b981;
|
||||
margin-top: 0;
|
||||
}
|
||||
.footer {
|
||||
background: #0a0f16;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #94a3b8;
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #2d3748;
|
||||
}
|
||||
.code-box {
|
||||
background: #0a0f16;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #10b981;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
font-family: monospace;
|
||||
font-size: 24px;
|
||||
color: #10b981;
|
||||
letter-spacing: 3px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.warning {
|
||||
background: #7c2d12;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #dc2626;
|
||||
margin: 20px 0;
|
||||
color: #fca5a5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🔐 Two-Factor Authentication Enabled</h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2>Great job, ${username}!</h2>
|
||||
<p>You've successfully enabled Two-Factor Authentication (2FA) on your TurboTrades account. Your account is now more secure.</p>
|
||||
|
||||
<div class="warning">
|
||||
<strong>⚠️ Security Notice</strong>
|
||||
<p style="margin: 10px 0 0 0; font-size: 14px;">
|
||||
If you did not enable 2FA on your account, please contact support immediately and change your password.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p style="color: #94a3b8; font-size: 14px; margin-top: 20px;">
|
||||
<strong>What this means:</strong><br>
|
||||
• You'll now need your authenticator app code when logging in<br>
|
||||
• Your account has an extra layer of protection<br>
|
||||
• Keep your recovery code safe (shown during setup)<br>
|
||||
• Never share your 2FA codes with anyone
|
||||
</p>
|
||||
|
||||
<p style="color: #10b981; font-weight: 600; margin-top: 30px;">
|
||||
✅ Your account is now protected by 2FA
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>© ${new Date().getFullYear()} TurboTrades. All rights reserved.</p>
|
||||
<p>Premium CS2 & Rust Skin Marketplace</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const text = `
|
||||
Two-Factor Authentication Enabled
|
||||
|
||||
Great job, ${username}!
|
||||
|
||||
You've successfully enabled Two-Factor Authentication (2FA) on your TurboTrades account.
|
||||
|
||||
⚠️ Security Notice
|
||||
|
||||
If you did not enable 2FA on your account, please contact support immediately and change your password.
|
||||
|
||||
What this means:
|
||||
• You'll now need your authenticator app code when logging in
|
||||
• Your account has an extra layer of protection
|
||||
• Keep your recovery code safe (shown during setup)
|
||||
• Never share your 2FA codes with anyone
|
||||
• You can use this code to disable 2FA if you lose your authenticator device
|
||||
|
||||
© ${new Date().getFullYear()} TurboTrades
|
||||
`.trim();
|
||||
|
||||
return sendEmail({
|
||||
to: email,
|
||||
subject: "🔐 Two-Factor Authentication Enabled - TurboTrades",
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Send session alert email
|
||||
* @param {string} email - User email
|
||||
* @param {string} username - User username
|
||||
* @param {Object} session - Session details
|
||||
*/
|
||||
export const sendSessionAlertEmail = async (email, username, session) => {
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background-color: #0f1923;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 40px auto;
|
||||
background: linear-gradient(135deg, #1a2332 0%, #0f1923 100%);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #2d3748;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
color: #ffffff;
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.content {
|
||||
padding: 40px 30px;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
.content h2 {
|
||||
color: #3b82f6;
|
||||
margin-top: 0;
|
||||
}
|
||||
.info-box {
|
||||
background: #0a0f16;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #2d3748;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #2d3748;
|
||||
}
|
||||
.info-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.info-label {
|
||||
color: #94a3b8;
|
||||
font-weight: 600;
|
||||
}
|
||||
.info-value {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
.footer {
|
||||
background: #0a0f16;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #94a3b8;
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #2d3748;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🔔 New Login Detected</h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2>Hello, ${username}</h2>
|
||||
<p>We detected a new login to your TurboTrades account.</p>
|
||||
|
||||
<div class="info-box">
|
||||
<div class="info-row">
|
||||
<span class="info-label">Time:</span>
|
||||
<span class="info-value">${new Date(
|
||||
session.createdAt
|
||||
).toLocaleString()}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">IP Address:</span>
|
||||
<span class="info-value">${session.ip || "Unknown"}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Device:</span>
|
||||
<span class="info-value">${session.device || "Unknown"}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Location:</span>
|
||||
<span class="info-value">${session.location || "Unknown"}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p style="color: #94a3b8; font-size: 14px;">
|
||||
If this was you, you can safely ignore this email. If you don't recognize this login, please secure your account immediately by:
|
||||
</p>
|
||||
|
||||
<ul style="color: #94a3b8; font-size: 14px;">
|
||||
<li>Changing your Steam password</li>
|
||||
<li>Enabling Two-Factor Authentication (2FA) on TurboTrades</li>
|
||||
<li>Reviewing your active sessions in account settings</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>© ${new Date().getFullYear()} TurboTrades. All rights reserved.</p>
|
||||
<p>Premium CS2 & Rust Skin Marketplace</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const text = `
|
||||
New Login Detected - TurboTrades
|
||||
|
||||
Hello, ${username}
|
||||
|
||||
We detected a new login to your TurboTrades account.
|
||||
|
||||
Login Details:
|
||||
- Time: ${new Date(session.createdAt).toLocaleString()}
|
||||
- IP Address: ${session.ip || "Unknown"}
|
||||
- Device: ${session.device || "Unknown"}
|
||||
- Location: ${session.location || "Unknown"}
|
||||
|
||||
If this was you, you can safely ignore this email.
|
||||
|
||||
If you don't recognize this login, please secure your account immediately by:
|
||||
• Changing your Steam password
|
||||
• Enabling Two-Factor Authentication (2FA) on TurboTrades
|
||||
• Reviewing your active sessions in account settings
|
||||
|
||||
© ${new Date().getFullYear()} TurboTrades
|
||||
`.trim();
|
||||
|
||||
return sendEmail({
|
||||
to: email,
|
||||
subject: "🔔 New Login Detected - TurboTrades",
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
sendEmail,
|
||||
sendVerificationEmail,
|
||||
send2FASetupEmail,
|
||||
sendSessionAlertEmail,
|
||||
};
|
||||
Reference in New Issue
Block a user