1import { setCookie, getCookie } from '../utils.js'; 2 3let cachedAuthToken = null; 4 5/** Utility to set secure key */ 6function setKey(name, value, exdays) { 7 setCookie(name, value, exdays, window.location.hostname, true); 8} 9 10/** Utility to get secure key */ 11function getKey(name) { 12 return getCookie(name); 13} 14 15/** Utility to clear secure key */ 16function clearKeyCookie(name) { 17 setCookie(name, '', -1, window.location.hostname, true); 18} 19 20/** Convert ArrayBuffer to Base64 */ 21function bufToBase64(buf) { 22 return btoa(String.fromCharCode(...new Uint8Array(buf))); 23} 24 25/** Convert Base64 to ArrayBuffer */ 26function base64ToBuf(b64) { 27 return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)); 28} 29 30/** 31 * Import a raw Base64-encoded AES key into CryptoKey format 32 * @param {string} b64Key - Base64-encoded AES key 33 * @returns {Promise<CryptoKey>} - Imported CryptoKey 34 */ 35async function importKey(b64Key) { 36 const raw = base64ToBuf(b64Key); 37 return crypto.subtle.importKey( 38 'raw', 39 raw, 40 { name: 'AES-CBC' }, 41 true, 42 ['encrypt', 'decrypt'], 43 ); 44} 45 46/** 47 * Decrypt data using AES-CBC 48 * @param {string} encryptedBase64 - Base64 ciphertext 49 * @param {string} ivBase64 - Base64 initialization vector 50 * @param {CryptoKey} key - AES key 51 * @returns {Promise<string>} - Decrypted plaintext 52 */ 53async function decrypt(encryptedBase64, ivBase64, key) { 54 const encrypted = base64ToBuf(encryptedBase64); 55 const encryptionIv = base64ToBuf(ivBase64); 56 try { 57 const decrypted = await window.crypto.subtle.decrypt( 58 { name: 'AES-CBC', iv: encryptionIv }, 59 key, 60 encrypted, 61 ); 62 return new TextDecoder().decode(decrypted); 63 } catch (e) { 64 console.warn('Decryption failed:', e); 65 return null; 66 } 67} 68 69/** 70 * Initialize and retrieve the decrypted auth user token. 71 * Reads the encrypted token, encryptionIv, and stored key from cookies/session storage, 72 * then decrypts it and caches the result. 73 * 74 * @returns {Promise<string|null>} The decrypted token or null if unavailable. 75 */ 76async function initAuthUserToken() { 77 try { 78 const encrypted = getCookie('auth_user_token'); 79 const encryptionIv = getCookie('iv_auth_user_token'); 80 const exportedKey = getKey('sKey'); 81 82 if (!encrypted || !encryptionIv || !exportedKey) return null; 83 84 const sKey = await importKey(exportedKey); 85 cachedAuthToken = await decrypt(encrypted, encryptionIv, sKey); 86 return cachedAuthToken; 87 } catch { 88 return null; 89 } 90} 91 92/** Generate a new AES-256 key */ 93async function generateKey() { 94 return window.crypto.subtle.generateKey( 95 { name: 'AES-CBC', length: 256 }, 96 true, 97 ['encrypt', 'decrypt'], 98 ); 99} 100 101/** Export AES key to Base64 */ 102async function exportKey(key) { 103 const raw = await crypto.subtle.exportKey('raw', key); 104 return bufToBase64(raw); 105} 106 107/** 108 * Retrieve or generate AES key used for encryption 109 * Stores key in cookie if not already present 110 * @returns {Promise<CryptoKey>} 111 */ 112async function getCryptoKey() { 113 let exportedKey = getKey('sKey'); 114 let sKey; 115 116 if (!exportedKey) { 117 sKey = await generateKey(); 118 exportedKey = await exportKey(sKey); 119 setKey('sKey', exportedKey, 7); 120 } else { 121 sKey = await importKey(exportedKey); 122 } 123 124 return sKey; 125} 126 127/** 128 * Encrypt text using AES-CBC with random encryptionIv 129 */ 130async function encrypt(text, key) { 131 const encryptionIv = window.crypto.getRandomValues(new Uint8Array(16)); 132 const encoded = new TextEncoder().encode(text); 133 134 const encrypted = await window.crypto.subtle.encrypt( 135 { name: 'AES-CBC', iv: encryptionIv }, 136 key, 137 encoded, 138 ); 139 140 return { 141 encrypted: bufToBase64(encrypted), 142 encryptionIv: bufToBase64(encryptionIv), 143 }; 144} 145 146/** 147 * Retrieve and decrypt a secure item stored in sessionStorage 148 * @param {string} key - The key name 149 * @returns {Promise<string|null>} - Decrypted value or null 150 */ 151export async function getSecureItem(key) { 152 try { 153 const encrypted = sessionStorage.getItem(key); 154 const encryptionIv = sessionStorage.getItem(`iv_${key}`); 155 const exportedKey = getKey('sKey'); 156 157 if (!encrypted || !encryptionIv || !exportedKey) return null; 158 159 const sKey = await importKey(exportedKey); 160 return await decrypt(encrypted, encryptionIv, sKey); 161 } catch { 162 return null; 163 } 164} 165 166/** 167 * Encrypt and store a secure item in sessionStorage 168 * @param {string} key - The key name 169 * @param {string} value - Plaintext value 170 */ 171export async function setSecureItem(key, value) { 172 if (typeof value !== 'string') return; 173 174 const sKey = await getCryptoKey(); 175 const { encrypted, encryptionIv }
175 = await encrypt(value, sKey); 176 177 sessionStorage.setItem(key, encrypted); 178 sessionStorage.setItem(`iv_${key}`, encryptionIv); 179} 180 181// Aliases for email and userId using generic functions 182export const setUserEmail = (key, value) => setSecureItem(key, value); 183export const getUserEmail = (key) => getSecureItem(key); 184 185export const setUserId = (key, value) => setSecureItem(key, value); 186export const getUserId = (key) => getSecureItem(key); 187 188// Restrict special characters not allowed in standard emails 189export function restrictSpecialCharacterEmail(emailInp) { 190 emailInp.addEventListener('input', () => { 191 emailInp.value = emailInp.value.replace(/[^a-zA-Z0-9@._\-+]/g, ''); 192 }); 193} 194 195// Restrict input to allow only letters, spaces, apostrophes, and hyphens 196export function restrictSpecialCharacterText(input) { 197 input.addEventListener('input', () => { 198 input.value = input.value.replace(/[^0-9\s.,!?'"()\-\p{L}]/gu, ''); 199 }); 200} 201 202/** Get cached auth user token */ 203export function getAuthUserToken() { 204 return cachedAuthToken; 205} 206 207/** Clear cookies & cache */ 208function clearAuthUserToken() { 209 setCookie('auth_user_token', '', -1); 210 setCookie('iv_auth_user_token', '', -1); 211 clearKeyCookie('sKey'); 212 // Remove cached token 213 cachedAuthToken = null; 214} 215 216/** 217 * Encrypt and set auth user token in cookies 218 * @param {string} data - Plaintext token 219 * @param {number} cookieExpiryDays - Expiry in days 220 */ 221export async function setAuthUserToken(data, cookieExpiryDays) { 222 // If clearing empty string or negative expiry, just wipe everything 223 if (!data || cookieExpiryDays < 0) { 224 clearAuthUserToken(); 225 return; 226 } 227 228 if (typeof data !== 'string') return; 229 230 const sKey = await getCryptoKey(); 231 const { encrypted, encryptionIv } = await encrypt(data, sKey); 232 233 // Store encrypted token + encryptionIv in cookies 234 setCookie('auth_user_token', encrypted, cookieExpiryDays); 235 setCookie('iv_auth_user_token', encryptionIv, cookieExpiryDays); 236 // Update cache immediately with plain token 237 cachedAuthToken = data; 238} 239 240// At app startup 241(async () => { 242 await initAuthUserToken(); 243})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.