first commit: Complete phishing test management panel with Node.js backend and React frontend
This commit is contained in:
96
backend/src/services/mail.service.js
Normal file
96
backend/src/services/mail.service.js
Normal file
@@ -0,0 +1,96 @@
|
||||
const nodemailer = require('nodemailer');
|
||||
const handlebars = require('handlebars');
|
||||
const logger = require('../config/logger');
|
||||
const { Settings } = require('../models');
|
||||
|
||||
class MailService {
|
||||
constructor() {
|
||||
this.transporter = null;
|
||||
}
|
||||
|
||||
async initializeTransporter() {
|
||||
try {
|
||||
// Get Gmail settings from database
|
||||
const gmailUser = await Settings.findOne({ where: { key: 'gmail_user' } });
|
||||
const gmailPassword = await Settings.findOne({ where: { key: 'gmail_password' } });
|
||||
const gmailFromName = await Settings.findOne({ where: { key: 'gmail_from_name' } });
|
||||
|
||||
// Fallback to env variables
|
||||
const user = gmailUser?.value || process.env.GMAIL_USER;
|
||||
const pass = gmailPassword?.value || process.env.GMAIL_APP_PASSWORD;
|
||||
const fromName = gmailFromName?.value || process.env.GMAIL_FROM_NAME || 'Güvenlik Ekibi';
|
||||
|
||||
if (!user || !pass) {
|
||||
throw new Error('Gmail credentials not configured');
|
||||
}
|
||||
|
||||
this.transporter = nodemailer.createTransport({
|
||||
service: 'gmail',
|
||||
auth: {
|
||||
user,
|
||||
pass,
|
||||
},
|
||||
});
|
||||
|
||||
this.fromAddress = `"${fromName}" <${user}>`;
|
||||
|
||||
// Verify transporter
|
||||
await this.transporter.verify();
|
||||
logger.info('Mail service initialized successfully');
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error('Failed to initialize mail service:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async sendMail(to, subject, htmlBody) {
|
||||
try {
|
||||
if (!this.transporter) {
|
||||
await this.initializeTransporter();
|
||||
}
|
||||
|
||||
const mailOptions = {
|
||||
from: this.fromAddress,
|
||||
to,
|
||||
subject,
|
||||
html: htmlBody,
|
||||
};
|
||||
|
||||
const info = await this.transporter.sendMail(mailOptions);
|
||||
|
||||
logger.info(`Mail sent to ${to}: ${info.messageId}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
messageId: info.messageId,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`Failed to send mail to ${to}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
renderTemplate(templateHtml, data) {
|
||||
try {
|
||||
const template = handlebars.compile(templateHtml);
|
||||
return template(data);
|
||||
} catch (error) {
|
||||
logger.error('Failed to render template:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
try {
|
||||
await this.initializeTransporter();
|
||||
return { success: true, message: 'Gmail connection successful' };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new MailService();
|
||||
|
||||
109
backend/src/services/telegram.service.js
Normal file
109
backend/src/services/telegram.service.js
Normal file
@@ -0,0 +1,109 @@
|
||||
const TelegramBot = require('node-telegram-bot-api');
|
||||
const logger = require('../config/logger');
|
||||
const { Settings } = require('../models');
|
||||
|
||||
class TelegramService {
|
||||
constructor() {
|
||||
this.bot = null;
|
||||
this.chatId = null;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
try {
|
||||
// Get Telegram settings from database
|
||||
const botToken = await Settings.findOne({ where: { key: 'telegram_bot_token' } });
|
||||
const chatId = await Settings.findOne({ where: { key: 'telegram_chat_id' } });
|
||||
|
||||
// Fallback to env variables
|
||||
const token = botToken?.value || process.env.TELEGRAM_BOT_TOKEN;
|
||||
const chat = chatId?.value || process.env.TELEGRAM_CHAT_ID;
|
||||
|
||||
if (!token || !chat) {
|
||||
throw new Error('Telegram credentials not configured');
|
||||
}
|
||||
|
||||
this.bot = new TelegramBot(token, { polling: false });
|
||||
this.chatId = chat;
|
||||
|
||||
logger.info('Telegram service initialized successfully');
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error('Failed to initialize Telegram service:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async sendNotification(data) {
|
||||
try {
|
||||
if (!this.bot) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
const {
|
||||
companyName,
|
||||
targetEmail,
|
||||
employeeName,
|
||||
ipAddress,
|
||||
country,
|
||||
city,
|
||||
browser,
|
||||
os,
|
||||
timestamp,
|
||||
clickCount,
|
||||
companyTotalClicks,
|
||||
companyTotalTokens,
|
||||
} = data;
|
||||
|
||||
const message = `
|
||||
🚨 YENİ TIKLAMA ALGILANDI!
|
||||
|
||||
🏢 Şirket: ${companyName}
|
||||
📧 Hedef: ${targetEmail}
|
||||
${employeeName ? `👤 Çalışan: ${employeeName}` : ''}
|
||||
|
||||
🌍 IP: ${ipAddress}
|
||||
📍 Konum: ${city}, ${country}
|
||||
💻 Cihaz: ${browser} (${os})
|
||||
⏰ Zaman: ${timestamp}
|
||||
|
||||
📊 Bu token için toplam tıklama: ${clickCount}
|
||||
📈 Şirket toplam tıklama: ${companyTotalClicks} (${companyTotalTokens} tokenden)
|
||||
`.trim();
|
||||
|
||||
await this.bot.sendMessage(this.chatId, message);
|
||||
|
||||
logger.info(`Telegram notification sent for ${targetEmail}`);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
logger.error('Failed to send Telegram notification:', error);
|
||||
// Don't throw error - notification failure shouldn't break tracking
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
async sendTestMessage() {
|
||||
try {
|
||||
if (!this.bot) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
const message = `
|
||||
✅ TEST MESAJI
|
||||
|
||||
Telegram bot başarıyla yapılandırıldı!
|
||||
|
||||
⏰ ${new Date().toLocaleString('tr-TR')}
|
||||
`.trim();
|
||||
|
||||
await this.bot.sendMessage(this.chatId, message);
|
||||
|
||||
return { success: true, message: 'Test message sent successfully' };
|
||||
} catch (error) {
|
||||
logger.error('Failed to send test message:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new TelegramService();
|
||||
|
||||
146
backend/src/services/token.service.js
Normal file
146
backend/src/services/token.service.js
Normal file
@@ -0,0 +1,146 @@
|
||||
const { TrackingToken, Company, MailTemplate } = require('../models');
|
||||
const { generateTrackingToken } = require('../utils/tokenGenerator');
|
||||
const mailService = require('./mail.service');
|
||||
const logger = require('../config/logger');
|
||||
|
||||
class TokenService {
|
||||
async createToken(data) {
|
||||
const { company_id, target_email, employee_name, template_type } = data;
|
||||
|
||||
// Generate unique token
|
||||
let token = generateTrackingToken();
|
||||
let isUnique = false;
|
||||
let attempts = 0;
|
||||
|
||||
// Ensure token is unique
|
||||
while (!isUnique && attempts < 5) {
|
||||
const existing = await TrackingToken.findOne({ where: { token } });
|
||||
if (!existing) {
|
||||
isUnique = true;
|
||||
} else {
|
||||
token = generateTrackingToken();
|
||||
attempts++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isUnique) {
|
||||
throw new Error('Failed to generate unique token');
|
||||
}
|
||||
|
||||
// Get company and template
|
||||
const company = await Company.findByPk(company_id);
|
||||
if (!company) {
|
||||
throw new Error('Company not found');
|
||||
}
|
||||
|
||||
const template = await MailTemplate.findOne({ where: { template_type } });
|
||||
if (!template) {
|
||||
throw new Error('Mail template not found');
|
||||
}
|
||||
|
||||
// Create tracking token
|
||||
const trackingToken = await TrackingToken.create({
|
||||
token,
|
||||
company_id,
|
||||
target_email,
|
||||
employee_name,
|
||||
template_type,
|
||||
mail_subject: template.subject_template.replace('{{company_name}}', company.name),
|
||||
});
|
||||
|
||||
// Update company stats
|
||||
await company.increment('total_tokens');
|
||||
|
||||
logger.info(`Token created: ${token} for ${target_email}`);
|
||||
|
||||
return trackingToken;
|
||||
}
|
||||
|
||||
async sendMail(tokenId) {
|
||||
const token = await TrackingToken.findByPk(tokenId, {
|
||||
include: [{ model: Company, as: 'company' }],
|
||||
});
|
||||
|
||||
if (!token) {
|
||||
throw new Error('Token not found');
|
||||
}
|
||||
|
||||
if (token.mail_sent) {
|
||||
throw new Error('Mail already sent for this token');
|
||||
}
|
||||
|
||||
// Get mail template
|
||||
const template = await MailTemplate.findOne({
|
||||
where: { template_type: token.template_type },
|
||||
});
|
||||
|
||||
if (!template) {
|
||||
throw new Error('Mail template not found');
|
||||
}
|
||||
|
||||
// Prepare template data
|
||||
const trackingUrl = `${process.env.BASE_URL}/t/${token.token}`;
|
||||
const currentDate = new Date().toLocaleDateString('tr-TR', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const templateData = {
|
||||
company_name: token.company.name,
|
||||
employee_name: token.employee_name,
|
||||
tracking_url: trackingUrl,
|
||||
current_date: currentDate,
|
||||
current_year: currentYear,
|
||||
};
|
||||
|
||||
// Render mail body
|
||||
const htmlBody = mailService.renderTemplate(template.body_html, templateData);
|
||||
const subject = mailService.renderTemplate(template.subject_template, templateData);
|
||||
|
||||
// Send mail
|
||||
await mailService.sendMail(token.target_email, subject, htmlBody);
|
||||
|
||||
// Update token
|
||||
await token.update({
|
||||
mail_sent: true,
|
||||
sent_at: new Date(),
|
||||
});
|
||||
|
||||
logger.info(`Mail sent for token: ${token.token} to ${token.target_email}`);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
async updateCompanyStats(companyId) {
|
||||
const company = await Company.findByPk(companyId);
|
||||
if (!company) return;
|
||||
|
||||
// Count tokens
|
||||
const totalTokens = await TrackingToken.count({
|
||||
where: { company_id: companyId },
|
||||
});
|
||||
|
||||
const clickedTokens = await TrackingToken.count({
|
||||
where: { company_id: companyId, clicked: true },
|
||||
});
|
||||
|
||||
const totalClicks = await TrackingToken.sum('click_count', {
|
||||
where: { company_id: companyId },
|
||||
});
|
||||
|
||||
const clickRate = totalTokens > 0 ? ((clickedTokens / totalTokens) * 100).toFixed(2) : 0;
|
||||
|
||||
await company.update({
|
||||
total_tokens: totalTokens,
|
||||
total_clicks: totalClicks || 0,
|
||||
click_rate: clickRate,
|
||||
});
|
||||
|
||||
logger.info(`Company stats updated for: ${company.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new TokenService();
|
||||
|
||||
Reference in New Issue
Block a user