Mail schemes updated auto system

This commit is contained in:
salvacybersec
2025-11-11 03:02:11 +03:00
parent a8bedc7fbc
commit 06136294da
4 changed files with 358 additions and 63 deletions

View File

@@ -2,6 +2,25 @@ const ollamaService = require('../services/ollama.service');
const { Settings, MailTemplate } = require('../models'); const { Settings, MailTemplate } = require('../models');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
/**
* Get available template themes
*/
exports.getThemes = async (req, res, next) => {
try {
const themes = ollamaService.getAvailableThemes();
res.json({
success: true,
data: {
themes,
},
});
} catch (error) {
logger.error('Failed to get themes:', error);
next(error);
}
};
/** /**
* Test Ollama connection * Test Ollama connection
*/ */
@@ -96,7 +115,7 @@ exports.updateSettings = async (req, res, next) => {
*/ */
exports.generateTemplate = async (req, res, next) => { exports.generateTemplate = async (req, res, next) => {
try { try {
const { company_name, scenario, employee_info, custom_prompt, template_name, template_type } = req.body; const { company_name, scenario, employee_info, custom_prompt, template_name, template_type, generate_html, template_theme } = req.body;
// Validation // Validation
if (!company_name || !scenario) { if (!company_name || !scenario) {
@@ -106,7 +125,7 @@ exports.generateTemplate = async (req, res, next) => {
}); });
} }
logger.info(`AI template generation requested for: ${company_name} - ${scenario}`); logger.info(`AI template generation requested for: ${company_name} - ${scenario} (HTML: ${generate_html ? 'yes' : 'no'}, Theme: ${template_theme || 'red'})`);
// Generate template using Ollama // Generate template using Ollama
const templateData = await ollamaService.generateMailTemplate({ const templateData = await ollamaService.generateMailTemplate({
@@ -114,6 +133,8 @@ exports.generateTemplate = async (req, res, next) => {
scenario, scenario,
employee_info, employee_info,
custom_prompt, custom_prompt,
generate_html: generate_html || false,
template_theme: template_theme || 'red',
}); });
// Save to database if template_name is provided // Save to database if template_name is provided

View File

@@ -6,6 +6,9 @@ const { requireAuth } = require('../middlewares/auth');
// All routes require authentication // All routes require authentication
router.use(requireAuth); router.use(requireAuth);
// Get available template themes
router.get('/themes', ollamaController.getThemes);
// Test Ollama connection // Test Ollama connection
router.get('/test', ollamaController.testConnection); router.get('/test', ollamaController.testConnection);

View File

@@ -9,32 +9,29 @@ class OllamaService {
this.initialized = false; this.initialized = false;
} }
/**
* Initialize Ollama service with settings from database
*/
async initialize() { async initialize() {
if (this.initialized && this.serverUrl && this.model) {
return;
}
try { try {
const serverUrlSetting = await Settings.findOne({ const settings = await Settings.findAll();
where: { key: 'ollama_server_url' }, const settingsMap = {};
}); settings.forEach(s => {
const modelSetting = await Settings.findOne({ settingsMap[s.key] = s.value;
where: { key: 'ollama_model' },
}); });
this.serverUrl = serverUrlSetting?.value || process.env.OLLAMA_URL || 'http://localhost:11434'; this.serverUrl = settingsMap['ollama_server_url'] || 'http://localhost:11434';
this.model = modelSetting?.value || process.env.OLLAMA_MODEL || 'llama3.2'; this.model = settingsMap['ollama_model'] || 'llama3.2:latest';
this.initialized = true; this.initialized = true;
logger.info(`Ollama service initialized: ${this.serverUrl} with model ${this.model}`);
logger.info(`Ollama initialized: ${this.serverUrl}, model: ${this.model}`);
} catch (error) { } catch (error) {
logger.error('Failed to initialize Ollama service:', error); logger.error('Failed to initialize Ollama:', error.message);
throw error; throw new Error('Ollama ayarları yüklenemedi');
} }
} }
/**
* Test Ollama connection
*/
async testConnection() { async testConnection() {
if (!this.initialized) { if (!this.initialized) {
await this.initialize(); await this.initialize();
@@ -45,24 +42,22 @@ class OllamaService {
timeout: 5000, timeout: 5000,
}); });
const models = response.data.models || [];
return { return {
success: true, success: true,
models: response.data.models || [],
message: 'Ollama bağlantısı başarılı', message: 'Ollama bağlantısı başarılı',
models: models,
}; };
} catch (error) { } catch (error) {
logger.error('Ollama connection test failed:', error.message); logger.error('Ollama connection test failed:', error.message);
return { return {
success: false, success: false,
error: error.message, message: 'Ollama bağlantısı başarısız: ' + error.message,
message: 'Ollama sunucusuna bağlanılamadı',
}; };
} }
} }
/**
* List available models
*/
async listModels() { async listModels() {
if (!this.initialized) { if (!this.initialized) {
await this.initialize(); await this.initialize();
@@ -80,23 +75,19 @@ class OllamaService {
} }
} }
/**
* Generate mail template using Ollama
* @param {Object} params - Template generation parameters
* @param {string} params.company_name - Target company name
* @param {string} params.scenario - Phishing scenario type
* @param {string} params.employee_info - Employee information (optional)
* @param {string} params.custom_prompt - Custom instructions (optional)
*/
async generateMailTemplate(params) { async generateMailTemplate(params) {
if (!this.initialized) { if (!this.initialized) {
await this.initialize(); await this.initialize();
} }
const { company_name, scenario, employee_info, custom_prompt } = params; const { company_name, scenario, employee_info, custom_prompt, generate_html = false } = params;
// Build the prompt // Build the prompt based on mode
const systemPrompt = `Sen profesyonel bir siber güvenlik uzmanısın ve şirketler için phishing farkındalık testi mail şablonları oluşturuyorsun. let systemPrompt, userPrompt;
if (generate_html) {
// Full HTML generation mode
systemPrompt = `Sen profesyonel bir siber güvenlik uzmanısın ve şirketler için phishing farkındalık testi mail şablonları oluşturuyorsun.
GÖREV: Gerçekçi, ikna edici ve profesyonel phishing test mailleri oluştur. GÖREV: Gerçekçi, ikna edici ve profesyonel phishing test mailleri oluştur.
@@ -106,42 +97,75 @@ KURALLAR:
3. İkna edici ve inandırıcı olmalı 3. İkna edici ve inandırıcı olmalı
4. Türkçe dil bilgisi ve imla kurallarına uygun olmalı 4. Türkçe dil bilgisi ve imla kurallarına uygun olmalı
5. Kullanıcıyı aciliyet hissi ile harekete geçirmeli 5. Kullanıcıyı aciliyet hissi ile harekete geçirmeli
6. Şirket logosu/branding için placeholder kullan
ZORUNLU PLACEHOLDER'LAR: ZORUNLU PLACEHOLDER'LAR:
- {{company_name}} - Şirket adı - {{company_name}} - Şirket adı
- {{employee_name}} - Çalışan adı (varsa "Sayın {{employee_name}}", yoksa "Sayın Yetkili") - {{employee_name}} - Çalışan adı
- {{tracking_url}} - Tıklama linki (button veya link olarak) - {{tracking_url}} - Tıklama linki
YANIT FORMATI: Sadece ve sadece JSON, hiçbir ek açıklama yok!`; YANIT FORMATI: Sadece JSON!`;
let userPrompt = `Aşağıdaki bilgilere göre profesyonel bir phishing test mail şablonu oluştur: userPrompt = `Aşağıdaki bilgilere göre profesyonel bir phishing test mail şablonu oluştur:
📌 HEDEF ŞİRKET: ${company_name} 📌 HEDEF ŞİRKET: ${company_name}
📌 SENARYO: ${scenario}`; 📌 SENARYO: ${scenario}`;
if (employee_info) { if (employee_info) userPrompt += `\n📌 HEDEF KİTLE: ${employee_info}`;
userPrompt += ` if (custom_prompt) userPrompt += `\n📌 ÖZEL TALİMATLAR: ${custom_prompt}`;
📌 HEDEF KİTLE: ${employee_info}`;
}
if (custom_prompt) {
userPrompt += `
📌 ÖZEL TALİMATLAR: ${custom_prompt}`;
}
userPrompt += ` userPrompt += `
JSON YANIT FORMATI (AYNEN BU YAPIDA): JSON YANIT FORMATI:
{ {
"subject": "İkna edici mail konusu buraya (max 70 karakter)", "subject": "İkna edici mail konusu (max 70 karakter)",
"body": "<!DOCTYPE html><html><head><meta charset='UTF-8'><style>body{font-family:Arial,sans-serif;line-height:1.6;color:#333;max-width:600px;margin:0 auto;padding:20px;}h2{color:#d32f2f;}.button{display:inline-block;padding:12px 30px;background:#d32f2f;color:white;text-decoration:none;border-radius:5px;margin:20px 0;}.footer{font-size:12px;color:#666;margin-top:30px;border-top:1px solid #ddd;padding-top:15px;}</style></head><body><h2>Başlık</h2><p>Sayın {{employee_name}},</p><p>Mail içeriği buraya - ikna edici ve aciliyet vurgulu</p><a href='{{tracking_url}}' class='button'>Butona tıklat</a><div class='footer'>{{company_name}} © 2024</div></body></html>" "body": "<!DOCTYPE html><html><head><meta charset='UTF-8'><style>body{font-family:Arial,sans-serif;line-height:1.6;color:#333;max-width:600px;margin:0 auto;padding:20px;}h2{color:#d32f2f;}.button{display:inline-block;padding:12px 30px;background:#d32f2f;color:white;text-decoration:none;border-radius:5px;margin:20px 0;}</style></head><body><h2>Başlık</h2><p>Sayın {{employee_name}},</p><p>İçerik</p><a href='{{tracking_url}}' class='button'>Tıklayın</a></body></html>"
} }
⚠️ SADECE JSON DÖNDÜR!ıklama, not, yorum YAZMA! ⚠️ SADECE JSON DÖNDÜR!`;
⚠️ Body içinde CSS stillendir, responsive yap!
⚠️ Konuyu çekici ve acil yap! } else {
⚠️ HTML'i tam ve geçerli oluştur!`; // Text-only generation mode (RECOMMENDED)
systemPrompt = `Sen profesyonel bir siber güvenlik uzmanısın ve phishing test mail içerikleri yazıyorsun.
GÖREV: İkna edici, gerçekçi ve profesyonel mail içerikleri yaz.
KURALLAR:
1. Gerçek şirket maillerine benzer dil ve ton kullan
2. İkna edici ve inandırıcı ol
3. Türkçe dil bilgisi ve imla kurallarına uygun yaz
4. Kullanıcıyı aciliyet hissi ile harekete geçir
5. SADECE METİN İÇERİĞİ yaz, HTML TAG'LERİ KULLANMA!
ZORUNLU PLACEHOLDER'LAR:
- {{company_name}} - Şirket adı
- {{employee_name}} - Çalışan adı
- {{tracking_url}} - Link metni (örn: "buraya tıklayın", "hesabınızı doğrulayın")
YANIT FORMATI: Sadece JSON!`;
userPrompt = `Aşağıdaki bilgilere göre profesyonel bir phishing test mail içeriği yaz:
📌 HEDEF ŞİRKET: ${company_name}
📌 SENARYO: ${scenario}`;
if (employee_info) userPrompt += `\n📌 HEDEF KİTLE: ${employee_info}`;
if (custom_prompt) userPrompt += `\n📌 ÖZEL TALİMATLAR: ${custom_prompt}`;
userPrompt += `
JSON YANIT FORMATI:
{
"subject": "İkna edici mail konusu (max 70 karakter)",
"body": "Mail içeriği buraya. Sayın {{employee_name}}, [mesajın içeriği]. Link metni için {{tracking_url}} placeholder'ını kullan. İmza kısmı."
}
⚠️ ÖNEMLİ:
- Body içinde HTML TAG kullanma (<p>, <div> gibi)!
- Sadece düz metin yaz!
- Satır atlamak için sadece yeni satır kullan!
- {{tracking_url}} placeholder'ını link metni içinde kullan (örn: "{{tracking_url}} buraya tıklayın")
- SADECE JSON DÖNDÜR!`;
}
try { try {
logger.info(`Generating template for company: ${company_name}, scenario: ${scenario}`); logger.info(`Generating template for company: ${company_name}, scenario: ${scenario}`);
@@ -201,11 +225,20 @@ JSON YANIT FORMATI (AYNEN BU YAPIDA):
templateData.subject = subject; templateData.subject = subject;
templateData.body = body; templateData.body = body;
// If generate_html is false, wrap the content in our template
let finalBody = body;
if (!generate_html) {
const theme = params.template_theme || 'red';
finalBody = this.wrapInTemplate(body, subject, theme);
}
return { return {
subject_template: templateData.subject, subject_template: templateData.subject,
body_template: templateData.body, body_template: finalBody,
generated_by: 'ollama', generated_by: 'ollama',
model: this.model, model: this.model,
html_generated: generate_html,
theme: params.template_theme || 'red',
}; };
} catch (error) { } catch (error) {
logger.error('Failed to generate template with Ollama:', error.message); logger.error('Failed to generate template with Ollama:', error.message);
@@ -213,9 +246,180 @@ JSON YANIT FORMATI (AYNEN BU YAPIDA):
} }
} }
/** getTemplateStyles(theme = 'red') {
* Generate a simple text completion const themes = {
*/ red: {
name: 'Kırmızı (Acil/Uyarı)',
gradient: 'linear-gradient(135deg, #e53935 0%, #c62828 100%)',
button: '#e53935',
buttonHover: '#c62828',
accent: '#e53935',
},
green: {
name: 'Yeşil (Başarı/Onay)',
gradient: 'linear-gradient(135deg, #43a047 0%, #2e7d32 100%)',
button: '#43a047',
buttonHover: '#2e7d32',
accent: '#43a047',
},
blue: {
name: 'Mavi (Kurumsal)',
gradient: 'linear-gradient(135deg, #1e88e5 0%, #1565c0 100%)',
button: '#1e88e5',
buttonHover: '#1565c0',
accent: '#1e88e5',
},
purple: {
name: 'Mor (Premium)',
gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
button: '#667eea',
buttonHover: '#5568d3',
accent: '#667eea',
},
orange: {
name: 'Turuncu (Dikkat)',
gradient: 'linear-gradient(135deg, #fb8c00 0%, #ef6c00 100%)',
button: '#fb8c00',
buttonHover: '#ef6c00',
accent: '#fb8c00',
},
dark: {
name: 'Siyah (Ciddi)',
gradient: 'linear-gradient(135deg, #37474f 0%, #263238 100%)',
button: '#37474f',
buttonHover: '#263238',
accent: '#37474f',
},
};
return themes[theme] || themes.red;
}
wrapInTemplate(textContent, subject, theme = 'red') {
const style = this.getTemplateStyles(theme);
// Convert text to HTML paragraphs
const paragraphs = textContent
.split('\n\n')
.filter(p => p.trim())
.map(p => {
// Replace {{tracking_url}} placeholder with styled link
if (p.includes('{{tracking_url}}')) {
return `<p>${p.replace(/\{\{tracking_url\}\}/g, '<a href="{{tracking_url}}" class="button">Buraya Tıklayın</a>')}</p>`;
}
return `<p>${p}</p>`;
})
.join('\n');
return `<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${subject}</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
margin: 0;
padding: 0;
}
.container {
max-width: 600px;
margin: 20px auto;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header {
background: ${style.gradient};
color: white;
padding: 30px 20px;
text-align: center;
}
.header h1 {
margin: 0;
font-size: 24px;
font-weight: 600;
}
.content {
padding: 30px;
}
.content p {
margin: 0 0 15px 0;
color: #555;
}
.button {
display: inline-block;
padding: 14px 32px;
background: ${style.button};
color: white !important;
text-decoration: none;
border-radius: 5px;
margin: 20px 0;
font-weight: 600;
transition: background 0.3s;
}
.button:hover {
background: ${style.buttonHover};
}
.footer {
background: #f8f9fa;
padding: 20px;
text-align: center;
font-size: 12px;
color: #666;
border-top: 1px solid #e0e0e0;
}
.warning {
background: #fff3cd;
border-left: 4px solid ${style.accent};
padding: 15px;
margin: 20px 0;
border-radius: 4px;
}
@media only screen and (max-width: 600px) {
.container {
margin: 0;
border-radius: 0;
}
.content {
padding: 20px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>{{company_name}}</h1>
</div>
<div class="content">
${paragraphs}
</div>
<div class="footer">
<p>&copy; 2024 {{company_name}}. Tüm hakları saklıdır.</p>
<p style="margin-top: 10px; font-size: 11px;">Bu mail {{company_name}} tarafından gönderilmiştir.</p>
</div>
</div>
</body>
</html>`;
}
getAvailableThemes() {
return [
{ value: 'red', label: '🔴 Kırmızı (Acil/Uyarı)', description: 'Acil durumlar, güvenlik uyarıları' },
{ value: 'green', label: '🟢 Yeşil (Başarı/Onay)', description: 'Onay mailleri, başarı bildirimleri' },
{ value: 'blue', label: '🔵 Mavi (Kurumsal)', description: 'Resmi kurumsal mailler' },
{ value: 'purple', label: '🟣 Mor (Premium)', description: 'Özel teklifler, premium içerik' },
{ value: 'orange', label: '🟠 Turuncu (Dikkat)', description: 'Bildirimler, hatırlatmalar' },
{ value: 'dark', label: '⚫ Siyah (Ciddi)', description: 'Ciddi/resmi konular' },
];
}
async generate(prompt) { async generate(prompt) {
if (!this.initialized) { if (!this.initialized) {
await this.initialize(); await this.initialize();
@@ -243,4 +447,3 @@ JSON YANIT FORMATI (AYNEN BU YAPIDA):
} }
module.exports = new OllamaService(); module.exports = new OllamaService();

View File

@@ -21,6 +21,7 @@ import {
FormControlLabel, FormControlLabel,
Tabs, Tabs,
Tab, Tab,
MenuItem,
} from '@mui/material'; } from '@mui/material';
import { import {
Add, Add,
@@ -68,14 +69,38 @@ function Templates() {
custom_prompt: '', custom_prompt: '',
template_name: '', template_name: '',
template_type: '', template_type: '',
generate_html: false, // Default: use template, generate text only
template_theme: 'red', // Default theme
}); });
const [testMailDialogOpen, setTestMailDialogOpen] = useState(false); const [testMailDialogOpen, setTestMailDialogOpen] = useState(false);
const [testMailAddress, setTestMailAddress] = useState(''); const [testMailAddress, setTestMailAddress] = useState('');
const [availableThemes, setAvailableThemes] = useState([]);
useEffect(() => { useEffect(() => {
loadTemplates(); loadTemplates();
loadThemes();
}, []); }, []);
const loadThemes = async () => {
try {
const response = await axios.get(`${API_URL}/api/ollama/themes`, {
withCredentials: true,
});
setAvailableThemes(response.data.data.themes);
} catch (error) {
console.error('Failed to load themes:', error);
// Set default themes if API fails
setAvailableThemes([
{ value: 'red', label: '🔴 Kırmızı (Acil/Uyarı)', description: 'Acil durumlar, güvenlik uyarıları' },
{ value: 'green', label: '🟢 Yeşil (Başarı/Onay)', description: 'Onay mailleri, başarı bildirimleri' },
{ value: 'blue', label: '🔵 Mavi (Kurumsal)', description: 'Resmi kurumsal mailler' },
{ value: 'purple', label: '🟣 Mor (Premium)', description: 'Özel teklifler, premium içerik' },
{ value: 'orange', label: '🟠 Turuncu (Dikkat)', description: 'Bildirimler, hatırlatmalar' },
{ value: 'dark', label: '⚫ Siyah (Ciddi)', description: 'Ciddi/resmi konular' },
]);
}
};
const loadTemplates = async () => { const loadTemplates = async () => {
try { try {
setLoading(true); setLoading(true);
@@ -178,6 +203,8 @@ function Templates() {
custom_prompt: '', custom_prompt: '',
template_name: '', template_name: '',
template_type: '', template_type: '',
generate_html: false,
template_theme: 'red',
}); });
loadTemplates(); loadTemplates();
} catch (error) { } catch (error) {
@@ -617,6 +644,47 @@ function Templates() {
onChange={(e) => setAiForm({ ...aiForm, custom_prompt: e.target.value })} onChange={(e) => setAiForm({ ...aiForm, custom_prompt: e.target.value })}
helperText="AI'ya özel talimatlar (örn: 'Resmi dil kullan', 'Aciliyet vurgusu yap')" helperText="AI'ya özel talimatlar (örn: 'Resmi dil kullan', 'Aciliyet vurgusu yap')"
/> />
{!aiForm.generate_html && (
<TextField
fullWidth
margin="normal"
select
label="HTML Şablon Teması"
value={aiForm.template_theme}
onChange={(e) => setAiForm({ ...aiForm, template_theme: e.target.value })}
helperText="Mailin görsel teması (sadece Text modu için)"
>
{availableThemes.map((theme) => (
<MenuItem key={theme.value} value={theme.value}>
<Box>
<Typography variant="body1">{theme.label}</Typography>
<Typography variant="caption" color="text.secondary">
{theme.description}
</Typography>
</Box>
</MenuItem>
))}
</TextField>
)}
<FormControlLabel
control={
<Switch
checked={aiForm.generate_html}
onChange={(e) => setAiForm({ ...aiForm, generate_html: e.target.checked })}
color="primary"
/>
}
label="AI ile Full HTML Oluştur (İleri Seviye)"
sx={{ mt: 2 }}
/>
<Typography variant="caption" color="text.secondary" display="block" sx={{ ml: 4, mt: -1 }}>
{aiForm.generate_html
? ' AI hem içeriği hem HTML yapısını oluşturacak (bazen hatalı olabilir)'
: ' Önerilen: AI sadece içerik oluşturur, HTML şablonu hazır kullanılır'
}
</Typography>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => setAiDialogOpen(false)} disabled={aiGenerating}> <Button onClick={() => setAiDialogOpen(false)} disabled={aiGenerating}>