zerokey
Version:
Zero-knowledge cross-domain secret sharing library using ECDH encryption
1 lines • 32.4 kB
Source Map (JSON)
{"version":3,"sources":["../src/crypto.ts","../src/client.ts"],"names":[],"mappings":"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","file":"client.cjs","sourcesContent":["// Base64url encoding/decoding utilities\n\n/**\n * Encodes an ArrayBuffer to a base64url string.\n *\n * Base64url is a URL-safe variant of base64 encoding that replaces\n * '+' with '-', '/' with '_', and removes padding '=' characters.\n * This makes it suitable for use in URLs and filenames.\n *\n * @param {ArrayBuffer} buffer - The binary data to encode.\n * @returns {string} The base64url-encoded string.\n * @internal\n */\nfunction base64urlEncode(buffer: ArrayBuffer): string {\n const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer)));\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n}\n\n/**\n * Decodes a base64url string to an ArrayBuffer.\n *\n * Reverses the base64url encoding by restoring the standard base64\n * characters and padding before decoding to binary data.\n *\n * @param {string} str - The base64url-encoded string to decode.\n * @returns {ArrayBuffer} The decoded binary data.\n * @internal\n */\nfunction base64urlDecode(str: string): ArrayBuffer {\n const base64 = str\n .replace(/-/g, '+')\n .replace(/_/g, '/')\n .padEnd(str.length + ((4 - (str.length % 4)) % 4), '=');\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n// Type definitions\n\n/**\n * Represents an asymmetric cryptographic key pair for ECDH operations.\n *\n * @interface KeyPair\n * @property {string} publicKey - The public key encoded as a base64url string.\n * This key can be safely shared with others.\n * @property {CryptoKey} privateKey - The private key as a native CryptoKey object.\n * This key must be kept secret and never shared.\n *\n * @example\n * ```typescript\n * const keyPair = await generateKeyPair();\n * console.log(keyPair.publicKey); // Safe to share\n * // keyPair.privateKey is kept secret\n * ```\n */\nexport interface KeyPair {\n publicKey: string;\n privateKey: CryptoKey;\n}\n\n/**\n * Internal structure for storing encrypted data along with the ephemeral public key.\n * Used in hybrid encryption scheme.\n *\n * @interface EncryptedPayload\n * @internal\n */\ninterface EncryptedPayload {\n ephemeralPublicKey: string;\n encryptedData: string;\n}\n\n/**\n * Generates a new ECDH (Elliptic Curve Diffie-Hellman) key pair using the P-256 curve.\n *\n * This function creates a cryptographically secure key pair suitable for key exchange\n * and hybrid encryption operations. The P-256 curve (also known as secp256r1) provides\n * 128-bit security strength.\n *\n * @returns {Promise<KeyPair>} A promise that resolves to a KeyPair object containing\n * the public key (as base64url string) and private key (as CryptoKey).\n *\n * @throws {Error} Throws if the Web Crypto API is not available or key generation fails.\n *\n * @example\n * ```typescript\n * const keyPair = await generateKeyPair();\n * // Store keyPair.publicKey in database or share with others\n * // Keep keyPair.privateKey secure and never transmit it\n * ```\n *\n * @see {@link https://www.w3.org/TR/WebCryptoAPI/#ecdh|W3C Web Crypto API ECDH}\n */\nexport async function generateKeyPair(): Promise<KeyPair> {\n const keyPair = await crypto.subtle.generateKey(\n {\n name: 'ECDH',\n namedCurve: 'P-256'\n },\n true,\n ['deriveKey']\n );\n\n const publicKeyExported = await exportPublicKey(keyPair.publicKey);\n\n return {\n publicKey: publicKeyExported,\n privateKey: keyPair.privateKey\n };\n}\n\n/**\n * Exports a CryptoKey public key to a base64url-encoded string format.\n *\n * This function converts a native CryptoKey object to a portable string format\n * that can be safely transmitted over networks, stored in databases, or shared\n * between different systems. The base64url encoding is URL-safe and doesn't\n * require additional encoding.\n *\n * @param {CryptoKey} publicKey - The public key to export. Must be an ECDH P-256 public key.\n *\n * @returns {Promise<string>} A promise that resolves to the base64url-encoded public key.\n *\n * @throws {Error} Throws if the key export fails or if the provided key is not a valid public key.\n *\n * @example\n * ```typescript\n * const keyPair = await generateKeyPair();\n * const publicKeyString = await exportPublicKey(keyPair.publicKey);\n * // publicKeyString can now be stored or transmitted\n * ```\n */\nexport async function exportPublicKey(publicKey: CryptoKey): Promise<string> {\n const exported = await crypto.subtle.exportKey('raw', publicKey);\n return base64urlEncode(exported);\n}\n\n/**\n * Imports a public key from a base64url-encoded string to a CryptoKey object.\n *\n * This function converts a portable string representation of a public key back\n * into a native CryptoKey object that can be used for cryptographic operations.\n * The key must be a valid ECDH P-256 public key in raw format.\n *\n * @param {string} publicKeyString - The base64url-encoded public key string to import.\n *\n * @returns {Promise<CryptoKey>} A promise that resolves to the imported public key as a CryptoKey object.\n *\n * @throws {Error} Throws if the string is not a valid base64url-encoded key or if import fails.\n *\n * @example\n * ```typescript\n * const publicKeyString = \"your-base64url-encoded-public-key\";\n * const publicKey = await importPublicKey(publicKeyString);\n * // publicKey can now be used for encryption operations\n * ```\n */\nexport async function importPublicKey(publicKeyString: string): Promise<CryptoKey> {\n const keyData = base64urlDecode(publicKeyString);\n return await crypto.subtle.importKey(\n 'raw',\n keyData,\n {\n name: 'ECDH',\n namedCurve: 'P-256'\n },\n false,\n []\n );\n}\n\n/**\n * Serializes a private key to a JSON string for secure storage.\n *\n * This function exports a private key in JWK (JSON Web Key) format, which preserves\n * all key parameters and can be safely stored in secure storage systems. The resulting\n * string contains sensitive key material and must be protected accordingly.\n *\n * @param {CryptoKey} privateKey - The private key to serialize. Must be an ECDH P-256 private key.\n *\n * @returns {Promise<string>} A promise that resolves to the JSON-serialized private key.\n *\n * @throws {Error} Throws if the key export fails or if the provided key is not extractable.\n *\n * @example\n * ```typescript\n * const keyPair = await generateKeyPair();\n * const serialized = await serializePrivateKey(keyPair.privateKey);\n * // Store 'serialized' in secure storage (e.g., encrypted database, secure keychain)\n * ```\n *\n * @security This function returns sensitive key material. Ensure the output is stored\n * securely and never transmitted over insecure channels.\n */\nexport async function serializePrivateKey(privateKey: CryptoKey): Promise<string> {\n const exported = await crypto.subtle.exportKey('jwk', privateKey);\n return JSON.stringify(exported);\n}\n\n/**\n * Deserializes a private key from a JSON string back to a CryptoKey object.\n *\n * This function imports a private key that was previously serialized using\n * serializePrivateKey(). It restores the key to a usable CryptoKey object\n * for cryptographic operations.\n *\n * @param {string} privateKeyString - The JSON-serialized private key string to deserialize.\n *\n * @returns {Promise<CryptoKey>} A promise that resolves to the imported private key as a CryptoKey object.\n *\n * @throws {Error} Throws if the string is not valid JSON or contains an invalid JWK structure.\n *\n * @example\n * ```typescript\n * // Retrieve serialized key from secure storage\n * const serialized = await getFromSecureStorage('privateKey');\n * const privateKey = await deserializePrivateKey(serialized);\n * // privateKey can now be used for decryption operations\n * ```\n *\n * @security Handle the input string with care as it contains sensitive key material.\n */\nexport async function deserializePrivateKey(privateKeyString: string): Promise<CryptoKey> {\n const jwk = JSON.parse(privateKeyString);\n return await crypto.subtle.importKey(\n 'jwk',\n jwk,\n {\n name: 'ECDH',\n namedCurve: 'P-256'\n },\n true,\n ['deriveKey']\n );\n}\n\n/**\n * Derives a shared secret key using ECDH key agreement.\n *\n * This function performs the ECDH key agreement protocol to derive\n * a shared AES-256 key from a private key and a public key. The\n * resulting key can be used for symmetric encryption/decryption.\n *\n * @param {CryptoKey} privateKey - The private key for the ECDH operation.\n * @param {CryptoKey} publicKey - The public key for the ECDH operation.\n * @returns {Promise<CryptoKey>} A promise that resolves to the derived AES-256 key.\n * @internal\n */\nasync function deriveSharedSecret(privateKey: CryptoKey, publicKey: CryptoKey): Promise<CryptoKey> {\n return await crypto.subtle.deriveKey(\n {\n name: 'ECDH',\n public: publicKey\n },\n privateKey,\n {\n name: 'AES-GCM',\n length: 256\n },\n false,\n ['encrypt', 'decrypt']\n );\n}\n\n/**\n * Encrypts a string using AES-GCM with a random IV.\n *\n * AES-GCM provides authenticated encryption, ensuring both confidentiality\n * and integrity of the data. A 96-bit random IV is generated for each\n * encryption operation and prepended to the encrypted data.\n *\n * @param {CryptoKey} key - The AES-256 key for encryption.\n * @param {string} data - The plaintext string to encrypt.\n * @returns {Promise<ArrayBuffer>} A promise that resolves to the combined IV + encrypted data.\n * @internal\n */\nasync function aesEncrypt(key: CryptoKey, data: string): Promise<ArrayBuffer> {\n const encoder = new TextEncoder();\n const iv = crypto.getRandomValues(new Uint8Array(12));\n\n const encrypted = await crypto.subtle.encrypt(\n {\n name: 'AES-GCM',\n iv: iv\n },\n key,\n encoder.encode(data)\n );\n\n // Combine iv and encrypted data\n const combined = new Uint8Array(iv.length + encrypted.byteLength);\n combined.set(iv, 0);\n combined.set(new Uint8Array(encrypted), iv.length);\n\n return combined.buffer;\n}\n\n/**\n * Decrypts data that was encrypted using AES-GCM.\n *\n * Extracts the IV from the beginning of the combined data and uses it\n * along with the provided key to decrypt the remaining encrypted data.\n * AES-GCM also verifies the authenticity of the data during decryption.\n *\n * @param {CryptoKey} key - The AES-256 key for decryption.\n * @param {ArrayBuffer} combinedData - The combined IV + encrypted data.\n * @returns {Promise<string>} A promise that resolves to the decrypted plaintext string.\n * @throws {Error} Throws if decryption fails or authentication tag verification fails.\n * @internal\n */\nasync function aesDecrypt(key: CryptoKey, combinedData: ArrayBuffer): Promise<string> {\n const data = new Uint8Array(combinedData);\n const iv = data.slice(0, 12);\n const encrypted = data.slice(12);\n\n const decrypted = await crypto.subtle.decrypt(\n {\n name: 'AES-GCM',\n iv: iv\n },\n key,\n encrypted\n );\n\n const decoder = new TextDecoder();\n return decoder.decode(decrypted);\n}\n\n/**\n * Encrypts a secret using hybrid encryption (ECDH + AES-GCM).\n *\n * This function implements a hybrid encryption scheme that combines the security of\n * asymmetric encryption with the efficiency of symmetric encryption. It generates\n * an ephemeral key pair, derives a shared secret using ECDH, and then encrypts\n * the data using AES-GCM with the derived key.\n *\n * The encryption process:\n * 1. Generates an ephemeral ECDH key pair\n * 2. Derives a shared AES key using ECDH with the recipient's public key\n * 3. Encrypts the secret using AES-GCM\n * 4. Packages the ephemeral public key with the encrypted data\n *\n * @param {string} secret - The secret data to encrypt. Can be any string value.\n * @param {string} publicKeyString - The recipient's public key as a base64url-encoded string.\n *\n * @returns {Promise<string>} A promise that resolves to a base64url-encoded encrypted payload\n * containing both the ephemeral public key and encrypted data.\n *\n * @throws {Error} Throws if encryption fails or if the public key is invalid.\n *\n * @example\n * ```typescript\n * const recipientPublicKey = \"their-public-key-string\";\n * const secretMessage = \"This is a secret message\";\n *\n * const encrypted = await encryptWithPublicKey(secretMessage, recipientPublicKey);\n * // 'encrypted' can be safely transmitted to the recipient\n * // Only the holder of the corresponding private key can decrypt it\n * ```\n *\n * @see {@link decryptWithPrivateKey} for the corresponding decryption function\n */\nexport async function encryptWithPublicKey(\n secret: string,\n publicKeyString: string\n): Promise<string> {\n // Generate ephemeral keypair for this encryption\n const ephemeralKeyPair = await generateKeyPair();\n\n // Import recipient's public key\n const recipientPublicKey = await importPublicKey(publicKeyString);\n\n // Derive shared secret\n const sharedKey = await deriveSharedSecret(ephemeralKeyPair.privateKey, recipientPublicKey);\n\n // Encrypt the secret with AES-GCM\n const encryptedData = await aesEncrypt(sharedKey, secret);\n\n // Create the final payload: ephemeral public key + encrypted data\n const payload: EncryptedPayload = {\n ephemeralPublicKey: ephemeralKeyPair.publicKey,\n encryptedData: base64urlEncode(encryptedData)\n };\n\n return base64urlEncode(new TextEncoder().encode(JSON.stringify(payload)));\n}\n\n/**\n * Decrypts data that was encrypted using hybrid encryption (ECDH + AES-GCM).\n *\n * This function reverses the encryption performed by encryptWithPublicKey().\n * It extracts the ephemeral public key from the encrypted payload, derives\n * the same shared secret using ECDH, and then decrypts the data using AES-GCM.\n *\n * The decryption process:\n * 1. Extracts the ephemeral public key from the encrypted payload\n * 2. Derives the shared AES key using ECDH with the private key\n * 3. Decrypts the data using AES-GCM with the derived key\n *\n * @param {string} encryptedString - The base64url-encoded encrypted payload containing\n * the ephemeral public key and encrypted data.\n * @param {CryptoKey} privateKey - The recipient's private key for decryption.\n *\n * @returns {Promise<string>} A promise that resolves to the decrypted secret string.\n *\n * @throws {Error} Throws \"Failed to decrypt secret\" if decryption fails for any reason,\n * including invalid encrypted data, wrong private key, or corrupted payload.\n *\n * @example\n * ```typescript\n * const keyPair = await generateKeyPair();\n * // Receive encrypted message from sender\n * const encryptedMessage = \"encrypted-payload-from-sender\";\n *\n * try {\n * const decrypted = await decryptWithPrivateKey(encryptedMessage, keyPair.privateKey);\n * console.log(\"Decrypted message:\", decrypted);\n * } catch (error) {\n * console.error(\"Decryption failed:\", error);\n * }\n * ```\n *\n * @see {@link encryptWithPublicKey} for the corresponding encryption function\n */\nexport async function decryptWithPrivateKey(\n encryptedString: string,\n privateKey: CryptoKey\n): Promise<string> {\n try {\n // Decode the payload\n const payloadBytes = base64urlDecode(encryptedString);\n const payloadText = new TextDecoder().decode(payloadBytes);\n const payload: EncryptedPayload = JSON.parse(payloadText);\n\n // Import ephemeral public key\n const ephemeralPublicKey = await importPublicKey(payload.ephemeralPublicKey);\n\n // Derive shared secret\n const sharedKey = await deriveSharedSecret(privateKey, ephemeralPublicKey);\n\n // Decrypt the data\n const encryptedData = base64urlDecode(payload.encryptedData);\n return await aesDecrypt(sharedKey, encryptedData);\n } catch (error) {\n console.error('Decryption failed:', error);\n throw new Error('Failed to decrypt secret');\n }\n}\n","/**\n * @module client\n *\n * Zero-knowledge secret sharing client implementation.\n *\n * This module provides client-side functionality for securely transferring secrets\n * from an authentication domain to a client application without the auth domain\n * ever having access to the client's private key.\n *\n * The zero-knowledge protocol works as follows:\n * 1. Client generates an ephemeral key pair\n * 2. Client sends only the public key to the auth domain\n * 3. Auth domain encrypts the secret with the public key\n * 4. Client receives and decrypts the secret with its private key\n * 5. The private key never leaves the client\n *\n * @packageDocumentation\n */\n\nimport {\n decryptWithPrivateKey,\n deserializePrivateKey,\n generateKeyPair,\n serializePrivateKey\n} from './crypto.js';\n\n/** localStorage key for storing the decrypted secret */\nconst STORAGE_KEY_SECRET = 'zerokey_secret';\n\n/** localStorage key for storing pending authentication data */\nconst STORAGE_KEY_PENDING = 'zerokey_pending';\n\n/** Expiration time for pending keys (5 minutes in milliseconds) */\nconst PENDING_KEY_EXPIRY = 5 * 60 * 1000;\n\n// Type definitions\n/**\n * Parameters extracted from the URL fragment after returning from the auth domain.\n * @internal\n */\ninterface FragmentParams {\n /** The encrypted secret received from the auth domain */\n secret: string | null;\n /** The state parameter for CSRF protection verification */\n state: string | null;\n}\n\n/**\n * Data structure for storing pending key information during the authentication flow.\n * @internal\n */\ninterface PendingKeyData {\n /** The serialized private key awaiting the encrypted secret */\n privateKey: string;\n /** The state parameter for CSRF protection */\n state: string;\n /** Timestamp when the pending key was created (for expiration) */\n timestamp: number;\n}\n\n// Global window extension for zerokey event\n/**\n * Extends the window event map to include the zerokey:ready event.\n * This event is dispatched when the secret has been successfully decrypted and stored.\n */\ndeclare global {\n interface WindowEventMap {\n /** Event fired when the secret is successfully decrypted and ready for use */\n 'zerokey:ready': Event;\n }\n}\n\n/**\n * Checks if the current page load is a return from the authentication domain.\n * @internal\n * @returns True if the URL contains a secret parameter in the fragment\n */\nfunction isReturningFromAuth(): boolean {\n return window.location.hash?.includes('secret=');\n}\n\n/**\n * Parses the URL fragment to extract authentication parameters.\n * @internal\n * @returns An object containing the encrypted secret and state parameters\n */\nfunction parseFragment(): FragmentParams {\n const fragment = window.location.hash.substring(1);\n const params = new URLSearchParams(fragment);\n return {\n secret: params.get('secret'),\n state: params.get('state')\n };\n}\n\n/**\n * Removes the fragment from the URL to prevent the sensitive data from being\n * visible in the browser's address bar or history.\n * @internal\n */\nfunction clearFragment(): void {\n window.history.replaceState(null, '', window.location.pathname + window.location.search);\n}\n\n/**\n * Stores the private key and state in localStorage while waiting for the auth flow to complete.\n * The data includes a timestamp for automatic expiration after 5 minutes.\n * @internal\n * @param privateKey - The serialized private key to store\n * @param state - The CSRF protection state parameter\n */\nfunction storePendingKey(privateKey: string, state: string): void {\n const data: PendingKeyData = {\n privateKey,\n state,\n timestamp: Date.now()\n };\n localStorage.setItem(STORAGE_KEY_PENDING, JSON.stringify(data));\n}\n\n/**\n * Retrieves the pending key data from localStorage if it hasn't expired.\n * Automatically cleans up expired data.\n * @internal\n * @returns The pending key data if valid and not expired, null otherwise\n */\nfunction getPendingKey(): PendingKeyData | null {\n const data = localStorage.getItem(STORAGE_KEY_PENDING);\n if (!data) return null;\n\n try {\n const parsed: PendingKeyData = JSON.parse(data);\n const age = Date.now() - parsed.timestamp;\n\n if (age > PENDING_KEY_EXPIRY) {\n localStorage.removeItem(STORAGE_KEY_PENDING);\n return null;\n }\n\n return parsed;\n } catch (error) {\n localStorage.removeItem(STORAGE_KEY_PENDING);\n return null;\n }\n}\n\n/**\n * Removes the pending key data from localStorage.\n * @internal\n */\nfunction clearPendingKey(): void {\n localStorage.removeItem(STORAGE_KEY_PENDING);\n}\n\n/**\n * Initializes the zero-knowledge secret sharing client.\n *\n * This function handles the complete flow of securely transferring a secret from an auth domain\n * to the client application without the auth domain ever knowing the client's private key.\n *\n * The flow works as follows:\n * 1. On first call: Generates a key pair, stores the private key locally, and redirects to the auth domain\n * with the public key.\n * 2. On return from auth domain: Extracts the encrypted secret from the URL fragment, decrypts it\n * using the stored private key, and stores the decrypted secret in localStorage.\n *\n * @param authUrl - The URL of the authentication domain that will provide the encrypted secret.\n * This URL should handle the public key and return the encrypted secret.\n *\n * @throws {Error} If key generation fails during the initial flow\n *\n * @example\n * ```typescript\n * // Initialize the client on page load\n * await initSecretClient('https://auth.example.com/zerokey');\n *\n * // Listen for when the secret is ready\n * window.addEventListener('zerokey:ready', () => {\n * const secret = getSecret();\n * console.log('Secret is now available:', secret);\n * });\n * ```\n *\n * @fires zerokey:ready - Fired when the secret has been successfully decrypted and stored\n */\nexport async function initSecretClient(authUrl: string): Promise<void> {\n // Check if we're returning from auth\n if (isReturningFromAuth()) {\n const { secret: encryptedSecret, state } = parseFragment();\n clearFragment();\n\n if (!encryptedSecret) {\n console.error('No encrypted secret in URL fragment');\n return;\n }\n\n // Get pending key\n const pending = getPendingKey();\n if (!pending) {\n console.error('No pending key found or key expired');\n // Could restart the flow here\n return;\n }\n\n // Verify state matches\n if (pending.state !== state) {\n console.error('State mismatch - possible CSRF');\n clearPendingKey();\n return;\n }\n\n try {\n // Deserialize and decrypt\n const privateKey = await deserializePrivateKey(pending.privateKey);\n const decryptedSecret = await decryptWithPrivateKey(encryptedSecret, privateKey);\n\n // Store the decrypted secret\n localStorage.setItem(STORAGE_KEY_SECRET, decryptedSecret);\n\n // Clean up\n clearPendingKey();\n\n // Emit event for app to know secret is ready\n window.dispatchEvent(new Event('zerokey:ready'));\n } catch (error) {\n console.error('Failed to decrypt secret:', error);\n clearPendingKey();\n // Could restart the flow here\n }\n } else {\n // Starting new flow\n try {\n // Generate new keypair\n const { publicKey, privateKey } = await generateKeyPair();\n\n // Generate state for CSRF protection\n const state = crypto.randomUUID();\n\n // Serialize and store private key\n const serializedPrivateKey = await serializePrivateKey(privateKey);\n storePendingKey(serializedPrivateKey, state);\n\n // Build auth URL with parameters\n const url = new URL(authUrl);\n url.searchParams.set('publicKey', publicKey);\n url.searchParams.set('redirect', window.location.href);\n url.searchParams.set('state', state);\n\n // Redirect to auth domain\n window.location.href = url.toString();\n } catch (error) {\n console.error('Failed to initiate secret transfer:', error);\n throw error;\n }\n }\n}\n\n/**\n * Retrieves the decrypted secret from localStorage.\n *\n * This function should be called after the 'zerokey:ready' event has been fired,\n * indicating that the secret has been successfully decrypted and stored.\n *\n * @returns The decrypted secret string if available, or null if no secret has been stored yet\n * or if the authentication flow hasn't completed.\n *\n * @example\n * ```typescript\n * // Get the secret after initialization\n * const secret = getSecret();\n * if (secret) {\n * // Use the secret for API authentication\n * fetch('/api/data', {\n * headers: {\n * 'Authorization': `Bearer ${secret}`\n * }\n * });\n * } else {\n * console.log('Secret not yet available');\n * }\n * ```\n */\nexport function getSecret(): string | null {\n return localStorage.getItem(STORAGE_KEY_SECRET);\n}\n\n/**\n * Clears the stored secret and any pending authentication data from localStorage.\n *\n * This function should be called when:\n * - The user logs out\n * - The secret expires or becomes invalid\n * - You need to restart the authentication flow\n *\n * It removes both the decrypted secret and any pending key data that might be\n * waiting for the authentication flow to complete.\n *\n * @example\n * ```typescript\n * // Clear the secret on logout\n * function logout() {\n * clearSecret();\n * // Redirect to login page or reinitialize\n * window.location.href = '/login';\n * }\n *\n * // Or clear and restart the flow\n * clearSecret();\n * await initSecretClient('https://auth.example.com/zerokey');\n * ```\n */\nexport function clearSecret(): void {\n localStorage.removeItem(STORAGE_KEY_SECRET);\n clearPendingKey();\n}\n"]}