// Create EmailBadger namespace window.EmailBadger = window.EmailBadger || {}; // Global variables let currentUser = null; let analyzedEmails = []; let userSettings = { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, dark_mode: false, vt_api_key: "", tc_api_key: "", vt_service_enabled: false, tc_service_enabled: false }; // Add VirusTotal API rate limiter and cache const vtRateLimiter = { queue: [], processing: false, lastRequestTime: 0, minInterval: 15000, // 15 seconds between requests (4 per minute) cache: new Map(), // Cache for VT results async processQueue() { if (this.processing || this.queue.length === 0) return; this.processing = true; const now = Date.now(); const timeSinceLastRequest = now - this.lastRequestTime; if (timeSinceLastRequest < this.minInterval) { await new Promise(resolve => setTimeout(resolve, this.minInterval - timeSinceLastRequest)); } const { type, identifier, resolve, reject } = this.queue.shift(); try { const result = await this.checkVT(type, identifier); this.lastRequestTime = Date.now(); resolve(result); } catch (error) { reject(error); } this.processing = false; this.processQueue(); }, async checkVT(type, identifier) { // Check cache first const cacheKey = `${type}:${identifier}`; if (this.cache.has(cacheKey)) { console.log(`Cache hit for ${cacheKey}`); return this.cache.get(cacheKey); } // Add to queue if not in cache return new Promise((resolve, reject) => { this.queue.push({ type, identifier, resolve, reject }); this.processQueue(); }); }, // Add result to cache cacheResult(type, identifier, result) { const cacheKey = `${type}:${identifier}`; this.cache.set(cacheKey, result); } }; // Function to check multiple items with VT async function checkWithVirusTotal(items) { const results = new Map(); const uniqueItems = new Set(); // Collect unique items to check items.forEach(item => { if (item.sha1) uniqueItems.add(`hash:${item.sha1}`); if (item.url) uniqueItems.add(`url:${item.url}`); if (item.base_domain) uniqueItems.add(`domain:${item.base_domain}`); if (item.fqdn) uniqueItems.add(`fqdn:${item.fqdn}`); }); // Process each unique item for (const item of uniqueItems) { const [type, identifier] = item.split(':'); try { const result = await vtRateLimiter.checkVT(type, identifier); results.set(item, result); } catch (error) { console.error(`Error checking ${type} ${identifier}:`, error); results.set(item, { detections: 0, total: 0 }); } } return results; } // Function to load current user information async function loadCurrentUser() { try { console.log('Loading current user information...'); const response = await fetch('/auth/current-user/', { credentials: 'include' }); if (!response.ok) { if (response.status === 401) { console.warn('Authentication required. Redirecting to login page.'); if (window.location.pathname !== '/login') { window.location.href = '/login'; } return false; } throw new Error(`Failed to load user info: ${response.statusText}`); } const userInfo = await response.json(); console.log('Loaded user info:', userInfo); // Update currentUser variable currentUser = userInfo; // Update admin-only elements visibility const adminOnlyElements = document.querySelectorAll('.admin-only'); if (currentUser && currentUser.role === 'Administrator') { adminOnlyElements.forEach(element => { element.style.display = ''; }); } else { adminOnlyElements.forEach(element => { element.style.display = 'none'; }); } return true; } catch (error) { console.error('Error loading current user:', error); return false; } } // Define missing functions if they don't exist if (typeof loadUserSettings !== 'function') { window.loadUserSettings = async function() { console.log('Loading user settings...'); try { const response = await fetch('/settings/', { credentials: 'include' }); if (!response.ok) { // If unauthorized, redirect to login page if (response.status === 401) { console.warn('Authentication required. Redirecting to login page.'); // Only redirect if not already on login page if (window.location.pathname !== '/login') { window.location.href = '/login'; } return false; } throw new Error(`Failed to load settings: ${response.statusText}`); } const settings = await response.json(); console.log('Loaded user settings:', settings); // Update user settings object userSettings.timezone = settings.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone; userSettings.dark_mode = settings.dark_mode || false; userSettings.vt_api_key = settings.vt_api_key || ""; userSettings.tc_api_key = settings.tc_api_key || ""; userSettings.vt_service_enabled = settings.vt_service_enabled || false; userSettings.tc_service_enabled = settings.tc_service_enabled || false; // Apply dark mode if needed toggleDarkMode(userSettings.dark_mode); // Update service toggle states in the UI const vtToggle = document.getElementById('vt-service-toggle'); const tcToggle = document.getElementById('tc-service-toggle'); if (vtToggle) { vtToggle.checked = userSettings.vt_service_enabled; } if (tcToggle) { tcToggle.checked = userSettings.tc_service_enabled; } return true; } catch (error) { console.error('Error loading user settings:', error); return false; } }; } if (typeof initializeFileUpload !== 'function') { window.initializeFileUpload = function() { console.log('Initializing file upload...'); // Set up file input change handler const fileInput = document.getElementById('email-file'); if (fileInput) { console.log('Found email-file input, adding change event listener'); // Remove any existing listeners by cloning and replacing const newFileInput = fileInput.cloneNode(true); fileInput.parentNode.replaceChild(newFileInput, fileInput); newFileInput.addEventListener('change', function() { console.log('File input changed:', this.files[0]?.name); const fileName = this.files[0]?.name || 'Choose file'; const label = this.nextElementSibling; if (label) { label.textContent = fileName; } }); } else { console.error('Could not find email-file input element'); } // Set up upload form submit handler const uploadForm = document.getElementById('upload-form'); if (uploadForm) { console.log('Found upload-form, removing any existing listeners and adding new submit event listener'); // Remove any existing listeners by cloning and replacing the form const newForm = uploadForm.cloneNode(true); uploadForm.parentNode.replaceChild(newForm, uploadForm); // Re-initialize the file input after form replacement const newFileInput = newForm.querySelector('#email-file'); if (newFileInput) { console.log('Re-initializing file input after form replacement'); newFileInput.addEventListener('change', function() { console.log('File input changed after form replacement:', this.files[0]?.name); const fileName = this.files[0]?.name || 'Choose file'; const label = this.nextElementSibling; if (label) { label.textContent = fileName; } }); } newForm.addEventListener('submit', async function(e) { e.preventDefault(); console.log('Upload form submitted'); // Prevent double submission if (this.dataset.submitting === 'true') { console.log('Form already submitting, ignoring duplicate submission'); return; } this.dataset.submitting = 'true'; const fileInput = this.querySelector('#email-file'); if (!fileInput || !fileInput.files || !fileInput.files[0]) { alert('Please select a file to analyze.'); this.dataset.submitting = 'false'; return; } const file = fileInput.files[0]; const progressDiv = document.getElementById('upload-progress'); const resultsContainer = document.getElementById('results-container'); const analysisResults = document.getElementById('analysis-results'); if (progressDiv) { progressDiv.innerHTML = '

