Files
fast-next-template/frontend/src/lib/auth/crypto.ts
Felipe Cardoso cf5bb41c17 Refactor config, auth, and storage modules with runtime validation and encryption
- Centralized and refactored configuration management (`config`) with runtime validation for environment variables.
- Introduced utilities for secure token storage, including AES-GCM encryption and fallback handling.
- Enhanced `authStore` state management with token validation, secure persistence, and initialization from storage.
- Modularized authentication utilities and updated export structure for better maintainability.
- Improved error handling, input validation, and added detailed comments for enhanced clarity.
2025-10-31 22:00:45 +01:00

109 lines
2.9 KiB
TypeScript

/**
* Cryptographic utilities for secure token storage
* Implements AES-GCM encryption for localStorage fallback
*/
const ENCRYPTION_KEY_NAME = 'auth_encryption_key';
/**
* Generate or retrieve encryption key
* Key is stored in sessionStorage (cleared on browser close)
*/
async function getEncryptionKey(): Promise<CryptoKey> {
// Check if key exists in session
const storedKey = sessionStorage.getItem(ENCRYPTION_KEY_NAME);
if (storedKey) {
const keyData = JSON.parse(storedKey);
return await crypto.subtle.importKey(
'jwk',
keyData,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
}
// Generate new key
const key = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
// Store key in sessionStorage
const exportedKey = await crypto.subtle.exportKey('jwk', key);
sessionStorage.setItem(ENCRYPTION_KEY_NAME, JSON.stringify(exportedKey));
return key;
}
/**
* Encrypt data using AES-GCM
* @param data - Data to encrypt
* @returns Base64 encoded encrypted data with IV
*/
export async function encryptData(data: string): Promise<string> {
try {
const key = await getEncryptionKey();
const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit IV for GCM
const encoder = new TextEncoder();
const encodedData = encoder.encode(data);
const encryptedData = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
encodedData
);
// Combine IV and encrypted data
const combined = new Uint8Array(iv.length + encryptedData.byteLength);
combined.set(iv);
combined.set(new Uint8Array(encryptedData), iv.length);
// Convert to base64
return btoa(String.fromCharCode(...combined));
} catch (error) {
console.error('Encryption failed:', error);
throw new Error('Failed to encrypt data');
}
}
/**
* Decrypt data encrypted with encryptData
* @param encryptedData - Base64 encoded encrypted data with IV
* @returns Decrypted string
*/
export async function decryptData(encryptedData: string): Promise<string> {
try {
const key = await getEncryptionKey();
// Decode from base64
const combined = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0));
// Extract IV and encrypted data
const iv = combined.slice(0, 12);
const data = combined.slice(12);
const decryptedData = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
data
);
const decoder = new TextDecoder();
return decoder.decode(decryptedData);
} catch (error) {
console.error('Decryption failed:', error);
throw new Error('Failed to decrypt data');
}
}
/**
* Clear encryption key from session
* Call this on logout to invalidate encrypted data
*/
export function clearEncryptionKey(): void {
sessionStorage.removeItem(ENCRYPTION_KEY_NAME);
}