From 06136294dad6b1fab17a427b074fad3618430df7 Mon Sep 17 00:00:00 2001 From: salvacybersec Date: Tue, 11 Nov 2025 03:02:11 +0300 Subject: [PATCH] Mail schemes updated auto system --- backend/src/controllers/ollama.controller.js | 25 +- backend/src/routes/ollama.routes.js | 3 + backend/src/services/ollama.service.js | 325 +++++++++++++++---- frontend/src/pages/Templates.jsx | 68 ++++ 4 files changed, 358 insertions(+), 63 deletions(-) diff --git a/backend/src/controllers/ollama.controller.js b/backend/src/controllers/ollama.controller.js index 6670d70..4b5c0b7 100644 --- a/backend/src/controllers/ollama.controller.js +++ b/backend/src/controllers/ollama.controller.js @@ -2,6 +2,25 @@ const ollamaService = require('../services/ollama.service'); const { Settings, MailTemplate } = require('../models'); 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 */ @@ -96,7 +115,7 @@ exports.updateSettings = async (req, res, next) => { */ exports.generateTemplate = async (req, res, next) => { 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 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 const templateData = await ollamaService.generateMailTemplate({ @@ -114,6 +133,8 @@ exports.generateTemplate = async (req, res, next) => { scenario, employee_info, custom_prompt, + generate_html: generate_html || false, + template_theme: template_theme || 'red', }); // Save to database if template_name is provided diff --git a/backend/src/routes/ollama.routes.js b/backend/src/routes/ollama.routes.js index 80a6d67..6adfe8c 100644 --- a/backend/src/routes/ollama.routes.js +++ b/backend/src/routes/ollama.routes.js @@ -6,6 +6,9 @@ const { requireAuth } = require('../middlewares/auth'); // All routes require authentication router.use(requireAuth); +// Get available template themes +router.get('/themes', ollamaController.getThemes); + // Test Ollama connection router.get('/test', ollamaController.testConnection); diff --git a/backend/src/services/ollama.service.js b/backend/src/services/ollama.service.js index d4e626c..dae07ba 100644 --- a/backend/src/services/ollama.service.js +++ b/backend/src/services/ollama.service.js @@ -9,32 +9,29 @@ class OllamaService { this.initialized = false; } - /** - * Initialize Ollama service with settings from database - */ async initialize() { + if (this.initialized && this.serverUrl && this.model) { + return; + } + try { - const serverUrlSetting = await Settings.findOne({ - where: { key: 'ollama_server_url' }, - }); - const modelSetting = await Settings.findOne({ - where: { key: 'ollama_model' }, + const settings = await Settings.findAll(); + const settingsMap = {}; + settings.forEach(s => { + settingsMap[s.key] = s.value; }); - this.serverUrl = serverUrlSetting?.value || process.env.OLLAMA_URL || 'http://localhost:11434'; - this.model = modelSetting?.value || process.env.OLLAMA_MODEL || 'llama3.2'; - + this.serverUrl = settingsMap['ollama_server_url'] || 'http://localhost:11434'; + this.model = settingsMap['ollama_model'] || 'llama3.2:latest'; 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) { - logger.error('Failed to initialize Ollama service:', error); - throw error; + logger.error('Failed to initialize Ollama:', error.message); + throw new Error('Ollama ayarları yüklenemedi'); } } - /** - * Test Ollama connection - */ async testConnection() { if (!this.initialized) { await this.initialize(); @@ -45,24 +42,22 @@ class OllamaService { timeout: 5000, }); + const models = response.data.models || []; + return { success: true, - models: response.data.models || [], message: 'Ollama bağlantısı başarılı', + models: models, }; } catch (error) { logger.error('Ollama connection test failed:', error.message); return { success: false, - error: error.message, - message: 'Ollama sunucusuna bağlanılamadı', + message: 'Ollama bağlantısı başarısız: ' + error.message, }; } } - /** - * List available models - */ async listModels() { if (!this.initialized) { 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) { if (!this.initialized) { 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 - const systemPrompt = `Sen profesyonel bir siber güvenlik uzmanısın ve şirketler için phishing farkındalık testi mail şablonları oluşturuyorsun. + // Build the prompt based on mode + 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. @@ -106,42 +97,75 @@ KURALLAR: 3. İkna edici ve inandırıcı olmalı 4. Türkçe dil bilgisi ve imla kurallarına uygun olmalı 5. Kullanıcıyı aciliyet hissi ile harekete geçirmeli -6. Şirket logosu/branding için placeholder kullan ZORUNLU PLACEHOLDER'LAR: - {{company_name}} - Şirket adı -- {{employee_name}} - Çalışan adı (varsa "Sayın {{employee_name}}", yoksa "Sayın Yetkili") -- {{tracking_url}} - Tıklama linki (button veya link olarak) +- {{employee_name}} - Çalışan adı +- {{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} 📌 SENARYO: ${scenario}`; - if (employee_info) { + if (employee_info) userPrompt += `\n📌 HEDEF KİTLE: ${employee_info}`; + if (custom_prompt) userPrompt += `\n📌 ÖZEL TALİMATLAR: ${custom_prompt}`; + userPrompt += ` -📌 HEDEF KİTLE: ${employee_info}`; - } - if (custom_prompt) { - userPrompt += ` -📌 ÖZEL TALİMATLAR: ${custom_prompt}`; - } - - userPrompt += ` - -JSON YANIT FORMATI (AYNEN BU YAPIDA): +JSON YANIT FORMATI: { - "subject": "İkna edici mail konusu buraya (max 70 karakter)", - "body": "

Başlık

Sayın {{employee_name}},

Mail içeriği buraya - ikna edici ve aciliyet vurgulu

Butona tıklat" + "subject": "İkna edici mail konusu (max 70 karakter)", + "body": "

Başlık

Sayın {{employee_name}},

İçerik

Tıklayın" } -⚠️ SADECE JSON DÖNDÜR! Açıklama, not, yorum YAZMA! -⚠️ Body içinde CSS stillendir, responsive yap! -⚠️ Konuyu çekici ve acil yap! -⚠️ HTML'i tam ve geçerli oluştur!`; +⚠️ SADECE JSON DÖNDÜR!`; + + } else { + // 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 (

,

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 { logger.info(`Generating template for company: ${company_name}, scenario: ${scenario}`); @@ -201,11 +225,20 @@ JSON YANIT FORMATI (AYNEN BU YAPIDA): templateData.subject = subject; 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 { subject_template: templateData.subject, - body_template: templateData.body, + body_template: finalBody, generated_by: 'ollama', model: this.model, + html_generated: generate_html, + theme: params.template_theme || 'red', }; } catch (error) { logger.error('Failed to generate template with Ollama:', error.message); @@ -213,9 +246,180 @@ JSON YANIT FORMATI (AYNEN BU YAPIDA): } } - /** - * Generate a simple text completion - */ + getTemplateStyles(theme = 'red') { + 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.replace(/\{\{tracking_url\}\}/g, 'Buraya Tıklayın')}

`; + } + return `

${p}

`; + }) + .join('\n'); + + return ` + + + + + ${subject} + + + +
+
+

{{company_name}}

+
+
+ ${paragraphs} +
+ +
+ +`; + } + + 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) { if (!this.initialized) { await this.initialize(); @@ -243,4 +447,3 @@ JSON YANIT FORMATI (AYNEN BU YAPIDA): } module.exports = new OllamaService(); - diff --git a/frontend/src/pages/Templates.jsx b/frontend/src/pages/Templates.jsx index 634be33..6112e29 100644 --- a/frontend/src/pages/Templates.jsx +++ b/frontend/src/pages/Templates.jsx @@ -21,6 +21,7 @@ import { FormControlLabel, Tabs, Tab, + MenuItem, } from '@mui/material'; import { Add, @@ -68,14 +69,38 @@ function Templates() { custom_prompt: '', template_name: '', template_type: '', + generate_html: false, // Default: use template, generate text only + template_theme: 'red', // Default theme }); const [testMailDialogOpen, setTestMailDialogOpen] = useState(false); const [testMailAddress, setTestMailAddress] = useState(''); + const [availableThemes, setAvailableThemes] = useState([]); useEffect(() => { 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 () => { try { setLoading(true); @@ -178,6 +203,8 @@ function Templates() { custom_prompt: '', template_name: '', template_type: '', + generate_html: false, + template_theme: 'red', }); loadTemplates(); } catch (error) { @@ -617,6 +644,47 @@ function Templates() { onChange={(e) => setAiForm({ ...aiForm, custom_prompt: e.target.value })} helperText="AI'ya özel talimatlar (örn: 'Resmi dil kullan', 'Aciliyet vurgusu yap')" /> + + {!aiForm.generate_html && ( + setAiForm({ ...aiForm, template_theme: e.target.value })} + helperText="Mailin görsel teması (sadece Text modu için)" + > + {availableThemes.map((theme) => ( + + + {theme.label} + + {theme.description} + + + + ))} + + )} + + setAiForm({ ...aiForm, generate_html: e.target.checked })} + color="primary" + /> + } + label="AI ile Full HTML Oluştur (İleri Seviye)" + sx={{ mt: 2 }} + /> + + {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' + } +