mirror of
https://github.com/stackblitz/bolt.new
synced 2025-06-26 18:17:50 +00:00
- Enhanced encryption system with AES-GCM and PBKDF2 - Added secure authentication system with session management - Implemented security middleware with headers and rate limiting - Created secure storage utilities for sensitive data - Added login system with brute force protection - Implemented security components (password strength, 2FA) - Added security utility functions for threat detection Copyright (c) 2024 Ervin Remus Radosavlevici
132 lines
3.8 KiB
TypeScript
132 lines
3.8 KiB
TypeScript
import { encrypt, decrypt } from '~/lib/crypto';
|
|
import { createScopedLogger } from './logger';
|
|
|
|
const logger = createScopedLogger('SecureStorage');
|
|
|
|
/**
|
|
* SecureStorage provides encrypted local storage functionality
|
|
* to protect sensitive data stored in the browser
|
|
*/
|
|
export class SecureStorage {
|
|
private readonly prefix: string;
|
|
private readonly encryptionKey: string;
|
|
|
|
/**
|
|
* Create a new SecureStorage instance
|
|
* @param namespace - Namespace to prefix all keys with
|
|
* @param encryptionKey - Key used for encryption (should be a strong, unique key)
|
|
*/
|
|
constructor(namespace: string, encryptionKey: string) {
|
|
this.prefix = `secure_${namespace}_`;
|
|
this.encryptionKey = encryptionKey;
|
|
}
|
|
|
|
/**
|
|
* Store a value securely
|
|
* @param key - Storage key
|
|
* @param value - Value to store (will be encrypted)
|
|
*/
|
|
async setItem(key: string, value: any): Promise<void> {
|
|
try {
|
|
const serialized = JSON.stringify(value);
|
|
const encrypted = await encrypt(this.encryptionKey, serialized);
|
|
localStorage.setItem(this.prefix + key, encrypted);
|
|
} catch (error) {
|
|
logger.error('Failed to securely store item:', error);
|
|
throw new Error('Failed to securely store data');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve and decrypt a stored value
|
|
* @param key - Storage key
|
|
* @returns The decrypted value or null if not found
|
|
*/
|
|
async getItem<T>(key: string): Promise<T | null> {
|
|
try {
|
|
const encrypted = localStorage.getItem(this.prefix + key);
|
|
|
|
if (!encrypted) {
|
|
return null;
|
|
}
|
|
|
|
const decrypted = await decrypt(this.encryptionKey, encrypted);
|
|
return JSON.parse(decrypted) as T;
|
|
} catch (error) {
|
|
logger.error('Failed to retrieve secure item:', error);
|
|
// If decryption fails, remove the corrupted item
|
|
this.removeItem(key);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove a stored value
|
|
* @param key - Storage key to remove
|
|
*/
|
|
removeItem(key: string): void {
|
|
localStorage.removeItem(this.prefix + key);
|
|
}
|
|
|
|
/**
|
|
* Clear all values stored in this namespace
|
|
*/
|
|
clear(): void {
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
const key = localStorage.key(i);
|
|
if (key && key.startsWith(this.prefix)) {
|
|
localStorage.removeItem(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all keys in this namespace
|
|
* @returns Array of keys (without the namespace prefix)
|
|
*/
|
|
keys(): string[] {
|
|
const keys: string[] = [];
|
|
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
const key = localStorage.key(i);
|
|
if (key && key.startsWith(this.prefix)) {
|
|
keys.push(key.slice(this.prefix.length));
|
|
}
|
|
}
|
|
|
|
return keys;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a secure storage instance with a derived key
|
|
* @param namespace - Storage namespace
|
|
* @param userIdentifier - User-specific identifier to derive the key
|
|
*/
|
|
export function createSecureStorage(namespace: string, userIdentifier: string): SecureStorage {
|
|
// Derive a deterministic key from the user identifier
|
|
// In production, this should be combined with a server-provided secret
|
|
const derivedKey = deriveKeyFromIdentifier(userIdentifier);
|
|
return new SecureStorage(namespace, derivedKey);
|
|
}
|
|
|
|
/**
|
|
* Derive a deterministic key from a user identifier
|
|
* This is a simplified version - in production use a more robust approach
|
|
*/
|
|
function deriveKeyFromIdentifier(identifier: string): string {
|
|
// Simple key derivation - in production use a more secure approach
|
|
// This is just to demonstrate the concept
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(identifier);
|
|
|
|
// Create a simple hash of the identifier
|
|
let hash = 0;
|
|
for (let i = 0; i < data.length; i++) {
|
|
hash = ((hash << 5) - hash) + data[i];
|
|
hash |= 0; // Convert to 32bit integer
|
|
}
|
|
|
|
// Convert to a string and pad
|
|
return hash.toString(36).padStart(16, '0');
|
|
} |