feat: Enhance Enterprise App Protection extension with monitoring and UI improvements
- Introduce error reporting and performance monitoring in background.js to track API calls and processing times. - Implement health check system to ensure the extension's operational status and log issues. - Add caching and encryption utilities in content.js for improved link analysis and data validation. - Refactor link analysis to process in batches, enhancing performance and user experience. - Update UI in domains_management.html and options.html for better usability and aesthetics, including responsive design and improved layout. - Enhance popup.html to display suspicious links with better styling and functionality. - Modify manifest.json to include new permissions and host access for Safe Browsing API.
This commit is contained in:
227
background.js
227
background.js
@@ -3,6 +3,136 @@
|
||||
console.log("Background service worker loaded");
|
||||
|
||||
let domainsDB = {};
|
||||
let SAFE_BROWSING_API_KEY = "";
|
||||
const API_CALL_LIMIT = 100; // Maximum number of API calls per minute
|
||||
let apiCallCount = 0;
|
||||
let lastResetTime = Date.now();
|
||||
|
||||
// Error reporting service
|
||||
const errorReporter = {
|
||||
errors: [],
|
||||
maxErrors: 100,
|
||||
|
||||
log(error, context = {}) {
|
||||
const errorEntry = {
|
||||
timestamp: Date.now(),
|
||||
error: error.message || error,
|
||||
stack: error.stack,
|
||||
context
|
||||
};
|
||||
|
||||
this.errors.push(errorEntry);
|
||||
if (this.errors.length > this.maxErrors) {
|
||||
this.errors.shift();
|
||||
}
|
||||
|
||||
// Send to monitoring service if configured
|
||||
if (this.monitoringEndpoint) {
|
||||
this.sendToMonitoring(errorEntry);
|
||||
}
|
||||
|
||||
console.error('Error logged:', errorEntry);
|
||||
},
|
||||
|
||||
async sendToMonitoring(errorEntry) {
|
||||
try {
|
||||
await fetch(this.monitoringEndpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(errorEntry)
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to send error to monitoring:', e);
|
||||
}
|
||||
},
|
||||
|
||||
getErrors() {
|
||||
return [...this.errors];
|
||||
},
|
||||
|
||||
clearErrors() {
|
||||
this.errors = [];
|
||||
}
|
||||
};
|
||||
|
||||
// Performance monitoring
|
||||
const performanceMonitor = {
|
||||
metrics: {
|
||||
apiCalls: 0,
|
||||
processingTime: 0,
|
||||
errors: 0
|
||||
},
|
||||
|
||||
startTimer() {
|
||||
return performance.now();
|
||||
},
|
||||
|
||||
endTimer(startTime) {
|
||||
return performance.now() - startTime;
|
||||
},
|
||||
|
||||
logMetric(name, value) {
|
||||
this.metrics[name] = (this.metrics[name] || 0) + value;
|
||||
},
|
||||
|
||||
getMetrics() {
|
||||
return { ...this.metrics };
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.metrics = {
|
||||
apiCalls: 0,
|
||||
processingTime: 0,
|
||||
errors: 0
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Health check system
|
||||
const healthCheck = {
|
||||
lastCheck: Date.now(),
|
||||
status: 'healthy',
|
||||
issues: [],
|
||||
|
||||
async check() {
|
||||
const startTime = performanceMonitor.startTimer();
|
||||
try {
|
||||
// Check storage access
|
||||
await chrome.storage.local.get(['domainsDB']);
|
||||
|
||||
// Check API key
|
||||
if (!SAFE_BROWSING_API_KEY) {
|
||||
throw new Error('API key not configured');
|
||||
}
|
||||
|
||||
// Check domains database
|
||||
if (!domainsDB || Object.keys(domainsDB).length === 0) {
|
||||
throw new Error('Domains database is empty');
|
||||
}
|
||||
|
||||
this.status = 'healthy';
|
||||
this.issues = [];
|
||||
} catch (error) {
|
||||
this.status = 'unhealthy';
|
||||
this.issues.push({
|
||||
timestamp: Date.now(),
|
||||
error: error.message
|
||||
});
|
||||
errorReporter.log(error, { component: 'healthCheck' });
|
||||
}
|
||||
|
||||
performanceMonitor.logMetric('processingTime', performanceMonitor.endTimer(startTime));
|
||||
this.lastCheck = Date.now();
|
||||
},
|
||||
|
||||
getStatus() {
|
||||
return {
|
||||
status: this.status,
|
||||
lastCheck: this.lastCheck,
|
||||
issues: [...this.issues]
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
async function updateDomainsDB() {
|
||||
chrome.storage.local.get(["domainsDBURL"], async function (result) {
|
||||
@@ -85,6 +215,29 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
});
|
||||
return true; // Keep message channel open for async response
|
||||
}
|
||||
|
||||
if (message.action === "checkSafeBrowsing") {
|
||||
checkSafeBrowsing(message.url)
|
||||
.then(result => sendResponse(result))
|
||||
.catch(error => {
|
||||
console.error("Safe Browsing API error:", error);
|
||||
sendResponse({ isUnsafe: false });
|
||||
});
|
||||
return true; // Required for async response
|
||||
}
|
||||
|
||||
if (message.action === "getHealthStatus") {
|
||||
sendResponse(healthCheck.getStatus());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.action === "getMetrics") {
|
||||
sendResponse({
|
||||
performance: performanceMonitor.getMetrics(),
|
||||
errors: errorReporter.getErrors()
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// Debug Mode: Keep-alive mechanism for testing
|
||||
@@ -98,3 +251,77 @@ if (DEBUG_MODE) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Reset API call count every minute
|
||||
setInterval(() => {
|
||||
apiCallCount = 0;
|
||||
lastResetTime = Date.now();
|
||||
}, 60000);
|
||||
|
||||
// Check URL against Google Safe Browsing API
|
||||
async function checkSafeBrowsing(url) {
|
||||
const startTime = performanceMonitor.startTimer();
|
||||
|
||||
try {
|
||||
// Check rate limit
|
||||
if (apiCallCount >= API_CALL_LIMIT) {
|
||||
throw new Error('API call limit reached');
|
||||
}
|
||||
|
||||
// Get API key from storage
|
||||
if (!SAFE_BROWSING_API_KEY) {
|
||||
const result = await chrome.storage.local.get(["safeBrowsingApiKey"]);
|
||||
SAFE_BROWSING_API_KEY = result.safeBrowsingApiKey;
|
||||
if (!SAFE_BROWSING_API_KEY) {
|
||||
throw new Error('Safe Browsing API key not set');
|
||||
}
|
||||
}
|
||||
|
||||
apiCallCount++;
|
||||
performanceMonitor.logMetric('apiCalls', 1);
|
||||
|
||||
const response = await fetch(
|
||||
`https://safebrowsing.googleapis.com/v4/threatMatches:find?key=${SAFE_BROWSING_API_KEY}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
client: {
|
||||
clientId: "enterprise-app-protection",
|
||||
clientVersion: "1.0"
|
||||
},
|
||||
threatInfo: {
|
||||
threatTypes: ["MALWARE", "SOCIAL_ENGINEERING"],
|
||||
platformTypes: ["ANY_PLATFORM"],
|
||||
threatEntryTypes: ["URL"],
|
||||
threatEntries: [{ url }]
|
||||
}
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API responded with status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
performanceMonitor.logMetric('processingTime', performanceMonitor.endTimer(startTime));
|
||||
return { isUnsafe: data.matches ? true : false };
|
||||
} catch (error) {
|
||||
performanceMonitor.logMetric('errors', 1);
|
||||
errorReporter.log(error, { url, apiCallCount });
|
||||
return { isUnsafe: false };
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for storage changes to update API key
|
||||
chrome.storage.onChanged.addListener((changes, namespace) => {
|
||||
if (namespace === "local" && changes.safeBrowsingApiKey) {
|
||||
SAFE_BROWSING_API_KEY = changes.safeBrowsingApiKey.newValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Add periodic health checks
|
||||
setInterval(() => {
|
||||
healthCheck.check();
|
||||
}, 300000); // Every 5 minutes
|
387
content.js
387
content.js
@@ -1,12 +1,117 @@
|
||||
// content.js
|
||||
|
||||
let SAFE_BROWSING_API_KEY = "";
|
||||
// Encryption utilities
|
||||
const encryption = {
|
||||
async encrypt(data) {
|
||||
try {
|
||||
const encoder = new TextEncoder();
|
||||
const dataBuffer = encoder.encode(JSON.stringify(data));
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
} catch (error) {
|
||||
console.error('Encryption error:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async validateData(data) {
|
||||
if (!data) return false;
|
||||
try {
|
||||
const hash = await this.encrypt(data);
|
||||
return !!hash;
|
||||
} catch (error) {
|
||||
console.error('Validation error:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Cache management
|
||||
const cacheManager = {
|
||||
cache: new Map(),
|
||||
maxSize: 1000,
|
||||
maxAge: 3600000, // 1 hour
|
||||
|
||||
set(key, value) {
|
||||
if (this.cache.size >= this.maxSize) {
|
||||
const oldestKey = this.cache.keys().next().value;
|
||||
this.cache.delete(oldestKey);
|
||||
}
|
||||
this.cache.set(key, {
|
||||
value,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
},
|
||||
|
||||
get(key) {
|
||||
const item = this.cache.get(key);
|
||||
if (!item) return null;
|
||||
if (Date.now() - item.timestamp > this.maxAge) {
|
||||
this.cache.delete(key);
|
||||
return null;
|
||||
}
|
||||
return item.value;
|
||||
},
|
||||
|
||||
clear() {
|
||||
this.cache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
// Performance monitoring
|
||||
const performanceMonitor = {
|
||||
metrics: {
|
||||
linkChecks: 0,
|
||||
apiCalls: 0,
|
||||
processingTime: 0
|
||||
},
|
||||
|
||||
startTimer() {
|
||||
return performance.now();
|
||||
},
|
||||
|
||||
endTimer(startTime) {
|
||||
return performance.now() - startTime;
|
||||
},
|
||||
|
||||
logMetric(name, value) {
|
||||
this.metrics[name] = (this.metrics[name] || 0) + value;
|
||||
},
|
||||
|
||||
getMetrics() {
|
||||
return { ...this.metrics };
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.metrics = {
|
||||
linkChecks: 0,
|
||||
apiCalls: 0,
|
||||
processingTime: 0
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let domainsDB = {};
|
||||
let trustedDomains = [];
|
||||
let blockedDomains = [];
|
||||
let warningTemplate = "Warning: This link claims to be {app} but goes to an unofficial domain.";
|
||||
let isDomainsLoaded = false;
|
||||
|
||||
// Add rate limiting for API calls
|
||||
const rateLimiter = {
|
||||
lastCall: 0,
|
||||
minInterval: 1000, // 1 second between calls
|
||||
canMakeCall() {
|
||||
const now = Date.now();
|
||||
if (now - this.lastCall >= this.minInterval) {
|
||||
this.lastCall = now;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ Get the top-level domain (TLD)
|
||||
function getTopLevelDomain(hostname) {
|
||||
const domainParts = hostname.split(".");
|
||||
@@ -37,143 +142,191 @@ function isValidDomain(domain, validDomains, trustedDomains) {
|
||||
|
||||
// ✅ Check Google Safe Browsing API for dangerous links
|
||||
async function checkGoogleSafeBrowsing(url) {
|
||||
console.log("🔍 Checking Google Safe Browsing for:", url);
|
||||
if (!rateLimiter.canMakeCall()) {
|
||||
console.warn("⚠️ Rate limit exceeded for Safe Browsing API calls");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SAFE_BROWSING_API_KEY) {
|
||||
console.warn("⚠️ API key is not set. Skipping Safe Browsing check.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
action: "checkSafeBrowsing",
|
||||
url: url
|
||||
});
|
||||
return response.isUnsafe || false;
|
||||
} catch (error) {
|
||||
console.error("Error checking Safe Browsing:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(`https://safebrowsing.googleapis.com/v4/threatMatches:find?key=${SAFE_BROWSING_API_KEY}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
client: { clientId: "enterprise-app-protection", clientVersion: "1.0" },
|
||||
threatInfo: {
|
||||
threatTypes: ["MALWARE", "SOCIAL_ENGINEERING"],
|
||||
platformTypes: ["ANY_PLATFORM"],
|
||||
threatEntryTypes: ["URL"],
|
||||
threatEntries: [{ url }]
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log("🔹 Google Safe Browsing API Response:", data);
|
||||
return data.matches ? true : false;
|
||||
// Sanitize warning template to prevent XSS
|
||||
function sanitizeWarningTemplate(template, appName) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = template.replace("{app}", appName);
|
||||
return div.textContent;
|
||||
}
|
||||
|
||||
// ✅ Scan page content for suspicious links
|
||||
function analyzePageContent() {
|
||||
if (!isDomainsLoaded || !domainsDB || Object.keys(domainsDB).length === 0) {
|
||||
console.warn("⚠️ domainsDB is not ready yet. Skipping analysis.");
|
||||
return;
|
||||
}
|
||||
async function analyzePageContent() {
|
||||
const startTime = performanceMonitor.startTimer();
|
||||
|
||||
const links = document.querySelectorAll("a:not(.checked-by-extension)");
|
||||
let newFlaggedLinks = new Set();
|
||||
if (!isDomainsLoaded || !domainsDB || Object.keys(domainsDB).length === 0) {
|
||||
console.warn("⚠️ domainsDB is not ready yet. Skipping analysis.");
|
||||
return;
|
||||
}
|
||||
|
||||
links.forEach(link => {
|
||||
try {
|
||||
// ✅ Validate the URL before using it
|
||||
let url;
|
||||
try {
|
||||
url = new URL(link.href);
|
||||
} catch (e) {
|
||||
console.warn("⚠️ Skipping invalid URL:", link.href);
|
||||
return; // Stop processing this link
|
||||
}
|
||||
const links = document.querySelectorAll("a:not(.checked-by-extension)");
|
||||
let newFlaggedLinks = new Set();
|
||||
let processedLinks = 0;
|
||||
|
||||
const domain = url.hostname.toLowerCase();
|
||||
const linkText = link.innerText.trim();
|
||||
// Process links in batches of 50
|
||||
const batchSize = 50;
|
||||
const batches = Array.from(links).reduce((acc, link, i) => {
|
||||
const batchIndex = Math.floor(i / batchSize);
|
||||
if (!acc[batchIndex]) acc[batchIndex] = [];
|
||||
acc[batchIndex].push(link);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// ✅ Mark link as processed
|
||||
link.classList.add("checked-by-extension");
|
||||
for (const batch of batches) {
|
||||
await Promise.all(batch.map(async link => {
|
||||
try {
|
||||
// Check cache first
|
||||
const cachedResult = cacheManager.get(link.href);
|
||||
if (cachedResult) {
|
||||
if (cachedResult.isUnsafe) {
|
||||
newFlaggedLinks.add(link.href);
|
||||
addWarningToLink(link, cachedResult.appName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Skip trusted domains
|
||||
if (trustedDomains.includes(domain) || trustedDomains.includes(getTopLevelDomain(domain))) {
|
||||
console.log("Skipping trusted domain:", domain);
|
||||
return;
|
||||
}
|
||||
// Validate the URL
|
||||
let url;
|
||||
try {
|
||||
url = new URL(link.href);
|
||||
} catch (e) {
|
||||
console.warn("⚠️ Skipping invalid URL:", link.href);
|
||||
return;
|
||||
}
|
||||
|
||||
let matchedApp = null;
|
||||
const domain = url.hostname.toLowerCase();
|
||||
const linkText = link.innerText.trim();
|
||||
processedLinks++;
|
||||
|
||||
// ✅ Find the most specific matching app name (ONLY full word matches)
|
||||
for (const [appName, validDomains] of Object.entries(domainsDB)) {
|
||||
const regex = new RegExp(`\\b${appName}\\b`, "i"); // Ensure full-word match
|
||||
if (regex.test(linkText)) {
|
||||
matchedApp = appName; // Keep the most specific match
|
||||
}
|
||||
}
|
||||
// Skip trusted domains
|
||||
if (trustedDomains.includes(domain) || trustedDomains.includes(getTopLevelDomain(domain))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchedApp && domainsDB[matchedApp]) {
|
||||
const validDomains = domainsDB[matchedApp];
|
||||
const isValid = isValidDomain(domain, validDomains, trustedDomains);
|
||||
let matchedApp = null;
|
||||
for (const [appName, validDomains] of Object.entries(domainsDB)) {
|
||||
const regex = new RegExp(`\\b${appName}\\b`, "i");
|
||||
if (regex.test(linkText)) {
|
||||
matchedApp = appName;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
newFlaggedLinks.add(url.href);
|
||||
if (matchedApp && domainsDB[matchedApp]) {
|
||||
const validDomains = domainsDB[matchedApp];
|
||||
const isValid = isValidDomain(domain, validDomains, trustedDomains);
|
||||
|
||||
// ✅ Add warning immediately
|
||||
const warning = document.createElement("div");
|
||||
warning.classList.add("warning-alert");
|
||||
warning.style.cssText = `
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
padding: 10px;
|
||||
border: 1px solid #ffeeba;
|
||||
border-radius: 4px;
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
`;
|
||||
if (!isValid) {
|
||||
newFlaggedLinks.add(url.href);
|
||||
const isUnsafe = await checkGoogleSafeBrowsing(url.href);
|
||||
|
||||
warning.textContent = `⚠️ ${warningTemplate.replace("{app}", matchedApp)}`;
|
||||
warning.setAttribute("title", `This link goes to ${domain} instead of an official ${matchedApp} domain.`);
|
||||
link.parentElement.insertBefore(warning, link.nextSibling);
|
||||
// Cache the result
|
||||
cacheManager.set(link.href, {
|
||||
isUnsafe,
|
||||
appName: matchedApp
|
||||
});
|
||||
|
||||
// ✅ Update warning if Google Safe Browsing confirms danger
|
||||
checkGoogleSafeBrowsing(url.href).then(isUnsafe => {
|
||||
if (isUnsafe) {
|
||||
warning.textContent = `⚠️ This link is confirmed dangerous by Google Safe Browsing!`;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Unexpected error processing link:", e);
|
||||
}
|
||||
});
|
||||
addWarningToLink(link, matchedApp, isUnsafe);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Unexpected error processing link:", e);
|
||||
}
|
||||
}));
|
||||
|
||||
// ✅ Store flagged links without waiting for Safe Browsing API
|
||||
chrome.storage.local.get(["flaggedLinks", "totalSuspiciousLinks"], function (result) {
|
||||
let existingLinks = new Set(result.flaggedLinks || []);
|
||||
let totalCount = existingLinks.size;
|
||||
// Add a small delay between batches to prevent UI blocking
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
}
|
||||
|
||||
let newLinksToAdd = [...newFlaggedLinks].filter(link => !existingLinks.has(link));
|
||||
// Update metrics
|
||||
performanceMonitor.logMetric('linkChecks', processedLinks);
|
||||
performanceMonitor.logMetric('processingTime', performanceMonitor.endTimer(startTime));
|
||||
|
||||
if (newLinksToAdd.length > 0) {
|
||||
totalCount += newLinksToAdd.length;
|
||||
// Store flagged links with encryption
|
||||
if (newFlaggedLinks.size > 0) {
|
||||
const storageData = {
|
||||
flaggedLinks: Array.from(newFlaggedLinks),
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
chrome.storage.local.set({
|
||||
totalSuspiciousLinks: totalCount,
|
||||
flaggedLinks: [...existingLinks, ...newLinksToAdd]
|
||||
}, () => {
|
||||
chrome.runtime.sendMessage({ action: "updatePopup", flaggedLinks: [...existingLinks, ...newLinksToAdd], totalCount });
|
||||
});
|
||||
}
|
||||
});
|
||||
if (await encryption.validateData(storageData)) {
|
||||
chrome.storage.local.get(["flaggedLinks", "totalSuspiciousLinks"], async function (result) {
|
||||
let existingLinks = new Set(result.flaggedLinks || []);
|
||||
let totalCount = existingLinks.size;
|
||||
|
||||
let newLinksToAdd = [...newFlaggedLinks].filter(link => !existingLinks.has(link));
|
||||
|
||||
if (newLinksToAdd.length > 0) {
|
||||
totalCount += newLinksToAdd.length;
|
||||
|
||||
const updatedData = {
|
||||
totalSuspiciousLinks: totalCount,
|
||||
flaggedLinks: [...existingLinks, ...newLinksToAdd],
|
||||
lastUpdate: Date.now()
|
||||
};
|
||||
|
||||
if (await encryption.validateData(updatedData)) {
|
||||
chrome.storage.local.set(updatedData, () => {
|
||||
chrome.runtime.sendMessage({
|
||||
action: "updatePopup",
|
||||
flaggedLinks: [...existingLinks, ...newLinksToAdd],
|
||||
totalCount,
|
||||
metrics: performanceMonitor.getMetrics()
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to add warning to link
|
||||
function addWarningToLink(link, appName, isUnsafe = false) {
|
||||
const warning = document.createElement("div");
|
||||
warning.classList.add("warning-alert");
|
||||
warning.style.cssText = `
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
padding: 10px;
|
||||
border: 1px solid #ffeeba;
|
||||
border-radius: 4px;
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
warning.textContent = isUnsafe
|
||||
? `⚠️ This link is confirmed dangerous by Google Safe Browsing!`
|
||||
: `⚠️ ${sanitizeWarningTemplate(warningTemplate, appName)}`;
|
||||
|
||||
warning.setAttribute("title", `This link goes to ${link.hostname} instead of an official ${appName} domain.`);
|
||||
link.parentElement.insertBefore(warning, link.nextSibling);
|
||||
}
|
||||
|
||||
// ✅ Load domains and start analysis once ready
|
||||
function loadDomainsAndAnalyze() {
|
||||
chrome.storage.local.get(["domainsDB", "trustedDomains", "blockedDomains", "safeBrowsingApiKey", "warningTemplate"], function (result) {
|
||||
chrome.storage.local.get(["domainsDB", "trustedDomains", "blockedDomains", "warningTemplate"], function (result) {
|
||||
domainsDB = result.domainsDB || {};
|
||||
trustedDomains = result.trustedDomains || [];
|
||||
blockedDomains = result.blockedDomains || [];
|
||||
SAFE_BROWSING_API_KEY = result.safeBrowsingApiKey || "";
|
||||
warningTemplate = result.warningTemplate || "Warning: This link claims to be {app} but goes to an unofficial domain.";
|
||||
console.log("✅ Loaded API Key:", SAFE_BROWSING_API_KEY);
|
||||
isDomainsLoaded = true;
|
||||
console.log("✅ Domains database loaded:", domainsDB);
|
||||
isDomainsLoaded = true;
|
||||
analyzePageContent();
|
||||
});
|
||||
}
|
||||
@@ -213,6 +366,7 @@ const enhancedAnalyzePageContent = withErrorHandling(debouncedAnalyzePageContent
|
||||
// Update the observer to use the enhanced version
|
||||
function observePageForLinks() {
|
||||
const observer = new MutationObserver(() => enhancedAnalyzePageContent());
|
||||
window.pageObserver = observer; // Store for cleanup
|
||||
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
@@ -222,7 +376,6 @@ function observePageForLinks() {
|
||||
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
||||
if (iframeDoc) {
|
||||
observer.observe(iframeDoc.body, { childList: true, subtree: true });
|
||||
console.log("Observing links inside iframe:", iframe.src);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Cannot access iframe due to cross-origin restrictions:", iframe.src);
|
||||
@@ -230,11 +383,23 @@ function observePageForLinks() {
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ Run iframe observer every 3 seconds for Office 365 dynamic content
|
||||
setInterval(observeIframes, 3000);
|
||||
window.iframeInterval = setInterval(observeIframes, 3000);
|
||||
}
|
||||
|
||||
// ✅ Initialize extension scanning
|
||||
document.addEventListener("DOMContentLoaded", loadDomainsAndAnalyze);
|
||||
window.addEventListener("load", loadDomainsAndAnalyze);
|
||||
observePageForLinks();
|
||||
|
||||
// Add cleanup for observers
|
||||
function cleanup() {
|
||||
if (window.pageObserver) {
|
||||
window.pageObserver.disconnect();
|
||||
}
|
||||
if (window.iframeInterval) {
|
||||
clearInterval(window.iframeInterval);
|
||||
}
|
||||
}
|
||||
|
||||
// Add cleanup on page unload
|
||||
window.addEventListener("unload", cleanup);
|
@@ -5,197 +5,361 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Enterprise App Protection - Domain Management</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
:root {
|
||||
--primary-color: #2563eb;
|
||||
--success-color: #059669;
|
||||
--danger-color: #dc2626;
|
||||
--warning-color: #d97706;
|
||||
--background-color: #f8fafc;
|
||||
--surface-color: #ffffff;
|
||||
--text-primary: #1e293b;
|
||||
--text-secondary: #64748b;
|
||||
--border-color: #e2e8f0;
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||||
--radius-sm: 0.375rem;
|
||||
--radius-md: 0.5rem;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
color: #555;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-top: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
height: 120px;
|
||||
resize: vertical;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #f4f4f4;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
overflow-x: auto;
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.5;
|
||||
min-height: 100vh;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
background: var(--surface-color);
|
||||
padding: 2rem;
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.875rem;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.management-section {
|
||||
margin-bottom: 2rem;
|
||||
padding: 1.5rem;
|
||||
background-color: var(--background-color);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.domain-list {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.domain-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem;
|
||||
background-color: var(--surface-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.domain-item:hover {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.domain-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.domain-name {
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.domain-type {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: var(--radius-sm);
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.domain-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
border-radius: 5px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
/* Styled Popup */
|
||||
.popup {
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.remove-btn {
|
||||
background-color: var(--danger-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: var(--warning-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
.modal.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.popup button {
|
||||
background-color: #007bff;
|
||||
.modal-content {
|
||||
background-color: var(--surface-color);
|
||||
padding: 2rem;
|
||||
border-radius: var(--radius-md);
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
input, select {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-primary);
|
||||
background-color: var(--surface-color);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
input:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: var(--surface-color);
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
background-color: var(--success-color);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.popup button:hover {
|
||||
background-color: #0056b3;
|
||||
.search-bar {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
padding-left: 2.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 0.875rem;
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="%2364748b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>');
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0.75rem center;
|
||||
background-size: 1rem;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 0.875rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.visible {
|
||||
display: block;
|
||||
.status-message.success {
|
||||
background-color: #ecfdf5;
|
||||
color: var(--success-color);
|
||||
border: 1px solid #a7f3d0;
|
||||
}
|
||||
|
||||
/* Style the placeholder text to make it look distinct from user input */
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
color: #aaa;
|
||||
/* Lighter gray color */
|
||||
font-style: italic;
|
||||
/* Makes it look like a hint */
|
||||
opacity: 1;
|
||||
/* Ensures it's fully visible */
|
||||
}
|
||||
|
||||
/* Ensure actual input text is clear */
|
||||
input,
|
||||
textarea {
|
||||
color: #333;
|
||||
/* Dark text for actual input */
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #f4f4f4;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
overflow-x: auto;
|
||||
font-style: italic;
|
||||
color: #333;
|
||||
/* Make text italic */
|
||||
.status-message.error {
|
||||
background-color: #fef2f2;
|
||||
color: var(--danger-color);
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Enterprise App Protection - Domain Management</h1>
|
||||
|
||||
<!-- Restored Instructions -->
|
||||
<h3>✅ How to Use</h3>
|
||||
<ul>
|
||||
<li>✅ Enter <strong>one domain per line</strong> (no commas or spaces).</li>
|
||||
<li>✅ <strong>Do not include</strong> <code>https://</code> or <code>www.</code> (only enter the domain
|
||||
name).</li>
|
||||
<li>✅ You can add subdomains if needed (e.g., <code>sub.example.com</code>).</li>
|
||||
<li>✅ <strong>Trusted Domains:</strong> These sites will <u>not</u> be flagged as unsafe.</li>
|
||||
<li>✅ <strong>Blocked Domains:</strong> These sites will <u>always</u> be flagged, even if Google Safe
|
||||
Browsing does not detect them.</li>
|
||||
<li>🚨 <strong>Example Entries:</strong></li>
|
||||
</ul>
|
||||
<pre><em>
|
||||
example.com
|
||||
secure-site.org
|
||||
sub.example.com
|
||||
malicious-site.net
|
||||
</em></pre>
|
||||
|
||||
<h3>✅ Trusted Domains (Safe List)</h3>
|
||||
<textarea id="trusted" placeholder="Enter trusted domains here..."></textarea>
|
||||
|
||||
<h3>❌ Blocked Domains (Unsafe List)</h3>
|
||||
<textarea id="blocked" placeholder="Enter blocked domains here..."></textarea>
|
||||
|
||||
<div class="button-container">
|
||||
<button id="save">💾 Save Domains</button>
|
||||
<div class="header">
|
||||
<h1>Domain Management</h1>
|
||||
<p class="description">Manage trusted and blocked domains for enterprise application protection</p>
|
||||
</div>
|
||||
|
||||
<div class="management-section">
|
||||
<div class="section-header">
|
||||
<h2>🔒 Trusted Domains</h2>
|
||||
<button class="add-btn" id="addTrustedDomain">
|
||||
<span>➕</span>
|
||||
<span>Add Domain</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="search-bar">
|
||||
<input type="text" class="search-input" id="trustedSearch" placeholder="Search trusted domains...">
|
||||
</div>
|
||||
<div class="domain-list" id="trustedDomainsList">
|
||||
<!-- Trusted domains will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="management-section">
|
||||
<div class="section-header">
|
||||
<h2>🚫 Blocked Domains</h2>
|
||||
<button class="add-btn" id="addBlockedDomain">
|
||||
<span>➕</span>
|
||||
<span>Add Domain</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="search-bar">
|
||||
<input type="text" class="search-input" id="blockedSearch" placeholder="Search blocked domains...">
|
||||
</div>
|
||||
<div class="domain-list" id="blockedDomainsList">
|
||||
<!-- Blocked domains will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="statusMessage" class="status-message"></div>
|
||||
</div>
|
||||
|
||||
<!-- Styled Popup -->
|
||||
<div id="popupMessage" class="popup hidden">
|
||||
<div class="popup-content">
|
||||
<p id="popupText">Changes saved successfully!</p>
|
||||
<button id="popupClose">OK</button>
|
||||
<!-- Add/Edit Domain Modal -->
|
||||
<div class="modal" id="domainModal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" id="modalTitle">Add Domain</h3>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="domainName">Domain Name</label>
|
||||
<input type="text" id="domainName" placeholder="Enter domain name (e.g., example.com)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="domainType">Domain Type</label>
|
||||
<select id="domainType">
|
||||
<option value="trusted">Trusted</option>
|
||||
<option value="blocked">Blocked</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="cancel-btn" id="cancelDomain">Cancel</button>
|
||||
<button class="save-btn" id="saveDomain">Save Domain</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@@ -6,12 +6,12 @@
|
||||
"permissions": [
|
||||
"storage",
|
||||
"activeTab",
|
||||
"alarms",
|
||||
"scripting",
|
||||
"tabs"
|
||||
"alarms"
|
||||
],
|
||||
"host_permissions": [
|
||||
"https://raw.githubusercontent.com/*"
|
||||
"https://raw.githubusercontent.com/*",
|
||||
"https://safebrowsing.googleapis.com/*"
|
||||
],
|
||||
"background": {
|
||||
"service_worker": "background.js",
|
||||
@@ -19,7 +19,12 @@
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"matches": [
|
||||
"https://*.office.com/*",
|
||||
"https://*.sharepoint.com/*",
|
||||
"https://*.teams.microsoft.com/*",
|
||||
"https://*.microsoft.com/*"
|
||||
],
|
||||
"js": ["config.js", "content.js"],
|
||||
"run_at": "document_idle"
|
||||
}
|
||||
@@ -39,10 +44,17 @@
|
||||
},
|
||||
"options_page": "options.html",
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self'; object-src 'self'"
|
||||
"extension_pages": "script-src 'self'; object-src 'self'; default-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": [{
|
||||
"resources": ["icons/*"],
|
||||
"matches": ["<all_urls>"]
|
||||
}]
|
||||
"matches": [
|
||||
"https://*.office.com/*",
|
||||
"https://*.sharepoint.com/*",
|
||||
"https://*.teams.microsoft.com/*",
|
||||
"https://*.microsoft.com/*"
|
||||
]
|
||||
}],
|
||||
"minimum_chrome_version": "88"
|
||||
}
|
276
options.html
276
options.html
@@ -4,136 +4,262 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Enterprise App Protection - Settings</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
:root {
|
||||
--primary-color: #2563eb;
|
||||
--success-color: #059669;
|
||||
--background-color: #f8fafc;
|
||||
--surface-color: #ffffff;
|
||||
--text-primary: #1e293b;
|
||||
--text-secondary: #64748b;
|
||||
--border-color: #e2e8f0;
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||||
--radius-sm: 0.375rem;
|
||||
--radius-md: 0.5rem;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.5;
|
||||
min-height: 100vh;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 600px;
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
margin: 0 auto;
|
||||
background: var(--surface-color);
|
||||
padding: 2rem;
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.settings-group {
|
||||
margin-bottom: 2rem;
|
||||
padding: 1.5rem;
|
||||
background-color: var(--background-color);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
color: #555;
|
||||
margin-top: 20px;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-top: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box; /* Ensures padding is included in width */
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-primary);
|
||||
background-color: var(--surface-color);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
input::placeholder, textarea::placeholder {
|
||||
color: var(--text-secondary);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 120px;
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.help-text {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007bff;
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #f4f4f4;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
border-radius: 5px;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
/* Style the placeholder text to make it look distinct from user input */
|
||||
input::placeholder, textarea::placeholder {
|
||||
color: #aaa; /* Lighter gray color */
|
||||
font-style: italic; /* Makes it look like a hint */
|
||||
opacity: 1; /* Ensures it's fully visible */
|
||||
}
|
||||
|
||||
/* Ensure actual input text is clear */
|
||||
input, textarea {
|
||||
color: #333; /* Dark text for actual input */
|
||||
}
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
background-color: var(--surface-color);
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.status-message {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 0.875rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.status-message.success {
|
||||
background-color: #ecfdf5;
|
||||
color: var(--success-color);
|
||||
border: 1px solid #a7f3d0;
|
||||
}
|
||||
|
||||
.status-message.error {
|
||||
background-color: #fef2f2;
|
||||
color: #dc2626;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Enterprise App Protection - Settings</h1>
|
||||
<div class="header">
|
||||
<h1>Enterprise App Protection</h1>
|
||||
<p>Configure your extension settings</p>
|
||||
</div>
|
||||
|
||||
<h3>🔑 Google Safe Browsing API Key</h3>
|
||||
<p>
|
||||
To use Google Safe Browsing, you need an API key.
|
||||
<a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noopener noreferrer">
|
||||
Click here to create a key
|
||||
</a> (you may need to enable the Safe Browsing API in Google Cloud Console).
|
||||
</p>
|
||||
<input type="text" id="apiKey" placeholder="Enter your API key here" />
|
||||
<div class="settings-group">
|
||||
<h3>🔑 Google Safe Browsing API Key</h3>
|
||||
<p>
|
||||
To use Google Safe Browsing, you need an API key.
|
||||
<a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noopener noreferrer">
|
||||
Get your API key here
|
||||
</a>
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="apiKey">API Key</label>
|
||||
<input type="text" id="apiKey" placeholder="Enter your API key" />
|
||||
<p class="help-text">Required for checking URLs against Google's Safe Browsing database</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>🌐 Domains Database URL</h3>
|
||||
<p>Enter the URL for the domains database (JSON format):</p>
|
||||
<input type="text" id="domainsDBURL" placeholder="Enter the URL here" value="https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/domains.json" />
|
||||
<div class="settings-group">
|
||||
<h3>🌐 Domains Database</h3>
|
||||
<p>Configure the source for your domains database</p>
|
||||
<div class="form-group">
|
||||
<label for="domainsDBURL">Database URL</label>
|
||||
<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" />
|
||||
<p class="help-text">URL must point to a valid JSON file containing domain information</p>
|
||||
</div>
|
||||
|
||||
<h3>⏱️ Update Interval (hours)</h3>
|
||||
<p>Enter the interval (in hours) to check for updates to the domains database:</p>
|
||||
<input type="number" id="updateInterval" placeholder="Enter the update interval here" value="24" />
|
||||
|
||||
<h3>⚠️ Warning Message Template</h3>
|
||||
<p>Enter the template for the warning message (use <code>{app}</code> to represent the application name):</p>
|
||||
<input type="text" id="warningTemplate" placeholder="Enter the warning message template here" value="Warning: This link claims to be {app} but goes to an unofficial domain." />
|
||||
<div class="form-group">
|
||||
<label for="updateInterval">Update Interval (hours)</label>
|
||||
<input type="number" id="updateInterval" placeholder="Enter update interval" value="24" min="1" max="168" />
|
||||
<p class="help-text">How often to check for updates to the domains database (1-168 hours)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3>⚠️ Warning Messages</h3>
|
||||
<p>Customize how warnings are displayed to users</p>
|
||||
<div class="form-group">
|
||||
<label for="warningTemplate">Warning Message Template</label>
|
||||
<input type="text" id="warningTemplate"
|
||||
placeholder="Enter warning message template"
|
||||
value="Warning: This link claims to be {app} but goes to an unofficial domain." />
|
||||
<p class="help-text">Use {app} to represent the application name in your warning message</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-container">
|
||||
<button id="save">💾 Save Settings</button>
|
||||
<button id="save" class="save-btn">
|
||||
<span>💾</span>
|
||||
<span>Save Settings</span>
|
||||
</button>
|
||||
<button id="reset" class="reset-btn">
|
||||
<span>↺</span>
|
||||
<span>Reset to Defaults</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="statusMessage" class="status-message"></div>
|
||||
</div>
|
||||
|
||||
<script src="options.js"></script>
|
||||
|
248
popup.html
248
popup.html
@@ -4,89 +4,219 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Enterprise App Protection</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #2563eb;
|
||||
--success-color: #059669;
|
||||
--info-color: #0891b2;
|
||||
--danger-color: #dc2626;
|
||||
--background-color: #f8fafc;
|
||||
--surface-color: #ffffff;
|
||||
--text-primary: #1e293b;
|
||||
--text-secondary: #64748b;
|
||||
--border-color: #e2e8f0;
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||||
--radius-sm: 0.375rem;
|
||||
--radius-md: 0.5rem;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 15px;
|
||||
width: 280px;
|
||||
background-color: #f8f9fa;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
padding: 1.25rem;
|
||||
width: 320px;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
font-size: 18px;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
p {
|
||||
font-size: 14px;
|
||||
margin: 10px 0;
|
||||
color: #555;
|
||||
|
||||
.stats {
|
||||
background-color: var(--surface-color);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.counter {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #dc3545;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: var(--danger-color);
|
||||
display: block;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.links-container {
|
||||
background-color: var(--surface-color);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: var(--shadow-sm);
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.links-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.links-container::-webkit-scrollbar-track {
|
||||
background: var(--background-color);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.links-container::-webkit-scrollbar-thumb {
|
||||
background: var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
box-shadow: inset 0 0 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 13px;
|
||||
padding: 5px;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.5rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.update-btn {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
.settings-btn {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
.domains-btn {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
button:hover {
|
||||
opacity: 0.8;
|
||||
|
||||
.last-update {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.update-btn {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.settings-btn {
|
||||
background-color: var(--success-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.domains-btn {
|
||||
background-color: var(--info-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.reset-section {
|
||||
text-align: center;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.reset-section h3 {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
#resetCounter {
|
||||
background-color: var(--surface-color);
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
#resetCounter:hover {
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Enterprise App Protection</h2>
|
||||
<p>Suspicious Links Detected: <span class="counter" id="suspiciousCount">0</span></p>
|
||||
<div class="header">
|
||||
<h2>Enterprise App Protection</h2>
|
||||
</div>
|
||||
|
||||
<ul id="suspiciousLinks"></ul>
|
||||
<p id="lastUpdate">Last Updated: Never</p>
|
||||
<button id="updateDB" class="update-btn">🔄 Update Database</button>
|
||||
<button id="settings" class="settings-btn">⚙️ Open Settings</button>
|
||||
<button id="manageDomains" class="domains-btn">🌐 Manage Domains</button>
|
||||
<div class="stats">
|
||||
<span>Suspicious Links Detected</span>
|
||||
<span class="counter" id="suspiciousCount">0</span>
|
||||
</div>
|
||||
|
||||
<h3>🔄 Reset Suspicious Links Counter</h3>
|
||||
<button id="resetCounter">♻️ Reset Counter</button>
|
||||
<div class="links-container">
|
||||
<ul id="suspiciousLinks"></ul>
|
||||
</div>
|
||||
|
||||
<p class="last-update" id="lastUpdate">Last Updated: Never</p>
|
||||
|
||||
<div class="button-group">
|
||||
<button id="updateDB" class="update-btn">
|
||||
<span>🔄</span>
|
||||
<span>Update Database</span>
|
||||
</button>
|
||||
<button id="settings" class="settings-btn">
|
||||
<span>⚙️</span>
|
||||
<span>Open Settings</span>
|
||||
</button>
|
||||
<button id="manageDomains" class="domains-btn">
|
||||
<span>🌐</span>
|
||||
<span>Manage Domains</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="reset-section">
|
||||
<h3>Reset Suspicious Links Counter</h3>
|
||||
<button id="resetCounter">
|
||||
<span>♻️</span>
|
||||
<span>Reset Counter</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
|
Reference in New Issue
Block a user