Files
TurboTrades/utils/email.js
2026-01-10 04:57:43 +00:00

519 lines
15 KiB
JavaScript

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,
};