first commit: Complete phishing test management panel with Node.js backend and React frontend

This commit is contained in:
salvacybersec
2025-11-10 17:00:40 +03:00
commit 19e551f33b
77 changed files with 6677 additions and 0 deletions

View 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();

View 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();

View 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();