Starting analysis of file: ' + file.name + '

'; progressDiv.style.minHeight = 'auto'; progressDiv.style.height = 'auto'; progressDiv.style.overflow = 'visible'; } try { // Set a flag to prevent dashboard refresh from triggering analysis window.skipAnalysis = true; // Create FormData object to send the file const formData = new FormData(); formData.append('file', file); // Update progress if (progressDiv) { progressDiv.innerHTML += '

Uploading file...

'; } // Send the file to the server const response = await fetch('/upload/', { method: 'POST', credentials: 'include', body: formData }); if (!response.ok) { throw new Error(`Upload failed: ${response.statusText}`); } // Get the response data const data = await response.json(); console.log('Upload response:', data); // Update progress if (progressDiv) { progressDiv.innerHTML += '

File uploaded successfully.

'; progressDiv.innerHTML += '

Analyzing email content...

'; } // Get the message_id from the response const messageId = data.message_id; // Fetch the analysis results console.log('Fetching analysis for message ID:', messageId); const analysisResponse = await fetch(`/analysis/${messageId}`, { credentials: 'include' }); if (!analysisResponse.ok) { throw new Error(`Analysis failed: ${analysisResponse.statusText}`); } // Get the analysis data const analysisData = await analysisResponse.json(); console.log('Analysis data received:', { messageId: analysisData.message_id, hasHops: !!(analysisData && analysisData.hops), hopsLength: analysisData.hops ? analysisData.hops.length : 0, hopData: analysisData.hops, dataKeys: Object.keys(analysisData) }); // Update progress if (progressDiv) { progressDiv.innerHTML += '

Analysis complete!

'; } // Show the results container if (resultsContainer) { resultsContainer.classList.remove('d-none'); } // Reset the file input and form newForm.reset(); const label = newForm.querySelector('.custom-file-label'); if (label) { label.textContent = 'Choose file'; } // Display the analysis results if (analysisResults) { console.log('Calling displayAnalysisResults with data:', analysisData); displayAnalysisResults(analysisData, messageId); } // Update the dashboard table without triggering a new analysis if (window.location.hash === '#dashboard') { const tableBody = document.getElementById('emails-table-body'); if (tableBody) { // Add the new email to the top of the table const newRow = ` ${formatDateInUserTimezone(analysisData.created_at)} ${analysisData.recipient || 'N/A'} ${analysisData.sender || 'N/A'} ${analysisData.subject || 'N/A'} ${analysisData.num_attachments || 0} ${analysisData.num_hyperlinks || 0} ${analysisData.uploaded_by || 'N/A'} `; // Insert at the top of the table const firstRow = tableBody.querySelector('tr'); if (firstRow) { firstRow.insertAdjacentHTML('beforebegin', newRow); } else { tableBody.innerHTML = newRow; } // Add click event to the new row const newRowElement = tableBody.querySelector(`tr[data-message-id="${messageId}"]`); if (newRowElement) { newRowElement.addEventListener('click', function() { showEmailAnalysis(messageId); }); } } } // Reset the skip analysis flag after a short delay setTimeout(() => { window.skipAnalysis = false; newForm.dataset.submitting = 'false'; }, 1000); } catch (error) { console.error('Error uploading and analyzing file:', error); // Reset the skip analysis flag and form state window.skipAnalysis = false; newForm.dataset.submitting = 'false'; if (progressDiv) { progressDiv.innerHTML += `

Error: ${error.message}

`; } // Show a fallback message if (resultsContainer) { resultsContainer.classList.remove('d-none'); } if (analysisResults) { analysisResults.innerHTML = `
An error occurred while analyzing the email: ${error.message}

Please try again or contact support if the problem persists.

