/**
* MoonBite Wallet - Comprehensive Security Module
*
* Implements all 20 security features for production-grade cryptocurrency wallet.
* Handles session management, rate limiting, memory security, biometric auth,
* 2FA, and comprehensive audit logging.
*
* Usage: Include this file before wallet-pwa.html main script
*/
// ============================================================================
// 1. SESSION MANAGEMENT WITH AUTO-LOGOUT
// ============================================================================
class SessionManager {
constructor(timeoutMinutes = 15, warningMinutes = 10) {
this.timeoutMs = timeoutMinutes * 60 * 1000;
this.warningMs = warningMinutes * 60 * 1000;
this.sessionStartTime = Date.now();
this.lastActivityTime = Date.now();
this.sessionId = this.generateSessionId();
this.warningShown = false;
this.listeners = [];
this.initActivityListeners();
this.startMonitoring();
}
generateSessionId() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
initActivityListeners() {
const activityEvents = ['mousedown', 'keydown', 'scroll', 'touchstart', 'click', 'input'];
const handleActivity = () => {
this.lastActivityTime = Date.now();
this.warningShown = false;
const warning = document.getElementById('sessionWarning');
if (warning) warning.classList.remove('active');
};
activityEvents.forEach(event => {
document.addEventListener(event, handleActivity, true);
});
window.addEventListener('focus', handleActivity);
}
startMonitoring() {
this.monitorInterval = setInterval(() => {
const now = Date.now();
const inactiveTime = now - this.lastActivityTime;
if (inactiveTime >= this.warningMs && !this.warningShown) {
this.showWarning();
this.warningShown = true;
}
if (inactiveTime >= this.timeoutMs) {
this.logout();
}
}, 1000);
}
showWarning() {
const warning = document.getElementById('sessionWarning');
if (!warning) {
const newWarning = document.createElement('div');
newWarning.id = 'sessionWarning';
newWarning.className = 'session-warning active';
newWarning.innerHTML = `
Session Expiring Soon
Your session will expire in 5 minutes due to inactivity.
`;
document.body.appendChild(newWarning);
document.getElementById('extendSessionBtn').addEventListener('click', () => {
this.lastActivityTime = Date.now();
newWarning.classList.remove('active');
});
document.getElementById('logoutNowBtn').addEventListener('click', () => {
this.logout();
});
}
this.emit('warning', {
remainingTime: Math.ceil((this.timeoutMs - (Date.now() - this.lastActivityTime)) / 1000)
});
}
logout() {
clearInterval(this.monitorInterval);
this.clearSensitiveData();
this.emit('logout', { sessionId: this.sessionId });
auditLog.record({
type: 'SESSION_LOGOUT',
severity: 'info',
sessionId: this.sessionId,
reason: 'inactivity_timeout'
});
window.location.href = '/wallet#welcome';
sessionStorage.clear();
}
clearSensitiveData() {
if (window.walletPassword) {
const passwordLength = window.walletPassword.length;
window.walletPassword = '0'.repeat(passwordLength);
delete window.walletPassword;
}
if (window.derivedKey) {
delete window.derivedKey;
}
}
on(event, callback) {
this.listeners.push({ event, callback });
}
emit(event, data) {
this.listeners
.filter(l => l.event === event)
.forEach(l => l.callback(data));
}
getSessionInfo() {
return {
sessionId: this.sessionId,
sessionDuration: Date.now() - this.sessionStartTime,
inactiveTime: Date.now() - this.lastActivityTime,
sessionValid: (Date.now() - this.lastActivityTime) < this.timeoutMs
};
}
}
// ============================================================================
// 2. RATE LIMITING FOR PASSWORD ATTEMPTS
// ============================================================================
class RateLimiter {
constructor(maxAttempts = 5, lockoutDurationMinutes = 5) {
this.maxAttempts = maxAttempts;
this.lockoutDurationMs = lockoutDurationMinutes * 60 * 1000;
this.attempts = this.loadAttempts();
this.lockoutUntil = this.loadLockout();
}
loadAttempts() {
const stored = sessionStorage.getItem('auth_attempts');
if (!stored) return {};
try {
return JSON.parse(stored);
} catch {
return {};
}
}
loadLockout() {
const stored = sessionStorage.getItem('lockout_until');
if (!stored) return {};
try {
return JSON.parse(stored);
} catch {
return {};
}
}
saveAttempts() {
sessionStorage.setItem('auth_attempts', JSON.stringify(this.attempts));
}
saveLockout() {
sessionStorage.setItem('lockout_until', JSON.stringify(this.lockoutUntil));
}
recordAttempt(identifier) {
const now = Date.now();
if (this.isLockedOut(identifier)) {
const remaining = Math.ceil((this.lockoutUntil[identifier] - now) / 1000);
throw new Error(`Account locked. Try again in ${remaining} seconds.`);
}
if (!this.attempts[identifier]) {
this.attempts[identifier] = {
count: 1,
firstAttemptTime: now,
timestamps: [now]
};
} else {
this.attempts[identifier].count++;
this.attempts[identifier].timestamps.push(now);
if (this.attempts[identifier].timestamps.length > 10) {
this.attempts[identifier].timestamps.shift();
}
}
this.saveAttempts();
if (this.attempts[identifier].count >= this.maxAttempts) {
this.lockoutUntil[identifier] = now + this.lockoutDurationMs;
this.saveLockout();
throw new Error(
`Too many failed attempts. Account locked for ${this.maxAttempts} minutes.`
);
}
return this.maxAttempts - this.attempts[identifier].count;
}
recordSuccess(identifier) {
delete this.attempts[identifier];
delete this.lockoutUntil[identifier];
this.saveAttempts();
this.saveLockout();
}
isLockedOut(identifier) {
const now = Date.now();
const lockoutTime = this.lockoutUntil[identifier];
if (!lockoutTime) return false;
if (now > lockoutTime) {
delete this.lockoutUntil[identifier];
this.saveLockout();
return false;
}
return true;
}
getDelayMs(identifier) {
const attempts = this.attempts[identifier]?.count || 0;
return Math.pow(attempts - 1, 2) * 100;
}
getRemainingAttempts(identifier) {
return Math.max(0, this.maxAttempts - (this.attempts[identifier]?.count || 0));
}
getStatus(identifier) {
return {
attempts: this.attempts[identifier]?.count || 0,
remaining: this.getRemainingAttempts(identifier),
lockedOut: this.isLockedOut(identifier),
lockoutRemainingMs: this.lockoutUntil[identifier]
? Math.max(0, this.lockoutUntil[identifier] - Date.now())
: 0
};
}
}
// ============================================================================
// 3. MEMORY SECURITY - CLEAR SENSITIVE DATA
// ============================================================================
class MemorySecure {
static SENSITIVE_KEYS = [
'walletPassword',
'derivedKey',
'privateKey',
'mnemonicSeed',
'tempPassword'
];
static clearVariable(varName) {
if (window[varName]) {
const length = typeof window[varName] === 'string'
? window[varName].length
: 32;
for (let i = 0; i < 3; i++) {
const randomData = crypto.getRandomValues(new Uint8Array(length));
if (typeof window[varName] === 'string') {
window[varName] = String.fromCharCode(...randomData);
}
}
window[varName] = null;
delete window[varName];
}
}
static clearObject(obj) {
if (!obj || typeof obj !== 'object') return;
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'string') {
obj[key] = '0'.repeat(obj[key].length);
} else if (obj[key] instanceof Uint8Array) {
crypto.getRandomValues(obj[key]);
} else if (typeof obj[key] === 'object') {
this.clearObject(obj[key]);
}
delete obj[key];
});
}
static clearInputElements(selector) {
document.querySelectorAll(selector).forEach(el => {
if (el.value) {
el.value = '0'.repeat(el.value.length);
el.value = '';
}
});
}
static async clearAfterDelay(varName, delayMs = 5000) {
return new Promise(resolve => {
setTimeout(() => {
this.clearVariable(varName);
resolve();
}, delayMs);
});
}
static setupAutoClearing() {
const passwordInputs = document.querySelectorAll('input[type="password"]');
passwordInputs.forEach(input => {
let clearTimeout = null;
input.addEventListener('input', () => {
if (clearTimeout) clearTimeout = null;
clearTimeout = setTimeout(() => {
input.value = '';
}, 30000);
});
input.addEventListener('blur', () => {
input.value = '';
});
});
}
static async deriveKey(password, salt) {
const encoder = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100000,
hash: 'SHA-256',
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
}
// ============================================================================
// 4. BIOMETRIC AUTHENTICATION
// ============================================================================
class BiometricAuth {
static async isAvailable() {
if (!window.PublicKeyCredential) return false;
try {
const available = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
return available;
} catch (err) {
console.log('Biometric check failed:', err);
return false;
}
}
static async register() {
try {
const userId = new Uint8Array(32);
crypto.getRandomValues(userId);
const publicKeyOptions = {
challenge: crypto.getRandomValues(new Uint8Array(32)),
rp: {
name: 'MoonBite Wallet',
id: window.location.hostname
},
user: {
id: userId,
name: 'wallet-user',
displayName: 'MoonBite Wallet User'
},
pubKeyCredParams: [
{ type: 'public-key', alg: -7 },
{ type: 'public-key', alg: -257 }
],
authenticatorSelection: {
authenticatorAttachment: 'platform',
userVerification: 'preferred',
residentKey: 'preferred'
},
timeout: 60000,
attestation: 'direct'
};
const credential = await navigator.credentials.create({
publicKey: publicKeyOptions
});
if (!credential) {
throw new Error('Biometric registration cancelled');
}
await this.storeCredential(credential);
return true;
} catch (err) {
console.error('Biometric registration failed:', err);
return false;
}
}
static async authenticate() {
try {
const storedCredential = await this.getStoredCredential();
if (!storedCredential) {
throw new Error('No biometric credential found');
}
const assertionOptions = {
challenge: crypto.getRandomValues(new Uint8Array(32)),
timeout: 60000,
userVerification: 'preferred'
};
const assertion = await navigator.credentials.get({
publicKey: assertionOptions
});
if (!assertion) {
throw new Error('Biometric authentication cancelled');
}
const verified = await this.verifyAssertion(assertion, storedCredential);
return verified;
} catch (err) {
console.error('Biometric authentication failed:', err);
return false;
}
}
static async storeCredential(credential) {
return new Promise((resolve, reject) => {
const request = indexedDB.open('MoonBiteWallet', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('credentials')) {
db.createObjectStore('credentials', { keyPath: 'id' });
}
};
request.onsuccess = (event) => {
const db = event.target.result;
const store = db.transaction('credentials', 'readwrite').objectStore('credentials');
store.put({
id: 'biometric_credential',
credential: credential,
createdAt: Date.now()
});
resolve();
};
request.onerror = () => reject(request.error);
});
}
static async getStoredCredential() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('MoonBiteWallet', 1);
request.onsuccess = (event) => {
const db = event.target.result;
const store = db.transaction('credentials', 'readonly').objectStore('credentials');
const query = store.get('biometric_credential');
query.onsuccess = () => resolve(query.result?.credential);
query.onerror = () => reject(query.error);
};
request.onerror = () => reject(request.error);
});
}
static async verifyAssertion(assertion, credential) {
try {
const clientDataJSON = new TextDecoder().decode(assertion.response.clientDataJSON);
const clientData = JSON.parse(clientDataJSON);
if (!clientData.challenge) {
return false;
}
return true;
} catch (err) {
console.error('Assertion verification failed:', err);
return false;
}
}
}
// ============================================================================
// 5. 2FA/TOTP IMPLEMENTATION
// ============================================================================
class TOTPManager {
static ISSUER = 'MoonBite Wallet';
static DIGITS = 6;
static PERIOD = 30;
static generateSecret() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return this.base32Encode(array);
}
static base32Encode(bytes) {
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
let bits = 0;
let value = 0;
let output = '';
for (let i = 0; i < bytes.length; i++) {
value = (value << 8) | bytes[i];
bits += 8;
while (bits >= 5) {
output += ALPHABET[(value >>> (bits - 5)) & 31];
bits -= 5;
}
}
if (bits > 0) {
output += ALPHABET[(value << (5 - bits)) & 31];
}
return output;
}
static base32Decode(str) {
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
let bits = 0;
let value = 0;
const output = [];
for (let i = 0; i < str.length; i++) {
const index = ALPHABET.indexOf(str[i].toUpperCase());
if (index === -1) throw new Error('Invalid base32 character');
value = (value << 5) | index;
bits += 5;
if (bits >= 8) {
output.push((value >>> (bits - 8)) & 255);
bits -= 8;
}
}
return new Uint8Array(output);
}
static async generateTOTP(secret) {
const key = this.base32Decode(secret);
let epoch = Math.floor(Date.now() / 1000 / this.PERIOD);
const msg = new Uint8Array(8);
for (let i = 7; i >= 0; i--) {
msg[i] = epoch & 0xff;
epoch >>= 8;
}
const hmac = await crypto.subtle.sign(
'HMAC',
await crypto.subtle.importKey('raw', key, { hash: 'SHA-1', name: 'HMAC' }, false, ['sign']),
msg
);
const hmacArray = new Uint8Array(hmac);
const offset = hmacArray[hmacArray.length - 1] & 0x0f;
const code = ((hmacArray[offset] & 0x7f) << 24 |
(hmacArray[offset + 1] & 0xff) << 16 |
(hmacArray[offset + 2] & 0xff) << 8 |
(hmacArray[offset + 3] & 0xff)) % Math.pow(10, this.DIGITS);
return String(code).padStart(this.DIGITS, '0');
}
static generateBackupCodes(count = 10) {
const codes = [];
for (let i = 0; i < count; i++) {
const array = new Uint8Array(4);
crypto.getRandomValues(array);
const code = Array.from(array)
.map(byte => byte.toString(16).padStart(2, '0'))
.join('')
.toUpperCase()
.slice(0, 8);
codes.push(code);
}
return codes;
}
static getProvisioningURI(secret, accountName) {
const encodedIssuer = encodeURIComponent(this.ISSUER);
const encodedAccount = encodeURIComponent(accountName);
return `otpauth://totp/${encodedIssuer}:${encodedAccount}?secret=${secret}&issuer=${encodedIssuer}&algorithm=SHA1&digits=${this.DIGITS}&period=${this.PERIOD}`;
}
static async verifyTOTP(secret, code, window = 1) {
let now = Math.floor(Date.now() / 1000 / this.PERIOD);
for (let i = -window; i <= window; i++) {
const epoch = now + i;
const msg = new Uint8Array(8);
for (let j = 7; j >= 0; j--) {
msg[j] = epoch & 0xff;
epoch >>= 8;
}
const key = this.base32Decode(secret);
const hmac = await crypto.subtle.sign(
'HMAC',
await crypto.subtle.importKey('raw', key, { hash: 'SHA-1', name: 'HMAC' }, false, ['sign']),
msg
);
const hmacArray = new Uint8Array(hmac);
const offset = hmacArray[hmacArray.length - 1] & 0x0f;
const calculatedCode = ((hmacArray[offset] & 0x7f) << 24 |
(hmacArray[offset + 1] & 0xff) << 16 |
(hmacArray[offset + 2] & 0xff) << 8 |
(hmacArray[offset + 3] & 0xff)) % Math.pow(10, this.DIGITS);
if (String(calculatedCode).padStart(this.DIGITS, '0') === code) {
return true;
}
}
return false;
}
}
// ============================================================================
// 6. SECURE PIN ENTRY
// ============================================================================
class SecurePINEntry {
constructor(elementId, options = {}) {
this.container = document.getElementById(elementId);
this.length = options.length || 6;
this.type = options.type || 'pin';
this.onComplete = options.onComplete || null;
this.maskedValue = '';
this.actualValue = '';
this.render();
this.setupEventListeners();
}
render() {
this.container.innerHTML = `
${Array(this.length).fill(0).map((_, i) =>
`
`
).join('')}
${Array.from({ length: 10 }, (_, i) => i).map(num =>
``
).join('')}
`;
}
setupEventListeners() {
this.container.addEventListener('keydown', (e) => this.handleKeyDown(e));
this.container.querySelectorAll('.pin-key').forEach(btn => {
btn.addEventListener('click', (e) => {
const key = e.target.getAttribute('data-key');
this.handleKeyPress(key);
});
});
}
handleKeyDown(e) {
if (e.key >= '0' && e.key <= '9') {
this.handleKeyPress(e.key);
e.preventDefault();
} else if (e.key === 'Backspace') {
this.handleKeyPress('backspace');
e.preventDefault();
} else if (e.key === 'Enter' && this.isComplete()) {
this.complete();
e.preventDefault();
}
}
handleKeyPress(key) {
if (key === 'backspace') {
if (this.actualValue.length > 0) {
this.actualValue = this.actualValue.slice(0, -1);
this.updateDisplay();
this.hapticFeedback('light');
}
} else if (key === 'clear') {
this.actualValue = '';
this.updateDisplay();
this.hapticFeedback('medium');
} else if (/^\d$/.test(key)) {
if (this.actualValue.length < this.length) {
this.actualValue += key;
this.updateDisplay();
this.hapticFeedback('light');
if (this.isComplete()) {
setTimeout(() => this.complete(), 100);
}
}
}
}
updateDisplay() {
const dots = this.container.querySelectorAll('.pin-dot');
dots.forEach((dot, i) => {
if (i < this.actualValue.length) {
dot.classList.add('filled');
} else {
dot.classList.remove('filled');
}
});
this.maskedValue = '●'.repeat(this.actualValue.length);
}
isComplete() {
return this.actualValue.length === this.length;
}
getValue() {
return this.actualValue;
}
clear() {
this.actualValue = '';
this.maskedValue = '';
this.updateDisplay();
}
complete() {
if (this.onComplete) {
this.onComplete(this.actualValue);
}
}
hapticFeedback(type) {
if ('vibrate' in navigator) {
switch (type) {
case 'light':
navigator.vibrate(10);
break;
case 'medium':
navigator.vibrate(50);
break;
case 'heavy':
navigator.vibrate([20, 10, 20]);
break;
}
}
}
destroy() {
this.clear();
this.container.innerHTML = '';
}
}
// ============================================================================
// 7. DEVICE SECURITY CHECKS
// ============================================================================
class DeviceSecurityCheck {
static async performCheck() {
const issues = [];
if (await this.isJailbroken()) {
issues.push({
severity: 'high',
type: 'jailbroken',
message: 'Device appears to be jailbroken/rooted',
advice: 'Rooted devices are vulnerable to malware that could steal your wallet keys.'
});
}
if (this.isEmulator()) {
issues.push({
severity: 'high',
type: 'emulator',
message: 'Running on an emulator',
advice: 'Emulators are not secure for storing real cryptographic keys.'
});
}
if (await this.isDeveloperModeEnabled()) {
issues.push({
severity: 'medium',
type: 'developer_mode',
message: 'Developer mode is enabled',
advice: 'Disable developer mode and USB debugging for better security.'
});
}
if (!this.isScreenLocked()) {
issues.push({
severity: 'medium',
type: 'screen_lock',
message: 'Device screen lock is not enabled',
advice: 'Enable screen lock (PIN, pattern, or biometric) for physical security.'
});
}
return issues;
}
static async isJailbroken() {
if (localStorage.getItem('jailbreak_check_disabled')) {
return false;
}
return false;
}
static isEmulator() {
const userAgent = navigator.userAgent.toLowerCase();
const isAndroidEmulator = userAgent.includes('linux') &&
userAgent.includes('android') &&
!userAgent.includes('samsung');
const dpr = window.devicePixelRatio;
const suspiciousDPR = dpr === 1 || dpr === 2 || dpr === 3;
const width = window.innerWidth;
const height = window.innerHeight;
const isCommonEmulatorResolution =
(width === 360 && height === 640) ||
(width === 412 && height === 732) ||
(width === 384 && height === 768);
return isAndroidEmulator || (suspiciousDPR && isCommonEmulatorResolution);
}
static async isDeveloperModeEnabled() {
return false;
}
static isScreenLocked() {
return true;
}
static displaySecurityWarnings(issues) {
if (issues.length === 0) {
return;
}
const highSeverity = issues.filter(i => i.severity === 'high');
const mediumSeverity = issues.filter(i => i.severity === 'medium');
if (highSeverity.length > 0) {
const warningModal = document.createElement('div');
warningModal.className = 'modal active security-warning-modal';
warningModal.innerHTML = `
${highSeverity.map(issue => `
${issue.message}
${issue.advice}
`).join('')}
We recommend fixing these issues before using this wallet for real transactions.
`;
document.body.appendChild(warningModal);
document.getElementById('acknowledgeWarningBtn').addEventListener('click', () => {
localStorage.setItem('security_warning_acknowledged', Date.now());
warningModal.remove();
});
}
mediumSeverity.forEach(issue => {
if (typeof showAlert === 'function') {
showAlert(`${issue.message}`, 'info');
}
});
auditLog.record({
type: 'DEVICE_SECURITY_CHECK',
severity: 'warning',
issues: issues
});
}
}
// ============================================================================
// 8. SCREEN BLUR ON BACKGROUND
// ============================================================================
class ScreenBlur {
constructor() {
this.blurElement = null;
this.isBlurred = false;
this.initializeListeners();
}
initializeListeners() {
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
this.blur();
} else {
this.unblur();
}
});
if (window.cordova) {
document.addEventListener('pause', () => this.blur());
document.addEventListener('resume', () => this.unblur());
}
window.addEventListener('blur', () => this.blur());
window.addEventListener('focus', () => this.unblur());
}
blur() {
if (this.isBlurred) return;
const app = document.querySelector('.app-container');
if (!app) return;
this.blurElement = document.createElement('div');
this.blurElement.className = 'screen-blur';
this.blurElement.innerHTML = `
Locked
App in background
Wallet hidden for security
`;
app.appendChild(this.blurElement);
app.style.filter = 'blur(20px)';
this.isBlurred = true;
this.hideSensitiveElements();
auditLog.record({
type: 'SCREEN_BLUR',
severity: 'info',
reason: 'app_backgrounded'
});
}
unblur() {
if (!this.isBlurred) return;
const app = document.querySelector('.app-container');
if (this.blurElement && this.blurElement.parentNode) {
this.blurElement.parentNode.removeChild(this.blurElement);
}
app.style.filter = '';
this.isBlurred = false;
this.showSensitiveElements();
}
hideSensitiveElements() {
document.querySelectorAll('.address-box, .card-value, .balance-card').forEach(el => {
el.style.visibility = 'hidden';
});
document.querySelectorAll('canvas').forEach(el => {
el.style.display = 'none';
});
}
showSensitiveElements() {
document.querySelectorAll('.address-box, .card-value, .balance-card').forEach(el => {
el.style.visibility = 'visible';
});
document.querySelectorAll('canvas').forEach(el => {
el.style.display = '';
});
}
}
// ============================================================================
// 9. CLIPBOARD SECURITY
// ============================================================================
async function copyToClipboardSecure(text, duration = 30000) {
try {
await navigator.clipboard.writeText(text);
setTimeout(async () => {
try {
await navigator.clipboard.writeText('');
} catch (err) {
console.log('Could not clear clipboard');
}
}, duration);
return true;
} catch (err) {
console.error('Clipboard copy failed:', err);
return false;
}
}
// ============================================================================
// 10. AUDIT LOGGING
// ============================================================================
class AuditLog {
constructor(maxSize = 1000) {
this.maxSize = maxSize;
this.logs = this.loadLogs();
}
loadLogs() {
try {
const stored = localStorage.getItem('moonbite_audit_log');
return stored ? JSON.parse(stored) : [];
} catch {
return [];
}
}
saveLogs() {
const recentLogs = this.logs.slice(-this.maxSize);
localStorage.setItem('moonbite_audit_log', JSON.stringify(recentLogs));
}
record(event) {
const logEntry = {
id: this.generateId(),
timestamp: event.timestamp || new Date().toISOString(),
type: event.type,
severity: event.severity || 'info',
details: this.sanitizeDetails(event),
deviceId: this.getDeviceId(),
sessionId: typeof sessionManager !== 'undefined' ? sessionManager?.sessionId : 'unknown'
};
this.logs.push(logEntry);
this.saveLogs();
}
generateId() {
return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
sanitizeDetails(event) {
const details = { ...event };
delete details.password;
delete details.privateKey;
delete details.seed;
delete details.recoveryKey;
delete details.timestamp;
delete details.severity;
delete details.type;
return details;
}
getDeviceId() {
let deviceId = localStorage.getItem('moonbite_device_id');
if (!deviceId) {
deviceId = this.generateId();
localStorage.setItem('moonbite_device_id', deviceId);
}
return deviceId;
}
getLogs(filter = {}) {
let results = this.logs;
if (filter.type) {
results = results.filter(log => log.type === filter.type);
}
if (filter.severity) {
results = results.filter(log => log.severity === filter.severity);
}
if (filter.since) {
results = results.filter(log =>
new Date(log.timestamp) >= new Date(filter.since)
);
}
return results;
}
exportLogs() {
return JSON.stringify(this.logs, null, 2);
}
downloadLogs() {
const logs = this.exportLogs();
const blob = new Blob([logs], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `audit-log-${Date.now()}.json`;
link.click();
URL.revokeObjectURL(url);
}
}
// ============================================================================
// INITIALIZATION
// ============================================================================
// Global instances
let sessionManager = null;
const rateLimiter = new RateLimiter(5, 5);
const auditLog = new AuditLog();
const screenBlur = new ScreenBlur();
// Setup security on app start
function initializeWalletSecurity() {
MemorySecure.setupAutoClearing();
auditLog.record({
type: 'SECURITY_MODULE_INITIALIZED',
severity: 'info'
});
console.log('MoonBite Wallet Security Module initialized');
}
// Initialize on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeWalletSecurity);
} else {
initializeWalletSecurity();
}