const AWS = require('aws-sdk'); const nodemailer = require('nodemailer'); const axios = require('axios'); const crypto = require('crypto'); const config = require('../config'); // Инициализация AWS SES let sesClient = null; let transporter = null; const initializeEmailService = () => { const emailProvider = process.env.EMAIL_PROVIDER || 'aws'; // aws, yandex, smtp if (emailProvider === 'aws' && config.email?.aws) { const accessKeyId = config.email.aws.accessKeyId; const secretAccessKey = config.email.aws.secretAccessKey; // Проверка наличия credentials if (!accessKeyId || !secretAccessKey) { console.error('[Email] ❌ AWS SES credentials не установлены!'); console.error('[Email] Установите AWS_SES_ACCESS_KEY_ID и AWS_SES_SECRET_ACCESS_KEY в .env'); console.error('[Email] Или используйте EMAIL_PROVIDER=yandex или EMAIL_PROVIDER=smtp'); sesClient = null; return; } 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 = { accessKeyId: accessKeyId, secretAccessKey: secretAccessKey, region: awsRegion }; // Для Yandex Cloud Postbox нужен кастомный endpoint if (endpointUrl) { // Yandex Cloud Postbox использует SESv2 API, сохраняем конфигурацию для прямых запросов sesConfig.endpoint = endpointUrl; sesConfig.isYandexCloud = true; console.log(`[Email] Используется Yandex Cloud Postbox с endpoint: ${endpointUrl}`); // Не создаем SES клиент для Yandex Cloud, будем использовать прямые HTTP запросы sesClient = { config: sesConfig, isYandexCloud: true }; } else if (!validAWSRegions.includes(awsRegion)) { console.warn(`[Email] Невалидный регион AWS SES: ${awsRegion}. Используется us-east-1`); sesConfig.region = 'us-east-1'; sesClient = new AWS.SES(sesConfig); console.log('[Email] ✅ AWS SES клиент инициализирован'); } else { sesClient = new AWS.SES(sesConfig); console.log('[Email] ✅ AWS SES клиент инициализирован'); } } else if (emailProvider === 'yandex' || emailProvider === 'smtp') { const emailConfig = config.email?.[emailProvider] || config.email?.smtp || {}; const smtpHost = emailConfig.host || process.env.SMTP_HOST || process.env.YANDEX_SMTP_HOST; const smtpPort = emailConfig.port || parseInt(process.env.SMTP_PORT || process.env.YANDEX_SMTP_PORT || '587', 10); const smtpSecure = emailConfig.secure !== undefined ? emailConfig.secure : (process.env.SMTP_SECURE === 'true' || process.env.YANDEX_SMTP_SECURE === 'true' || smtpPort === 465); const smtpUser = emailConfig.user || process.env.SMTP_USER || process.env.YANDEX_SMTP_USER; const smtpPassword = emailConfig.password || process.env.SMTP_PASSWORD || process.env.YANDEX_SMTP_PASSWORD; console.log('[Email] Настройка SMTP:', { provider: emailProvider, host: smtpHost, port: smtpPort, secure: smtpSecure, user: smtpUser ? `${smtpUser.substring(0, 3)}***` : 'не указан', hasPassword: !!smtpPassword, envVars: { YANDEX_SMTP_HOST: !!process.env.YANDEX_SMTP_HOST, YANDEX_SMTP_USER: !!process.env.YANDEX_SMTP_USER, YANDEX_SMTP_PASSWORD: !!process.env.YANDEX_SMTP_PASSWORD, SMTP_HOST: !!process.env.SMTP_HOST, SMTP_USER: !!process.env.SMTP_USER, SMTP_PASSWORD: !!process.env.SMTP_PASSWORD } }); if (!smtpHost || !smtpUser || !smtpPassword) { console.error('[Email] Неполная конфигурация SMTP:', { hasHost: !!smtpHost, hasUser: !!smtpUser, hasPassword: !!smtpPassword, emailConfig: emailConfig, configEmail: config.email }); throw new Error('SMTP конфигурация неполная. Проверьте настройки в .env. Для Yandex используйте YANDEX_SMTP_* переменные.'); } transporter = nodemailer.createTransport({ host: smtpHost, port: smtpPort, secure: smtpSecure, auth: { user: smtpUser, pass: smtpPassword }, tls: { rejectUnauthorized: false // Для отладки, в production лучше true } }); console.log('[Email] SMTP transporter создан успешно'); } }; // Генерация HTML письма с кодом const generateVerificationEmail = (code) => { return `

