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.
This commit is contained in:
@@ -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() {
|
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 {
|
try {
|
||||||
const response = await fetch(domainsDBURL);
|
const config = await getConfig();
|
||||||
if (!response.ok) {
|
console.log("Updating domains database from:", config.domainsDBURL);
|
||||||
console.error("updateDomainsDB: Fetch failed with status", response.status, response.statusText);
|
const response = await fetch(config.domainsDBURL);
|
||||||
return;
|
if (!response.ok) {
|
||||||
}
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
domainsDB = await response.json();
|
}
|
||||||
chrome.storage.local.set({
|
const data = await response.json();
|
||||||
domainsDB: domainsDB,
|
await chrome.storage.local.set({ domainsDB: data });
|
||||||
lastUpdate: Date.now()
|
await updateLastUpdate();
|
||||||
}, () => {
|
console.log('Domains database updated successfully');
|
||||||
console.log("updateDomainsDB: Database updated successfully at", new Date().toLocaleString());
|
return true;
|
||||||
});
|
|
||||||
} catch (error) {
|
} 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
|
// Run updates when the extension is installed or started
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
// config.js
|
// config.js
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
// URL to fetch the approved enterprise domains list
|
// 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 in hours
|
||||||
UPDATE_INTERVAL: 24,
|
UPDATE_INTERVAL: 24,
|
||||||
// Warning message template for suspicious links
|
// Warning message template for suspicious links
|
||||||
|
@@ -1,39 +1,204 @@
|
|||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const saveButton = document.getElementById("save");
|
// Get UI elements
|
||||||
const trustedInput = document.getElementById("trusted");
|
const addTrustedBtn = document.getElementById("addTrustedDomain");
|
||||||
const blockedInput = document.getElementById("blocked");
|
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
|
// Load saved domains
|
||||||
chrome.storage.local.get(["trustedDomains", "blockedDomains"], function (data) {
|
loadDomains();
|
||||||
if (data.trustedDomains) trustedInput.value = data.trustedDomains.join("\n");
|
|
||||||
if (data.blockedDomains) blockedInput.value = data.blockedDomains.join("\n");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save button event
|
// Add domain buttons
|
||||||
saveButton.addEventListener("click", function () {
|
if (addTrustedBtn) {
|
||||||
// Retrieve domains from input fields
|
addTrustedBtn.addEventListener("click", () => openAddModal('trusted'));
|
||||||
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);
|
if (addBlockedBtn) {
|
||||||
|
addBlockedBtn.addEventListener("click", () => openAddModal('blocked'));
|
||||||
|
}
|
||||||
|
|
||||||
// Save to Chrome storage
|
// Modal buttons
|
||||||
chrome.storage.local.set({ trustedDomains, blockedDomains }, function () {
|
if (saveDomainBtn) {
|
||||||
showPopup("✅ Changes saved successfully!");
|
saveDomainBtn.addEventListener("click", saveDomain);
|
||||||
});
|
}
|
||||||
});
|
if (cancelDomainBtn) {
|
||||||
|
cancelDomainBtn.addEventListener("click", closeModal);
|
||||||
|
}
|
||||||
|
|
||||||
// Function to show styled popup message
|
// Search functionality
|
||||||
function showPopup(message) {
|
if (trustedSearch) {
|
||||||
const popup = document.getElementById("popupMessage");
|
trustedSearch.addEventListener("input", (e) => filterDomains('trusted', e.target.value));
|
||||||
const popupText = document.getElementById("popupText");
|
}
|
||||||
const popupClose = document.getElementById("popupClose");
|
if (blockedSearch) {
|
||||||
|
blockedSearch.addEventListener("input", (e) => filterDomains('blocked', e.target.value));
|
||||||
|
}
|
||||||
|
|
||||||
popupText.innerText = message;
|
// Functions
|
||||||
popup.classList.remove("hidden");
|
function loadDomains() {
|
||||||
popup.classList.add("visible");
|
chrome.storage.local.get(["trustedDomains", "blockedDomains"], function (data) {
|
||||||
|
renderDomainList('trusted', data.trustedDomains || []);
|
||||||
popupClose.addEventListener("click", function () {
|
renderDomainList('blocked', data.blockedDomains || []);
|
||||||
popup.classList.remove("visible");
|
|
||||||
popup.classList.add("hidden");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div class="domain-info">
|
||||||
|
<span class="domain-name">${domain}</span>
|
||||||
|
<span class="domain-type">${type}</span>
|
||||||
|
</div>
|
||||||
|
<div class="domain-actions">
|
||||||
|
<button class="edit-btn" onclick="editDomain('${domain}', '${type}')">
|
||||||
|
<span>✏️</span>
|
||||||
|
<span>Edit</span>
|
||||||
|
</button>
|
||||||
|
<button class="remove-btn" onclick="removeDomain('${domain}', '${type}')">
|
||||||
|
<span>🗑️</span>
|
||||||
|
<span>Remove</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
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;
|
||||||
});
|
});
|
@@ -43,7 +43,7 @@
|
|||||||
},
|
},
|
||||||
"options_page": "options.html",
|
"options_page": "options.html",
|
||||||
"content_security_policy": {
|
"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'"
|
"sandbox": "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self'"
|
||||||
},
|
},
|
||||||
"web_accessible_resources": [{
|
"web_accessible_resources": [{
|
||||||
|
@@ -225,7 +225,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="domainsDBURL">Database URL</label>
|
<label for="domainsDBURL">Database URL</label>
|
||||||
<input type="text" id="domainsDBURL" placeholder="Enter the URL for your domains database"
|
<input type="text" id="domainsDBURL" placeholder="Enter the URL for your domains database"
|
||||||
value="https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/domains.json" />
|
value="https://git.365devnet.eu/365DevNet/EnterpriseAppProtection/raw/branch/main/domains.json" />
|
||||||
<p class="help-text">URL must point to a valid JSON file containing domain information</p>
|
<p class="help-text">URL must point to a valid JSON file containing domain information</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
28
options.js
28
options.js
@@ -11,35 +11,35 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
console.error("⚠️ Element with ID 'warningTemplate' not found in options.html!");
|
console.error("⚠️ Element with ID 'warningTemplate' not found in options.html!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default values
|
// Default configuration
|
||||||
const defaultValues = {
|
const DEFAULT_CONFIG = {
|
||||||
safeBrowsingApiKey: "",
|
apiKey: '',
|
||||||
domainsDBURL: "https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/domains.json",
|
domainsDBURL: 'https://git.365devnet.eu/365DevNet/EnterpriseAppProtection/raw/branch/main/domains.json',
|
||||||
updateInterval: 24,
|
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
|
// Load stored settings and apply defaults where needed
|
||||||
chrome.storage.local.get(["safeBrowsingApiKey", "domainsDBURL", "updateInterval", "warningTemplate"], function (data) {
|
chrome.storage.local.get(["safeBrowsingApiKey", "domainsDBURL", "updateInterval", "warningTemplate"], function (data) {
|
||||||
if (apiKeyElement) apiKeyElement.value = data.safeBrowsingApiKey || defaultValues.safeBrowsingApiKey;
|
if (apiKeyElement) apiKeyElement.value = data.safeBrowsingApiKey || DEFAULT_CONFIG.apiKey;
|
||||||
if (domainsDBURLElement) domainsDBURLElement.value = data.domainsDBURL || defaultValues.domainsDBURL;
|
if (domainsDBURLElement) domainsDBURLElement.value = data.domainsDBURL || DEFAULT_CONFIG.domainsDBURL;
|
||||||
if (updateIntervalElement) updateIntervalElement.value = data.updateInterval || defaultValues.updateInterval;
|
if (updateIntervalElement) updateIntervalElement.value = data.updateInterval || DEFAULT_CONFIG.updateInterval;
|
||||||
if (warningTemplateElement) {
|
if (warningTemplateElement) {
|
||||||
warningTemplateElement.value = data.warningTemplate || defaultValues.warningTemplate;
|
warningTemplateElement.value = data.warningTemplate || DEFAULT_CONFIG.warningTemplate;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save settings when clicking "Save"
|
// Save settings when clicking "Save"
|
||||||
if (saveButton) {
|
if (saveButton) {
|
||||||
saveButton.addEventListener("click", function () {
|
saveButton.addEventListener("click", function () {
|
||||||
const apiKey = apiKeyElement && apiKeyElement.value.trim() ? apiKeyElement.value.trim() : defaultValues.safeBrowsingApiKey;
|
const apiKey = apiKeyElement && apiKeyElement.value.trim() ? apiKeyElement.value.trim() : DEFAULT_CONFIG.apiKey;
|
||||||
const domainsDBURL = domainsDBURLElement && domainsDBURLElement.value.trim() ? domainsDBURLElement.value.trim() : defaultValues.domainsDBURL;
|
const domainsDBURL = domainsDBURLElement && domainsDBURLElement.value.trim() ? domainsDBURLElement.value.trim() : DEFAULT_CONFIG.domainsDBURL;
|
||||||
const updateInterval = updateIntervalElement && updateIntervalElement.value.trim()
|
const updateInterval = updateIntervalElement && updateIntervalElement.value.trim()
|
||||||
? parseInt(updateIntervalElement.value.trim()) || defaultValues.updateInterval
|
? parseInt(updateIntervalElement.value.trim()) || DEFAULT_CONFIG.updateInterval
|
||||||
: defaultValues.updateInterval;
|
: DEFAULT_CONFIG.updateInterval;
|
||||||
const warningTemplate = warningTemplateElement && warningTemplateElement.value.trim()
|
const warningTemplate = warningTemplateElement && warningTemplateElement.value.trim()
|
||||||
? warningTemplateElement.value.trim()
|
? warningTemplateElement.value.trim()
|
||||||
: defaultValues.warningTemplate;
|
: DEFAULT_CONFIG.warningTemplate;
|
||||||
|
|
||||||
chrome.storage.local.set({
|
chrome.storage.local.set({
|
||||||
safeBrowsingApiKey: apiKey,
|
safeBrowsingApiKey: apiKey,
|
||||||
|
Reference in New Issue
Block a user