@korajs/auth 0.1.0 → 0.3.0
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/README.md +69 -30
- package/dist/chunk-FSU4SK32.js +786 -0
- package/dist/chunk-FSU4SK32.js.map +1 -0
- package/dist/chunk-HOZXDR6Y.js +52 -0
- package/dist/chunk-HOZXDR6Y.js.map +1 -0
- package/dist/index.cjs +1538 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +708 -5
- package/dist/index.d.ts +708 -5
- package/dist/index.js +926 -3
- package/dist/index.js.map +1 -1
- package/dist/{device-identity-DiwdLsUB.d.cts → operation-encryptor-DRmKNWpF.d.cts} +148 -2
- package/dist/{device-identity-DiwdLsUB.d.ts → operation-encryptor-DRmKNWpF.d.ts} +148 -2
- package/dist/{auth-client-CrDNuh10.d.cts → org-client-BVTLKcIk.d.cts} +177 -3
- package/dist/{auth-client-CrDNuh10.d.ts → org-client-BVTLKcIk.d.ts} +177 -3
- package/dist/password-hash-HDH6VQCQ.js +9 -0
- package/dist/password-hash-HDH6VQCQ.js.map +1 -0
- package/dist/react.cjs +183 -2
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +107 -2
- package/dist/react.d.ts +107 -2
- package/dist/react.js +186 -1
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +4945 -224
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +3039 -29
- package/dist/server.d.ts +3039 -29
- package/dist/server.js +4272 -92
- package/dist/server.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-L554ZDPY.js +0 -174
- package/dist/chunk-L554ZDPY.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,148 @@
|
|
|
1
|
-
export { A as AuthClient, a as AuthClientConfig, b as AuthError, c as AuthState, d as AuthUser } from './
|
|
2
|
-
import { A as AuthTokens } from './
|
|
3
|
-
export { a as AuthConfig, b as AuthEvent, c as AuthEventType, d as AuthStatus, C as CryptoUnavailableError, D as DeviceIdentityError, T as TokenPayload,
|
|
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.js';
|
|
2
|
+
import { A as AuthTokens } from './operation-encryptor-DRmKNWpF.js';
|
|
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.js';
|
|
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
|
-
|
|
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 };
|