Код подтверждения

Ваш код для регистрации в Nakama:

${code}

Код действителен в течение 15 минут.

`; }; 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'; if (emailProvider === 'aws') { if (!sesClient) { const errorMsg = 'AWS SES не инициализирован. Проверьте AWS_SES_ACCESS_KEY_ID и AWS_SES_SECRET_ACCESS_KEY в .env файле. Или используйте EMAIL_PROVIDER=yandex или EMAIL_PROVIDER=smtp'; console.error('[Email] ❌', errorMsg); throw new Error(errorMsg); } // Проверка на Yandex Cloud Postbox if (sesClient.isYandexCloud) { // Yandex Cloud Postbox использует SESv2 API - используем прямой HTTP запрос const endpoint = sesClient.config.endpoint; const payload = { 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' } } } } }; // Используем AWS SDK для создания подписи, но отправляем через axios // Создаем временный SES клиент для подписи запроса const tempSES = new AWS.SES({ accessKeyId: sesClient.config.accessKeyId, secretAccessKey: sesClient.config.secretAccessKey, region: sesClient.config.region, endpoint: endpoint }); // Пробуем использовать обычный SES API (может не работать) try { const params = { Source: fromEmail, Destination: { ToAddresses: [to] }, Message: { 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 tempSES.sendEmail(params).promise(); return { success: true, messageId: result.MessageId }; } catch (sesError) { // Если SES API не работает, пробуем через SMTP console.warn('[Email] SES API не работает с Yandex Cloud, используйте EMAIL_PROVIDER=smtp или yandex'); throw new Error('Yandex Cloud Postbox требует SESv2 API. Используйте EMAIL_PROVIDER=yandex или smtp'); } } else { // Обычный AWS SES const params = { Source: fromEmail, Destination: { ToAddresses: [to] }, Message: { 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 sesClient.sendEmail(params).promise(); return { success: true, messageId: result.MessageId }; } } 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); // Более информативные сообщения об ошибках if (error.code === 'CredentialsError' || (error.message && error.message.includes('Missing credentials'))) { const errorMsg = 'AWS SES credentials не настроены. Установите AWS_SES_ACCESS_KEY_ID и AWS_SES_SECRET_ACCESS_KEY в .env файле. Или используйте EMAIL_PROVIDER=yandex или EMAIL_PROVIDER=smtp'; console.error('[Email] ❌', errorMsg); throw new Error(errorMsg); } else if (error.code === 'EAUTH') { throw new Error('Неверные учетные данные SMTP. Проверьте YANDEX_SMTP_USER и YANDEX_SMTP_PASSWORD в .env файле. Для Yandex используйте пароль приложения, а не основной пароль.'); } else if (error.code === 'ECONNECTION') { throw new Error('Не удалось подключиться к SMTP серверу. Проверьте YANDEX_SMTP_HOST и YANDEX_SMTP_PORT.'); } else if (error.message && error.message.includes('Authentication credentials invalid')) { throw new Error('Неверные учетные данные SMTP. Убедитесь, что используете пароль приложения для Yandex, а не основной пароль аккаунта.'); } 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 ? `

Пользователь: @${userInfo.username} (${userInfo.firstName})

${userInfo.adminNumber ? `

Номер админа: ${userInfo.adminNumber}

` : ''} ` : ''; return `

Подтверждение ${actionText}

${userDetails}

Код подтверждения:

${code}

Код действителен в течение 5 минут.

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