@korajs/auth 0.1.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,7 +1,148 @@
1
- export { A as AuthClient, a as AuthClientConfig, b as AuthError, c as AuthState, d as AuthUser } from './auth-client-CrDNuh10.cjs';
2
- import { A as AuthTokens } from './device-identity-DiwdLsUB.cjs';
3
- export { a as AuthConfig, b as AuthEvent, c as AuthEventType, d as AuthStatus, C as CryptoUnavailableError, D as DeviceIdentityError, T as TokenPayload, e as TokenType, f as computePublicKeyThumbprint, g as exportPublicKeyJwk, h as fromBase64Url, i as generateDeviceKeyPair, s as signChallenge, t as toBase64Url, v as verifyChallenge } from './device-identity-DiwdLsUB.cjs';
4
- import '@korajs/core';
1
+ export { A as AuthClient, a as AuthClientConfig, b as AuthError, c as AuthState, d as AuthUser, C as ClientInvitation, e as ClientMembership, f as ClientOrganization, O as OrgClient, g as OrgClientConfig, h as OrgClientError } from './org-client-BVTLKcIk.cjs';
2
+ import { A as AuthTokens } from './operation-encryptor-DRmKNWpF.cjs';
3
+ export { a as AuthConfig, b as AuthEvent, c as AuthEventType, d as AuthStatus, C as CryptoUnavailableError, D as DeviceIdentityError, O as OperationEncryptionError, e as OperationEncryptor, f as OperationEncryptorConfig, T as TokenPayload, g as TokenType, h as computePublicKeyThumbprint, i as exportPublicKeyJwk, j as fromBase64Url, k as generateDeviceKeyPair, l as isEncryptedField, s as signChallenge, t as toBase64Url, v as verifyChallenge } from './operation-encryptor-DRmKNWpF.cjs';
4
+ import { KoraError } from '@korajs/core';
5
+
6
+ /**
7
+ * Thrown when a device key store operation fails.
8
+ * Provides context about the operation and the underlying cause.
9
+ */
10
+ declare class DeviceKeyStoreError extends KoraError {
11
+ constructor(message: string, context?: Record<string, unknown>);
12
+ }
13
+ /**
14
+ * Persistent storage interface for ECDSA P-256 device key pairs.
15
+ *
16
+ * Device key pairs are used for proof-of-possession authentication:
17
+ * the private key never leaves the device, and the public key is
18
+ * registered with the server. This store persists key pairs across
19
+ * page reloads and app restarts.
20
+ *
21
+ * In the browser, CryptoKey objects are structured-cloneable, so they
22
+ * can be stored directly in IndexedDB without serialization. In Node.js
23
+ * or test environments, an in-memory implementation is used instead.
24
+ */
25
+ interface DeviceKeyStore {
26
+ /**
27
+ * Persist a key pair for the given device.
28
+ *
29
+ * Overwrites any previously stored key pair for the same device ID.
30
+ *
31
+ * @param deviceId - The unique device identifier (typically a UUID v7)
32
+ * @param keyPair - The ECDSA P-256 CryptoKeyPair to store
33
+ * @throws {DeviceKeyStoreError} If the storage operation fails
34
+ */
35
+ saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void>;
36
+ /**
37
+ * Load a previously stored key pair for the given device.
38
+ *
39
+ * @param deviceId - The unique device identifier
40
+ * @returns The stored CryptoKeyPair, or null if no key pair exists for the device
41
+ * @throws {DeviceKeyStoreError} If the storage operation fails
42
+ */
43
+ loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null>;
44
+ /**
45
+ * Delete a stored key pair for the given device.
46
+ *
47
+ * No-op if no key pair exists for the device ID.
48
+ *
49
+ * @param deviceId - The unique device identifier
50
+ * @throws {DeviceKeyStoreError} If the storage operation fails
51
+ */
52
+ deleteKeyPair(deviceId: string): Promise<void>;
53
+ /**
54
+ * Check whether a key pair exists for the given device.
55
+ *
56
+ * @param deviceId - The unique device identifier
57
+ * @returns True if a key pair is stored for the device, false otherwise
58
+ * @throws {DeviceKeyStoreError} If the storage operation fails
59
+ */
60
+ hasKeyPair(deviceId: string): Promise<boolean>;
61
+ }
62
+ /**
63
+ * Browser-based device key store backed by IndexedDB.
64
+ *
65
+ * CryptoKey objects are structured-cloneable, so they can be stored
66
+ * directly in IndexedDB without needing to export/import them as JWK.
67
+ * This preserves the non-extractable flag on private keys, ensuring
68
+ * they cannot be read even from storage.
69
+ *
70
+ * The database uses a single object store (`keypairs`) with the device ID
71
+ * as the key and the full CryptoKeyPair as the value.
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * const store = new IndexedDBDeviceKeyStore()
76
+ * const keyPair = await generateDeviceKeyPair()
77
+ * await store.saveKeyPair('device-123', keyPair)
78
+ *
79
+ * const loaded = await store.loadKeyPair('device-123')
80
+ * // loaded.privateKey is still non-extractable
81
+ * ```
82
+ */
83
+ declare class IndexedDBDeviceKeyStore implements DeviceKeyStore {
84
+ private dbPromise;
85
+ /**
86
+ * Opens (or creates) the IndexedDB database.
87
+ *
88
+ * The database connection is lazily initialized on first use and
89
+ * reused for subsequent operations. If the database does not exist,
90
+ * it is created with the `keypairs` object store.
91
+ */
92
+ private openDatabase;
93
+ /** @inheritdoc */
94
+ saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void>;
95
+ /** @inheritdoc */
96
+ loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null>;
97
+ /** @inheritdoc */
98
+ deleteKeyPair(deviceId: string): Promise<void>;
99
+ /** @inheritdoc */
100
+ hasKeyPair(deviceId: string): Promise<boolean>;
101
+ }
102
+ /**
103
+ * In-memory device key store for Node.js and testing environments.
104
+ *
105
+ * Key pairs are stored in a plain Map and do not survive process restarts.
106
+ * This is suitable for server-side rendering, Node.js scripts, and unit tests
107
+ * where IndexedDB is not available.
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * const store = new InMemoryDeviceKeyStore()
112
+ * const keyPair = await generateDeviceKeyPair()
113
+ * await store.saveKeyPair('device-123', keyPair)
114
+ *
115
+ * const loaded = await store.loadKeyPair('device-123')
116
+ * ```
117
+ */
118
+ declare class InMemoryDeviceKeyStore implements DeviceKeyStore {
119
+ private readonly store;
120
+ /** @inheritdoc */
121
+ saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void>;
122
+ /** @inheritdoc */
123
+ loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null>;
124
+ /** @inheritdoc */
125
+ deleteKeyPair(deviceId: string): Promise<void>;
126
+ /** @inheritdoc */
127
+ hasKeyPair(deviceId: string): Promise<boolean>;
128
+ }
129
+ /**
130
+ * Creates a DeviceKeyStore appropriate for the current environment.
131
+ *
132
+ * In browsers where IndexedDB is available, returns an {@link IndexedDBDeviceKeyStore}
133
+ * that persists CryptoKeyPair objects as structured clones. In Node.js, SSR,
134
+ * or environments without IndexedDB, returns an {@link InMemoryDeviceKeyStore}.
135
+ *
136
+ * @returns A DeviceKeyStore instance for the current environment
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * const store = createDeviceKeyStore()
141
+ * const keyPair = await generateDeviceKeyPair()
142
+ * await store.saveKeyPair(deviceId, keyPair)
143
+ * ```
144
+ */
145
+ declare function createDeviceKeyStore(): DeviceKeyStore;
5
146
 
