Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
167 lines
4.4 KiB
JavaScript
167 lines
4.4 KiB
JavaScript
/**
|
|
* 📧 Email Emulation Helper
|
|
*
|
|
* Для тестирования email без реальной отправки.
|
|
* Использует локальный Postfix (/var/mail/).
|
|
*
|
|
* Сервер использует 127.0.0.1:25 (local postfix) для отправки.
|
|
* Письма попадают в /var/mail/root если получатель локальный.
|
|
*
|
|
* Для тестовых emails (@test.godcrm.local) — они не доставляются реально,
|
|
* поэтому можно либо:
|
|
* 1. Мокать SMTPService в тестах
|
|
* 2. Читать /var/mail/root для писем на локальные адреса
|
|
* 3. Использовать MailHog/MailCatcher для перехвата
|
|
*/
|
|
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
const MAIL_FILE = '/var/mail/root';
|
|
const MAIL_LOG = '/var/log/mail.log';
|
|
|
|
/**
|
|
* Читает последние N писем из локального mail spool
|
|
*/
|
|
export function readLocalMail(limit = 10) {
|
|
try {
|
|
if (!fs.existsSync(MAIL_FILE)) {
|
|
return { success: false, error: 'Mail file not found' };
|
|
}
|
|
|
|
const content = fs.readFileSync(MAIL_FILE, 'utf8');
|
|
const emails = content.split(/^From /m).filter(Boolean);
|
|
|
|
const parsed = emails.slice(-limit).map(raw => {
|
|
const lines = raw.split('\n');
|
|
const headers = {};
|
|
let body = '';
|
|
let inBody = false;
|
|
|
|
for (const line of lines) {
|
|
if (inBody) {
|
|
body += line + '\n';
|
|
} else if (line === '') {
|
|
inBody = true;
|
|
} else {
|
|
const match = line.match(/^([^:]+):\s*(.*)$/);
|
|
if (match) {
|
|
headers[match[1].toLowerCase()] = match[2];
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
from: headers['from'] || '',
|
|
to: headers['to'] || '',
|
|
subject: headers['subject'] || '',
|
|
date: headers['date'] || '',
|
|
body: body.slice(0, 500) // First 500 chars
|
|
};
|
|
});
|
|
|
|
return { success: true, emails: parsed };
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ищет письмо по email или теме
|
|
*/
|
|
export function findEmail(searchTerm) {
|
|
const result = readLocalMail(50);
|
|
if (!result.success) return result;
|
|
|
|
const found = result.emails.filter(e =>
|
|
e.to.includes(searchTerm) ||
|
|
e.subject.includes(searchTerm) ||
|
|
e.from.includes(searchTerm)
|
|
);
|
|
|
|
return { success: true, found };
|
|
}
|
|
|
|
/**
|
|
* Извлекает verification code из письма
|
|
*/
|
|
export function extractVerificationCode(emailBody) {
|
|
// Типичные паттерны для verification codes
|
|
const patterns = [
|
|
/код[:\s]+(\d{6})/i,
|
|
/code[:\s]+(\d{6})/i,
|
|
/verification[:\s]+(\d{6})/i,
|
|
/\b(\d{6})\b/ // Fallback: любые 6 цифр
|
|
];
|
|
|
|
for (const pattern of patterns) {
|
|
const match = emailBody.match(pattern);
|
|
if (match) return match[1];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Мок для SMTPService.sendTestEmail
|
|
* Используется в unit тестах
|
|
*/
|
|
export function createEmailMock() {
|
|
const sentEmails = [];
|
|
|
|
return {
|
|
sendTestEmail: async (config, toEmail, code) => {
|
|
sentEmails.push({
|
|
timestamp: new Date().toISOString(),
|
|
to: toEmail,
|
|
code,
|
|
config
|
|
});
|
|
return { success: true, messageId: `mock-${Date.now()}` };
|
|
},
|
|
|
|
getSentEmails: () => [...sentEmails],
|
|
|
|
getLastEmail: () => sentEmails[sentEmails.length - 1],
|
|
|
|
findByRecipient: (email) => sentEmails.filter(e => e.to === email),
|
|
|
|
clear: () => sentEmails.length = 0
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Проверяет, настроен ли локальный mail transport
|
|
*/
|
|
export async function checkEmailSetup() {
|
|
const checks = {
|
|
mailFile: fs.existsSync(MAIL_FILE),
|
|
mailLog: fs.existsSync(MAIL_LOG),
|
|
smtpConfig: false
|
|
};
|
|
|
|
try {
|
|
const configPath = path.join(process.cwd(), 'backend', 'smtp-config.json');
|
|
if (fs.existsSync(configPath)) {
|
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
checks.smtpConfig = config.accounts?.length > 0;
|
|
checks.smtpAccounts = config.accounts?.map(a => ({
|
|
name: a.name,
|
|
host: a.host,
|
|
isLocal: a.isLocal || a.host === '127.0.0.1'
|
|
}));
|
|
}
|
|
} catch (e) {
|
|
checks.smtpConfig = false;
|
|
}
|
|
|
|
return checks;
|
|
}
|
|
|
|
export default {
|
|
readLocalMail,
|
|
findEmail,
|
|
extractVerificationCode,
|
|
createEmailMock,
|
|
checkEmailSetup
|
|
};
|