2025-12-08 23:42:32 +00:00
|
|
|
|
const AWS = require('aws-sdk');
|
|
|
|
|
|
const nodemailer = require('nodemailer');
|
|
|
|
|
|
const config = require('../config');
|
|
|
|
|
|
|
2025-12-14 14:52:58 +00:00
|
|
|
|
// Инициализация AWS SES / SESv2
|
2025-12-08 23:42:32 +00:00
|
|
|
|
let sesClient = null;
|
2025-12-14 14:52:58 +00:00
|
|
|
|
let sesv2Client = null;
|
2025-12-08 23:42:32 +00:00
|
|
|
|
let transporter = null;
|
|
|
|
|
|
|
|
|
|
|
|
const initializeEmailService = () => {
|
|
|
|
|
|
const emailProvider = process.env.EMAIL_PROVIDER || 'aws'; // aws, yandex, smtp
|
|
|
|
|
|
|
|
|
|
|
|
if (emailProvider === 'aws' && config.email?.aws) {
|
2025-12-14 14:41:29 +00:00
|
|
|
|
const awsRegion = config.email.aws.region || 'us-east-1';
|
|
|
|
|
|
const validAWSRegions = [
|
|
|
|
|
|
'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
|
|
|
|
|
|
'eu-west-1', 'eu-west-2', 'eu-west-3', 'eu-central-1',
|
|
|
|
|
|
'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1',
|
|
|
|
|
|
'sa-east-1', 'ca-central-1'
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// Проверка на Yandex Cloud Postbox (использует ru-central1)
|
|
|
|
|
|
const isYandexCloud = awsRegion === 'ru-central1';
|
|
|
|
|
|
const endpointUrl = config.email.aws.endpoint || process.env.AWS_SES_ENDPOINT_URL ||
|
|
|
|
|
|
(isYandexCloud ? 'https://postbox.cloud.yandex.net' : null);
|
|
|
|
|
|
|
|
|
|
|
|
const sesConfig = {
|
2025-12-08 23:42:32 +00:00
|
|
|
|
accessKeyId: config.email.aws.accessKeyId,
|
|
|
|
|
|
secretAccessKey: config.email.aws.secretAccessKey,
|
2025-12-14 14:41:29 +00:00
|
|
|
|
region: awsRegion
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-14 14:52:58 +00:00
|
|
|
|
// Для Yandex Cloud Postbox нужен кастомный endpoint и SESv2 API
|
2025-12-14 14:41:29 +00:00
|
|
|
|
if (endpointUrl) {
|
|
|
|
|
|
sesConfig.endpoint = endpointUrl;
|
|
|
|
|
|
console.log(`[Email] Используется Yandex Cloud Postbox с endpoint: ${endpointUrl}`);
|
2025-12-14 14:52:58 +00:00
|
|
|
|
// Yandex Cloud Postbox использует SESv2 API
|
|
|
|
|
|
sesv2Client = new AWS.SESv2(sesConfig);
|
2025-12-14 14:41:29 +00:00
|
|
|
|
} else if (!validAWSRegions.includes(awsRegion)) {
|
|
|
|
|
|
console.warn(`[Email] Невалидный регион AWS SES: ${awsRegion}. Используется us-east-1`);
|
|
|
|
|
|
sesConfig.region = 'us-east-1';
|
2025-12-14 14:52:58 +00:00
|
|
|
|
sesClient = new AWS.SES(sesConfig);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
sesClient = new AWS.SES(sesConfig);
|
2025-12-14 14:41:29 +00:00
|
|
|
|
}
|
2025-12-08 23:42:32 +00:00
|
|
|
|
} else if (emailProvider === 'yandex' || emailProvider === 'smtp') {
|
|
|
|
|
|
const emailConfig = config.email?.[emailProvider] || config.email?.smtp || {};
|
|
|
|
|
|
|
|
|
|
|
|
transporter = nodemailer.createTransport({
|
|
|
|
|
|
host: emailConfig.host || process.env.SMTP_HOST,
|
|
|
|
|
|
port: emailConfig.port || parseInt(process.env.SMTP_PORT || '587', 10),
|
|
|
|
|
|
secure: emailConfig.secure === true || process.env.SMTP_SECURE === 'true',
|
|
|
|
|
|
auth: {
|
|
|
|
|
|
user: emailConfig.user || process.env.SMTP_USER,
|
|
|
|
|
|
pass: emailConfig.password || process.env.SMTP_PASSWORD
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Генерация HTML письма с кодом
|
|
|
|
|
|
const generateVerificationEmail = (code) => {
|
|
|
|
|
|
return `
|
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html>
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
|
<style>
|
|
|
|
|
|
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
|
|
|
|
|
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
|
|
|
|
.code { font-size: 32px; font-weight: bold; color: #007bff;
|
|
|
|
|
|
text-align: center; padding: 20px; background: #f8f9fa;
|
|
|
|
|
|
border-radius: 8px; margin: 20px 0; letter-spacing: 8px; }
|
|
|
|
|
|
.footer { margin-top: 30px; font-size: 12px; color: #666; }
|
|
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<div class="container">
|
|
|
|
|
|
<h1>Код подтверждения</h1>
|
|
|
|
|
|
<p>Ваш код для регистрации в Nakama:</p>
|
|
|
|
|
|
<div class="code">${code}</div>
|
|
|
|
|
|
<p>Код действителен в течение 15 минут.</p>
|
|
|
|
|
|
<div class="footer">
|
|
|
|
|
|
<p>Если вы не запрашивали этот код, просто проигнорируйте это письмо.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|
|
|
|
|
|
`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const sendEmail = async (to, subject, html, text) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const emailProvider = process.env.EMAIL_PROVIDER || 'aws';
|
|
|
|
|
|
const fromEmail = process.env.EMAIL_FROM || config.email?.from || 'noreply@nakama.guru';
|
|
|
|
|
|
|
2025-12-14 14:52:58 +00:00
|
|
|
|
// Использовать SESv2 для Yandex Cloud Postbox, SES для обычного AWS
|
|
|
|
|
|
if (emailProvider === 'aws' && (sesClient || sesv2Client)) {
|
|
|
|
|
|
if (sesv2Client) {
|
|
|
|
|
|
// Отправка через AWS SESv2 (Yandex Cloud Postbox)
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
FromEmailAddress: fromEmail,
|
|
|
|
|
|
Destination: {
|
|
|
|
|
|
ToAddresses: [to]
|
|
|
|
|
|
},
|
|
|
|
|
|
Content: {
|
|
|
|
|
|
Simple: {
|
|
|
|
|
|
Subject: {
|
|
|
|
|
|
Data: subject,
|
|
|
|
|
|
Charset: 'UTF-8'
|
|
|
|
|
|
},
|
|
|
|
|
|
Body: {
|
|
|
|
|
|
Html: {
|
|
|
|
|
|
Data: html,
|
|
|
|
|
|
Charset: 'UTF-8'
|
|
|
|
|
|
},
|
|
|
|
|
|
Text: {
|
|
|
|
|
|
Data: text || html.replace(/<[^>]*>/g, ''),
|
|
|
|
|
|
Charset: 'UTF-8'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const result = await sesv2Client.sendEmail(params).promise();
|
|
|
|
|
|
return { success: true, messageId: result.MessageId };
|
|
|
|
|
|
} else if (sesClient) {
|
|
|
|
|
|
// Отправка через AWS SES (обычный AWS)
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
Source: fromEmail,
|
|
|
|
|
|
Destination: {
|
|
|
|
|
|
ToAddresses: [to]
|
2025-12-08 23:42:32 +00:00
|
|
|
|
},
|
2025-12-14 14:52:58 +00:00
|
|
|
|
Message: {
|
|
|
|
|
|
Subject: {
|
|
|
|
|
|
Data: subject,
|
2025-12-08 23:42:32 +00:00
|
|
|
|
Charset: 'UTF-8'
|
|
|
|
|
|
},
|
2025-12-14 14:52:58 +00:00
|
|
|
|
Body: {
|
|
|
|
|
|
Html: {
|
|
|
|
|
|
Data: html,
|
|
|
|
|
|
Charset: 'UTF-8'
|
|
|
|
|
|
},
|
|
|
|
|
|
Text: {
|
|
|
|
|
|
Data: text || html.replace(/<[^>]*>/g, ''),
|
|
|
|
|
|
Charset: 'UTF-8'
|
|
|
|
|
|
}
|
2025-12-08 23:42:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-14 14:52:58 +00:00
|
|
|
|
};
|
2025-12-08 23:42:32 +00:00
|
|
|
|
|
2025-12-14 14:52:58 +00:00
|
|
|
|
const result = await sesClient.sendEmail(params).promise();
|
|
|
|
|
|
return { success: true, messageId: result.MessageId };
|
|
|
|
|
|
}
|
2025-12-08 23:42:32 +00:00
|
|
|
|
} else if (transporter) {
|
|
|
|
|
|
// Отправка через SMTP (Yandex, Gmail и т.д.)
|
|
|
|
|
|
const info = await transporter.sendMail({
|
|
|
|
|
|
from: fromEmail,
|
|
|
|
|
|
to,
|
|
|
|
|
|
subject,
|
|
|
|
|
|
html,
|
|
|
|
|
|
text: text || html.replace(/<[^>]*>/g, '')
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return { success: true, messageId: info.messageId };
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error('Email service not configured');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Ошибка отправки email:', error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const sendVerificationCode = async (email, code) => {
|
|
|
|
|
|
const subject = 'Код подтверждения регистрации - Nakama';
|
|
|
|
|
|
const html = generateVerificationEmail(code);
|
|
|
|
|
|
const text = `Ваш код подтверждения: ${code}. Код действителен 15 минут.`;
|
|
|
|
|
|
|
|
|
|
|
|
return await sendEmail(email, subject, html, text);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Генерация HTML письма с кодом для админа
|
|
|
|
|
|
const generateAdminConfirmationEmail = (code, action, userInfo) => {
|
|
|
|
|
|
const actionText = action === 'add' ? 'добавления админа' : 'удаления админа';
|
|
|
|
|
|
const userDetails = userInfo ? `
|
|
|
|
|
|
<p><strong>Пользователь:</strong> @${userInfo.username} (${userInfo.firstName})</p>
|
|
|
|
|
|
${userInfo.adminNumber ? `<p><strong>Номер админа:</strong> ${userInfo.adminNumber}</p>` : ''}
|
|
|
|
|
|
` : '';
|
|
|
|
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html>
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
|
<style>
|
|
|
|
|
|
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
|
|
|
|
|
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
|
|
|
|
.code { font-size: 32px; font-weight: bold; color: #007bff;
|
|
|
|
|
|
text-align: center; padding: 20px; background: #f8f9fa;
|
|
|
|
|
|
border-radius: 8px; margin: 20px 0; letter-spacing: 8px; }
|
|
|
|
|
|
.footer { margin-top: 30px; font-size: 12px; color: #666; }
|
|
|
|
|
|
.info { background: #e7f3ff; padding: 15px; border-radius: 8px; margin: 20px 0; }
|
|
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<div class="container">
|
|
|
|
|
|
<h1>Подтверждение ${actionText}</h1>
|
|
|
|
|
|
${userDetails}
|
|
|
|
|
|
<div class="info">
|
|
|
|
|
|
<p><strong>Код подтверждения:</strong></p>
|
|
|
|
|
|
<div class="code">${code}</div>
|
|
|
|
|
|
<p>Код действителен в течение 5 минут.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="footer">
|
|
|
|
|
|
<p>Если вы не запрашивали это подтверждение, проигнорируйте это письмо.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|
|
|
|
|
|
`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const sendAdminConfirmationCode = async (code, action, userInfo) => {
|
|
|
|
|
|
const ownerEmail = config.ownerEmail || process.env.OWNER_EMAIL || 'aaem9848@gmail.com';
|
|
|
|
|
|
const actionText = action === 'add' ? 'добавления админа' : 'удаления админа';
|
|
|
|
|
|
const subject = `Код подтверждения ${actionText} - Nakama Moderation`;
|
|
|
|
|
|
const html = generateAdminConfirmationEmail(code, action, userInfo);
|
|
|
|
|
|
const text = `Код подтверждения ${actionText}: ${code}\n\nПользователь: @${userInfo?.username || 'не указан'}\nКод действителен 5 минут.`;
|
|
|
|
|
|
|
|
|
|
|
|
return await sendEmail(ownerEmail, subject, html, text);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Инициализация при загрузке модуля
|
|
|
|
|
|
initializeEmailService();
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
|
sendEmail,
|
|
|
|
|
|
sendVerificationCode,
|
|
|
|
|
|
sendAdminConfirmationCode,
|
|
|
|
|
|
initializeEmailService
|
|
|
|
|
|
};
|
|
|
|
|
|
|