UNPKG

expo-geofencing

Version:

Production-ready geofencing and activity recognition for Expo React Native with offline support, security features, and enterprise-grade reliability

389 lines (388 loc) 14.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SecurityManager = void 0; const crypto_js_1 = __importDefault(require("crypto-js")); class SecurityManager { constructor(config = {}) { this.privacyZones = new Map(); this.auditLog = []; this.config = { encryptionEnabled: true, dataAnonymization: false, privacyZonesEnabled: true, minAccuracy: 100, // 100 meters locationPrecisionReduction: 2, // Reduce to ~1km accuracy auditLogging: true, sensitiveDataMasking: true, ...config }; this.encryptionKey = config.encryptionKey || this.generateEncryptionKey(); this.initializeDefaultPrivacyZones(); } // Privacy Zone Management addPrivacyZone(zone) { const id = this.generateId(); const privacyZone = { ...zone, id, isActive: true }; this.privacyZones.set(id, privacyZone); this.logAuditEvent('privacy_zone_added', { zoneId: id, type: zone.type }); return id; } removePrivacyZone(zoneId) { const removed = this.privacyZones.delete(zoneId); if (removed) { this.logAuditEvent('privacy_zone_removed', { zoneId }); } return removed; } updatePrivacyZone(zoneId, updates) { const zone = this.privacyZones.get(zoneId); if (!zone) return false; this.privacyZones.set(zoneId, { ...zone, ...updates }); this.logAuditEvent('privacy_zone_updated', { zoneId, updates }); return true; } getPrivacyZones() { return Array.from(this.privacyZones.values()); } getActivePrivacyZones() { return this.getPrivacyZones().filter(zone => zone.isActive); } // Location Processing and Privacy Protection processLocation(location) { const result = { allowed: true, violations: [], appliedPolicies: [] }; // Check minimum accuracy requirement if (location.accuracy > this.config.minAccuracy) { result.violations.push(`Location accuracy ${location.accuracy}m exceeds minimum required ${this.config.minAccuracy}m`); result.allowed = false; } // Check privacy zones if (this.config.privacyZonesEnabled) { const violatedZones = this.checkPrivacyZoneViolations(location); if (violatedZones.length > 0) { result.violations.push(...violatedZones.map(zone => `Location within privacy zone: ${zone.name} (${zone.type})`)); result.allowed = false; } } // Apply data processing if allowed if (result.allowed || this.shouldProcessDespiteViolations(result.violations)) { result.processedLocation = this.applyLocationProcessingPolicies(location); result.appliedPolicies = this.getAppliedPolicies(); } // Audit log the processing this.logAuditEvent('location_processed', { allowed: result.allowed, violations: result.violations, appliedPolicies: result.appliedPolicies, userId: location.userId }, result.processedLocation); return result; } checkPrivacyZoneViolations(location) { const violations = []; const activeZones = this.getActivePrivacyZones(); for (const zone of activeZones) { const distance = this.calculateDistance(location.latitude, location.longitude, zone.center.latitude, zone.center.longitude); const effectiveRadius = zone.radius + (zone.bufferZone || 0); if (distance <= effectiveRadius) { violations.push(zone); } } return violations; } applyLocationProcessingPolicies(location) { let processedLocation = { ...location }; // Apply anonymization if enabled if (this.config.dataAnonymization) { processedLocation = this.anonymizeLocation(processedLocation); } // Apply precision reduction if (this.config.locationPrecisionReduction > 0) { processedLocation = this.reducePrecision(processedLocation, this.config.locationPrecisionReduction); } return processedLocation; } shouldProcessDespiteViolations(violations) { // Allow processing for some non-critical violations const nonCriticalKeywords = ['accuracy']; return violations.every(violation => nonCriticalKeywords.some(keyword => violation.toLowerCase().includes(keyword))); } getAppliedPolicies() { const policies = []; if (this.config.dataAnonymization) { policies.push('data_anonymization'); } if (this.config.locationPrecisionReduction > 0) { policies.push('precision_reduction'); } if (this.config.privacyZonesEnabled) { policies.push('privacy_zones_check'); } if (this.config.minAccuracy) { policies.push('accuracy_filtering'); } return policies; } // Data Encryption and Decryption encryptData(data) { if (!this.config.encryptionEnabled) { return JSON.stringify(data); } try { const jsonString = JSON.stringify(data); return crypto_js_1.default.AES.encrypt(jsonString, this.encryptionKey).toString(); } catch (error) { throw new Error(`Encryption failed: ${error}`); } } decryptData(encryptedData) { if (!this.config.encryptionEnabled) { return JSON.parse(encryptedData); } try { const bytes = crypto_js_1.default.AES.decrypt(encryptedData, this.encryptionKey); const decryptedString = bytes.toString(crypto_js_1.default.enc.Utf8); return JSON.parse(decryptedString); } catch (error) { throw new Error(`Decryption failed: ${error}`); } } // Data Anonymization anonymizeLocation(location) { // Add random noise to coordinates (differential privacy approach) const noiseScale = 0.001; // ~111 meters at equator const latNoise = (Math.random() - 0.5) * noiseScale; const lngNoise = (Math.random() - 0.5) * noiseScale; return { latitude: location.latitude + latNoise, longitude: location.longitude + lngNoise, accuracy: Math.max(location.accuracy, 100) // Ensure minimum accuracy uncertainty }; } reducePrecision(location, decimalPlaces) { const factor = Math.pow(10, Math.max(0, 6 - decimalPlaces)); return { latitude: Math.round(location.latitude * factor) / factor, longitude: Math.round(location.longitude * factor) / factor, accuracy: Math.max(location.accuracy, this.calculatePrecisionAccuracy(decimalPlaces)) }; } calculatePrecisionAccuracy(decimalPlaces) { // Approximate accuracy based on decimal places const accuracyMap = { 0: 111000, // ~111 km 1: 11100, // ~11 km 2: 1110, // ~1.1 km 3: 111, // ~111 m 4: 11, // ~11 m 5: 1, // ~1 m 6: 0.1 // ~10 cm }; return accuracyMap[Math.max(0, 6 - decimalPlaces)] || 111000; } // Sensitive Data Masking maskSensitiveData(data) { if (!this.config.sensitiveDataMasking) { return data; } const sensitiveFields = ['userId', 'deviceId', 'email', 'phone']; const masked = { ...data }; for (const field of sensitiveFields) { if (masked[field]) { masked[field] = this.maskValue(masked[field]); } } return masked; } maskValue(value) { if (value.length <= 4) { return '*'.repeat(value.length); } const start = value.substring(0, 2); const end = value.substring(value.length - 2); const middle = '*'.repeat(value.length - 4); return start + middle + end; } // Audit Logging logAuditEvent(action, metadata, locationData, userId) { if (!this.config.auditLogging) return; const entry = { id: this.generateId(), timestamp: Date.now(), action, userId, locationData, metadata, hash: '' // Will be set below }; // Generate integrity hash const { hash, ...entryWithoutHash } = entry; entry.hash = this.generateAuditHash(entryWithoutHash); this.auditLog.push(entry); // Keep only recent entries to prevent memory issues if (this.auditLog.length > 10000) { this.auditLog = this.auditLog.slice(-5000); } } getAuditLog(filter) { let filteredLog = [...this.auditLog]; if (filter) { filteredLog = filteredLog.filter(entry => { if (filter.action && entry.action !== filter.action) return false; if (filter.userId && entry.userId !== filter.userId) return false; if (filter.startTime && entry.timestamp < filter.startTime) return false; if (filter.endTime && entry.timestamp > filter.endTime) return false; return true; }); } return filteredLog; } verifyAuditLogIntegrity() { const corruptedEntries = []; for (const entry of this.auditLog) { const expectedHash = this.generateAuditHash({ ...entry, hash: '' }); if (entry.hash !== expectedHash) { corruptedEntries.push(entry.id); } } return { valid: corruptedEntries.length === 0, corruptedEntries }; } generateAuditHash(entry) { const dataToHash = JSON.stringify(entry, Object.keys(entry).sort()); return crypto_js_1.default.SHA256(dataToHash).toString(); } // Certificate Pinning (for API communications) validateCertificate(domain, certificate) { if (!this.config.certificatePinning) return true; const pinnedCerts = this.config.certificatePinning.certificates; const pinnedDomains = this.config.certificatePinning.domains; if (!pinnedDomains.includes(domain)) return true; return pinnedCerts.includes(certificate); } // Utility Methods calculateDistance(lat1, lon1, lat2, lon2) { const R = 6371000; // Earth's radius in meters const dLat = this.toRadians(lat2 - lat1); const dLon = this.toRadians(lon2 - lon1); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.toRadians(lat1)) * Math.cos(this.toRadians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } toRadians(degrees) { return degrees * (Math.PI / 180); } generateId() { return Date.now().toString(36) + Math.random().toString(36).substr(2); } generateEncryptionKey() { return crypto_js_1.default.lib.WordArray.random(256 / 8).toString(); } // Initialize default privacy zones for common sensitive locations initializeDefaultPrivacyZones() { // These are example zones - in practice, these would be loaded from a database const defaultZones = [ // Example: Hospital { name: 'General Hospital Privacy Zone', type: 'hospital', center: { latitude: 37.7749, longitude: -122.4194 }, // Example coordinates radius: 200, bufferZone: 50, isActive: true, metadata: { reason: 'HIPAA privacy protection' } }, // Example: Government building { name: 'Government Building Privacy Zone', type: 'government', center: { latitude: 37.7849, longitude: -122.4094 }, radius: 100, bufferZone: 25, isActive: true, metadata: { reason: 'Security sensitive area' } } ]; // Only add if privacy zones are enabled if (this.config.privacyZonesEnabled) { defaultZones.forEach(zone => this.addPrivacyZone(zone)); } } // Configuration Management updateConfig(updates) { const oldConfig = { ...this.config }; this.config = { ...this.config, ...updates }; this.logAuditEvent('security_config_updated', { oldConfig: this.maskSensitiveData(oldConfig), newConfig: this.maskSensitiveData(this.config) }); } getConfig() { return { ...this.config }; } // Security Health Check performSecurityHealthCheck() { const issues = []; const recommendations = []; let score = 100; // Check encryption if (!this.config.encryptionEnabled) { issues.push('Data encryption is disabled'); recommendations.push('Enable data encryption for sensitive location data'); score -= 20; } // Check privacy zones if (!this.config.privacyZonesEnabled) { issues.push('Privacy zones are disabled'); recommendations.push('Enable privacy zones to protect sensitive locations'); score -= 15; } // Check audit logging if (!this.config.auditLogging) { issues.push('Audit logging is disabled'); recommendations.push('Enable audit logging for compliance and security monitoring'); score -= 15; } // Check minimum accuracy if (this.config.minAccuracy > 500) { issues.push('Minimum accuracy threshold is too high'); recommendations.push('Consider lowering minimum accuracy for better location filtering'); score -= 10; } // Check certificate pinning if (!this.config.certificatePinning) { recommendations.push('Consider implementing certificate pinning for API communications'); score -= 5; } return { score: Math.max(0, score), recommendations, issues }; } } exports.SecurityManager = SecurityManager;