From 87506ae85abe422fa98c4b9d401e0491ff8d8a9b Mon Sep 17 00:00:00 2001 From: becarta Date: Sat, 10 May 2025 03:52:10 +0200 Subject: [PATCH] feat: Enhance domain management and configuration in the extension - Introduce default configuration settings for API key, domains database URL, update interval, and warning message template in background.js and options.js. - Update domains database URL to a new source for improved reliability. - Refactor domain management UI in domains_management.js to support adding, editing, and removing trusted and blocked domains with enhanced user experience. - Implement search functionality for filtering domains in the management interface. - Improve status messaging for user feedback during domain operations. --- background.js | 66 +++++++++---- config.js | 2 +- domains_management.js | 223 ++++++++++++++++++++++++++++++++++++------ manifest.json | 2 +- options.html | 2 +- options.js | 28 +++--- 6 files changed, 260 insertions(+), 63 deletions(-) diff --git a/background.js b/background.js index 6a107ff..b8d7d50 100644 --- a/background.js +++ b/background.js @@ -134,27 +134,59 @@ const healthCheck = { } }; +// Default configuration +const DEFAULT_CONFIG = { + apiKey: '', + domainsDBURL: 'https://git.365devnet.eu/365DevNet/EnterpriseAppProtection/raw/branch/main/domains.json', + updateInterval: 24, // hours + warningTemplate: 'Warning: This link claims to be {app} but goes to an unofficial domain.', + lastUpdate: null, + suspiciousCount: 0, + suspiciousLinks: [] +}; + +// Function to get configuration +async function getConfig() { + return new Promise((resolve) => { + chrome.storage.local.get([ + "safeBrowsingApiKey", + "domainsDBURL", + "updateInterval", + "warningTemplate" + ], (result) => { + resolve({ + apiKey: result.safeBrowsingApiKey || DEFAULT_CONFIG.apiKey, + domainsDBURL: result.domainsDBURL || DEFAULT_CONFIG.domainsDBURL, + updateInterval: result.updateInterval || DEFAULT_CONFIG.updateInterval, + warningTemplate: result.warningTemplate || DEFAULT_CONFIG.warningTemplate + }); + }); + }); +} + +// Function to update last update timestamp +async function updateLastUpdate() { + await chrome.storage.local.set({ lastUpdate: Date.now() }); +} + +// Function to update domains database async function updateDomainsDB() { - chrome.storage.local.get(["domainsDBURL"], async function (result) { - const domainsDBURL = result.domainsDBURL || "https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/domains.json"; - console.log("updateDomainsDB: Starting update from URL:", domainsDBURL); try { - const response = await fetch(domainsDBURL); - if (!response.ok) { - console.error("updateDomainsDB: Fetch failed with status", response.status, response.statusText); - return; - } - domainsDB = await response.json(); - chrome.storage.local.set({ - domainsDB: domainsDB, - lastUpdate: Date.now() - }, () => { - console.log("updateDomainsDB: Database updated successfully at", new Date().toLocaleString()); - }); + const config = await getConfig(); + console.log("Updating domains database from:", config.domainsDBURL); + const response = await fetch(config.domainsDBURL); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + await chrome.storage.local.set({ domainsDB: data }); + await updateLastUpdate(); + console.log('Domains database updated successfully'); + return true; } catch (error) { - console.error("updateDomainsDB: Failed to update domains database:", error); + console.error('Failed to update domains database:', error); + return false; } - }); } // Run updates when the extension is installed or started diff --git a/config.js b/config.js index 70c80dd..77d21ad 100644 --- a/config.js +++ b/config.js @@ -1,7 +1,7 @@ // config.js const CONFIG = { // URL to fetch the approved enterprise domains list - DOMAINS_DB_URL: 'https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/domains.json', + DOMAINS_DB_URL: 'https://git.365devnet.eu/365DevNet/EnterpriseAppProtection/raw/branch/main/domains.json', // Update interval in hours UPDATE_INTERVAL: 24, // Warning message template for suspicious links diff --git a/domains_management.js b/domains_management.js index 855671f..d0c2c63 100644 --- a/domains_management.js +++ b/domains_management.js @@ -1,39 +1,204 @@ document.addEventListener("DOMContentLoaded", function () { - const saveButton = document.getElementById("save"); - const trustedInput = document.getElementById("trusted"); - const blockedInput = document.getElementById("blocked"); + // Get UI elements + const addTrustedBtn = document.getElementById("addTrustedDomain"); + const addBlockedBtn = document.getElementById("addBlockedDomain"); + const domainModal = document.getElementById("domainModal"); + const modalTitle = document.getElementById("modalTitle"); + const domainNameInput = document.getElementById("domainName"); + const domainTypeSelect = document.getElementById("domainType"); + const saveDomainBtn = document.getElementById("saveDomain"); + const cancelDomainBtn = document.getElementById("cancelDomain"); + const trustedSearch = document.getElementById("trustedSearch"); + const blockedSearch = document.getElementById("blockedSearch"); + const trustedDomainsList = document.getElementById("trustedDomainsList"); + const blockedDomainsList = document.getElementById("blockedDomainsList"); + const statusMessage = document.getElementById("statusMessage"); + + let currentDomainType = 'trusted'; + let editingDomain = null; // Load saved domains - chrome.storage.local.get(["trustedDomains", "blockedDomains"], function (data) { - if (data.trustedDomains) trustedInput.value = data.trustedDomains.join("\n"); - if (data.blockedDomains) blockedInput.value = data.blockedDomains.join("\n"); - }); + loadDomains(); - // Save button event - saveButton.addEventListener("click", function () { - // Retrieve domains from input fields - const trustedDomains = trustedInput.value.split("\n").map(d => d.trim()).filter(d => d); - const blockedDomains = blockedInput.value.split("\n").map(d => d.trim()).filter(d => d); + // Add domain buttons + if (addTrustedBtn) { + addTrustedBtn.addEventListener("click", () => openAddModal('trusted')); + } + if (addBlockedBtn) { + addBlockedBtn.addEventListener("click", () => openAddModal('blocked')); + } - // Save to Chrome storage - chrome.storage.local.set({ trustedDomains, blockedDomains }, function () { - showPopup("✅ Changes saved successfully!"); - }); - }); + // Modal buttons + if (saveDomainBtn) { + saveDomainBtn.addEventListener("click", saveDomain); + } + if (cancelDomainBtn) { + cancelDomainBtn.addEventListener("click", closeModal); + } - // Function to show styled popup message - function showPopup(message) { - const popup = document.getElementById("popupMessage"); - const popupText = document.getElementById("popupText"); - const popupClose = document.getElementById("popupClose"); + // Search functionality + if (trustedSearch) { + trustedSearch.addEventListener("input", (e) => filterDomains('trusted', e.target.value)); + } + if (blockedSearch) { + blockedSearch.addEventListener("input", (e) => filterDomains('blocked', e.target.value)); + } - popupText.innerText = message; - popup.classList.remove("hidden"); - popup.classList.add("visible"); - - popupClose.addEventListener("click", function () { - popup.classList.remove("visible"); - popup.classList.add("hidden"); + // Functions + function loadDomains() { + chrome.storage.local.get(["trustedDomains", "blockedDomains"], function (data) { + renderDomainList('trusted', data.trustedDomains || []); + renderDomainList('blocked', data.blockedDomains || []); }); } + + function renderDomainList(type, domains) { + const list = type === 'trusted' ? trustedDomainsList : blockedDomainsList; + if (!list) return; + + list.innerHTML = ''; + domains.forEach(domain => { + const item = createDomainItem(domain, type); + list.appendChild(item); + }); + } + + function createDomainItem(domain, type) { + const div = document.createElement('div'); + div.className = 'domain-item'; + div.innerHTML = ` +
+ ${domain} + ${type} +
+
+ + +
+ `; + return div; + } + + function openAddModal(type) { + currentDomainType = type; + editingDomain = null; + modalTitle.textContent = `Add ${type === 'trusted' ? 'Trusted' : 'Blocked'} Domain`; + domainNameInput.value = ''; + domainTypeSelect.value = type; + domainModal.classList.add('active'); + } + + function closeModal() { + domainModal.classList.remove('active'); + editingDomain = null; + } + + function saveDomain() { + const domain = domainNameInput.value.trim(); + if (!domain) { + showStatus('Please enter a domain name', 'error'); + return; + } + + chrome.storage.local.get(["trustedDomains", "blockedDomains"], function (data) { + const trustedDomains = data.trustedDomains || []; + const blockedDomains = data.blockedDomains || []; + const type = domainTypeSelect.value; + + // Remove from both lists if editing + if (editingDomain) { + const oldType = currentDomainType; + if (oldType === 'trusted') { + const index = trustedDomains.indexOf(editingDomain); + if (index > -1) trustedDomains.splice(index, 1); + } else { + const index = blockedDomains.indexOf(editingDomain); + if (index > -1) blockedDomains.splice(index, 1); + } + } + + // Add to appropriate list + if (type === 'trusted' && !trustedDomains.includes(domain)) { + trustedDomains.push(domain); + } else if (type === 'blocked' && !blockedDomains.includes(domain)) { + blockedDomains.push(domain); + } + + // Save to storage + chrome.storage.local.set({ + trustedDomains: trustedDomains, + blockedDomains: blockedDomains + }, function () { + loadDomains(); + closeModal(); + showStatus(`Domain ${editingDomain ? 'updated' : 'added'} successfully`, 'success'); + }); + }); + } + + function editDomain(domain, type) { + currentDomainType = type; + editingDomain = domain; + modalTitle.textContent = `Edit ${type === 'trusted' ? 'Trusted' : 'Blocked'} Domain`; + domainNameInput.value = domain; + domainTypeSelect.value = type; + domainModal.classList.add('active'); + } + + function removeDomain(domain, type) { + if (confirm(`Are you sure you want to remove ${domain}?`)) { + chrome.storage.local.get(["trustedDomains", "blockedDomains"], function (data) { + const trustedDomains = data.trustedDomains || []; + const blockedDomains = data.blockedDomains || []; + + if (type === 'trusted') { + const index = trustedDomains.indexOf(domain); + if (index > -1) trustedDomains.splice(index, 1); + } else { + const index = blockedDomains.indexOf(domain); + if (index > -1) blockedDomains.splice(index, 1); + } + + chrome.storage.local.set({ + trustedDomains: trustedDomains, + blockedDomains: blockedDomains + }, function () { + loadDomains(); + showStatus('Domain removed successfully', 'success'); + }); + }); + } + } + + function filterDomains(type, searchTerm) { + const list = type === 'trusted' ? trustedDomainsList : blockedDomainsList; + if (!list) return; + + const items = list.getElementsByClassName('domain-item'); + searchTerm = searchTerm.toLowerCase(); + + Array.from(items).forEach(item => { + const domainName = item.querySelector('.domain-name').textContent.toLowerCase(); + item.style.display = domainName.includes(searchTerm) ? '' : 'none'; + }); + } + + function showStatus(message, type) { + statusMessage.textContent = message; + statusMessage.className = `status-message ${type}`; + statusMessage.style.display = 'block'; + setTimeout(() => { + statusMessage.style.display = 'none'; + }, 3000); + } + + // Make functions available globally for onclick handlers + window.editDomain = editDomain; + window.removeDomain = removeDomain; }); \ No newline at end of file diff --git a/manifest.json b/manifest.json index f76fdb6..516a2b9 100644 --- a/manifest.json +++ b/manifest.json @@ -43,7 +43,7 @@ }, "options_page": "options.html", "content_security_policy": { - "extension_pages": "script-src 'self'; object-src 'self'; connect-src 'self' https://raw.githubusercontent.com/ https://safebrowsing.googleapis.com/", + "extension_pages": "script-src 'self'; object-src 'self'; connect-src 'self' https://git.365devnet.eu/ https://safebrowsing.googleapis.com/", "sandbox": "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self'" }, "web_accessible_resources": [{ diff --git a/options.html b/options.html index 2ab1963..0d51385 100644 --- a/options.html +++ b/options.html @@ -225,7 +225,7 @@
+ value="https://git.365devnet.eu/365DevNet/EnterpriseAppProtection/raw/branch/main/domains.json" />

URL must point to a valid JSON file containing domain information

diff --git a/options.js b/options.js index 4b0c2c8..1155b56 100644 --- a/options.js +++ b/options.js @@ -11,35 +11,35 @@ document.addEventListener("DOMContentLoaded", function () { console.error("⚠️ Element with ID 'warningTemplate' not found in options.html!"); } - // Default values - const defaultValues = { - safeBrowsingApiKey: "", - domainsDBURL: "https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/domains.json", + // Default configuration + const DEFAULT_CONFIG = { + apiKey: '', + domainsDBURL: 'https://git.365devnet.eu/365DevNet/EnterpriseAppProtection/raw/branch/main/domains.json', updateInterval: 24, - warningTemplate: "Warning: This link claims to be {app} but goes to an unofficial domain." + warningTemplate: 'Warning: This link claims to be {app} but goes to an unofficial domain.' }; // Load stored settings and apply defaults where needed chrome.storage.local.get(["safeBrowsingApiKey", "domainsDBURL", "updateInterval", "warningTemplate"], function (data) { - if (apiKeyElement) apiKeyElement.value = data.safeBrowsingApiKey || defaultValues.safeBrowsingApiKey; - if (domainsDBURLElement) domainsDBURLElement.value = data.domainsDBURL || defaultValues.domainsDBURL; - if (updateIntervalElement) updateIntervalElement.value = data.updateInterval || defaultValues.updateInterval; + if (apiKeyElement) apiKeyElement.value = data.safeBrowsingApiKey || DEFAULT_CONFIG.apiKey; + if (domainsDBURLElement) domainsDBURLElement.value = data.domainsDBURL || DEFAULT_CONFIG.domainsDBURL; + if (updateIntervalElement) updateIntervalElement.value = data.updateInterval || DEFAULT_CONFIG.updateInterval; if (warningTemplateElement) { - warningTemplateElement.value = data.warningTemplate || defaultValues.warningTemplate; + warningTemplateElement.value = data.warningTemplate || DEFAULT_CONFIG.warningTemplate; } }); // Save settings when clicking "Save" if (saveButton) { saveButton.addEventListener("click", function () { - const apiKey = apiKeyElement && apiKeyElement.value.trim() ? apiKeyElement.value.trim() : defaultValues.safeBrowsingApiKey; - const domainsDBURL = domainsDBURLElement && domainsDBURLElement.value.trim() ? domainsDBURLElement.value.trim() : defaultValues.domainsDBURL; + const apiKey = apiKeyElement && apiKeyElement.value.trim() ? apiKeyElement.value.trim() : DEFAULT_CONFIG.apiKey; + const domainsDBURL = domainsDBURLElement && domainsDBURLElement.value.trim() ? domainsDBURLElement.value.trim() : DEFAULT_CONFIG.domainsDBURL; const updateInterval = updateIntervalElement && updateIntervalElement.value.trim() - ? parseInt(updateIntervalElement.value.trim()) || defaultValues.updateInterval - : defaultValues.updateInterval; + ? parseInt(updateIntervalElement.value.trim()) || DEFAULT_CONFIG.updateInterval + : DEFAULT_CONFIG.updateInterval; const warningTemplate = warningTemplateElement && warningTemplateElement.value.trim() ? warningTemplateElement.value.trim() - : defaultValues.warningTemplate; + : DEFAULT_CONFIG.warningTemplate; chrome.storage.local.set({ safeBrowsingApiKey: apiKey,