6
147
  /**
7
148
  * Client-side token storage for Kora auth tokens.
@@ -77,4 +218,566 @@ declare class TokenStore {
77
218
  getRefreshToken(): string | null;
78
219
  }
79
220
 
80
- export { AuthTokens, TokenStore };
221
+ /**
222
+ * Thrown when encrypted token storage operations fail.
223
+ * Includes context about what went wrong to aid debugging without
224
+ * exposing sensitive key or token material.
225
+ */
226
+ declare class EncryptedTokenStoreError extends KoraError {
227
+ constructor(message: string, context?: Record<string, unknown>);
228
+ }
229
+ /**
230
+ * Configuration for the encrypted token store.
231
+ */
232
+ interface EncryptedTokenStoreConfig {
233
+ /**
234
+ * Storage key prefix. Defaults to 'kora_auth_encrypted'.
235
+ * Use different keys if your app runs multiple Kora instances with separate auth.
236
+ */
237
+ storageKey?: string;
238
+ /**
239
+ * The AES-256-GCM CryptoKey used to encrypt and decrypt tokens.
240
+ *
241
+ * This can be:
242
+ * - A key derived from a user passphrase via {@link deriveEncryptionKey}
243
+ * - A device-derived key (e.g., from secure hardware or biometric unlock)
244
+ * - A randomly generated key from {@link generateEncryptionKey}
245
+ *
246
+ * The caller is responsible for obtaining the key before constructing the store.
247
+ */
248
+ key: CryptoKey;
249
+ }
250
+ /**
251
+ * Encrypted token store that protects auth tokens at rest.
252
+ *
253
+ * Addresses the security vulnerability of storing tokens in plaintext localStorage,
254
+ * which is accessible to any JavaScript running on the page (XSS attack surface).
255
+ * Tokens are encrypted with AES-256-GCM before being written to storage.
256
+ *
257
+ * The stored format is a JSON string with two base64url-encoded fields:
258
+ * - `iv`: the 12-byte initialization vector (unique per encryption)
259
+ * - `data`: the AES-256-GCM ciphertext of the JSON-serialized tokens
260
+ *
261
+ * AES-GCM provides both confidentiality (tokens are unreadable without the key)
262
+ * and integrity (tampered ciphertext is detected and rejected).
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * import { deriveEncryptionKey } from '@korajs/auth'
267
+ *
268
+ * // Derive a key from the user's passphrase
269
+ * const { key } = await deriveEncryptionKey('user-passphrase', storedSalt)
270
+ *
271
+ * const store = new EncryptedTokenStore({ key })
272
+ *
273
+ * // After login: encrypt and persist tokens
274
+ * await store.saveTokens({ accessToken: '...', refreshToken: '...' })
275
+ *
276
+ * // Before API calls: decrypt and retrieve
277
+ * const accessToken = await store.getAccessToken()
278
+ *
279
+ * // On logout: remove encrypted data
280
+ * store.clearTokens()
281
+ * ```
282
+ */
283
+ declare class EncryptedTokenStore {
284
+ private readonly storageKey;
285
+ private readonly key;
286
+ private readonly storage;
287
+ /**
288
+ * Creates a new EncryptedTokenStore instance.
289
+ *
290
+ * @param config - Configuration including the encryption key and optional storage key
291
+ */
292
+ constructor(config: EncryptedTokenStoreConfig);
293
+ /**
294
+ * Encrypt and save tokens to persistent storage.
295
+ *
296
+ * Serializes the tokens as JSON, encrypts with AES-256-GCM using a fresh
297
+ * random IV, then stores the result as a JSON object containing the
298
+ * base64url-encoded IV and ciphertext.
299
+ *
300
+ * Overwrites any previously stored tokens.
301
+ *
302
+ * @param tokens - The token set to encrypt and store
303
+ * @throws {EncryptedTokenStoreError} If encryption fails
304
+ */
305
+ saveTokens(tokens: AuthTokens): Promise<void>;
306
+ /**
307
+ * Load and decrypt tokens from storage.
308
+ *
309
+ * Reads the encrypted payload from localStorage, decodes the base64url IV
310
+ * and ciphertext, decrypts with AES-256-GCM, and parses the resulting JSON.
311
+ *
312
+ * Returns null (without throwing) if:
313
+ * - No tokens have been saved
314
+ * - The stored data is corrupted or not valid JSON
315
+ * - Decryption fails (wrong key, tampered ciphertext, or wrong IV)
316
+ * - The decrypted data does not contain valid token fields
317
+ *
318
+ * This fail-silent design prevents decryption errors from crashing the
319
+ * application. The caller should treat null as "no valid tokens available"
320
+ * and initiate a re-authentication flow.
321
+ *
322
+ * @returns The decrypted {@link AuthTokens}, or null if unavailable or decryption fails
323
+ */
324
+ loadTokens(): Promise<AuthTokens | null>;
325
+ /**
326
+ * Clear all stored encrypted tokens.
327
+ *
328
+ * Removes the encrypted payload from storage. This is a synchronous
329
+ * operation since it only removes the localStorage entry.
330
+ *
331
+ * Call this on logout to ensure no encrypted credential material
332
+ * remains in persistent storage.
333
+ */
334
+ clearTokens(): void;
335
+ /**
336
+ * Get the current access token by decrypting stored tokens.
337
+ *
338
+ * Returns the raw token string without validating expiration.
339
+ * The caller is responsible for checking whether the token is
340
+ * still valid and initiating a refresh if needed.
341
+ *
342
+ * @returns The decrypted access token string, or null if no valid tokens are stored
343
+ */
344
+ getAccessToken(): Promise<string | null>;
345
+ /**
346
+ * Get the current refresh token by decrypting stored tokens.
347
+ *
348
+ * @returns The decrypted refresh token string, or null if no valid tokens are stored
349
+ */
350
+ getRefreshToken(): Promise<string | null>;
351
+ }
352
+
353
+ /**
354
+ * Thrown when a passkey operation fails (registration, authentication,
355
+ * or browser API interaction).
356
+ */
357
+ declare class PasskeyError extends KoraError {
358
+ constructor(message: string, context?: Record<string, unknown>);
359
+ }
360
+ /**
361
+ * Thrown when the browser does not support WebAuthn.
362
+ * Passkey authentication requires a modern browser with the
363
+ * Web Authentication API (navigator.credentials).
364
+ */
365
+ declare class PasskeyUnsupportedError extends KoraError {
366
+ constructor();
367
+ }
368
+ /** Response from a passkey registration (credential creation). */
369
+ interface PasskeyRegistrationResponse {
370
+ /** Base64url-encoded credential ID */
371
+ credentialId: string;
372
+ /** Base64url-encoded public key (COSE format) */
373
+ publicKey: string;
374
+ /** Base64url-encoded clientDataJSON */
375
+ clientDataJSON: string;
376
+ /** Base64url-encoded attestation object */
377
+ attestationObject: string;
378
+ }
379
+ /** Response from a passkey authentication (assertion). */
380
+ interface PasskeyAuthenticationResponse {
381
+ /** Base64url-encoded credential ID */
382
+ credentialId: string;
383
+ /** Base64url-encoded authenticator data */
384
+ authenticatorData: string;
385
+ /** Base64url-encoded clientDataJSON */
386
+ clientDataJSON: string;
387
+ /** Base64url-encoded ECDSA signature */
388
+ signature: string;
389
+ /** Base64url-encoded user handle (may be null for non-resident keys) */
390
+ userHandle: string | null;
391
+ }
392
+ /**
393
+ * Check if WebAuthn/passkeys are supported in the current environment.
394
+ *
395
+ * Returns true if the `navigator.credentials` API is available and supports
396
+ * the `create` and `get` methods required for WebAuthn.
397
+ *
398
+ * @returns `true` if WebAuthn is available, `false` otherwise
399
+ *
400
+ * @example
401
+ * ```typescript
402
+ * if (isPasskeySupported()) {
403
+ * // Show passkey login option
404
+ * }
405
+ * ```
406
+ */
407
+ declare function isPasskeySupported(): boolean;
408
+ /**
409
+ * Check if a platform authenticator (biometric) is available.
410
+ *
411
+ * A platform authenticator is built into the device (Touch ID, Face ID,
412
+ * Windows Hello). Returns false if only roaming authenticators (security
413
+ * keys) are available, or if WebAuthn is not supported.
414
+ *
415
+ * @returns `true` if a platform authenticator is available
416
+ *
417
+ * @example
418
+ * ```typescript
419
+ * if (await isPlatformAuthenticatorAvailable()) {
420
+ * // Show "Sign in with Touch ID" button
421
+ * }
422
+ * ```
423
+ */
424
+ declare function isPlatformAuthenticatorAvailable(): Promise<boolean>;
425
+ /**
426
+ * Create a passkey credential (registration flow).
427
+ *
428
+ * Called during user registration. The server provides a challenge and user info,
429
+ * the browser prompts for biometric verification, and this function returns the
430
+ * credential data to send back to the server for verification.
431
+ *
432
+ * @param options - Registration options from the server
433
+ * @param options.challenge - Base64url-encoded challenge from server
434
+ * @param options.rpId - Relying party ID (your domain, e.g. "example.com")
435
+ * @param options.rpName - Relying party display name (e.g. "My App")
436
+ * @param options.userId - User ID as base64url-encoded opaque bytes
437
+ * @param options.userName - User's email or username for display
438
+ * @param options.userDisplayName - Human-readable display name
439
+ * @param options.excludeCredentialIds - Credential IDs to exclude (prevents re-registration)
440
+ * @param options.authenticatorSelection - Authenticator selection criteria
441
+ * @returns The credential response to send to the server for verification
442
+ * @throws {PasskeyUnsupportedError} If WebAuthn is not available
443
+ * @throws {PasskeyError} If credential creation fails or is cancelled by the user
444
+ *
445
+ * @example
446
+ * ```typescript
447
+ * const credential = await createPasskeyCredential({
448
+ * challenge: serverOptions.challenge,
449
+ * rpId: 'example.com',
450
+ * rpName: 'My App',
451
+ * userId: serverOptions.userId,
452
+ * userName: 'alice@example.com',
453
+ * userDisplayName: 'Alice',
454
+ * })
455
+ * // Send `credential` to server for verification
456
+ * ```
457
+ */
458
+ declare function createPasskeyCredential(options: {
459
+ challenge: string;
460
+ rpId: string;
461
+ rpName: string;
462
+ userId: string;
463
+ userName: string;
464
+ userDisplayName: string;
465
+ excludeCredentialIds?: string[];
466
+ authenticatorSelection?: {
467
+ authenticatorAttachment?: 'platform' | 'cross-platform';
468
+ residentKey?: 'required' | 'preferred' | 'discouraged';
469
+ userVerification?: 'required' | 'preferred' | 'discouraged';
470
+ };
471
+ }): Promise<PasskeyRegistrationResponse>;
472
+ /**
473
+ * Authenticate with a passkey (assertion flow).
474
+ *
475
+ * Called during login. The server provides a challenge, the browser prompts
476
+ * for biometric verification, and this function returns the signed assertion
477
+ * to send back to the server for verification.
478
+ *
479
+ * @param options - Authentication options from the server
480
+ * @param options.challenge - Base64url-encoded challenge from server
481
+ * @param options.rpId - Relying party ID (your domain)
482
+ * @param options.allowCredentialIds - Limit authentication to specific credentials
483
+ * @param options.userVerification - User verification requirement (default: 'preferred')
484
+ * @param options.timeout - Timeout in milliseconds (default: 60000)
485
+ * @returns The assertion response to send to the server for verification
486
+ * @throws {PasskeyUnsupportedError} If WebAuthn is not available
487
+ * @throws {PasskeyError} If authentication fails or is cancelled by the user
488
+ *
489
+ * @example
490
+ * ```typescript
491
+ * const assertion = await authenticateWithPasskey({
492
+ * challenge: serverOptions.challenge,
493
+ * rpId: 'example.com',
494
+ * })
495
+ * // Send `assertion` to server for verification
496
+ * ```
497
+ */
498
+ declare function authenticateWithPasskey(options: {
499
+ challenge: string;
500
+ rpId: string;
501
+ allowCredentialIds?: string[];
502
+ userVerification?: 'required' | 'preferred' | 'discouraged';
503
+ timeout?: number;
504
+ }): Promise<PasskeyAuthenticationResponse>;
505
+
506
+ /**
507
+ * Thrown when an encryption or decryption operation fails.
508
+ * Includes context about what went wrong to aid debugging.
509
+ */
510
+ declare class EncryptionError extends KoraError {
511
+ constructor(message: string, context?: Record<string, unknown>);
512
+ }
513
+ /**
514
+ * Generates a random 256-bit AES-GCM encryption key.
515
+ *
516
+ * The key is extractable so it can be exported for persistence (e.g., encrypted
517
+ * with a passphrase-derived key and stored locally). Use {@link exportKey} to
518
+ * get the raw bytes.
519
+ *
520
+ * @returns A CryptoKey for AES-256-GCM encryption and decryption
521
+ * @throws {CryptoUnavailableError} If `crypto.subtle` is not available
522
+ * @throws {EncryptionError} If key generation fails
523
+ *
524
+ * @example
525
+ * ```typescript
526
+ * const key = await generateEncryptionKey()
527
+ * // key can be used with encryptData() and decryptData()
528
+ * ```
529
+ */
530
+ declare function generateEncryptionKey(): Promise<CryptoKey>;
531
+ /**
532
+ * Encrypts data using AES-256-GCM with a randomly generated IV.
533
+ *
534
+ * Each call generates a fresh 12-byte IV, ensuring that encrypting the same
535
+ * plaintext twice produces different ciphertext. The IV must be stored alongside
536
+ * the ciphertext for decryption.
537
+ *
538
+ * AES-GCM provides both confidentiality and integrity: the ciphertext includes
539
+ * an authentication tag that detects tampering.
540
+ *
541
+ * @param key - An AES-256-GCM CryptoKey (from {@link generateEncryptionKey} or {@link importKey})
542
+ * @param plaintext - The data to encrypt
543
+ * @returns An object containing the ciphertext and the IV used for encryption
544
+ * @throws {CryptoUnavailableError} If `crypto.subtle` is not available
545
+ * @throws {EncryptionError} If encryption fails
546
+ *
547
+ * @example
548
+ * ```typescript
549
+ * const key = await generateEncryptionKey()
550
+ * const data = new TextEncoder().encode('sensitive data')
551
+ * const { ciphertext, iv } = await encryptData(key, data)
552
+ * // Store ciphertext and iv together; both are needed for decryption
553
+ * ```
554
+ */
555
+ declare function encryptData(key: CryptoKey, plaintext: Uint8Array): Promise<{
556
+ ciphertext: Uint8Array;
557
+ iv: Uint8Array;
558
+ }>;
559
+ /**
560
+ * Decrypts AES-256-GCM encrypted data.
561
+ *
562
+ * The IV must be the same one that was used during encryption. AES-GCM
563
+ * authenticates the ciphertext, so any tampering will cause decryption to fail.
564
+ *
565
+ * @param key - The AES-256-GCM CryptoKey used for encryption
566
+ * @param ciphertext - The encrypted data (from {@link encryptData})
567
+ * @param iv - The initialization vector used during encryption (from {@link encryptData})
568
+ * @returns The decrypted plaintext
569
+ * @throws {CryptoUnavailableError} If `crypto.subtle` is not available
570
+ * @throws {EncryptionError} If decryption fails (wrong key, tampered ciphertext, or wrong IV)
571
+ *
572
+ * @example
573
+ * ```typescript
574
+ * const decrypted = await decryptData(key, ciphertext, iv)
575
+ * const text = new TextDecoder().decode(decrypted)
576
+ * ```
577
+ */
578
+ declare function decryptData(key: CryptoKey, ciphertext: Uint8Array, iv: Uint8Array): Promise<Uint8Array>;
579
+ /**
580
+ * Exports an AES-256-GCM CryptoKey to its raw byte representation.
581
+ *
582
+ * The raw key is 32 bytes (256 bits). This is useful for persisting the key
583
+ * (e.g., encrypting it with a passphrase-derived key before storing to disk).
584
+ *
585
+ * **Security warning:** Raw key bytes are sensitive material. Never log them,
586
+ * store them in plaintext, or transmit them over the network without encryption.
587
+ *
588
+ * @param key - An extractable AES-256-GCM CryptoKey
589
+ * @returns The raw key bytes (32 bytes for AES-256)
590
+ * @throws {CryptoUnavailableError} If `crypto.subtle` is not available
591
+ * @throws {EncryptionError} If the key export fails (e.g., key is not extractable)
592
+ *
593
+ * @example
594
+ * ```typescript
595
+ * const key = await generateEncryptionKey()
596
+ * const rawBytes = await exportKey(key)
597
+ * // rawBytes.length === 32
598
+ * ```
599
+ */
600
+ declare function exportKey(key: CryptoKey): Promise<Uint8Array>;
601
+ /**
602
+ * Imports raw key bytes into an AES-256-GCM CryptoKey.
603
+ *
604
+ * The input must be exactly 32 bytes (256 bits). The imported key is extractable
605
+ * and supports both encrypt and decrypt operations.
606
+ *
607
+ * @param rawKey - Raw key bytes (must be exactly 32 bytes for AES-256)
608
+ * @returns A CryptoKey for AES-256-GCM operations
609
+ * @throws {CryptoUnavailableError} If `crypto.subtle` is not available
610
+ * @throws {EncryptionError} If the raw key is invalid or import fails
611
+ *
612
+ * @example
613
+ * ```typescript
614
+ * const rawBytes = new Uint8Array(32) // previously exported key bytes
615
+ * const key = await importKey(rawBytes)
616
+ * // key can now be used with encryptData() and decryptData()
617
+ * ```
618
+ */
619
+ declare function importKey(rawKey: Uint8Array): Promise<CryptoKey>;
620
+
621
+ /**
622
+ * Thrown when a key derivation operation fails.
623
+ */
624
+ declare class KeyDerivationError extends KoraError {
625
+ constructor(message: string, context?: Record<string, unknown>);
626
+ }
627
+ /**
628
+ * Generates a cryptographically random 32-byte salt for key derivation.
629
+ *
630
+ * Each call returns a unique salt. The salt should be stored alongside the
631
+ * encrypted data so that the same passphrase can reproduce the same key later.
632
+ *
633
+ * @returns A random 32-byte Uint8Array
634
+ *
635
+ * @example
636
+ * ```typescript
637
+ * const salt = generateSalt()
638
+ * // salt.length === 32
639
+ * ```
640
+ */
641
+ declare function generateSalt(): Uint8Array;
642
+ /**
643
+ * Derives an AES-256-GCM encryption key from a passphrase using PBKDF2.
644
+ *
645
+ * Uses PBKDF2 with SHA-256 and 600,000 iterations (OWASP-recommended minimum)
646
+ * to derive a 256-bit key from the passphrase. The derived key can be used with
647
+ * the database encryption functions ({@link encryptData}, {@link decryptData}).
648
+ *
649
+ * If no salt is provided, a random 32-byte salt is generated. The salt must be
650
+ * persisted alongside the encrypted data so the key can be re-derived later.
651
+ *
652
+ * **Deterministic:** The same passphrase and salt always produce the same key.
653
+ * This is essential for decryption: the user enters their passphrase, the stored
654
+ * salt is used, and the identical key is re-derived to decrypt the data.
655
+ *
656
+ * @param passphrase - The user's passphrase (any non-empty string)
657
+ * @param salt - Optional salt bytes. If omitted, a random 32-byte salt is generated.
658
+ * @returns An object containing the derived CryptoKey and the salt used
659
+ * @throws {KeyDerivationError} If the passphrase is empty, crypto is unavailable, or derivation fails
660
+ *
661
+ * @example
662
+ * ```typescript
663
+ * // First time: derive key with a new salt
664
+ * const { key, salt } = await deriveEncryptionKey('my-secure-passphrase')
665
+ * // Store the salt alongside the encrypted data
666
+ *
667
+ * // Later: re-derive the same key using the stored salt
668
+ * const { key: sameKey } = await deriveEncryptionKey('my-secure-passphrase', salt)
669
+ * ```
670
+ */
671
+ declare function deriveEncryptionKey(passphrase: string, salt?: Uint8Array): Promise<{
672
+ key: CryptoKey;
673
+ salt: Uint8Array;
674
+ }>;
675
+
676
+ /**
677
+ * Configuration for the AutoLockManager.
678
+ */
679
+ interface AutoLockConfig {
680
+ /** Inactivity timeout in milliseconds before auto-locking. */
681
+ timeout: number;
682
+ /** Callback invoked when the lock engages (either by timeout or manual lock). */
683
+ onLock: () => void;
684
+ }
685
+ /**
686
+ * Manages inactivity-based auto-locking for the encrypted local store.
687
+ *
688
+ * The AutoLockManager starts an inactivity timer when {@link start} is called.
689
+ * If no user activity is reported via {@link reportActivity} within the configured
690
+ * timeout, the manager transitions to the locked state and invokes the `onLock`
691
+ * callback.
692
+ *
693
+ * This class has no DOM dependencies. It uses `setTimeout` and `clearTimeout` for
694
+ * timing and accepts an `onLock` callback for side effects. The consuming code is
695
+ * responsible for wiring DOM events (e.g., visibility changes, user interactions)
696
+ * to {@link reportActivity}.
697
+ *
698
+ * @example
699
+ * ```typescript
700
+ * const manager = new AutoLockManager({
701
+ * timeout: 15 * 60 * 1000, // 15 minutes
702
+ * onLock: () => {
703
+ * // Clear decrypted data from memory
704
+ * // Show lock screen
705
+ * }
706
+ * })
707
+ *
708
+ * manager.start()
709
+ *
710
+ * // Call on user interactions to reset the timer
711
+ * document.addEventListener('click', () => manager.reportActivity())
712
+ * document.addEventListener('keydown', () => manager.reportActivity())
713
+ *
714
+ * // Check lock state
715
+ * if (manager.isLocked) {
716
+ * // Prompt for passphrase
717
+ * }
718
+ * ```
719
+ */
720
+ declare class AutoLockManager {
721
+ private readonly _timeout;
722
+ private readonly _onLock;
723
+ private _timerId;
724
+ private _isLocked;
725
+ private _isRunning;
726
+ constructor(config: AutoLockConfig);
727
+ /**
728
+ * Whether the manager is currently in the locked state.
729
+ *
730
+ * Becomes `true` when the inactivity timeout fires or {@link lock} is called manually.
731
+ * Returns to `false` only when {@link unlock} is called.
732
+ */
733
+ get isLocked(): boolean;
734
+ /**
735
+ * Starts monitoring for inactivity.
736
+ *
737
+ * Begins the inactivity countdown. If the manager is already running, this is
738
+ * a no-op to prevent creating multiple timers. Calling `start()` also resets
739
+ * the locked state if the manager was previously locked.
740
+ */
741
+ start(): void;
742
+ /**
743
+ * Stops monitoring for inactivity.
744
+ *
745
+ * Clears the pending inactivity timer. Does not change the lock state: if
746
+ * the manager was locked, it remains locked. If unlocked, it remains unlocked.
747
+ * To resume monitoring, call {@link start} again.
748
+ */
749
+ stop(): void;
750
+ /**
751
+ * Reports user activity, resetting the inactivity timer.
752
+ *
753
+ * Call this whenever the user interacts with the application (clicks, key presses,
754
+ * touches, etc.). If the manager is not running or is already locked, this is a no-op.
755
+ */
756
+ reportActivity(): void;
757
+ /**
758
+ * Manually locks the manager immediately.
759
+ *
760
+ * Clears the inactivity timer and transitions to the locked state. The `onLock`
761
+ * callback is invoked. The manager remains running but locked: call {@link unlock}
762
+ * to return to the unlocked state, which will restart the inactivity timer.
763
+ */
764
+ lock(): void;
765
+ /**
766
+ * Unlocks the manager, returning to the unlocked state.
767
+ *
768
+ * If the manager is running, the inactivity timer is restarted. If the manager
769
+ * is not running (was stopped), it simply clears the locked state without
770
+ * starting a timer.
771
+ */
772
+ unlock(): void;
773
+ /**
774
+ * Starts the inactivity timer. When it fires, the manager locks.
775
+ */
776
+ private _startTimer;
777
+ /**
778
+ * Clears the pending inactivity timer, if any.
779
+ */
780
+ private _clearTimer;
781
+ }
782
+
783
+ export { AuthTokens, type AutoLockConfig, AutoLockManager, type DeviceKeyStore, DeviceKeyStoreError, EncryptedTokenStore, type EncryptedTokenStoreConfig, EncryptedTokenStoreError, EncryptionError, InMemoryDeviceKeyStore, IndexedDBDeviceKeyStore, KeyDerivationError, type PasskeyAuthenticationResponse, PasskeyError, type PasskeyRegistrationResponse, PasskeyUnsupportedError, TokenStore, authenticateWithPasskey, createDeviceKeyStore, createPasskeyCredential, decryptData, deriveEncryptionKey, encryptData, exportKey, generateEncryptionKey, generateSalt, importKey, isPasskeySupported, isPlatformAuthenticatorAvailable };