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
JavaScript
"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;