@ursalock/client 0.2.2

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.
@@ -0,0 +1,583 @@
1
+ import { ZKCredential } from '@z-base/zero-knowledge-credentials';
2
+ export { ZKCredential } from '@z-base/zero-knowledge-credentials';
3
+
4
+ /**
5
+ * Common types for auth
6
+ */
7
+
8
+ /** User object returned by auth */
9
+ interface User {
10
+ id: string;
11
+ email?: string;
12
+ createdAt: number;
13
+ }
14
+ /** Current authentication state */
15
+ interface AuthState {
16
+ /** Whether user is authenticated */
17
+ isAuthenticated: boolean;
18
+ /** Current user (if authenticated) */
19
+ user: User | null;
20
+ /** Whether auth is still loading */
21
+ isLoading: boolean;
22
+ /** Auth error (if any) */
23
+ error: Error | null;
24
+ /** ZK Credential with derived keys (if authenticated with passkey) */
25
+ credential: ZKCredential | null;
26
+ }
27
+ /** Auth provider interface */
28
+ interface AuthProvider {
29
+ /** Sign up a new user */
30
+ signUp(options: SignUpOptions): Promise<ZKAuthResult$1>;
31
+ /** Sign in an existing user */
32
+ signIn(options: SignInOptions): Promise<ZKAuthResult$1>;
33
+ /** Sign out the current user */
34
+ signOut(): Promise<void>;
35
+ /** Get current auth state */
36
+ getState(): AuthState;
37
+ /** Subscribe to auth state changes */
38
+ subscribe(callback: (state: AuthState) => void): () => void;
39
+ }
40
+ /** Sign up options */
41
+ interface SignUpOptions {
42
+ email?: string;
43
+ password?: string;
44
+ /** Use passkey instead of email/password */
45
+ usePasskey?: boolean;
46
+ }
47
+ /** Sign in options */
48
+ interface SignInOptions {
49
+ email?: string;
50
+ password?: string;
51
+ /** Use passkey instead of email/password */
52
+ usePasskey?: boolean;
53
+ }
54
+ /** Result of auth operations (legacy, for email/password) */
55
+ interface AuthResult {
56
+ success: boolean;
57
+ user?: User;
58
+ token?: string;
59
+ error?: string;
60
+ }
61
+ /** Result of ZK auth operations (passkey with PRF) */
62
+ interface ZKAuthResult$1 {
63
+ success: boolean;
64
+ user?: User;
65
+ token?: string;
66
+ /** ZK Credential with derived encryption keys */
67
+ credential?: ZKCredential;
68
+ error?: string;
69
+ }
70
+
71
+ /**
72
+ * Auth provider interfaces
73
+ * Follows Open/Closed Principle - easy to add new auth methods
74
+ * Follows Dependency Inversion - VaultClient depends on abstractions
75
+ */
76
+
77
+ /**
78
+ * Extended auth result that includes optional ZK credential
79
+ * Used by providers that support zero-knowledge encryption (like passkey)
80
+ */
81
+ interface ZKAuthResult extends AuthResult {
82
+ /** ZK credential with encryption keys (optional) */
83
+ credential?: ZKCredential;
84
+ }
85
+ /**
86
+ * Base auth provider interface
87
+ * All auth methods must implement this interface
88
+ */
89
+ interface IAuthProvider {
90
+ /**
91
+ * Sign up a new user
92
+ * @param options Provider-specific signup options
93
+ * @returns Auth result with user and token
94
+ */
95
+ signUp(options: unknown): Promise<ZKAuthResult>;
96
+ /**
97
+ * Sign in an existing user
98
+ * @param options Provider-specific signin options
99
+ * @returns Auth result with user and token
100
+ */
101
+ signIn(options: unknown): Promise<ZKAuthResult>;
102
+ /**
103
+ * Check if this auth method is supported in the current environment
104
+ */
105
+ isSupported(): boolean;
106
+ /**
107
+ * Get the provider name/type
108
+ */
109
+ getName(): string;
110
+ }
111
+ /**
112
+ * Passkey-specific signup options
113
+ */
114
+ interface PasskeySignUpOptions {
115
+ displayName?: string;
116
+ }
117
+ /**
118
+ * Email-specific signup options
119
+ */
120
+ interface EmailSignUpOptions {
121
+ email: string;
122
+ password: string;
123
+ }
124
+ /**
125
+ * Email-specific signin options
126
+ */
127
+ interface EmailSignInOptions {
128
+ email: string;
129
+ password: string;
130
+ }
131
+
132
+ /**
133
+ * HTTP client interface for VaultClient
134
+ * Follows Dependency Inversion Principle
135
+ */
136
+ /**
137
+ * HTTP client interface
138
+ * Abstracts fetch API for testing and alternative implementations
139
+ */
140
+ interface IHttpClient {
141
+ /**
142
+ * Make an HTTP request
143
+ * @param url Full URL or path (will be prefixed with serverUrl if path)
144
+ * @param options Fetch options
145
+ * @returns Response
146
+ */
147
+ fetch(url: string, options?: RequestInit): Promise<Response>;
148
+ }
149
+ /**
150
+ * Default fetch-based HTTP client
151
+ */
152
+ declare class FetchHttpClient implements IHttpClient {
153
+ fetch(url: string, options?: RequestInit): Promise<Response>;
154
+ }
155
+
156
+ /**
157
+ * Passkey (WebAuthn PRF) Authentication
158
+ * Uses @z-base/zero-knowledge-credentials for PRF-based key derivation
159
+ *
160
+ * Refactored to follow SOLID principles:
161
+ * - Implements IAuthProvider interface (Dependency Inversion + Open/Closed)
162
+ * - Injectable HTTP client (Dependency Inversion)
163
+ */
164
+
165
+ interface PasskeyAuthOptions {
166
+ /** Server URL for WebAuthn endpoints */
167
+ serverUrl: string;
168
+ /** RP (Relying Party) name shown to user */
169
+ rpName?: string;
170
+ /** HTTP client for making requests (default: FetchHttpClient) */
171
+ httpClient?: IHttpClient;
172
+ }
173
+ /**
174
+ * Passkey authentication using WebAuthn PRF extension
175
+ * Derives encryption keys directly from the passkey - no recovery key needed
176
+ * Implements IAuthProvider for pluggable auth (Open/Closed Principle)
177
+ */
178
+ declare class PasskeyAuth implements IAuthProvider {
179
+ private options;
180
+ private httpClient;
181
+ constructor(options: PasskeyAuthOptions);
182
+ getName(): string;
183
+ /**
184
+ * Check if passkeys with PRF are supported in this browser
185
+ * Implements IAuthProvider.isSupported
186
+ */
187
+ isSupported(): boolean;
188
+ /**
189
+ * Check if passkeys with PRF are supported in this browser (static helper)
190
+ */
191
+ static isSupported(): boolean;
192
+ /**
193
+ * Sign up - Register a new passkey
194
+ * Creates a passkey with PRF extension and derives encryption keys
195
+ * Implements IAuthProvider.signUp
196
+ */
197
+ signUp(options: unknown): Promise<ZKAuthResult>;
198
+ /**
199
+ * Legacy method - kept for backward compatibility
200
+ * @deprecated Use signUp() instead
201
+ */
202
+ register(displayName?: string): Promise<ZKAuthResult>;
203
+ /**
204
+ * Sign in - Authenticate with an existing passkey
205
+ * Uses discoverCredential to authenticate and derive keys
206
+ * Implements IAuthProvider.signIn
207
+ */
208
+ signIn(_options: unknown): Promise<ZKAuthResult>;
209
+ /**
210
+ * Legacy method - kept for backward compatibility
211
+ * @deprecated Use signIn() instead
212
+ */
213
+ authenticate(): Promise<ZKAuthResult>;
214
+ /**
215
+ * Check if user has any registered passkeys
216
+ */
217
+ hasPasskey(opaqueId: string): Promise<boolean>;
218
+ }
219
+
220
+ /**
221
+ * Main Vault Client
222
+ * Combines auth (passkey + email) with API access
223
+ */
224
+
225
+ interface VaultClientOptions {
226
+ /** Server URL */
227
+ serverUrl: string;
228
+ /** RP name for passkeys (default: 'ursalock') */
229
+ rpName?: string;
230
+ /** Prefer passkey over email (default: true) */
231
+ preferPasskey?: boolean;
232
+ /** Storage key for auth (default: 'ursalock:auth') */
233
+ storageKey?: string;
234
+ }
235
+ /**
236
+ * Unified client for ursalock auth and API
237
+ */
238
+ declare class VaultClient {
239
+ private options;
240
+ private passkeyAuth;
241
+ private emailAuth;
242
+ private tokenManager;
243
+ private state;
244
+ private listeners;
245
+ constructor(options: VaultClientOptions);
246
+ /**
247
+ * Sign up a new user
248
+ */
249
+ signUp(options?: {
250
+ email?: string;
251
+ password?: string;
252
+ usePasskey?: boolean;
253
+ displayName?: string;
254
+ }): Promise<ZKAuthResult$1>;
255
+ /**
256
+ * Sign in an existing user
257
+ */
258
+ signIn(options?: {
259
+ email?: string;
260
+ password?: string;
261
+ usePasskey?: boolean;
262
+ }): Promise<ZKAuthResult$1>;
263
+ /**
264
+ * Sign out
265
+ */
266
+ signOut(): Promise<void>;
267
+ /**
268
+ * Check if passkeys are supported
269
+ */
270
+ supportsPasskey(): boolean;
271
+ /**
272
+ * Get current auth state
273
+ * Returns the same reference unless state changes (required for useSyncExternalStore)
274
+ */
275
+ getState(): AuthState;
276
+ /**
277
+ * Get current user
278
+ */
279
+ getUser(): User | null;
280
+ /**
281
+ * Get current ZK credential (with encryption keys)
282
+ */
283
+ getCredential(): ZKCredential | null;
284
+ /**
285
+ * Check if authenticated
286
+ */
287
+ isAuthenticated(): boolean;
288
+ /**
289
+ * Subscribe to auth state changes
290
+ */
291
+ subscribe(callback: (state: AuthState) => void): () => void;
292
+ /**
293
+ * Get authorization header
294
+ */
295
+ getAuthHeader(): Record<string, string>;
296
+ /**
297
+ * Make authenticated API request
298
+ */
299
+ fetch(path: string, options?: RequestInit): Promise<Response>;
300
+ private initialize;
301
+ private handleAuthSuccess;
302
+ private handleTokenExpire;
303
+ private updateState;
304
+ private saveUserToStorage;
305
+ private loadUserFromStorage;
306
+ private clearUserFromStorage;
307
+ }
308
+
309
+ /**
310
+ * Email/Password Authentication (fallback)
311
+ * Note: Email auth doesn't provide PRF-derived encryption keys.
312
+ * For E2EE, use passkey authentication instead.
313
+ *
314
+ * Refactored to follow SOLID principles:
315
+ * - Implements IAuthProvider interface (Open/Closed + Dependency Inversion)
316
+ * - Injectable HTTP client (Dependency Inversion)
317
+ */
318
+
319
+ interface EmailCredentials {
320
+ email: string;
321
+ password: string;
322
+ }
323
+ interface EmailAuthOptions {
324
+ /** Server URL for auth endpoints */
325
+ serverUrl: string;
326
+ /** HTTP client for making requests (default: FetchHttpClient) */
327
+ httpClient?: IHttpClient;
328
+ }
329
+ /**
330
+ * Email/password authentication (fallback for non-passkey browsers)
331
+ * Note: Email auth doesn't derive encryption keys - use passkeys for E2EE
332
+ * Implements IAuthProvider for pluggable auth (Open/Closed Principle)
333
+ */
334
+ declare class EmailAuth implements IAuthProvider {
335
+ private options;
336
+ private httpClient;
337
+ constructor(options: EmailAuthOptions);
338
+ getName(): string;
339
+ isSupported(): boolean;
340
+ /**
341
+ * Sign up - Register a new account with email/password
342
+ * Implements IAuthProvider.signUp
343
+ */
344
+ signUp(options: unknown): Promise<ZKAuthResult>;
345
+ /**
346
+ * Sign in - Authenticate with email/password
347
+ * Implements IAuthProvider.signIn
348
+ */
349
+ signIn(options: unknown): Promise<ZKAuthResult>;
350
+ /**
351
+ * Legacy method - kept for backward compatibility
352
+ * @deprecated Use signUp() instead
353
+ */
354
+ register(credentials: EmailCredentials): Promise<ZKAuthResult>;
355
+ /**
356
+ * Request password reset email
357
+ */
358
+ forgotPassword(email: string): Promise<{
359
+ success: boolean;
360
+ error?: string;
361
+ }>;
362
+ /**
363
+ * Reset password with token
364
+ */
365
+ resetPassword(token: string, newPassword: string): Promise<{
366
+ success: boolean;
367
+ error?: string;
368
+ }>;
369
+ /**
370
+ * Change password (when logged in)
371
+ */
372
+ changePassword(currentPassword: string, newPassword: string, authToken: string): Promise<{
373
+ success: boolean;
374
+ error?: string;
375
+ }>;
376
+ /**
377
+ * Validate email format
378
+ */
379
+ private isValidEmail;
380
+ }
381
+
382
+ /**
383
+ * JWT Token Manager
384
+ * Handles token storage, refresh, and expiry
385
+ */
386
+ interface Token {
387
+ /** JWT access token */
388
+ accessToken: string;
389
+ /** Token expiry timestamp (ms) */
390
+ expiresAt: number;
391
+ /** Refresh token (if available) */
392
+ refreshToken?: string;
393
+ }
394
+ interface TokenManagerOptions {
395
+ /** Storage key for token */
396
+ storageKey?: string;
397
+ /** Server URL for token refresh */
398
+ serverUrl?: string;
399
+ /** Callback when token expires (and refresh fails) */
400
+ onExpire?: () => void;
401
+ /** Refresh token before expiry (ms before expiry, default: 5min) */
402
+ refreshBuffer?: number;
403
+ /** Enable auto-refresh (default: true if serverUrl provided) */
404
+ autoRefresh?: boolean;
405
+ }
406
+ /**
407
+ * Manages JWT tokens with automatic refresh
408
+ */
409
+ declare class TokenManager {
410
+ private token;
411
+ private refreshTimer;
412
+ private options;
413
+ private listeners;
414
+ private isRefreshing;
415
+ constructor(options?: TokenManagerOptions);
416
+ /**
417
+ * Set a new token
418
+ */
419
+ setToken(token: Token): void;
420
+ /**
421
+ * Get current token
422
+ */
423
+ getToken(): Token | null;
424
+ /**
425
+ * Get access token string (convenience method)
426
+ */
427
+ getAccessToken(): string | null;
428
+ /**
429
+ * Check if token is valid
430
+ */
431
+ isValid(): boolean;
432
+ /**
433
+ * Clear token
434
+ */
435
+ clearToken(): void;
436
+ /**
437
+ * Subscribe to token changes
438
+ */
439
+ subscribe(callback: (token: Token | null) => void): () => void;
440
+ /**
441
+ * Manually refresh the token
442
+ * Returns true if refresh succeeded
443
+ */
444
+ refresh(): Promise<boolean>;
445
+ /**
446
+ * Parse JWT payload (without verification)
447
+ */
448
+ static parseToken(token: string): Record<string, unknown> | null;
449
+ private loadFromStorage;
450
+ private saveToStorage;
451
+ private removeFromStorage;
452
+ private scheduleRefresh;
453
+ private clearRefreshTimer;
454
+ private notifyListeners;
455
+ }
456
+
457
+ /**
458
+ * Hook to subscribe to auth state from VaultClient
459
+ *
460
+ * @example
461
+ * ```tsx
462
+ * const client = new VaultClient({ serverUrl: '...' })
463
+ *
464
+ * function App() {
465
+ * const { isAuthenticated, user, isLoading, credential } = useAuth(client)
466
+ *
467
+ * if (isLoading) return <Loading />
468
+ * if (!isAuthenticated) return <Login />
469
+ * return <Dashboard user={user} credential={credential} />
470
+ * }
471
+ * ```
472
+ */
473
+ declare function useAuth(client: VaultClient): AuthState;
474
+ /**
475
+ * Hook for sign up action
476
+ * Returns ZKAuthResult with credential containing encryption keys
477
+ *
478
+ * @example
479
+ * ```tsx
480
+ * const { signUp, isLoading, error } = useSignUp(client)
481
+ *
482
+ * const handleSubmit = async () => {
483
+ * const result = await signUp({ usePasskey: true })
484
+ * if (result.success && result.credential) {
485
+ * // Use credential.cipherJwk for encryption
486
+ * initializeVault(result.credential.cipherJwk)
487
+ * }
488
+ * }
489
+ * ```
490
+ */
491
+ declare function useSignUp(client: VaultClient): {
492
+ signUp: (options?: {
493
+ email?: string;
494
+ password?: string;
495
+ usePasskey?: boolean;
496
+ displayName?: string;
497
+ }) => Promise<ZKAuthResult$1>;
498
+ isLoading: boolean;
499
+ error: Error | null;
500
+ };
501
+ /**
502
+ * Hook for sign in action
503
+ * Returns ZKAuthResult with credential containing encryption keys
504
+ *
505
+ * @example
506
+ * ```tsx
507
+ * const { signIn, isLoading, error } = useSignIn(client)
508
+ *
509
+ * const handleLogin = async () => {
510
+ * const result = await signIn({ usePasskey: true })
511
+ * if (result.success && result.credential) {
512
+ * // Use credential.cipherJwk for encryption
513
+ * initializeVault(result.credential.cipherJwk)
514
+ * }
515
+ * }
516
+ * ```
517
+ */
518
+ declare function useSignIn(client: VaultClient): {
519
+ signIn: (options?: {
520
+ email?: string;
521
+ password?: string;
522
+ usePasskey?: boolean;
523
+ }) => Promise<ZKAuthResult$1>;
524
+ isLoading: boolean;
525
+ error: Error | null;
526
+ };
527
+ /**
528
+ * Hook for sign out action
529
+ *
530
+ * @example
531
+ * ```tsx
532
+ * const signOut = useSignOut(client)
533
+ *
534
+ * <button onClick={signOut}>Sign Out</button>
535
+ * ```
536
+ */
537
+ declare function useSignOut(client: VaultClient): () => Promise<void>;
538
+ /**
539
+ * Hook for current user
540
+ * Returns null if not authenticated
541
+ *
542
+ * @example
543
+ * ```tsx
544
+ * const user = useUser(client)
545
+ *
546
+ * if (user) {
547
+ * return <span>Hello, {user.email}</span>
548
+ * }
549
+ * ```
550
+ */
551
+ declare function useUser(client: VaultClient): User | null;
552
+ /**
553
+ * Hook for current ZK credential (encryption keys)
554
+ * Returns null if not authenticated or no credential available
555
+ *
556
+ * @example
557
+ * ```tsx
558
+ * const credential = useCredential(client)
559
+ *
560
+ * if (credential) {
561
+ * // credential.cipherJwk - AES-GCM key for encryption
562
+ * // credential.hmacJwk - HMAC key for signing
563
+ * }
564
+ * ```
565
+ */
566
+ declare function useCredential(client: VaultClient): ZKCredential | null;
567
+ /**
568
+ * Hook to check passkey support
569
+ *
570
+ * @example
571
+ * ```tsx
572
+ * const supportsPasskey = usePasskeySupport(client)
573
+ *
574
+ * {supportsPasskey ? (
575
+ * <PasskeyButton />
576
+ * ) : (
577
+ * <EmailPasswordForm />
578
+ * )}
579
+ * ```
580
+ */
581
+ declare function usePasskeySupport(client: VaultClient): boolean;
582
+
583
+ export { type AuthProvider, type AuthResult, type AuthState, EmailAuth, type EmailAuthOptions, type EmailCredentials, type EmailSignInOptions, type EmailSignUpOptions, FetchHttpClient, type IAuthProvider, type IHttpClient, PasskeyAuth, type PasskeyAuthOptions, type PasskeySignUpOptions, type Token, TokenManager, type TokenManagerOptions, type User, VaultClient, type VaultClientOptions, type ZKAuthResult, useAuth, useCredential, usePasskeySupport, useSignIn, useSignOut, useSignUp, useUser };