519 lines
15 KiB
JavaScript
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,
|
|
};
|