`; } } }); } else { console.error('Could not find upload-form element'); } }; } // Rest of the file remains unchanged... // Function to show email analysis for a specific message ID async function showEmailAnalysis(messageId) { try { console.log('Starting showEmailAnalysis for message ID:', messageId); // First ensure we're in the analyze view if (window.location.hash !== '#analyze') { console.log('Switching to analyze view first'); window.location.hash = '#analyze'; // Wait for the view to load await new Promise(resolve => setTimeout(resolve, 500)); } // Show loading indicator in main content const mainContent = document.getElementById('main-content'); if (mainContent) { mainContent.innerHTML = `

Loading email analysis...

`; } // Fetch the analysis results console.log('Fetching analysis data from /analysis/' + messageId); const response = await fetch(`/analysis/${messageId}`, { credentials: 'include' }); if (!response.ok) { throw new Error(`Failed to load analysis: ${response.statusText}`); } // Get the analysis data const analysisData = await response.json(); console.log('Analysis data received:', { messageId: analysisData.message_id, hasHops: !!(analysisData && analysisData.hops), hopsLength: analysisData.hops ? analysisData.hops.length : 0, hopData: analysisData.hops, fullData: analysisData }); // Instead of waiting for the analyze view elements, create our own results view if (mainContent) { mainContent.innerHTML = `

Email Analysis Results

`; } // Now we can safely get the analysis results container const analysisResults = document.getElementById('analysis-results'); if (!analysisResults) { throw new Error('Failed to create analysis results container'); } // Format and display the results console.log('Calling displayAnalysisResults with data:', analysisData); displayAnalysisResults(analysisData, messageId); } catch (error) { console.error('Error showing email analysis:', error); // Show error message in the main content const mainContent = document.getElementById('main-content'); if (mainContent) { mainContent.innerHTML = `

Error Loading Analysis

${error.message}


`; } else { alert(`Error loading email analysis: ${error.message}`); } } } // Function to display analysis results function displayAnalysisResults(data, messageId) { const analysisResults = document.getElementById('analysis-results'); if (!analysisResults) { console.error('Analysis results container not found'); return; } // Specific logging for hop data console.log('=== Hop Card Debug ==='); console.log('1. Raw hop data:', data.hops); console.log('2. Hop data type:', typeof data.hops); console.log('3. Is hops array?', Array.isArray(data.hops)); console.log('4. Data object keys:', Object.keys(data)); if (data.hops) { console.log('5. Number of hops:', data.hops.length); console.log('6. First hop:', data.hops[0]); console.log('7. First hop keys:', Object.keys(data.hops[0] || {})); } // Check each condition separately const hasData = !!data; const hasHopsProperty = !!(data && data.hops); const isArray = !!(data && data.hops && Array.isArray(data.hops)); const hasLength = !!(data && data.hops && Array.isArray(data.hops) && data.hops.length > 0); console.log('8. Hop card conditions:', { hasData, hasHopsProperty, isArray, hasLength, shouldShowCard: hasData && hasHopsProperty && isArray && hasLength }); console.log('==================='); // More detailed debug logging console.log('Full analysis data:', JSON.stringify(data, null, 2)); console.log('Data type:', typeof data); console.log('Hops property exists:', 'hops' in data); console.log('Hops value:', data.hops); console.log('Is hops an array?', Array.isArray(data.hops)); if (data.hops) { console.log('Number of hops:', data.hops.length); console.log('First hop:', data.hops[0]); } // Format the date in the user's timezone const formatDate = (dateString) => { if (!dateString) return 'N/A'; try { const date = new Date(dateString); return date.toLocaleString(); } catch (e) { console.warn('Error formatting date:', e); return dateString; } }; // Create the HTML content let html = `
Email Details
From ${data.sender || 'N/A'}
To ${data.recipient || 'N/A'}
Subject ${data.subject || 'N/A'}
Date Received ${data.created_at || 'N/A'}
Email Type ${data.content_type === 'text/html' ? 'HTML' : 'Plain Text'}
Attachments ${data.num_attachments || 0}
URLs ${data.num_hyperlinks || 0}
Reply-To ${data.reply_to || 'N/A'}
Headers
${data.full_header || 'No headers available'}
`; // Close the email details card html += `
`; // Add Message Routing Information Card - with detailed logging console.log('=== Message Routing Card Debug ==='); console.log('1. Current HTML length:', html.length); console.log('2. Data object:', { exists: !!data, keys: data ? Object.keys(data) : [], hasHops: !!(data && data.hops), hopsType: data && data.hops ? typeof data.hops : 'no hops', isArray: data && data.hops ? Array.isArray(data.hops) : false, hopsLength: data && data.hops && Array.isArray(data.hops) ? data.hops.length : 0 }); // Always add the Message Routing Information card html += `
Message Routing Information
`; // Ensure hops is always an array const hopsData = (data && data.hops && Array.isArray(data.hops)) ? data.hops : []; console.log('3. Processed hops data:', { isArray: Array.isArray(hopsData), length: hopsData.length, firstHop: hopsData[0], allHops: hopsData }); if (hopsData.length > 0) { console.log('4. Adding hop table with', hopsData.length, 'hops'); html += `
`; hopsData.forEach((hop, index) => { console.log('5. Processing hop', index + 1, ':', hop); const datetime = hop.datetime ? formatDate(hop.datetime) : 'N/A'; const ipAddress = hop.ip_address || 'N/A'; const country = hop.country || 'N/A'; const from = hop.from || 'N/A'; const by = hop.by || 'N/A'; html += ` `; }); html += `
Hop # Date/Time IP Address Country From By
${index + 1} ${datetime} ${ipAddress} ${country} ${from} ${by}
`; } else { console.log('6. No hops found, showing message'); html += `
No routing information available for this email.
`; } // Close the Message Routing Information card html += `
`; console.log('7. After adding Message Routing card, HTML length:', html.length); console.log('8. Card HTML snippet:', html.substring(html.indexOf('hop-information-card') - 50, html.indexOf('hop-information-card') + 200)); console.log('=== End Message Routing Card Debug ==='); // Add Email Security Card html += `
Email Security Analysis
SPF Analysis
Status ${data.spf_result ? ` ${data.spf_result.toUpperCase()} ` : 'Not Available'}
Domain ${data.spf_domain || 'N/A'}
IP Address ${data.spf_ip || 'N/A'}
Explanation ${data.spf_explanation || 'N/A'}
DKIM Analysis
Status ${data.dkim_result ? ` ${data.dkim_result.toUpperCase()} ` : 'Not Available'}
Domain ${data.dkim_domain || 'N/A'}
Selector ${data.dkim_selector || 'N/A'}
Signature ${data.dkim_signature ? ` ${data.dkim_signature} ` : 'N/A'}
${(!data.spf_result && !data.dkim_result) ? `
No SPF or DKIM information available for this email.
` : ''}
`; // Add URLs section (always show) html += `
URLs Found
`; if (data.urls && data.urls.length > 0) { data.urls.forEach(url => { const vtUrl = url.vt_url ? ` VT` : ''; const tcUrl = url.tc_url ? ` TC` : ''; html += ` `; }); } else { html += ` `; } html += `
URL Domain Actions
${url.url} ${url.base_domain} ${vtUrl} ${tcUrl}
No URLs found in this email
`; // Add attachments section (always show) html += `
Attachments
`; if (data.attachments && data.attachments.length > 0) { data.attachments.forEach(attachment => { html += ` `; }); } else { html += ` `; } html += `
Filename Type Size SHA1
${attachment.filename} ${attachment.file_type || 'Unknown'} ${attachment.file_size ? formatFileSize(attachment.file_size) : 'N/A'} ${attachment.sha1 || 'N/A'}
No attachments found in this email
`; // Add screenshot section (always show) html += `
Email Preview
`; if (data.screenshot_path) { html += ` Email Preview `; } else if (data.content_type === 'text/plain') { html += `
This is a plain text email. No preview is available.
`; } else { html += `
No preview is available for this email.
`; } html += `
`; // Close the card html += ` `; // Update the results container analysisResults.innerHTML = html; } // Helper function to format file size function formatFileSize(bytes) { if (!bytes) return 'N/A'; const units = ['B', 'KB', 'MB', 'GB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(1)} ${units[unitIndex]}`; } // Toggle dark mode function function toggleDarkMode(enabled) { console.log('Toggling dark mode:', enabled); if (enabled) { document.body.classList.add('dark-mode'); } else { document.body.classList.remove('dark-mode'); } // Save preference to localStorage localStorage.setItem('dark-mode', enabled); // Update user settings object userSettings.dark_mode = enabled; } function validateServiceSettings() { const vtApiKey = document.getElementById('vt-api-key').value.trim(); const tcApiKey = document.getElementById('tc-api-key').value.trim(); const vtToggle = document.getElementById('vt-service-toggle'); const tcToggle = document.getElementById('tc-service-toggle'); // Disable service toggles if no API key is provided if (vtToggle) { vtToggle.disabled = !vtApiKey; if (!vtApiKey) { vtToggle.checked = false; } } if (tcToggle) { tcToggle.disabled = !tcApiKey; if (!tcApiKey) { tcToggle.checked = false; } } } function setupServiceToggleValidation() { const vtApiKeyInput = document.getElementById('vt-api-key'); const tcApiKeyInput = document.getElementById('tc-api-key'); const vtToggle = document.getElementById('vt-service-toggle'); const tcToggle = document.getElementById('tc-service-toggle'); if (vtApiKeyInput && vtToggle) { vtApiKeyInput.addEventListener('input', validateServiceSettings); vtToggle.addEventListener('change', function() { if (this.checked && !vtApiKeyInput.value.trim()) { alert('Please provide a VirusTotal API key before enabling the service.'); this.checked = false; } }); } if (tcApiKeyInput && tcToggle) { tcApiKeyInput.addEventListener('input', validateServiceSettings); tcToggle.addEventListener('change', function() { if (this.checked && !tcApiKeyInput.value.trim()) { alert('Please provide a ThreatConnect API key before enabling the service.'); this.checked = false; } }); } } // Define functions first function formatDateInUserTimezone(dateString) { if (!dateString) return ""; const date = new Date(dateString); const options = { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: true }; return date.toLocaleString("en-US", options); } function checkUnauthorized(response) { if (response.status === 401) { // Only redirect if not already on login page if (window.location.pathname !== '/login') { window.location.href = "/login"; } return false; } return true; } async function loadAnalyzedEmails() { try { console.log('Loading analyzed emails...'); // If we're skipping analysis (after a new upload), just return if (window.skipAnalysis) { console.log('Skipping analysis as requested'); return; } // Try to use the inbox endpoint which now supports GET requests let response; try { console.log('Fetching from /inbox endpoint...'); response = await fetch('/inbox', { credentials: 'include', headers: { 'Accept': 'application/json' } }); console.log('Response status:', response.status); console.log('Response headers:', Object.fromEntries(response.headers.entries())); if (!response.ok) { console.warn(`API returned error: ${response.status} ${response.statusText}`); // Check if unauthorized if (response.status === 401) { console.log('Unauthorized access, redirecting to login...'); // Only redirect if not already on login page if (window.location.pathname !== '/login') { window.location.href = '/login'; } return; } // Show error message but don't fall back to sample data const tableBody = document.getElementById('emails-table-body'); if (tableBody) { tableBody.innerHTML = `Error loading emails: ${response.statusText}`; } return; } // Parse the response const emails = await response.json(); console.log('Loaded emails from /inbox:', emails); if (!Array.isArray(emails)) { console.error('Expected array of emails but got:', typeof emails); const tableBody = document.getElementById('emails-table-body'); if (tableBody) { tableBody.innerHTML = `Invalid response format from server`; } return; } // Store in global variable analyzedEmails = emails; // Populate the table const tableBody = document.getElementById('emails-table-body'); if (!tableBody) { console.error('emails-table-body element not found'); return; } if (emails.length === 0) { console.log('No emails found in database'); tableBody.innerHTML = 'No emails analyzed yet'; return; } console.log('Populating table with', emails.length, 'emails'); // Update table header to show admin column const tableHeader = document.querySelector('#emails-table thead tr'); if (tableHeader) { // First, check if the admin header exists let adminHeader = tableHeader.querySelector('.admin-only'); if (!adminHeader) { // If it doesn't exist, create it adminHeader = document.createElement('th'); adminHeader.className = 'admin-only'; adminHeader.textContent = 'Uploaded By'; // Insert it as the last column tableHeader.appendChild(adminHeader); } // Always update its visibility based on user role adminHeader.style.display = currentUser && currentUser.role === 'Administrator' ? '' : 'none'; // Also update the column header text const dateHeader = tableHeader.querySelector('th:first-child'); if (dateHeader) { dateHeader.textContent = 'Date Analyzed'; } } // Clear existing click event listeners const existingRows = document.querySelectorAll('.email-row'); existingRows.forEach(row => { row.replaceWith(row.cloneNode(true)); }); // Generate table HTML with message_id in data attribute tableBody.innerHTML = emails.map(email => { // Use the stored counts directly from the email record const numAttachments = email.num_attachments || 0; const numUrls = email.num_hyperlinks || 0; // Ensure message_id is available if (!email.message_id) { console.warn('Email missing message_id:', email); return ''; } return ` ${formatDateInUserTimezone(email.created_at)} ${email.recipient || 'N/A'} ${email.sender || 'N/A'} ${email.subject || 'N/A'} ${numAttachments} ${numUrls} ${email.uploaded_by || 'N/A'} `; }).join(''); // Set up click handlers for the new rows setupEmailRowClickHandlers(); } catch (error) { console.error('Failed to fetch from /inbox:', error); // Show error message but don't fall back to sample data const tableBody = document.getElementById('emails-table-body'); if (tableBody) { tableBody.innerHTML = `Error loading emails: ${error.message}`; } return; } } catch (error) { console.error('Error loading analyzed emails:', error); // Show error message but don't fall back to sample data const tableBody = document.getElementById('emails-table-body'); if (tableBody) { tableBody.innerHTML = `Error loading emails: ${error.message}`; } } } // Initialize settings functionality if (typeof initializeSettings !== 'function') { window.initializeSettings = function() { console.log('Initializing settings...'); // Populate timezone dropdown const timezoneSelect = document.getElementById('timezone-select'); if (timezoneSelect) { // Get all available timezones const timezones = [ 'UTC', 'America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles', 'America/Anchorage', 'America/Honolulu', 'America/Puerto_Rico', 'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Europe/Moscow', 'Asia/Tokyo', 'Asia/Shanghai', 'Asia/Kolkata', 'Asia/Dubai', 'Australia/Sydney', 'Australia/Perth', 'Pacific/Auckland' ]; // Sort timezones alphabetically timezones.sort(); // Add options to select timezones.forEach(timezone => { const option = document.createElement('option'); option.value = timezone; option.textContent = timezone; timezoneSelect.appendChild(option); }); // Set current timezone timezoneSelect.value = userSettings.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone; } // Set dark mode toggle const darkModeSelect = document.getElementById('dark-mode-select'); if (darkModeSelect) { // Set the initial state based on user settings darkModeSelect.checked = userSettings.dark_mode; // Add event listener to immediately apply dark mode changes when toggled darkModeSelect.addEventListener('change', function() { toggleDarkMode(this.checked); console.log('Dark mode toggled in settings to:', this.checked); }); } // Handle settings form submission const settingsForm = document.getElementById('settings-form'); if (settingsForm) { settingsForm.addEventListener('submit', async function(e) { e.preventDefault(); const timezone = timezoneSelect.value; const darkMode = darkModeSelect.checked; const statusDiv = document.getElementById('settings-status'); try { // Prepare data for API const settingsData = { timezone: timezone, dark_mode: darkMode, vt_api_key: null, tc_api_key: null }; // Save settings via API const response = await fetch('/settings/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(settingsData) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || `Failed to save settings: ${response.statusText}`); } // Show success message if (statusDiv) { statusDiv.innerHTML = `
Settings saved successfully
`; } // Update user settings object userSettings.timezone = timezone; userSettings.dark_mode = darkMode; // Apply dark mode if changed toggleDarkMode(darkMode); } catch (error) { console.error('Error saving settings:', error); if (statusDiv) { statusDiv.innerHTML = `
Error saving settings: ${error.message}
`; } } }); } // Handle password change form submission const passwordForm = document.getElementById('password-change-form'); if (passwordForm) { // Add password matching validation const newPassword = document.getElementById('new-password'); const confirmPassword = document.getElementById('confirm-password'); const passwordMatchFeedback = document.getElementById('password-match-feedback'); function validatePasswordMatch() { if (confirmPassword.value === '') { passwordMatchFeedback.innerHTML = ''; return false; } else if (newPassword.value === confirmPassword.value) { passwordMatchFeedback.innerHTML = '
Passwords match
'; return true; } else { passwordMatchFeedback.innerHTML = '
Passwords do not match
'; return false; } } newPassword.addEventListener('input', validatePasswordMatch); confirmPassword.addEventListener('input', validatePasswordMatch); // Handle form submission passwordForm.addEventListener('submit', async function(e) { e.preventDefault(); const currentPassword = document.getElementById('current-password').value; const newPasswordValue = newPassword.value; const confirmPasswordValue = confirmPassword.value; const statusDiv = document.getElementById('password-status'); // Validate form if (!currentPassword || !newPasswordValue || !confirmPasswordValue) { if (statusDiv) { statusDiv.innerHTML = `
All fields are required
`; } return; } if (newPasswordValue !== confirmPasswordValue) { if (statusDiv) { statusDiv.innerHTML = `
Passwords do not match
`; } return; } try { // Show progress if (statusDiv) { statusDiv.innerHTML = `
Changing password...
`; } // Call API to change password const response = await fetch('/auth/change-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ current_password: currentPassword, new_password: newPasswordValue }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || `Failed to change password: ${response.statusText}`); } // Show success message if (statusDiv) { statusDiv.innerHTML = `
Password changed successfully
`; } // Clear form passwordForm.reset(); passwordMatchFeedback.innerHTML = ''; } catch (error) { console.error('Error changing password:', error); if (statusDiv) { statusDiv.innerHTML = `
Error changing password: ${error.message}
`; } } }); } }; } // Initialize application document.addEventListener('DOMContentLoaded', async function() { // Load current user information first const userLoaded = await loadCurrentUser(); if (!userLoaded) { console.error('Failed to load user information'); return; } // Load user settings const settingsLoaded = await loadUserSettings(); if (!settingsLoaded) { console.error('Failed to load user settings'); return; } // Initialize file upload initializeFileUpload(); // Initialize settings initializeSettings(); // Initialize administration if user is admin if (currentUser && currentUser.role === 'Administrator') { console.log('Initializing administrative functions...'); initializeAdministration(); initializeUserManagement(); } // Set initial sidebar state updateSidebarActiveState(window.location.hash || '#dashboard'); // Load analyzed emails loadAnalyzedEmails(); }); // Update initializeAdministration function if (typeof initializeAdministration !== 'function') { window.initializeAdministration = function() { console.log('Initializing administration...'); // Load current API keys and service states async function loadApiKeys() { try { const response = await fetch('/settings/', { credentials: 'include' }); if (!response.ok) { throw new Error(`Failed to load settings: ${response.statusText}`); } const settings = await response.json(); console.log('Loaded settings:', settings); // Populate API key fields const vtKeyInput = document.getElementById('vt-api-key'); const tcKeyInput = document.getElementById('tc-api-key'); const vtServiceEnabled = document.getElementById('vt-service-toggle'); const tcServiceEnabled = document.getElementById('tc-service-toggle'); if (vtKeyInput) { vtKeyInput.value = settings.vt_api_key || ''; } if (tcKeyInput) { tcKeyInput.value = settings.tc_api_key || ''; } // Set service enabled checkboxes if (vtServiceEnabled) { vtServiceEnabled.checked = settings.vt_api_key && settings.vt_service_enabled !== false; vtServiceEnabled.disabled = !settings.vt_api_key; } if (tcServiceEnabled) { tcServiceEnabled.checked = settings.tc_api_key && settings.tc_service_enabled !== false; tcServiceEnabled.disabled = !settings.tc_api_key; } // Store the settings in user settings userSettings.vt_api_key = settings.vt_api_key || ''; userSettings.tc_api_key = settings.tc_api_key || ''; userSettings.vt_service_enabled = settings.vt_service_enabled !== false && !!settings.vt_api_key; userSettings.tc_service_enabled = settings.tc_service_enabled !== false && !!settings.tc_api_key; } catch (error) { console.error('Error loading API keys:', error); const statusDiv = document.getElementById('api-key-status'); if (statusDiv) { statusDiv.innerHTML = `
Error loading API keys: ${error.message}
`; } } } // Load API keys when the page loads loadApiKeys(); // Set up service toggle validation setupServiceToggleValidation(); // Handle API key form submission const apiKeyForm = document.getElementById('api-key-form'); if (apiKeyForm) { apiKeyForm.addEventListener('submit', async function(e) { e.preventDefault(); const vtApiKey = document.getElementById('vt-api-key').value.trim(); const tcApiKey = document.getElementById('tc-api-key').value.trim(); const vtServiceEnabled = document.getElementById('vt-service-toggle').checked; const tcServiceEnabled = document.getElementById('tc-service-toggle').checked; const statusDiv = document.getElementById('api-key-status'); try { // Show progress if (statusDiv) { statusDiv.innerHTML = `
Saving API keys...
`; } // Call API to save settings const response = await fetch('/settings/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ vt_api_key: vtApiKey, tc_api_key: tcApiKey, vt_service_enabled: vtServiceEnabled, tc_service_enabled: tcServiceEnabled }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || `Failed to save settings: ${response.statusText}`); } // Show success message if (statusDiv) { statusDiv.innerHTML = `
API keys saved successfully
`; } // Update user settings userSettings.vt_api_key = vtApiKey; userSettings.tc_api_key = tcApiKey; userSettings.vt_service_enabled = vtServiceEnabled; userSettings.tc_service_enabled = tcServiceEnabled; } catch (error) { console.error('Error saving API keys:', error); if (statusDiv) { statusDiv.innerHTML = `
Error saving API keys: ${error.message}
`; } } }); } }; } // Add the exportToPDF function async function exportToPDF(messageId) { try { // Show loading indicator const button = document.querySelector('button[onclick^="exportToPDF"]'); const originalContent = button.innerHTML; button.innerHTML = ' Generating PDF...'; button.disabled = true; // Get the analysis data const response = await fetch(`/analysis/${messageId}`, { credentials: 'include' }); if (!response.ok) { throw new Error(`Failed to load analysis: ${response.statusText}`); } const data = await response.json(); // Create PDF const { jsPDF } = window.jspdf; const doc = new jsPDF(); // Set up fonts and styles doc.setFont("helvetica", "bold"); doc.setFontSize(16); // Add title doc.text("Email Analysis Report", 20, 20); // Add email details doc.setFontSize(12); doc.setFont("helvetica", "normal"); let y = 40; // Email Details section doc.setFont("helvetica", "bold"); doc.text("Email Details", 20, y); y += 10; doc.setFont("helvetica", "normal"); const details = [ ["From:", data.sender || 'N/A'], ["To:", data.recipient || 'N/A'], ["Subject:", data.subject || 'N/A'], ["Date:", data.created_at || 'N/A'], ["Email Type:", data.content_type === 'text/html' ? 'HTML' : 'Plain Text'], ["Attachments:", data.num_attachments || 0], ["URLs:", data.num_hyperlinks || 0], ["Reply-To:", data.reply_to || 'N/A'] ]; details.forEach(([label, value]) => { doc.text(`${label} ${value}`, 20, y); y += 7; }); // Add headers section y += 5; doc.setFont("helvetica", "bold"); doc.text("Email Headers", 20, y); y += 10; doc.setFont("helvetica", "normal"); // Split headers into lines and add to PDF const headers = data.full_header || 'No headers available'; const headerLines = headers.split('\n'); headerLines.forEach(line => { if (y > 270) { // Check if we need a new page doc.addPage(); y = 20; } doc.text(line, 20, y); y += 7; }); // Add attachments section if there are any if (data.attachments && data.attachments.length > 0) { y += 5; doc.setFont("helvetica", "bold"); doc.text("Attachments", 20, y); y += 10; doc.setFont("helvetica", "normal"); data.attachments.forEach(attachment => { if (y > 270) { doc.addPage(); y = 20; } const vtDetections = attachment.vt_detections || 0; const vtTotal = attachment.vt_total || 0; doc.text([ `Filename: ${attachment.filename || 'N/A'}`, `Size: ${formatFileSize(attachment.file_size || 0)}`, `Type: ${attachment.file_type || 'N/A'}`, `SHA1: ${attachment.sha1 || 'N/A'}`, `VirusTotal: ${vtDetections}/${vtTotal} detections` ], 20, y); y += 25; }); } // Add hop information if available if (data.hops && data.hops.length > 0) { y += 5; doc.setFont("helvetica", "bold"); doc.text("Message Hop Information", 20, y); y += 10; doc.setFont("helvetica", "normal"); data.hops.forEach(hop => { if (y > 270) { doc.addPage(); y = 20; } const datetime = hop.datetime ? formatDate(hop.datetime) : 'N/A'; const ipAddress = hop.ip_address || 'N/A'; const country = hop.country || 'N/A'; const from = hop.from || 'N/A'; const by = hop.by || 'N/A'; doc.text([ `Date/Time: ${datetime}`, `IP Address: ${ipAddress}`, `Country: ${country}`, `From: ${from}`, `By: ${by}` ], 20, y); y += 25; }); } // Add Email Security Card html += `
Email Security Analysis
SPF Analysis
Status ${data.spf_result ? ` ${data.spf_result.toUpperCase()} ` : 'Not Available'}
Domain ${data.spf_domain || 'N/A'}
IP Address ${data.spf_ip || 'N/A'}
Explanation ${data.spf_explanation || 'N/A'}
DKIM Analysis
Status ${data.dkim_result ? ` ${data.dkim_result.toUpperCase()} ` : 'Not Available'}
Domain ${data.dkim_domain || 'N/A'}
Selector ${data.dkim_selector || 'N/A'}
Signature ${data.dkim_signature ? ` ${data.dkim_signature} ` : 'N/A'}
${(!data.spf_result && !data.dkim_result) ? `
No SPF or DKIM information available for this email.
` : ''}
`; // Add URLs section (always show) html += `
URLs Found
`; if (data.urls && data.urls.length > 0) { data.urls.forEach(url => { const vtUrl = url.vt_url ? ` VT` : ''; const tcUrl = url.tc_url ? ` TC` : ''; html += ` `; }); } else { html += ` `; } html += `
URL Domain Actions
${url.url} ${url.base_domain} ${vtUrl} ${tcUrl}
No URLs found in this email
`; // Add attachments section (always show) html += `
Attachments
`; if (data.attachments && data.attachments.length > 0) { data.attachments.forEach(attachment => { html += ` `; }); } else { html += ` `; } html += `
Filename Type Size SHA1
${attachment.filename} ${attachment.file_type || 'Unknown'} ${attachment.file_size ? formatFileSize(attachment.file_size) : 'N/A'} ${attachment.sha1 || 'N/A'}
No attachments found in this email
`; // Add screenshot section (always show) html += `
Email Preview
`; if (data.screenshot_path) { html += ` Email Preview `; } else if (data.content_type === 'text/plain') { html += `
This is a plain text email. No preview is available.
`; } else { html += `
No preview is available for this email.
`; } html += `
`; // Close the card html += ` `; // Update the results container analysisResults.innerHTML = html; // Save the PDF doc.save(`email-analysis-${messageId}.pdf`); // Reset button state button.innerHTML = originalContent; button.disabled = false; } catch (error) { console.error('Error generating PDF:', error); alert(`Error generating PDF: ${error.message}`); // Reset button state const button = document.querySelector('button[onclick^="exportToPDF"]'); if (button) { button.innerHTML = ' Export to PDF'; button.disabled = false; } } } // Add function to update sidebar active state function updateSidebarActiveState(hash) { // Remove active class from all sidebar items document.querySelectorAll('.nav-link').forEach(link => { link.classList.remove('active'); }); // Add active class to current section const currentSection = hash.replace('#', ''); const activeLink = document.querySelector(`.nav-link[href="#${currentSection}"]`); if (activeLink) { activeLink.classList.add('active'); // Also add active class to parent li if it exists const parentLi = activeLink.closest('li'); if (parentLi) { parentLi.classList.add('active'); } } // Handle admin-only visibility const adminOnlyElements = document.querySelectorAll('.admin-only'); if (currentUser && currentUser.role === 'Administrator') { adminOnlyElements.forEach(element => { element.style.display = ''; }); } else { adminOnlyElements.forEach(element => { element.style.display = 'none'; }); } } // Add event listener for hash changes window.addEventListener('hashchange', function() { updateSidebarActiveState(window.location.hash); }); // Initialize sidebar state and admin visibility on page load document.addEventListener('DOMContentLoaded', function() { // Set initial sidebar state updateSidebarActiveState(window.location.hash || '#dashboard'); // Ensure admin-only elements are properly hidden/shown const adminOnlyElements = document.querySelectorAll('.admin-only'); if (currentUser && currentUser.role === 'Administrator') { adminOnlyElements.forEach(element => { element.style.display = ''; }); } else { adminOnlyElements.forEach(element => { element.style.display = 'none'; }); } }); // Update the existing loadView function if (typeof EmailBadger.loadView !== 'function') { EmailBadger.loadView = async function(view) { console.log('Loading view:', view); try { // Get the view content const response = await fetch(`/views/${view}.html`); if (!response.ok) { console.error(`Failed to load ${view} view:`, response.statusText); return; } const content = await response.text(); const mainContent = document.getElementById('main-content'); if (mainContent) { mainContent.innerHTML = content; // Initialize view-specific functionality if (view === 'analyze') { // Check if we have a message_id in the URL const urlParams = new URLSearchParams(window.location.search); const messageId = urlParams.get('message_id'); if (messageId) { console.log('Found message_id in URL:', messageId); // Wait for the view to be fully loaded await new Promise(resolve => setTimeout(resolve, 100)); // Show the analysis await showEmailAnalysis(messageId); } else { // Initialize the upload form for new analysis initializeFileUpload(); } } else if (view === 'dashboard') { loadAnalyzedEmails(); } else if (view === 'settings') { initializeSettings(); } else if (view === 'administration' && currentUser && currentUser.role === 'Administrator') { initializeAdministration(); initializeUserManagement(); } } } catch (error) { console.error(`Error loading ${view} view:`, error); const mainContent = document.getElementById('main-content'); if (mainContent) { mainContent.innerHTML = `

Error Loading View

${error.message}

`; } } }; } // Update the email row click handler function setupEmailRowClickHandlers() { console.log('Setting up click handlers for email rows'); document.querySelectorAll('.email-row').forEach(row => { row.addEventListener('click', async function(e) { e.preventDefault(); e.stopPropagation(); const messageId = this.getAttribute('data-message-id'); console.log('Clicked email row with message_id:', messageId); if (!messageId) { console.error('No message_id found for clicked row'); return; } try { // First switch to analyze view and wait for it to load console.log('Switching to analyze view'); window.location.hash = '#analyze'; // Wait for the view to load await new Promise(resolve => setTimeout(resolve, 500)); // Show loading state const mainContent = document.getElementById('main-content'); if (mainContent) { mainContent.innerHTML = `

Loading email analysis...

`; } // Fetch and display the analysis await showEmailAnalysis(messageId); // Update the URL to include the message ID const newUrl = new URL(window.location.href); newUrl.searchParams.set('message_id', messageId); window.history.pushState({}, '', newUrl); } catch (error) { console.error('Error handling email click:', error); alert(`Error loading email analysis: ${error.message}`); } }); }); }