@ovixa/auth-client 0.1.3 → 0.2.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/CHANGELOG.md +13 -0
- package/README.md +104 -0
- package/dist/{chunk-CUACHOKT.js → chunk-5ZWKDQQM.js} +366 -2
- package/dist/chunk-5ZWKDQQM.js.map +1 -0
- package/dist/{chunk-DIZJA4PY.js → chunk-WON3EB4B.js} +2 -2
- package/dist/index.d.ts +370 -1
- package/dist/index.js +15 -3
- package/dist/middleware/astro.js +2 -2
- package/dist/middleware/express.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-CUACHOKT.js.map +0 -1
- /package/dist/{chunk-DIZJA4PY.js.map → chunk-WON3EB4B.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2026-01-29
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **WebAuthn/Passkey authentication support** - Passwordless authentication using FIDO2
|
|
13
|
+
- `auth.webauthn.getRegistrationOptions(realmId)` - Get options to register a new passkey
|
|
14
|
+
- `auth.webauthn.verifyRegistration(realmId, registration)` - Complete passkey registration
|
|
15
|
+
- `auth.webauthn.getAuthenticationOptions(realmId, email?)` - Get options to authenticate with passkey
|
|
16
|
+
- `auth.webauthn.authenticate(realmId, authentication)` - Authenticate and receive tokens
|
|
17
|
+
- `auth.webauthn.getCredentials(realmId)` - List user's registered passkeys
|
|
18
|
+
- `auth.webauthn.deleteCredential(realmId, credentialId)` - Remove a passkey
|
|
19
|
+
- `auth.webauthn.updateCredentialName(realmId, credentialId, name)` - Rename a passkey
|
|
20
|
+
|
|
8
21
|
## [0.1.3] - 2026-01-29
|
|
9
22
|
|
|
10
23
|
### Added
|
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ Client SDK for the Ovixa Auth service. Provides authentication, token verificati
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- Email/password authentication (signup, login, password reset)
|
|
8
|
+
- **Passkey (WebAuthn) authentication** - phishing-resistant passwordless sign-in
|
|
8
9
|
- OAuth integration (Google, GitHub)
|
|
9
10
|
- JWT verification with JWKS caching
|
|
10
11
|
- Automatic token refresh
|
|
@@ -243,6 +244,109 @@ try {
|
|
|
243
244
|
}
|
|
244
245
|
```
|
|
245
246
|
|
|
247
|
+
### Passkeys (WebAuthn)
|
|
248
|
+
|
|
249
|
+
Passkeys provide phishing-resistant, passwordless authentication using FIDO2/WebAuthn.
|
|
250
|
+
|
|
251
|
+
#### `webauthn.getRegistrationOptions(options)`
|
|
252
|
+
|
|
253
|
+
Get options for registering a new passkey. **Requires authentication.**
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const options = await auth.webauthn.getRegistrationOptions({
|
|
257
|
+
accessToken: 'user-access-token',
|
|
258
|
+
});
|
|
259
|
+
// Returns PublicKeyCredentialCreationOptions for navigator.credentials.create()
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### `webauthn.verifyRegistration(options)`
|
|
263
|
+
|
|
264
|
+
Verify and store a new passkey registration. **Requires authentication.**
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const result = await auth.webauthn.verifyRegistration({
|
|
268
|
+
accessToken: 'user-access-token',
|
|
269
|
+
registration: credentialResponse, // From navigator.credentials.create()
|
|
270
|
+
deviceName: 'My MacBook', // Optional friendly name
|
|
271
|
+
});
|
|
272
|
+
// Returns: { success: true, credential_id: string, device_name?: string }
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
#### `webauthn.getAuthenticationOptions(options)`
|
|
276
|
+
|
|
277
|
+
Get options for signing in with a passkey. Does not require authentication.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const options = await auth.webauthn.getAuthenticationOptions({
|
|
281
|
+
email: 'user@example.com', // Optional - provides allowCredentials hint
|
|
282
|
+
});
|
|
283
|
+
// Returns PublicKeyCredentialRequestOptions for navigator.credentials.get()
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
#### `webauthn.authenticate(options)`
|
|
287
|
+
|
|
288
|
+
Verify passkey authentication and receive tokens.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
const tokens = await auth.webauthn.authenticate({
|
|
292
|
+
authentication: credentialResponse, // From navigator.credentials.get()
|
|
293
|
+
});
|
|
294
|
+
// Returns: { access_token, refresh_token, token_type, expires_in }
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### Complete Passkey Flow Example
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import { OvixaAuth } from '@ovixa/auth-client';
|
|
301
|
+
|
|
302
|
+
const auth = new OvixaAuth({
|
|
303
|
+
authUrl: 'https://auth.ovixa.io',
|
|
304
|
+
realmId: 'your-realm-id',
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Register a passkey (user must be logged in)
|
|
308
|
+
async function registerPasskey(accessToken: string) {
|
|
309
|
+
// 1. Get registration options from server
|
|
310
|
+
const options = await auth.webauthn.getRegistrationOptions({ accessToken });
|
|
311
|
+
|
|
312
|
+
// 2. Create credential using browser API
|
|
313
|
+
const credential = await navigator.credentials.create({
|
|
314
|
+
publicKey: options,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
if (!credential) throw new Error('Registration cancelled');
|
|
318
|
+
|
|
319
|
+
// 3. Verify with server
|
|
320
|
+
const result = await auth.webauthn.verifyRegistration({
|
|
321
|
+
accessToken,
|
|
322
|
+
registration: credential as PublicKeyCredential,
|
|
323
|
+
deviceName: 'My Device',
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Sign in with passkey
|
|
330
|
+
async function signInWithPasskey(email?: string) {
|
|
331
|
+
// 1. Get authentication options
|
|
332
|
+
const options = await auth.webauthn.getAuthenticationOptions({ email });
|
|
333
|
+
|
|
334
|
+
// 2. Get credential using browser API
|
|
335
|
+
const credential = await navigator.credentials.get({
|
|
336
|
+
publicKey: options,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
if (!credential) throw new Error('Authentication cancelled');
|
|
340
|
+
|
|
341
|
+
// 3. Verify and get tokens
|
|
342
|
+
const tokens = await auth.webauthn.authenticate({
|
|
343
|
+
authentication: credential as PublicKeyCredential,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
return tokens;
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
246
350
|
### OAuth
|
|
247
351
|
|
|
248
352
|
#### `getOAuthUrl(options)`
|
|
@@ -442,6 +442,284 @@ var OvixaAuth = class {
|
|
|
442
442
|
url.searchParams.set("redirect_uri", options.redirectUri);
|
|
443
443
|
return url.toString();
|
|
444
444
|
}
|
|
445
|
+
// ============================================================
|
|
446
|
+
// WebAuthn / Passkey Methods
|
|
447
|
+
// ============================================================
|
|
448
|
+
/**
|
|
449
|
+
* Get registration options for creating a new passkey.
|
|
450
|
+
*
|
|
451
|
+
* The user must be logged in to register a passkey. Use the returned options
|
|
452
|
+
* with navigator.credentials.create() to create the credential.
|
|
453
|
+
*
|
|
454
|
+
* @param options - Registration options with access token
|
|
455
|
+
* @returns Registration options to pass to navigator.credentials.create()
|
|
456
|
+
* @throws {OvixaAuthError} If request fails
|
|
457
|
+
*
|
|
458
|
+
* @example
|
|
459
|
+
* ```typescript
|
|
460
|
+
* // User must be logged in
|
|
461
|
+
* const options = await auth.getPasskeyRegistrationOptions({
|
|
462
|
+
* accessToken: session.accessToken,
|
|
463
|
+
* });
|
|
464
|
+
*
|
|
465
|
+
* // Create the credential using the browser API
|
|
466
|
+
* const credential = await navigator.credentials.create({
|
|
467
|
+
* publicKey: {
|
|
468
|
+
* ...options,
|
|
469
|
+
* challenge: base64UrlToArrayBuffer(options.challenge),
|
|
470
|
+
* user: {
|
|
471
|
+
* ...options.user,
|
|
472
|
+
* id: new TextEncoder().encode(options.user.id),
|
|
473
|
+
* },
|
|
474
|
+
* excludeCredentials: options.excludeCredentials.map(c => ({
|
|
475
|
+
* ...c,
|
|
476
|
+
* id: base64UrlToArrayBuffer(c.id),
|
|
477
|
+
* })),
|
|
478
|
+
* },
|
|
479
|
+
* });
|
|
480
|
+
*
|
|
481
|
+
* // Verify the registration with the server
|
|
482
|
+
* await auth.verifyPasskeyRegistration({
|
|
483
|
+
* accessToken: session.accessToken,
|
|
484
|
+
* registration: formatRegistrationResponse(credential),
|
|
485
|
+
* deviceName: 'My MacBook',
|
|
486
|
+
* });
|
|
487
|
+
* ```
|
|
488
|
+
*/
|
|
489
|
+
async getPasskeyRegistrationOptions(options) {
|
|
490
|
+
const url = `${this.config.authUrl}/webauthn/register/options`;
|
|
491
|
+
try {
|
|
492
|
+
const response = await fetch(url, {
|
|
493
|
+
method: "POST",
|
|
494
|
+
headers: {
|
|
495
|
+
"Content-Type": "application/json",
|
|
496
|
+
Authorization: `Bearer ${options.accessToken}`
|
|
497
|
+
},
|
|
498
|
+
body: JSON.stringify({
|
|
499
|
+
realm_id: this.config.realmId
|
|
500
|
+
})
|
|
501
|
+
});
|
|
502
|
+
if (!response.ok) {
|
|
503
|
+
await this.handleErrorResponse(response);
|
|
504
|
+
}
|
|
505
|
+
return await response.json();
|
|
506
|
+
} catch (error) {
|
|
507
|
+
if (error instanceof OvixaAuthError) {
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
510
|
+
if (error instanceof Error) {
|
|
511
|
+
throw new OvixaAuthError(`Network error: ${error.message}`, "NETWORK_ERROR");
|
|
512
|
+
}
|
|
513
|
+
throw new OvixaAuthError("Request failed", "REQUEST_FAILED");
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Verify a passkey registration and store the credential.
|
|
518
|
+
*
|
|
519
|
+
* Call this after navigator.credentials.create() succeeds to complete the
|
|
520
|
+
* registration on the server.
|
|
521
|
+
*
|
|
522
|
+
* @param options - Verification options with registration response
|
|
523
|
+
* @returns Registration response with credential ID
|
|
524
|
+
* @throws {OvixaAuthError} If verification fails
|
|
525
|
+
*
|
|
526
|
+
* @example
|
|
527
|
+
* ```typescript
|
|
528
|
+
* const result = await auth.verifyPasskeyRegistration({
|
|
529
|
+
* accessToken: session.accessToken,
|
|
530
|
+
* registration: {
|
|
531
|
+
* id: credential.id,
|
|
532
|
+
* rawId: arrayBufferToBase64Url(credential.rawId),
|
|
533
|
+
* type: 'public-key',
|
|
534
|
+
* response: {
|
|
535
|
+
* clientDataJSON: arrayBufferToBase64Url(credential.response.clientDataJSON),
|
|
536
|
+
* attestationObject: arrayBufferToBase64Url(credential.response.attestationObject),
|
|
537
|
+
* transports: credential.response.getTransports?.(),
|
|
538
|
+
* },
|
|
539
|
+
* },
|
|
540
|
+
* deviceName: 'My MacBook',
|
|
541
|
+
* });
|
|
542
|
+
*
|
|
543
|
+
* console.log('Passkey registered:', result.credential_id);
|
|
544
|
+
* ```
|
|
545
|
+
*/
|
|
546
|
+
async verifyPasskeyRegistration(options) {
|
|
547
|
+
const url = `${this.config.authUrl}/webauthn/register/verify`;
|
|
548
|
+
const body = {
|
|
549
|
+
realm_id: this.config.realmId,
|
|
550
|
+
registration: options.registration
|
|
551
|
+
};
|
|
552
|
+
if (options.deviceName) {
|
|
553
|
+
body.device_name = options.deviceName;
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
const response = await fetch(url, {
|
|
557
|
+
method: "POST",
|
|
558
|
+
headers: {
|
|
559
|
+
"Content-Type": "application/json",
|
|
560
|
+
Authorization: `Bearer ${options.accessToken}`
|
|
561
|
+
},
|
|
562
|
+
body: JSON.stringify(body)
|
|
563
|
+
});
|
|
564
|
+
if (!response.ok) {
|
|
565
|
+
await this.handleErrorResponse(response);
|
|
566
|
+
}
|
|
567
|
+
return await response.json();
|
|
568
|
+
} catch (error) {
|
|
569
|
+
if (error instanceof OvixaAuthError) {
|
|
570
|
+
throw error;
|
|
571
|
+
}
|
|
572
|
+
if (error instanceof Error) {
|
|
573
|
+
throw new OvixaAuthError(`Network error: ${error.message}`, "NETWORK_ERROR");
|
|
574
|
+
}
|
|
575
|
+
throw new OvixaAuthError("Request failed", "REQUEST_FAILED");
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Get authentication options for signing in with a passkey.
|
|
580
|
+
*
|
|
581
|
+
* Use the returned options with navigator.credentials.get() to authenticate.
|
|
582
|
+
*
|
|
583
|
+
* @param options - Optional email for allowCredentials hint
|
|
584
|
+
* @returns Authentication options to pass to navigator.credentials.get()
|
|
585
|
+
* @throws {OvixaAuthError} If request fails
|
|
586
|
+
*
|
|
587
|
+
* @example
|
|
588
|
+
* ```typescript
|
|
589
|
+
* // Option 1: Discoverable credentials (no email needed)
|
|
590
|
+
* const options = await auth.getPasskeyAuthenticationOptions();
|
|
591
|
+
*
|
|
592
|
+
* // Option 2: Provide email for allowCredentials hint
|
|
593
|
+
* const options = await auth.getPasskeyAuthenticationOptions({
|
|
594
|
+
* email: 'user@example.com',
|
|
595
|
+
* });
|
|
596
|
+
*
|
|
597
|
+
* // Authenticate using the browser API
|
|
598
|
+
* const credential = await navigator.credentials.get({
|
|
599
|
+
* publicKey: {
|
|
600
|
+
* ...options,
|
|
601
|
+
* challenge: base64UrlToArrayBuffer(options.challenge),
|
|
602
|
+
* allowCredentials: options.allowCredentials.map(c => ({
|
|
603
|
+
* ...c,
|
|
604
|
+
* id: base64UrlToArrayBuffer(c.id),
|
|
605
|
+
* })),
|
|
606
|
+
* },
|
|
607
|
+
* });
|
|
608
|
+
*
|
|
609
|
+
* // Verify and get tokens
|
|
610
|
+
* const tokens = await auth.verifyPasskeyAuthentication({
|
|
611
|
+
* authentication: formatAuthenticationResponse(credential),
|
|
612
|
+
* });
|
|
613
|
+
* ```
|
|
614
|
+
*/
|
|
615
|
+
async getPasskeyAuthenticationOptions(options) {
|
|
616
|
+
const url = `${this.config.authUrl}/webauthn/authenticate/options`;
|
|
617
|
+
const body = {
|
|
618
|
+
realm_id: this.config.realmId
|
|
619
|
+
};
|
|
620
|
+
if (options?.email) {
|
|
621
|
+
body.email = options.email;
|
|
622
|
+
}
|
|
623
|
+
try {
|
|
624
|
+
const response = await fetch(url, {
|
|
625
|
+
method: "POST",
|
|
626
|
+
headers: {
|
|
627
|
+
"Content-Type": "application/json"
|
|
628
|
+
},
|
|
629
|
+
body: JSON.stringify(body)
|
|
630
|
+
});
|
|
631
|
+
if (!response.ok) {
|
|
632
|
+
await this.handleErrorResponse(response);
|
|
633
|
+
}
|
|
634
|
+
return await response.json();
|
|
635
|
+
} catch (error) {
|
|
636
|
+
if (error instanceof OvixaAuthError) {
|
|
637
|
+
throw error;
|
|
638
|
+
}
|
|
639
|
+
if (error instanceof Error) {
|
|
640
|
+
throw new OvixaAuthError(`Network error: ${error.message}`, "NETWORK_ERROR");
|
|
641
|
+
}
|
|
642
|
+
throw new OvixaAuthError("Request failed", "REQUEST_FAILED");
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Verify a passkey authentication and get tokens.
|
|
647
|
+
*
|
|
648
|
+
* Call this after navigator.credentials.get() succeeds to complete the
|
|
649
|
+
* authentication and receive access/refresh tokens.
|
|
650
|
+
*
|
|
651
|
+
* @param options - Verification options with authentication response
|
|
652
|
+
* @returns Token response with access and refresh tokens
|
|
653
|
+
* @throws {OvixaAuthError} If verification fails
|
|
654
|
+
*
|
|
655
|
+
* @example
|
|
656
|
+
* ```typescript
|
|
657
|
+
* const tokens = await auth.verifyPasskeyAuthentication({
|
|
658
|
+
* authentication: {
|
|
659
|
+
* id: credential.id,
|
|
660
|
+
* rawId: arrayBufferToBase64Url(credential.rawId),
|
|
661
|
+
* type: 'public-key',
|
|
662
|
+
* response: {
|
|
663
|
+
* clientDataJSON: arrayBufferToBase64Url(credential.response.clientDataJSON),
|
|
664
|
+
* authenticatorData: arrayBufferToBase64Url(credential.response.authenticatorData),
|
|
665
|
+
* signature: arrayBufferToBase64Url(credential.response.signature),
|
|
666
|
+
* userHandle: credential.response.userHandle
|
|
667
|
+
* ? arrayBufferToBase64Url(credential.response.userHandle)
|
|
668
|
+
* : null,
|
|
669
|
+
* },
|
|
670
|
+
* },
|
|
671
|
+
* });
|
|
672
|
+
*
|
|
673
|
+
* // Use the tokens
|
|
674
|
+
* console.log('Logged in!', tokens.access_token);
|
|
675
|
+
* ```
|
|
676
|
+
*/
|
|
677
|
+
async verifyPasskeyAuthentication(options) {
|
|
678
|
+
const url = `${this.config.authUrl}/webauthn/authenticate/verify`;
|
|
679
|
+
const body = {
|
|
680
|
+
realm_id: this.config.realmId,
|
|
681
|
+
authentication: options.authentication
|
|
682
|
+
};
|
|
683
|
+
try {
|
|
684
|
+
const response = await fetch(url, {
|
|
685
|
+
method: "POST",
|
|
686
|
+
headers: {
|
|
687
|
+
"Content-Type": "application/json"
|
|
688
|
+
},
|
|
689
|
+
body: JSON.stringify(body)
|
|
690
|
+
});
|
|
691
|
+
if (!response.ok) {
|
|
692
|
+
await this.handleErrorResponse(response);
|
|
693
|
+
}
|
|
694
|
+
return await response.json();
|
|
695
|
+
} catch (error) {
|
|
696
|
+
if (error instanceof OvixaAuthError) {
|
|
697
|
+
throw error;
|
|
698
|
+
}
|
|
699
|
+
if (error instanceof Error) {
|
|
700
|
+
throw new OvixaAuthError(`Network error: ${error.message}`, "NETWORK_ERROR");
|
|
701
|
+
}
|
|
702
|
+
throw new OvixaAuthError("Request failed", "REQUEST_FAILED");
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Handle error response from the server.
|
|
707
|
+
* @throws {OvixaAuthError} Always throws with appropriate error details
|
|
708
|
+
*/
|
|
709
|
+
async handleErrorResponse(response) {
|
|
710
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
711
|
+
const errorData = errorBody;
|
|
712
|
+
let errorCode;
|
|
713
|
+
let errorMessage;
|
|
714
|
+
if (typeof errorData.error === "object" && errorData.error) {
|
|
715
|
+
errorCode = errorData.error.code || this.mapHttpStatusToErrorCode(response.status);
|
|
716
|
+
errorMessage = errorData.error.message || "Request failed";
|
|
717
|
+
} else {
|
|
718
|
+
errorCode = this.mapHttpStatusToErrorCode(response.status);
|
|
719
|
+
errorMessage = typeof errorData.error === "string" ? errorData.error : "Request failed";
|
|
720
|
+
}
|
|
721
|
+
throw new OvixaAuthError(errorMessage, errorCode, response.status);
|
|
722
|
+
}
|
|
445
723
|
/**
|
|
446
724
|
* Exchange an OAuth authorization code for tokens.
|
|
447
725
|
*
|
|
@@ -773,9 +1051,95 @@ var OvixaAuthAdmin = class {
|
|
|
773
1051
|
}
|
|
774
1052
|
}
|
|
775
1053
|
};
|
|
1054
|
+
function arrayBufferToBase64Url(buffer) {
|
|
1055
|
+
const bytes = new Uint8Array(buffer);
|
|
1056
|
+
let binary = "";
|
|
1057
|
+
for (const byte of bytes) {
|
|
1058
|
+
binary += String.fromCharCode(byte);
|
|
1059
|
+
}
|
|
1060
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1061
|
+
}
|
|
1062
|
+
function base64UrlToArrayBuffer(base64url) {
|
|
1063
|
+
const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
|
|
1064
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
1065
|
+
const binary = atob(padded);
|
|
1066
|
+
const bytes = new Uint8Array(binary.length);
|
|
1067
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1068
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1069
|
+
}
|
|
1070
|
+
return bytes.buffer;
|
|
1071
|
+
}
|
|
1072
|
+
function prepareRegistrationOptions(options) {
|
|
1073
|
+
return {
|
|
1074
|
+
challenge: base64UrlToArrayBuffer(options.challenge),
|
|
1075
|
+
rp: options.rp,
|
|
1076
|
+
user: {
|
|
1077
|
+
id: new TextEncoder().encode(options.user.id),
|
|
1078
|
+
name: options.user.name,
|
|
1079
|
+
displayName: options.user.displayName
|
|
1080
|
+
},
|
|
1081
|
+
pubKeyCredParams: options.pubKeyCredParams,
|
|
1082
|
+
excludeCredentials: options.excludeCredentials.map((cred) => ({
|
|
1083
|
+
id: base64UrlToArrayBuffer(cred.id),
|
|
1084
|
+
type: cred.type,
|
|
1085
|
+
transports: cred.transports
|
|
1086
|
+
})),
|
|
1087
|
+
authenticatorSelection: options.authenticatorSelection,
|
|
1088
|
+
timeout: options.timeout,
|
|
1089
|
+
attestation: options.attestation
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
function prepareAuthenticationOptions(options) {
|
|
1093
|
+
return {
|
|
1094
|
+
challenge: base64UrlToArrayBuffer(options.challenge),
|
|
1095
|
+
rpId: options.rpId,
|
|
1096
|
+
allowCredentials: options.allowCredentials.map((cred) => ({
|
|
1097
|
+
id: base64UrlToArrayBuffer(cred.id),
|
|
1098
|
+
type: cred.type,
|
|
1099
|
+
transports: cred.transports
|
|
1100
|
+
})),
|
|
1101
|
+
userVerification: options.userVerification,
|
|
1102
|
+
timeout: options.timeout
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
function formatRegistrationResponse(credential) {
|
|
1106
|
+
const response = credential.response;
|
|
1107
|
+
return {
|
|
1108
|
+
id: credential.id,
|
|
1109
|
+
rawId: arrayBufferToBase64Url(credential.rawId),
|
|
1110
|
+
type: "public-key",
|
|
1111
|
+
response: {
|
|
1112
|
+
clientDataJSON: arrayBufferToBase64Url(response.clientDataJSON),
|
|
1113
|
+
attestationObject: arrayBufferToBase64Url(response.attestationObject),
|
|
1114
|
+
transports: response.getTransports?.()
|
|
1115
|
+
},
|
|
1116
|
+
authenticatorAttachment: credential.authenticatorAttachment
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
function formatAuthenticationResponse(credential) {
|
|
1120
|
+
const response = credential.response;
|
|
1121
|
+
return {
|
|
1122
|
+
id: credential.id,
|
|
1123
|
+
rawId: arrayBufferToBase64Url(credential.rawId),
|
|
1124
|
+
type: "public-key",
|
|
1125
|
+
response: {
|
|
1126
|
+
clientDataJSON: arrayBufferToBase64Url(response.clientDataJSON),
|
|
1127
|
+
authenticatorData: arrayBufferToBase64Url(response.authenticatorData),
|
|
1128
|
+
signature: arrayBufferToBase64Url(response.signature),
|
|
1129
|
+
userHandle: response.userHandle ? arrayBufferToBase64Url(response.userHandle) : null
|
|
1130
|
+
},
|
|
1131
|
+
authenticatorAttachment: credential.authenticatorAttachment
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
776
1134
|
|
|
777
1135
|
export {
|
|
778
1136
|
OvixaAuthError,
|
|
779
|
-
OvixaAuth
|
|
1137
|
+
OvixaAuth,
|
|
1138
|
+
arrayBufferToBase64Url,
|
|
1139
|
+
base64UrlToArrayBuffer,
|
|
1140
|
+
prepareRegistrationOptions,
|
|
1141
|
+
prepareAuthenticationOptions,
|
|
1142
|
+
formatRegistrationResponse,
|
|
1143
|
+
formatAuthenticationResponse
|
|
780
1144
|
};
|
|
781
|
-
//# sourceMappingURL=chunk-
|
|
1145
|
+
//# sourceMappingURL=chunk-5ZWKDQQM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @ovixa/auth-client\n *\n * Client SDK for Ovixa Auth service.\n * Provides authentication, token verification, and session management.\n */\n\nimport {\n createRemoteJWKSet,\n jwtVerify,\n type JWTPayload,\n type JWTVerifyResult,\n type JWTHeaderParameters,\n} from 'jose';\n\n/**\n * Configuration options for the OvixaAuth client.\n */\nexport interface AuthClientConfig {\n /** The base URL of the Ovixa Auth service */\n authUrl: string;\n /** The realm ID to authenticate against */\n realmId: string;\n /** Your application's client secret (for server-side use only) */\n clientSecret?: string;\n /** Cache duration for JWKS in milliseconds (defaults to 1 hour) */\n jwksCacheTtl?: number;\n}\n\n/**\n * JWT claims for an Ovixa access token.\n */\nexport interface AccessTokenPayload extends JWTPayload {\n /** Subject (user ID) */\n sub: string;\n /** User's email address */\n email: string;\n /** Whether the user's email has been verified */\n email_verified: boolean;\n /** Issued at timestamp */\n iat: number;\n /** Expiration timestamp */\n exp: number;\n /** Issuer */\n iss: string;\n /** Audience (realm ID) */\n aud: string;\n}\n\n/**\n * Result of a successful token verification.\n */\nexport interface VerifyResult {\n /** The decoded token payload */\n payload: AccessTokenPayload;\n /** The protected header */\n protectedHeader: JWTHeaderParameters;\n}\n\n/**\n * Token response from the auth service.\n */\nexport interface TokenResponse {\n /** The access token (JWT) */\n access_token: string;\n /** The refresh token (JWT) */\n refresh_token: string;\n /** Token type (always \"Bearer\") */\n token_type: 'Bearer';\n /** Access token expiration time in seconds */\n expires_in: number;\n /** Whether this is a new user (only present in OAuth callback) */\n is_new_user?: boolean;\n}\n\n/**\n * User information extracted from token payload.\n */\nexport interface User {\n /** User ID (UUID) */\n id: string;\n /** User's email address */\n email: string;\n /** Whether the email has been verified */\n emailVerified: boolean;\n}\n\n/**\n * Session information containing tokens and expiry.\n */\nexport interface Session {\n /** The access token (JWT) */\n accessToken: string;\n /** The refresh token for obtaining new access tokens */\n refreshToken: string;\n /** When the access token expires */\n expiresAt: Date;\n}\n\n/**\n * Combined authentication result with user and session data.\n */\nexport interface AuthResult {\n /** The authenticated user */\n user: User;\n /** The session tokens */\n session: Session;\n /** Whether this is a new user (only set for OAuth flows) */\n isNewUser?: boolean;\n}\n\n/**\n * Options for signup.\n */\nexport interface SignupOptions {\n /** User's email address */\n email: string;\n /** Password meeting requirements */\n password: string;\n /** Optional redirect URI after email verification */\n redirectUri?: string;\n}\n\n/**\n * Response from signup endpoint.\n */\nexport interface SignupResponse {\n /** Whether the operation succeeded */\n success: boolean;\n /** Status message */\n message: string;\n}\n\n/**\n * Options for login.\n */\nexport interface LoginOptions {\n /** User's email address */\n email: string;\n /** User's password */\n password: string;\n}\n\n/**\n * Options for email verification.\n */\nexport interface VerifyEmailOptions {\n /** Verification token from email */\n token: string;\n}\n\n/**\n * Options for resending verification email.\n */\nexport interface ResendVerificationOptions {\n /** User's email address */\n email: string;\n /** Optional redirect URI after verification */\n redirectUri?: string;\n}\n\n/**\n * Generic success response.\n */\nexport interface SuccessResponse {\n /** Whether the operation succeeded */\n success: boolean;\n /** Optional status message */\n message?: string;\n}\n\n/**\n * Options for forgot password request.\n */\nexport interface ForgotPasswordOptions {\n /** User's email address */\n email: string;\n /** Optional redirect URI for password reset page */\n redirectUri?: string;\n}\n\n/**\n * Options for password reset.\n */\nexport interface ResetPasswordOptions {\n /** Reset token from email */\n token: string;\n /** New password */\n password: string;\n}\n\n/**\n * Supported OAuth providers.\n */\nexport type OAuthProvider = 'google' | 'github';\n\n// ============================================================\n// WebAuthn / Passkey Types\n// ============================================================\n\n/**\n * Options for getting passkey registration options.\n */\nexport interface GetPasskeyRegistrationOptionsOptions {\n /** Access token (user must be logged in to register a passkey) */\n accessToken: string;\n}\n\n/**\n * Registration options returned from the server.\n * These are passed directly to navigator.credentials.create().\n */\nexport interface PasskeyRegistrationOptions {\n challenge: string;\n rp: { id: string; name: string };\n user: { id: string; name: string; displayName: string };\n pubKeyCredParams: { type: 'public-key'; alg: number }[];\n excludeCredentials: { id: string; type: 'public-key'; transports?: string[] }[];\n authenticatorSelection: {\n authenticatorAttachment?: 'platform' | 'cross-platform';\n residentKey: 'discouraged' | 'preferred' | 'required';\n userVerification: 'discouraged' | 'preferred' | 'required';\n };\n timeout: number;\n attestation: 'none';\n}\n\n/**\n * Options for verifying passkey registration.\n */\nexport interface VerifyPasskeyRegistrationOptions {\n /** Access token (user must be logged in) */\n accessToken: string;\n /** The registration response from navigator.credentials.create() */\n registration: {\n id: string;\n rawId: string;\n type: 'public-key';\n response: {\n clientDataJSON: string;\n attestationObject: string;\n authenticatorData?: string;\n transports?: string[];\n publicKeyAlgorithm?: number;\n publicKey?: string;\n };\n authenticatorAttachment?: 'platform' | 'cross-platform';\n clientExtensionResults?: Record<string, unknown>;\n };\n /** Optional friendly name for the passkey */\n deviceName?: string;\n}\n\n/**\n * Response from passkey registration verification.\n */\nexport interface PasskeyRegistrationResponse {\n success: boolean;\n credential_id: string;\n device_name?: string;\n}\n\n/**\n * Options for getting passkey authentication options.\n */\nexport interface GetPasskeyAuthenticationOptionsOptions {\n /** Optional email for allowCredentials hint (for non-discoverable credentials) */\n email?: string;\n}\n\n/**\n * Authentication options returned from the server.\n * These are passed directly to navigator.credentials.get().\n */\nexport interface PasskeyAuthenticationOptions {\n challenge: string;\n rpId: string;\n allowCredentials: { id: string; type: 'public-key'; transports?: string[] }[];\n userVerification: 'discouraged' | 'preferred' | 'required';\n timeout: number;\n}\n\n/**\n * Options for verifying passkey authentication.\n */\nexport interface VerifyPasskeyAuthenticationOptions {\n /** The authentication response from navigator.credentials.get() */\n authentication: {\n id: string;\n rawId: string;\n type: 'public-key';\n response: {\n clientDataJSON: string;\n authenticatorData: string;\n signature: string;\n userHandle?: string | null;\n };\n authenticatorAttachment?: 'platform' | 'cross-platform';\n clientExtensionResults?: Record<string, unknown>;\n };\n}\n\n/**\n * Options for generating OAuth URL.\n */\nexport interface GetOAuthUrlOptions {\n /** OAuth provider */\n provider: OAuthProvider;\n /** Redirect URI after OAuth completes */\n redirectUri: string;\n}\n\n/**\n * Options for deleting a user (admin operation).\n */\nexport interface DeleteUserOptions {\n /** The ID of the user to delete */\n userId: string;\n}\n\n/**\n * Options for deleting the current user's own account.\n */\nexport interface DeleteMyAccountOptions {\n /** The user's current access token */\n accessToken: string;\n /** The user's current password (for re-authentication) */\n password: string;\n}\n\n/**\n * Error thrown by OvixaAuth operations.\n */\nexport class OvixaAuthError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly statusCode?: number\n ) {\n super(message);\n this.name = 'OvixaAuthError';\n }\n}\n\ninterface InternalConfig {\n authUrl: string;\n realmId: string;\n clientSecret?: string;\n jwksCacheTtl: number;\n}\n\n/** Default JWKS cache TTL: 1 hour */\nconst DEFAULT_JWKS_CACHE_TTL = 60 * 60 * 1000;\n\n/**\n * Custom fetch function that adds cache headers for JWKS requests.\n * Used to implement client-side caching behavior.\n */\ntype JWKSFetcher = ReturnType<typeof createRemoteJWKSet>;\n\ninterface JWKSCache {\n fetcher: JWKSFetcher;\n createdAt: number;\n}\n\n/**\n * OvixaAuth client for authenticating with the Ovixa Auth service.\n *\n * @example\n * ```typescript\n * const auth = new OvixaAuth({\n * authUrl: 'https://auth.ovixa.io',\n * realmId: 'your-realm-id',\n * clientSecret: 'your-client-secret', // Optional, for server-side use\n * });\n *\n * // Verify a token\n * const result = await auth.verifyToken(accessToken);\n * console.log(result.payload.email);\n *\n * // Refresh tokens\n * const tokens = await auth.refreshToken(refreshToken);\n * ```\n */\nexport class OvixaAuth {\n private config: InternalConfig;\n private jwksCache: JWKSCache | null = null;\n\n constructor(config: AuthClientConfig) {\n // Normalize authUrl by removing trailing slash\n const authUrl = config.authUrl.replace(/\\/$/, '');\n\n this.config = {\n authUrl,\n realmId: config.realmId,\n clientSecret: config.clientSecret,\n jwksCacheTtl: config.jwksCacheTtl ?? DEFAULT_JWKS_CACHE_TTL,\n };\n }\n\n /** Get the configured auth URL */\n get authUrl(): string {\n return this.config.authUrl;\n }\n\n /** Get the configured realm ID */\n get realmId(): string {\n return this.config.realmId;\n }\n\n /**\n * Get the JWKS URL for the auth service.\n */\n get jwksUrl(): string {\n return `${this.config.authUrl}/.well-known/jwks.json`;\n }\n\n /**\n * Get the JWKS fetcher, creating a new one if necessary.\n * The fetcher is cached based on the configured TTL.\n */\n private getJwksFetcher(): JWKSFetcher {\n const now = Date.now();\n\n // Return cached fetcher if still valid\n if (this.jwksCache && now - this.jwksCache.createdAt < this.config.jwksCacheTtl) {\n return this.jwksCache.fetcher;\n }\n\n // Create new JWKS fetcher\n const jwksUrl = new URL(this.jwksUrl);\n const fetcher = createRemoteJWKSet(jwksUrl, {\n // jose library has its own internal caching, but we add our own TTL layer\n // to control when to refresh the JWKS set\n cacheMaxAge: this.config.jwksCacheTtl,\n });\n\n this.jwksCache = {\n fetcher,\n createdAt: now,\n };\n\n return fetcher;\n }\n\n /**\n * Verify an access token and return the decoded payload.\n *\n * This method fetches the public key from the JWKS endpoint (with caching)\n * and verifies the token's signature and claims.\n *\n * @param token - The access token (JWT) to verify\n * @returns The verified token payload and header\n * @throws {OvixaAuthError} If verification fails\n *\n * @example\n * ```typescript\n * try {\n * const result = await auth.verifyToken(accessToken);\n * console.log('User ID:', result.payload.sub);\n * console.log('Email:', result.payload.email);\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * console.error('Token verification failed:', error.code);\n * }\n * }\n * ```\n */\n async verifyToken(token: string): Promise<VerifyResult> {\n try {\n const jwks = this.getJwksFetcher();\n\n const result: JWTVerifyResult<AccessTokenPayload> = await jwtVerify(token, jwks, {\n algorithms: ['RS256'],\n issuer: this.config.authUrl,\n // Audience is the realm for this application\n audience: this.config.realmId,\n });\n\n // Validate required claims\n const payload = result.payload;\n if (!payload.sub || !payload.email || payload.email_verified === undefined) {\n throw new OvixaAuthError('Token is missing required claims', 'INVALID_CLAIMS');\n }\n\n return {\n payload: payload as AccessTokenPayload,\n protectedHeader: result.protectedHeader,\n };\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n\n // Handle jose library errors\n if (error instanceof Error) {\n const message = error.message;\n\n if (message.includes('expired')) {\n throw new OvixaAuthError('Token has expired', 'TOKEN_EXPIRED');\n }\n if (message.includes('signature')) {\n throw new OvixaAuthError('Invalid token signature', 'INVALID_SIGNATURE');\n }\n if (message.includes('issuer')) {\n throw new OvixaAuthError('Invalid token issuer', 'INVALID_ISSUER');\n }\n if (message.includes('audience')) {\n throw new OvixaAuthError('Invalid token audience', 'INVALID_AUDIENCE');\n }\n if (message.includes('malformed')) {\n throw new OvixaAuthError('Malformed token', 'MALFORMED_TOKEN');\n }\n\n throw new OvixaAuthError(`Token verification failed: ${message}`, 'VERIFICATION_FAILED');\n }\n\n throw new OvixaAuthError('Token verification failed', 'VERIFICATION_FAILED');\n }\n }\n\n /**\n * Refresh an access token using a refresh token.\n *\n * This method exchanges a valid refresh token for a new access token\n * and a new refresh token (token rotation).\n *\n * @param refreshToken - The refresh token to exchange\n * @returns New token response with access and refresh tokens\n * @throws {OvixaAuthError} If the refresh fails\n *\n * @example\n * ```typescript\n * try {\n * const tokens = await auth.refreshToken(currentRefreshToken);\n * // Store the new tokens\n * saveTokens(tokens.access_token, tokens.refresh_token);\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * // Refresh token is invalid or expired - user must re-authenticate\n * redirectToLogin();\n * }\n * }\n * ```\n */\n async refreshToken(refreshToken: string): Promise<TokenResponse> {\n const url = `${this.config.authUrl}/token/refresh`;\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n refresh_token: refreshToken,\n }),\n });\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({}));\n const errorMessage = (errorBody as { error?: string }).error || 'Token refresh failed';\n const errorCode = this.mapHttpStatusToErrorCode(response.status);\n\n throw new OvixaAuthError(errorMessage, errorCode, response.status);\n }\n\n const data = (await response.json()) as TokenResponse;\n return data;\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n\n if (error instanceof Error) {\n throw new OvixaAuthError(`Network error: ${error.message}`, 'NETWORK_ERROR');\n }\n\n throw new OvixaAuthError('Token refresh failed', 'REFRESH_FAILED');\n }\n }\n\n /**\n * Invalidate the cached JWKS fetcher.\n * Call this if you need to force a refresh of the public keys.\n */\n clearJwksCache(): void {\n this.jwksCache = null;\n }\n\n /**\n * Create a new user account.\n *\n * After signup, a verification email is sent. The user must verify their\n * email before they can log in.\n *\n * @param options - Signup options\n * @returns Signup response indicating success\n * @throws {OvixaAuthError} If signup fails\n *\n * @example\n * ```typescript\n * try {\n * await auth.signup({\n * email: 'user@example.com',\n * password: 'SecurePassword123!',\n * redirectUri: 'https://myapp.com/verify-callback',\n * });\n * console.log('Verification email sent!');\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'EMAIL_ALREADY_EXISTS') {\n * console.error('Email is already registered');\n * }\n * }\n * }\n * ```\n */\n async signup(options: SignupOptions): Promise<SignupResponse> {\n const url = `${this.config.authUrl}/signup`;\n\n const body: Record<string, string> = {\n email: options.email,\n password: options.password,\n realm_id: this.config.realmId,\n };\n\n if (options.redirectUri) {\n body.redirect_uri = options.redirectUri;\n }\n\n return this.makeRequest<SignupResponse>(url, body);\n }\n\n /**\n * Authenticate a user with email and password.\n *\n * @param options - Login options\n * @returns Token response with access and refresh tokens\n * @throws {OvixaAuthError} If login fails\n *\n * @example\n * ```typescript\n * try {\n * const tokens = await auth.login({\n * email: 'user@example.com',\n * password: 'SecurePassword123!',\n * });\n * console.log('Logged in!', tokens.access_token);\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'EMAIL_NOT_VERIFIED') {\n * console.error('Please verify your email first');\n * } else if (error.code === 'INVALID_CREDENTIALS') {\n * console.error('Invalid email or password');\n * }\n * }\n * }\n * ```\n */\n async login(options: LoginOptions): Promise<TokenResponse> {\n const url = `${this.config.authUrl}/login`;\n\n const body = {\n email: options.email,\n password: options.password,\n realm_id: this.config.realmId,\n };\n\n return this.makeRequest<TokenResponse>(url, body);\n }\n\n /**\n * Verify an email address using a verification token.\n *\n * This is the API flow that returns tokens. For browser redirect flow,\n * use `GET /verify` directly.\n *\n * @param options - Verification options\n * @returns Token response with access and refresh tokens\n * @throws {OvixaAuthError} If verification fails\n *\n * @example\n * ```typescript\n * // Get token from URL query params after clicking email link\n * const token = new URLSearchParams(window.location.search).get('token');\n *\n * try {\n * const tokens = await auth.verifyEmail({ token });\n * console.log('Email verified! Logged in.');\n * } catch (error) {\n * if (error instanceof OvixaAuthError && error.code === 'INVALID_TOKEN') {\n * console.error('Invalid or expired verification link');\n * }\n * }\n * ```\n */\n async verifyEmail(options: VerifyEmailOptions): Promise<TokenResponse> {\n const url = `${this.config.authUrl}/verify`;\n\n const body = {\n token: options.token,\n realm_id: this.config.realmId,\n type: 'email_verification',\n };\n\n return this.makeRequest<TokenResponse>(url, body);\n }\n\n /**\n * Resend a verification email for an unverified account.\n *\n * @param options - Resend options\n * @returns Success response\n * @throws {OvixaAuthError} If request fails\n *\n * @example\n * ```typescript\n * await auth.resendVerification({\n * email: 'user@example.com',\n * redirectUri: 'https://myapp.com/verify-callback',\n * });\n * console.log('Verification email sent!');\n * ```\n */\n async resendVerification(options: ResendVerificationOptions): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/verify/resend`;\n\n const body: Record<string, string> = {\n email: options.email,\n realm_id: this.config.realmId,\n };\n\n if (options.redirectUri) {\n body.redirect_uri = options.redirectUri;\n }\n\n return this.makeRequest<SuccessResponse>(url, body);\n }\n\n /**\n * Request a password reset email.\n *\n * Note: This endpoint always returns success to prevent email enumeration.\n *\n * @param options - Forgot password options\n * @returns Success response\n * @throws {OvixaAuthError} If request fails\n *\n * @example\n * ```typescript\n * await auth.forgotPassword({\n * email: 'user@example.com',\n * redirectUri: 'https://myapp.com/reset-password',\n * });\n * console.log('If the email exists, a reset link has been sent.');\n * ```\n */\n async forgotPassword(options: ForgotPasswordOptions): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/forgot-password`;\n\n const body: Record<string, string> = {\n email: options.email,\n realm_id: this.config.realmId,\n };\n\n if (options.redirectUri) {\n body.redirect_uri = options.redirectUri;\n }\n\n return this.makeRequest<SuccessResponse>(url, body);\n }\n\n /**\n * Reset password using a reset token.\n *\n * @param options - Reset password options\n * @returns Success response\n * @throws {OvixaAuthError} If reset fails\n *\n * @example\n * ```typescript\n * // Get token from URL query params\n * const token = new URLSearchParams(window.location.search).get('token');\n *\n * try {\n * await auth.resetPassword({\n * token,\n * password: 'NewSecurePassword123!',\n * });\n * console.log('Password reset successfully!');\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'INVALID_TOKEN') {\n * console.error('Invalid or expired reset link');\n * } else if (error.code === 'WEAK_PASSWORD') {\n * console.error('Password does not meet requirements');\n * }\n * }\n * }\n * ```\n */\n async resetPassword(options: ResetPasswordOptions): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/reset-password`;\n\n const body = {\n token: options.token,\n password: options.password,\n realm_id: this.config.realmId,\n };\n\n return this.makeRequest<SuccessResponse>(url, body);\n }\n\n /**\n * Revoke a refresh token (logout).\n *\n * @param refreshToken - The refresh token to revoke\n * @returns Success response\n * @throws {OvixaAuthError} If logout fails\n *\n * @example\n * ```typescript\n * await auth.logout(currentRefreshToken);\n * // Clear local token storage\n * localStorage.removeItem('refresh_token');\n * localStorage.removeItem('access_token');\n * ```\n */\n async logout(refreshToken: string): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/logout`;\n\n const body = {\n refresh_token: refreshToken,\n };\n\n return this.makeRequest<SuccessResponse>(url, body);\n }\n\n /**\n * Generate an OAuth authorization URL.\n *\n * Redirect the user to this URL to start the OAuth flow. After authentication,\n * the user will be redirected back to your `redirectUri` with an authorization code.\n * Use `exchangeOAuthCode()` to exchange the code for tokens.\n *\n * @param options - OAuth URL options\n * @returns The full OAuth authorization URL\n *\n * @example\n * ```typescript\n * const googleAuthUrl = auth.getOAuthUrl({\n * provider: 'google',\n * redirectUri: 'https://myapp.com/auth/callback',\n * });\n *\n * // Redirect user to start OAuth flow\n * window.location.href = googleAuthUrl;\n *\n * // In your callback handler:\n * // const code = new URLSearchParams(window.location.search).get('code');\n * // const tokens = await auth.exchangeOAuthCode(code);\n * ```\n */\n getOAuthUrl(options: GetOAuthUrlOptions): string {\n const url = new URL(`${this.config.authUrl}/oauth/${options.provider}`);\n url.searchParams.set('realm_id', this.config.realmId);\n url.searchParams.set('redirect_uri', options.redirectUri);\n return url.toString();\n }\n\n // ============================================================\n // WebAuthn / Passkey Methods\n // ============================================================\n\n /**\n * Get registration options for creating a new passkey.\n *\n * The user must be logged in to register a passkey. Use the returned options\n * with navigator.credentials.create() to create the credential.\n *\n * @param options - Registration options with access token\n * @returns Registration options to pass to navigator.credentials.create()\n * @throws {OvixaAuthError} If request fails\n *\n * @example\n * ```typescript\n * // User must be logged in\n * const options = await auth.getPasskeyRegistrationOptions({\n * accessToken: session.accessToken,\n * });\n *\n * // Create the credential using the browser API\n * const credential = await navigator.credentials.create({\n * publicKey: {\n * ...options,\n * challenge: base64UrlToArrayBuffer(options.challenge),\n * user: {\n * ...options.user,\n * id: new TextEncoder().encode(options.user.id),\n * },\n * excludeCredentials: options.excludeCredentials.map(c => ({\n * ...c,\n * id: base64UrlToArrayBuffer(c.id),\n * })),\n * },\n * });\n *\n * // Verify the registration with the server\n * await auth.verifyPasskeyRegistration({\n * accessToken: session.accessToken,\n * registration: formatRegistrationResponse(credential),\n * deviceName: 'My MacBook',\n * });\n * ```\n */\n async getPasskeyRegistrationOptions(\n options: GetPasskeyRegistrationOptionsOptions\n ): Promise<PasskeyRegistrationOptions> {\n const url = `${this.config.authUrl}/webauthn/register/options`;\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${options.accessToken}`,\n },\n body: JSON.stringify({\n realm_id: this.config.realmId,\n }),\n });\n\n if (!response.ok) {\n await this.handleErrorResponse(response);\n }\n\n return (await response.json()) as PasskeyRegistrationOptions;\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n if (error instanceof Error) {\n throw new OvixaAuthError(`Network error: ${error.message}`, 'NETWORK_ERROR');\n }\n throw new OvixaAuthError('Request failed', 'REQUEST_FAILED');\n }\n }\n\n /**\n * Verify a passkey registration and store the credential.\n *\n * Call this after navigator.credentials.create() succeeds to complete the\n * registration on the server.\n *\n * @param options - Verification options with registration response\n * @returns Registration response with credential ID\n * @throws {OvixaAuthError} If verification fails\n *\n * @example\n * ```typescript\n * const result = await auth.verifyPasskeyRegistration({\n * accessToken: session.accessToken,\n * registration: {\n * id: credential.id,\n * rawId: arrayBufferToBase64Url(credential.rawId),\n * type: 'public-key',\n * response: {\n * clientDataJSON: arrayBufferToBase64Url(credential.response.clientDataJSON),\n * attestationObject: arrayBufferToBase64Url(credential.response.attestationObject),\n * transports: credential.response.getTransports?.(),\n * },\n * },\n * deviceName: 'My MacBook',\n * });\n *\n * console.log('Passkey registered:', result.credential_id);\n * ```\n */\n async verifyPasskeyRegistration(\n options: VerifyPasskeyRegistrationOptions\n ): Promise<PasskeyRegistrationResponse> {\n const url = `${this.config.authUrl}/webauthn/register/verify`;\n\n const body: Record<string, unknown> = {\n realm_id: this.config.realmId,\n registration: options.registration,\n };\n\n if (options.deviceName) {\n body.device_name = options.deviceName;\n }\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${options.accessToken}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n await this.handleErrorResponse(response);\n }\n\n return (await response.json()) as PasskeyRegistrationResponse;\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n if (error instanceof Error) {\n throw new OvixaAuthError(`Network error: ${error.message}`, 'NETWORK_ERROR');\n }\n throw new OvixaAuthError('Request failed', 'REQUEST_FAILED');\n }\n }\n\n /**\n * Get authentication options for signing in with a passkey.\n *\n * Use the returned options with navigator.credentials.get() to authenticate.\n *\n * @param options - Optional email for allowCredentials hint\n * @returns Authentication options to pass to navigator.credentials.get()\n * @throws {OvixaAuthError} If request fails\n *\n * @example\n * ```typescript\n * // Option 1: Discoverable credentials (no email needed)\n * const options = await auth.getPasskeyAuthenticationOptions();\n *\n * // Option 2: Provide email for allowCredentials hint\n * const options = await auth.getPasskeyAuthenticationOptions({\n * email: 'user@example.com',\n * });\n *\n * // Authenticate using the browser API\n * const credential = await navigator.credentials.get({\n * publicKey: {\n * ...options,\n * challenge: base64UrlToArrayBuffer(options.challenge),\n * allowCredentials: options.allowCredentials.map(c => ({\n * ...c,\n * id: base64UrlToArrayBuffer(c.id),\n * })),\n * },\n * });\n *\n * // Verify and get tokens\n * const tokens = await auth.verifyPasskeyAuthentication({\n * authentication: formatAuthenticationResponse(credential),\n * });\n * ```\n */\n async getPasskeyAuthenticationOptions(\n options?: GetPasskeyAuthenticationOptionsOptions\n ): Promise<PasskeyAuthenticationOptions> {\n const url = `${this.config.authUrl}/webauthn/authenticate/options`;\n\n const body: Record<string, string> = {\n realm_id: this.config.realmId,\n };\n\n if (options?.email) {\n body.email = options.email;\n }\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n await this.handleErrorResponse(response);\n }\n\n return (await response.json()) as PasskeyAuthenticationOptions;\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n if (error instanceof Error) {\n throw new OvixaAuthError(`Network error: ${error.message}`, 'NETWORK_ERROR');\n }\n throw new OvixaAuthError('Request failed', 'REQUEST_FAILED');\n }\n }\n\n /**\n * Verify a passkey authentication and get tokens.\n *\n * Call this after navigator.credentials.get() succeeds to complete the\n * authentication and receive access/refresh tokens.\n *\n * @param options - Verification options with authentication response\n * @returns Token response with access and refresh tokens\n * @throws {OvixaAuthError} If verification fails\n *\n * @example\n * ```typescript\n * const tokens = await auth.verifyPasskeyAuthentication({\n * authentication: {\n * id: credential.id,\n * rawId: arrayBufferToBase64Url(credential.rawId),\n * type: 'public-key',\n * response: {\n * clientDataJSON: arrayBufferToBase64Url(credential.response.clientDataJSON),\n * authenticatorData: arrayBufferToBase64Url(credential.response.authenticatorData),\n * signature: arrayBufferToBase64Url(credential.response.signature),\n * userHandle: credential.response.userHandle\n * ? arrayBufferToBase64Url(credential.response.userHandle)\n * : null,\n * },\n * },\n * });\n *\n * // Use the tokens\n * console.log('Logged in!', tokens.access_token);\n * ```\n */\n async verifyPasskeyAuthentication(\n options: VerifyPasskeyAuthenticationOptions\n ): Promise<TokenResponse> {\n const url = `${this.config.authUrl}/webauthn/authenticate/verify`;\n\n const body = {\n realm_id: this.config.realmId,\n authentication: options.authentication,\n };\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n await this.handleErrorResponse(response);\n }\n\n return (await response.json()) as TokenResponse;\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n if (error instanceof Error) {\n throw new OvixaAuthError(`Network error: ${error.message}`, 'NETWORK_ERROR');\n }\n throw new OvixaAuthError('Request failed', 'REQUEST_FAILED');\n }\n }\n\n /**\n * Handle error response from the server.\n * @throws {OvixaAuthError} Always throws with appropriate error details\n */\n private async handleErrorResponse(response: Response): Promise<never> {\n const errorBody = await response.json().catch(() => ({}));\n const errorData = errorBody as { error?: { code?: string; message?: string } | string };\n\n let errorCode: string;\n let errorMessage: string;\n\n if (typeof errorData.error === 'object' && errorData.error) {\n errorCode = errorData.error.code || this.mapHttpStatusToErrorCode(response.status);\n errorMessage = errorData.error.message || 'Request failed';\n } else {\n errorCode = this.mapHttpStatusToErrorCode(response.status);\n errorMessage = typeof errorData.error === 'string' ? errorData.error : 'Request failed';\n }\n\n throw new OvixaAuthError(errorMessage, errorCode, response.status);\n }\n\n /**\n * Exchange an OAuth authorization code for tokens.\n *\n * After the OAuth flow completes, the user is redirected back to your app with\n * an authorization code in the URL query params. Use this method to exchange\n * that code for access and refresh tokens.\n *\n * Note: Authorization codes expire after 60 seconds.\n *\n * @param code - The authorization code from the OAuth callback URL\n * @returns Token response with access and refresh tokens\n * @throws {OvixaAuthError} If the exchange fails\n *\n * @example\n * ```typescript\n * // In your OAuth callback handler\n * const code = new URLSearchParams(window.location.search).get('code');\n *\n * if (code) {\n * try {\n * const tokens = await auth.exchangeOAuthCode(code);\n * console.log('OAuth successful!', tokens.access_token);\n *\n * // Check if this is a new user (first-time OAuth login)\n * if (tokens.is_new_user) {\n * // Show onboarding flow\n * }\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'INVALID_CODE') {\n * console.error('Invalid or expired authorization code');\n * }\n * }\n * }\n * }\n * ```\n */\n async exchangeOAuthCode(code: string): Promise<TokenResponse> {\n const url = `${this.config.authUrl}/oauth/token`;\n\n const body = {\n code,\n realm_id: this.config.realmId,\n };\n\n return this.makeRequest<TokenResponse>(url, body);\n }\n\n /**\n * Transform a token response into an AuthResult with user and session data.\n *\n * This method decodes the access token to extract user information and\n * creates a structured result object.\n *\n * @param tokenResponse - The token response from login, verify, or refresh\n * @returns AuthResult with user and session data\n * @throws {OvixaAuthError} If the access token cannot be decoded\n *\n * @example\n * ```typescript\n * const tokens = await auth.login({ email, password });\n * const result = await auth.toAuthResult(tokens);\n *\n * console.log('User ID:', result.user.id);\n * console.log('Email:', result.user.email);\n * console.log('Expires at:', result.session.expiresAt);\n * ```\n */\n async toAuthResult(tokenResponse: TokenResponse): Promise<AuthResult> {\n // Verify and decode the access token\n const verified = await this.verifyToken(tokenResponse.access_token);\n\n const user: User = {\n id: verified.payload.sub,\n email: verified.payload.email,\n emailVerified: verified.payload.email_verified,\n };\n\n const session: Session = {\n accessToken: tokenResponse.access_token,\n refreshToken: tokenResponse.refresh_token,\n expiresAt: new Date(Date.now() + tokenResponse.expires_in * 1000),\n };\n\n const result: AuthResult = { user, session };\n\n if (tokenResponse.is_new_user !== undefined) {\n result.isNewUser = tokenResponse.is_new_user;\n }\n\n return result;\n }\n\n /**\n * Delete the current user's own account.\n *\n * This permanently deletes the user's account and all associated data.\n * Password re-authentication is required to prevent accidental deletion.\n *\n * @param options - Delete account options\n * @returns Success response\n * @throws {OvixaAuthError} If deletion fails\n *\n * @example\n * ```typescript\n * try {\n * await auth.deleteMyAccount({\n * accessToken: session.accessToken,\n * password: 'current-password',\n * });\n * console.log('Account deleted successfully');\n * // Clear local session storage\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'INVALID_CREDENTIALS') {\n * console.error('Incorrect password');\n * } else if (error.code === 'UNAUTHORIZED') {\n * console.error('Invalid or expired access token');\n * }\n * }\n * }\n * ```\n */\n async deleteMyAccount(options: DeleteMyAccountOptions): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/me`;\n\n try {\n const response = await fetch(url, {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${options.accessToken}`,\n },\n body: JSON.stringify({\n password: options.password,\n }),\n });\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({}));\n const errorData = errorBody as { error?: { code?: string; message?: string } | string };\n\n let errorCode: string;\n let errorMessage: string;\n\n if (typeof errorData.error === 'object' && errorData.error) {\n errorCode = errorData.error.code || this.mapHttpStatusToErrorCode(response.status);\n errorMessage = errorData.error.message || 'Request failed';\n } else {\n errorCode = this.mapHttpStatusToErrorCode(response.status);\n errorMessage = typeof errorData.error === 'string' ? errorData.error : 'Request failed';\n }\n\n throw new OvixaAuthError(errorMessage, errorCode, response.status);\n }\n\n return (await response.json()) as SuccessResponse;\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n\n if (error instanceof Error) {\n throw new OvixaAuthError(`Network error: ${error.message}`, 'NETWORK_ERROR');\n }\n\n throw new OvixaAuthError('Request failed', 'REQUEST_FAILED');\n }\n }\n\n /**\n * Make an authenticated POST request to the auth service.\n */\n private async makeRequest<T>(url: string, body: Record<string, unknown>): Promise<T> {\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({}));\n const errorData = errorBody as { error?: { code?: string; message?: string } | string };\n\n let errorCode: string;\n let errorMessage: string;\n\n if (typeof errorData.error === 'object' && errorData.error) {\n errorCode = errorData.error.code || this.mapHttpStatusToErrorCode(response.status);\n errorMessage = errorData.error.message || 'Request failed';\n } else {\n errorCode = this.mapHttpStatusToErrorCode(response.status);\n errorMessage = typeof errorData.error === 'string' ? errorData.error : 'Request failed';\n }\n\n throw new OvixaAuthError(errorMessage, errorCode, response.status);\n }\n\n return (await response.json()) as T;\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n\n if (error instanceof Error) {\n throw new OvixaAuthError(`Network error: ${error.message}`, 'NETWORK_ERROR');\n }\n\n throw new OvixaAuthError('Request failed', 'REQUEST_FAILED');\n }\n }\n\n /**\n * Map HTTP status codes to error codes.\n */\n private mapHttpStatusToErrorCode(status: number): string {\n switch (status) {\n case 400:\n return 'BAD_REQUEST';\n case 401:\n return 'UNAUTHORIZED';\n case 403:\n return 'FORBIDDEN';\n case 404:\n return 'NOT_FOUND';\n case 429:\n return 'RATE_LIMITED';\n case 500:\n case 502:\n case 503:\n return 'SERVER_ERROR';\n default:\n return 'UNKNOWN_ERROR';\n }\n }\n\n /**\n * Get the admin API interface for realm-scoped operations.\n *\n * Admin operations require clientSecret to be configured.\n * These operations allow managing users within the realm boundary.\n *\n * @returns OvixaAuthAdmin interface for admin operations\n * @throws {OvixaAuthError} If clientSecret is not configured\n *\n * @example\n * ```typescript\n * const auth = new OvixaAuth({\n * authUrl: 'https://auth.ovixa.io',\n * realmId: 'your-realm',\n * clientSecret: process.env.OVIXA_CLIENT_SECRET,\n * });\n *\n * // Delete a user from the realm\n * await auth.admin.deleteUser({ userId: 'user-id-to-delete' });\n * ```\n */\n get admin(): OvixaAuthAdmin {\n if (!this.config.clientSecret) {\n throw new OvixaAuthError(\n 'clientSecret is required for admin operations',\n 'CLIENT_SECRET_REQUIRED'\n );\n }\n return new OvixaAuthAdmin(this.config);\n }\n}\n\n/**\n * Admin API interface for realm-scoped operations.\n *\n * This class provides methods for managing users within the realm boundary.\n * All operations are authenticated using the realm's client_secret.\n *\n * @example\n * ```typescript\n * // Access via OvixaAuth.admin\n * await auth.admin.deleteUser({ userId: 'user-id' });\n * ```\n */\nclass OvixaAuthAdmin {\n private config: InternalConfig;\n\n constructor(config: InternalConfig) {\n this.config = config;\n }\n\n /**\n * Delete a user from the realm.\n *\n * This permanently deletes the user and all associated data (tokens, OAuth accounts).\n * The user must belong to this realm.\n *\n * @param options - Delete user options\n * @returns Success response\n * @throws {OvixaAuthError} If deletion fails\n *\n * @example\n * ```typescript\n * try {\n * await auth.admin.deleteUser({ userId: 'user-id-to-delete' });\n * console.log('User deleted successfully');\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'NOT_FOUND') {\n * console.error('User not found');\n * } else if (error.code === 'FORBIDDEN') {\n * console.error('User does not belong to this realm');\n * }\n * }\n * }\n * ```\n */\n async deleteUser(options: DeleteUserOptions): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/realms/${this.config.realmId}/users/${options.userId}`;\n\n try {\n const response = await fetch(url, {\n method: 'DELETE',\n headers: {\n Authorization: `RealmSecret ${this.config.clientSecret}`,\n },\n });\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({}));\n const errorData = errorBody as { error?: { code?: string; message?: string } | string };\n\n let errorCode: string;\n let errorMessage: string;\n\n if (typeof errorData.error === 'object' && errorData.error) {\n errorCode = errorData.error.code || this.mapHttpStatusToErrorCode(response.status);\n errorMessage = errorData.error.message || 'Request failed';\n } else {\n errorCode = this.mapHttpStatusToErrorCode(response.status);\n errorMessage = typeof errorData.error === 'string' ? errorData.error : 'Request failed';\n }\n\n throw new OvixaAuthError(errorMessage, errorCode, response.status);\n }\n\n return (await response.json()) as SuccessResponse;\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n\n if (error instanceof Error) {\n throw new OvixaAuthError(`Network error: ${error.message}`, 'NETWORK_ERROR');\n }\n\n throw new OvixaAuthError('Request failed', 'REQUEST_FAILED');\n }\n }\n\n /**\n * Map HTTP status codes to error codes.\n */\n private mapHttpStatusToErrorCode(status: number): string {\n switch (status) {\n case 400:\n return 'BAD_REQUEST';\n case 401:\n return 'UNAUTHORIZED';\n case 403:\n return 'FORBIDDEN';\n case 404:\n return 'NOT_FOUND';\n case 429:\n return 'RATE_LIMITED';\n case 500:\n case 502:\n case 503:\n return 'SERVER_ERROR';\n default:\n return 'UNKNOWN_ERROR';\n }\n }\n}\n\n// ============================================================\n// WebAuthn Browser Utilities\n// ============================================================\n\n/**\n * Convert an ArrayBuffer to a base64url-encoded string.\n * Used to encode credential IDs and other binary data for API calls.\n *\n * @param buffer - The ArrayBuffer to encode\n * @returns Base64url-encoded string\n *\n * @example\n * ```typescript\n * const encoded = arrayBufferToBase64Url(credential.rawId);\n * ```\n */\nexport function arrayBufferToBase64Url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Convert a base64url-encoded string to an ArrayBuffer.\n * Used to decode challenge and credential IDs from API responses.\n *\n * @param base64url - The base64url-encoded string\n * @returns ArrayBuffer\n *\n * @example\n * ```typescript\n * const challenge = base64UrlToArrayBuffer(options.challenge);\n * ```\n */\nexport function base64UrlToArrayBuffer(base64url: string): ArrayBuffer {\n // Convert base64url to base64\n const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');\n // Add padding if needed\n const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);\n // Decode\n const binary = atob(padded);\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/**\n * Prepare registration options for navigator.credentials.create().\n * Converts server response to the format expected by the browser API.\n *\n * @param options - Registration options from getPasskeyRegistrationOptions()\n * @returns PublicKeyCredentialCreationOptions for navigator.credentials.create()\n *\n * @example\n * ```typescript\n * const serverOptions = await auth.getPasskeyRegistrationOptions({ accessToken });\n * const browserOptions = prepareRegistrationOptions(serverOptions);\n * const credential = await navigator.credentials.create({ publicKey: browserOptions });\n * ```\n */\nexport function prepareRegistrationOptions(\n options: PasskeyRegistrationOptions\n): PublicKeyCredentialCreationOptions {\n return {\n challenge: base64UrlToArrayBuffer(options.challenge),\n rp: options.rp,\n user: {\n id: new TextEncoder().encode(options.user.id),\n name: options.user.name,\n displayName: options.user.displayName,\n },\n pubKeyCredParams: options.pubKeyCredParams,\n excludeCredentials: options.excludeCredentials.map((cred) => ({\n id: base64UrlToArrayBuffer(cred.id),\n type: cred.type,\n transports: cred.transports as AuthenticatorTransport[] | undefined,\n })),\n authenticatorSelection: options.authenticatorSelection,\n timeout: options.timeout,\n attestation: options.attestation,\n };\n}\n\n/**\n * Prepare authentication options for navigator.credentials.get().\n * Converts server response to the format expected by the browser API.\n *\n * @param options - Authentication options from getPasskeyAuthenticationOptions()\n * @returns PublicKeyCredentialRequestOptions for navigator.credentials.get()\n *\n * @example\n * ```typescript\n * const serverOptions = await auth.getPasskeyAuthenticationOptions();\n * const browserOptions = prepareAuthenticationOptions(serverOptions);\n * const credential = await navigator.credentials.get({ publicKey: browserOptions });\n * ```\n */\nexport function prepareAuthenticationOptions(\n options: PasskeyAuthenticationOptions\n): PublicKeyCredentialRequestOptions {\n return {\n challenge: base64UrlToArrayBuffer(options.challenge),\n rpId: options.rpId,\n allowCredentials: options.allowCredentials.map((cred) => ({\n id: base64UrlToArrayBuffer(cred.id),\n type: cred.type,\n transports: cred.transports as AuthenticatorTransport[] | undefined,\n })),\n userVerification: options.userVerification,\n timeout: options.timeout,\n };\n}\n\n/**\n * Format a registration credential response for the server.\n * Converts browser API response to the format expected by verifyPasskeyRegistration().\n *\n * @param credential - The credential from navigator.credentials.create()\n * @returns Formatted registration object for verifyPasskeyRegistration()\n *\n * @example\n * ```typescript\n * const credential = await navigator.credentials.create({ publicKey: options });\n * const registration = formatRegistrationResponse(credential as PublicKeyCredential);\n * await auth.verifyPasskeyRegistration({ accessToken, registration });\n * ```\n */\nexport function formatRegistrationResponse(credential: PublicKeyCredential): {\n id: string;\n rawId: string;\n type: 'public-key';\n response: {\n clientDataJSON: string;\n attestationObject: string;\n transports?: string[];\n };\n authenticatorAttachment?: 'platform' | 'cross-platform';\n} {\n const response = credential.response as AuthenticatorAttestationResponse;\n\n return {\n id: credential.id,\n rawId: arrayBufferToBase64Url(credential.rawId),\n type: 'public-key',\n response: {\n clientDataJSON: arrayBufferToBase64Url(response.clientDataJSON),\n attestationObject: arrayBufferToBase64Url(response.attestationObject),\n transports: response.getTransports?.() as string[] | undefined,\n },\n authenticatorAttachment: credential.authenticatorAttachment as\n | 'platform'\n | 'cross-platform'\n | undefined,\n };\n}\n\n/**\n * Format an authentication credential response for the server.\n * Converts browser API response to the format expected by verifyPasskeyAuthentication().\n *\n * @param credential - The credential from navigator.credentials.get()\n * @returns Formatted authentication object for verifyPasskeyAuthentication()\n *\n * @example\n * ```typescript\n * const credential = await navigator.credentials.get({ publicKey: options });\n * const authentication = formatAuthenticationResponse(credential as PublicKeyCredential);\n * const tokens = await auth.verifyPasskeyAuthentication({ authentication });\n * ```\n */\nexport function formatAuthenticationResponse(credential: PublicKeyCredential): {\n id: string;\n rawId: string;\n type: 'public-key';\n response: {\n clientDataJSON: string;\n authenticatorData: string;\n signature: string;\n userHandle: string | null;\n };\n authenticatorAttachment?: 'platform' | 'cross-platform';\n} {\n const response = credential.response as AuthenticatorAssertionResponse;\n\n return {\n id: credential.id,\n rawId: arrayBufferToBase64Url(credential.rawId),\n type: 'public-key',\n response: {\n clientDataJSON: arrayBufferToBase64Url(response.clientDataJSON),\n authenticatorData: arrayBufferToBase64Url(response.authenticatorData),\n signature: arrayBufferToBase64Url(response.signature),\n userHandle: response.userHandle ? arrayBufferToBase64Url(response.userHandle) : null,\n },\n authenticatorAttachment: credential.authenticatorAttachment as\n | 'platform'\n | 'cross-platform'\n | undefined,\n };\n}\n"],"mappings":";AAOA;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAgUA,IAAM,iBAAN,cAA6B,MAAM;AAAA,EACxC,YACE,SACgB,MACA,YAChB;AACA,UAAM,OAAO;AAHG;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;AAUA,IAAM,yBAAyB,KAAK,KAAK;AAgClC,IAAM,YAAN,MAAgB;AAAA,EACb;AAAA,EACA,YAA8B;AAAA,EAEtC,YAAY,QAA0B;AAEpC,UAAM,UAAU,OAAO,QAAQ,QAAQ,OAAO,EAAE;AAEhD,SAAK,SAAS;AAAA,MACZ;AAAA,MACA,SAAS,OAAO;AAAA,MAChB,cAAc,OAAO;AAAA,MACrB,cAAc,OAAO,gBAAgB;AAAA,IACvC;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,UAAkB;AACpB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA,EAGA,IAAI,UAAkB;AACpB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAAkB;AACpB,WAAO,GAAG,KAAK,OAAO,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,iBAA8B;AACpC,UAAM,MAAM,KAAK,IAAI;AAGrB,QAAI,KAAK,aAAa,MAAM,KAAK,UAAU,YAAY,KAAK,OAAO,cAAc;AAC/E,aAAO,KAAK,UAAU;AAAA,IACxB;AAGA,UAAM,UAAU,IAAI,IAAI,KAAK,OAAO;AACpC,UAAM,UAAU,mBAAmB,SAAS;AAAA;AAAA;AAAA,MAG1C,aAAa,KAAK,OAAO;AAAA,IAC3B,CAAC;AAED,SAAK,YAAY;AAAA,MACf;AAAA,MACA,WAAW;AAAA,IACb;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBA,MAAM,YAAY,OAAsC;AACtD,QAAI;AACF,YAAM,OAAO,KAAK,eAAe;AAEjC,YAAM,SAA8C,MAAM,UAAU,OAAO,MAAM;AAAA,QAC/E,YAAY,CAAC,OAAO;AAAA,QACpB,QAAQ,KAAK,OAAO;AAAA;AAAA,QAEpB,UAAU,KAAK,OAAO;AAAA,MACxB,CAAC;AAGD,YAAM,UAAU,OAAO;AACvB,UAAI,CAAC,QAAQ,OAAO,CAAC,QAAQ,SAAS,QAAQ,mBAAmB,QAAW;AAC1E,cAAM,IAAI,eAAe,oCAAoC,gBAAgB;AAAA,MAC/E;AAEA,aAAO;AAAA,QACL;AAAA,QACA,iBAAiB,OAAO;AAAA,MAC1B;AAAA,IACF,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB;AACnC,cAAM;AAAA,MACR;AAGA,UAAI,iBAAiB,OAAO;AAC1B,cAAM,UAAU,MAAM;AAEtB,YAAI,QAAQ,SAAS,SAAS,GAAG;AAC/B,gBAAM,IAAI,eAAe,qBAAqB,eAAe;AAAA,QAC/D;AACA,YAAI,QAAQ,SAAS,WAAW,GAAG;AACjC,gBAAM,IAAI,eAAe,2BAA2B,mBAAmB;AAAA,QACzE;AACA,YAAI,QAAQ,SAAS,QAAQ,GAAG;AAC9B,gBAAM,IAAI,eAAe,wBAAwB,gBAAgB;AAAA,QACnE;AACA,YAAI,QAAQ,SAAS,UAAU,GAAG;AAChC,gBAAM,IAAI,eAAe,0BAA0B,kBAAkB;AAAA,QACvE;AACA,YAAI,QAAQ,SAAS,WAAW,GAAG;AACjC,gBAAM,IAAI,eAAe,mBAAmB,iBAAiB;AAAA,QAC/D;AAEA,cAAM,IAAI,eAAe,8BAA8B,OAAO,IAAI,qBAAqB;AAAA,MACzF;AAEA,YAAM,IAAI,eAAe,6BAA6B,qBAAqB;AAAA,IAC7E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0BA,MAAM,aAAa,cAA8C;AAC/D,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO;AAElC,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,eAAe;AAAA,QACjB,CAAC;AAAA,MACH,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACxD,cAAM,eAAgB,UAAiC,SAAS;AAChE,cAAM,YAAY,KAAK,yBAAyB,SAAS,MAAM;AAE/D,cAAM,IAAI,eAAe,cAAc,WAAW,SAAS,MAAM;AAAA,MACnE;AAEA,YAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB;AACnC,cAAM;AAAA,MACR;AAEA,UAAI,iBAAiB,OAAO;AAC1B,cAAM,IAAI,eAAe,kBAAkB,MAAM,OAAO,IAAI,eAAe;AAAA,MAC7E;AAEA,YAAM,IAAI,eAAe,wBAAwB,gBAAgB;AAAA,IACnE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAuB;AACrB,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8BA,MAAM,OAAO,SAAiD;AAC5D,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO;AAElC,UAAM,OAA+B;AAAA,MACnC,OAAO,QAAQ;AAAA,MACf,UAAU,QAAQ;AAAA,MAClB,UAAU,KAAK,OAAO;AAAA,IACxB;AAEA,QAAI,QAAQ,aAAa;AACvB,WAAK,eAAe,QAAQ;AAAA,IAC9B;AAEA,WAAO,KAAK,YAA4B,KAAK,IAAI;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BA,MAAM,MAAM,SAA+C;AACzD,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO;AAElC,UAAM,OAAO;AAAA,MACX,OAAO,QAAQ;AAAA,MACf,UAAU,QAAQ;AAAA,MAClB,UAAU,KAAK,OAAO;AAAA,IACxB;AAEA,WAAO,KAAK,YAA2B,KAAK,IAAI;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA2BA,MAAM,YAAY,SAAqD;AACrE,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO;AAElC,UAAM,OAAO;AAAA,MACX,OAAO,QAAQ;AAAA,MACf,UAAU,KAAK,OAAO;AAAA,MACtB,MAAM;AAAA,IACR;AAEA,WAAO,KAAK,YAA2B,KAAK,IAAI;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAM,mBAAmB,SAA8D;AACrF,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO;AAElC,UAAM,OAA+B;AAAA,MACnC,OAAO,QAAQ;AAAA,MACf,UAAU,KAAK,OAAO;AAAA,IACxB;AAEA,QAAI,QAAQ,aAAa;AACvB,WAAK,eAAe,QAAQ;AAAA,IAC9B;AAEA,WAAO,KAAK,YAA6B,KAAK,IAAI;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAM,eAAe,SAA0D;AAC7E,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO;AAElC,UAAM,OAA+B;AAAA,MACnC,OAAO,QAAQ;AAAA,MACf,UAAU,KAAK,OAAO;AAAA,IACxB;AAEA,QAAI,QAAQ,aAAa;AACvB,WAAK,eAAe,QAAQ;AAAA,IAC9B;AAEA,WAAO,KAAK,YAA6B,KAAK,IAAI;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+BA,MAAM,cAAc,SAAyD;AAC3E,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO;AAElC,UAAM,OAAO;AAAA,MACX,OAAO,QAAQ;AAAA,MACf,UAAU,QAAQ;AAAA,MAClB,UAAU,KAAK,OAAO;AAAA,IACxB;AAEA,WAAO,KAAK,YAA6B,KAAK,IAAI;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,OAAO,cAAgD;AAC3D,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO;AAElC,UAAM,OAAO;AAAA,MACX,eAAe;AAAA,IACjB;AAEA,WAAO,KAAK,YAA6B,KAAK,IAAI;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA2BA,YAAY,SAAqC;AAC/C,UAAM,MAAM,IAAI,IAAI,GAAG,KAAK,OAAO,OAAO,UAAU,QAAQ,QAAQ,EAAE;AACtE,QAAI,aAAa,IAAI,YAAY,KAAK,OAAO,OAAO;AACpD,QAAI,aAAa,IAAI,gBAAgB,QAAQ,WAAW;AACxD,WAAO,IAAI,SAAS;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+CA,MAAM,8BACJ,SACqC;AACrC,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO;AAElC,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,eAAe,UAAU,QAAQ,WAAW;AAAA,QAC9C;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,UAAU,KAAK,OAAO;AAAA,QACxB,CAAC;AAAA,MACH,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,KAAK,oBAAoB,QAAQ;AAAA,MACzC;AAEA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB;AACnC,cAAM;AAAA,MACR;AACA,UAAI,iBAAiB,OAAO;AAC1B,cAAM,IAAI,eAAe,kBAAkB,MAAM,OAAO,IAAI,eAAe;AAAA,MAC7E;AACA,YAAM,IAAI,eAAe,kBAAkB,gBAAgB;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgCA,MAAM,0BACJ,SACsC;AACtC,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO;AAElC,UAAM,OAAgC;AAAA,MACpC,UAAU,KAAK,OAAO;AAAA,MACtB,cAAc,QAAQ;AAAA,IACxB;AAEA,QAAI,QAAQ,YAAY;AACtB,WAAK,cAAc,QAAQ;AAAA,IAC7B;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,eAAe,UAAU,QAAQ,WAAW;AAAA,QAC9C;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,KAAK,oBAAoB,QAAQ;AAAA,MACzC;AAEA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB;AACnC,cAAM;AAAA,MACR;AACA,UAAI,iBAAiB,OAAO;AAC1B,cAAM,IAAI,eAAe,kBAAkB,MAAM,OAAO,IAAI,eAAe;AAAA,MAC7E;AACA,YAAM,IAAI,eAAe,kBAAkB,gBAAgB;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuCA,MAAM,gCACJ,SACuC;AACvC,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO;AAElC,UAAM,OAA+B;AAAA,MACnC,UAAU,KAAK,OAAO;AAAA,IACxB;AAEA,QAAI,SAAS,OAAO;AAClB,WAAK,QAAQ,QAAQ;AAAA,IACvB;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,KAAK,oBAAoB,QAAQ;AAAA,MACzC;AAEA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB;AACnC,cAAM;AAAA,MACR;AACA,UAAI,iBAAiB,OAAO;AAC1B,cAAM,IAAI,eAAe,kBAAkB,MAAM,OAAO,IAAI,eAAe;AAAA,MAC7E;AACA,YAAM,IAAI,eAAe,kBAAkB,gBAAgB;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkCA,MAAM,4BACJ,SACwB;AACxB,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO;AAElC,UAAM,OAAO;AAAA,MACX,UAAU,KAAK,OAAO;AAAA,MACtB,gBAAgB,QAAQ;AAAA,IAC1B;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,KAAK,oBAAoB,QAAQ;AAAA,MACzC;AAEA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB;AACnC,cAAM;AAAA,MACR;AACA,UAAI,iBAAiB,OAAO;AAC1B,cAAM,IAAI,eAAe,kBAAkB,MAAM,OAAO,IAAI,eAAe;AAAA,MAC7E;AACA,YAAM,IAAI,eAAe,kBAAkB,gBAAgB;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,oBAAoB,UAAoC;AACpE,UAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACxD,UAAM,YAAY;AAElB,QAAI;AACJ,QAAI;AAEJ,QAAI,OAAO,UAAU,UAAU,YAAY,UAAU,OAAO;AAC1D,kBAAY,UAAU,MAAM,QAAQ,KAAK,yBAAyB,SAAS,MAAM;AACjF,qBAAe,UAAU,MAAM,WAAW;AAAA,IAC5C,OAAO;AACL,kBAAY,KAAK,yBAAyB,SAAS,MAAM;AACzD,qBAAe,OAAO,UAAU,UAAU,WAAW,UAAU,QAAQ;AAAA,IACzE;AAEA,UAAM,IAAI,eAAe,cAAc,WAAW,SAAS,MAAM;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuCA,MAAM,kBAAkB,MAAsC;AAC5D,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO;AAElC,UAAM,OAAO;AAAA,MACX;AAAA,MACA,UAAU,KAAK,OAAO;AAAA,IACxB;AAEA,WAAO,KAAK,YAA2B,KAAK,IAAI;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBA,MAAM,aAAa,eAAmD;AAEpE,UAAM,WAAW,MAAM,KAAK,YAAY,cAAc,YAAY;AAElE,UAAM,OAAa;AAAA,MACjB,IAAI,SAAS,QAAQ;AAAA,MACrB,OAAO,SAAS,QAAQ;AAAA,MACxB,eAAe,SAAS,QAAQ;AAAA,IAClC;AAEA,UAAM,UAAmB;AAAA,MACvB,aAAa,cAAc;AAAA,MAC3B,cAAc,cAAc;AAAA,MAC5B,WAAW,IAAI,KAAK,KAAK,IAAI,IAAI,cAAc,aAAa,GAAI;AAAA,IAClE;AAEA,UAAM,SAAqB,EAAE,MAAM,QAAQ;AAE3C,QAAI,cAAc,gBAAgB,QAAW;AAC3C,aAAO,YAAY,cAAc;AAAA,IACnC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgCA,MAAM,gBAAgB,SAA2D;AAC/E,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO;AAElC,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,eAAe,UAAU,QAAQ,WAAW;AAAA,QAC9C;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,UAAU,QAAQ;AAAA,QACpB,CAAC;AAAA,MACH,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACxD,cAAM,YAAY;AAElB,YAAI;AACJ,YAAI;AAEJ,YAAI,OAAO,UAAU,UAAU,YAAY,UAAU,OAAO;AAC1D,sBAAY,UAAU,MAAM,QAAQ,KAAK,yBAAyB,SAAS,MAAM;AACjF,yBAAe,UAAU,MAAM,WAAW;AAAA,QAC5C,OAAO;AACL,sBAAY,KAAK,yBAAyB,SAAS,MAAM;AACzD,yBAAe,OAAO,UAAU,UAAU,WAAW,UAAU,QAAQ;AAAA,QACzE;AAEA,cAAM,IAAI,eAAe,cAAc,WAAW,SAAS,MAAM;AAAA,MACnE;AAEA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB;AACnC,cAAM;AAAA,MACR;AAEA,UAAI,iBAAiB,OAAO;AAC1B,cAAM,IAAI,eAAe,kBAAkB,MAAM,OAAO,IAAI,eAAe;AAAA,MAC7E;AAEA,YAAM,IAAI,eAAe,kBAAkB,gBAAgB;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,YAAe,KAAa,MAA2C;AACnF,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACxD,cAAM,YAAY;AAElB,YAAI;AACJ,YAAI;AAEJ,YAAI,OAAO,UAAU,UAAU,YAAY,UAAU,OAAO;AAC1D,sBAAY,UAAU,MAAM,QAAQ,KAAK,yBAAyB,SAAS,MAAM;AACjF,yBAAe,UAAU,MAAM,WAAW;AAAA,QAC5C,OAAO;AACL,sBAAY,KAAK,yBAAyB,SAAS,MAAM;AACzD,yBAAe,OAAO,UAAU,UAAU,WAAW,UAAU,QAAQ;AAAA,QACzE;AAEA,cAAM,IAAI,eAAe,cAAc,WAAW,SAAS,MAAM;AAAA,MACnE;AAEA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB;AACnC,cAAM;AAAA,MACR;AAEA,UAAI,iBAAiB,OAAO;AAC1B,cAAM,IAAI,eAAe,kBAAkB,MAAM,OAAO,IAAI,eAAe;AAAA,MAC7E;AAEA,YAAM,IAAI,eAAe,kBAAkB,gBAAgB;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,yBAAyB,QAAwB;AACvD,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,IAAI,QAAwB;AAC1B,QAAI,CAAC,KAAK,OAAO,cAAc;AAC7B,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,WAAO,IAAI,eAAe,KAAK,MAAM;AAAA,EACvC;AACF;AAcA,IAAM,iBAAN,MAAqB;AAAA,EACX;AAAA,EAER,YAAY,QAAwB;AAClC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BA,MAAM,WAAW,SAAsD;AACrE,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO,WAAW,KAAK,OAAO,OAAO,UAAU,QAAQ,MAAM;AAExF,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,eAAe,eAAe,KAAK,OAAO,YAAY;AAAA,QACxD;AAAA,MACF,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACxD,cAAM,YAAY;AAElB,YAAI;AACJ,YAAI;AAEJ,YAAI,OAAO,UAAU,UAAU,YAAY,UAAU,OAAO;AAC1D,sBAAY,UAAU,MAAM,QAAQ,KAAK,yBAAyB,SAAS,MAAM;AACjF,yBAAe,UAAU,MAAM,WAAW;AAAA,QAC5C,OAAO;AACL,sBAAY,KAAK,yBAAyB,SAAS,MAAM;AACzD,yBAAe,OAAO,UAAU,UAAU,WAAW,UAAU,QAAQ;AAAA,QACzE;AAEA,cAAM,IAAI,eAAe,cAAc,WAAW,SAAS,MAAM;AAAA,MACnE;AAEA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB;AACnC,cAAM;AAAA,MACR;AAEA,UAAI,iBAAiB,OAAO;AAC1B,cAAM,IAAI,eAAe,kBAAkB,MAAM,OAAO,IAAI,eAAe;AAAA,MAC7E;AAEA,YAAM,IAAI,eAAe,kBAAkB,gBAAgB;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,yBAAyB,QAAwB;AACvD,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AACF;AAkBO,SAAS,uBAAuB,QAA6B;AAClE,QAAM,QAAQ,IAAI,WAAW,MAAM;AACnC,MAAI,SAAS;AACb,aAAW,QAAQ,OAAO;AACxB,cAAU,OAAO,aAAa,IAAI;AAAA,EACpC;AACA,SAAO,KAAK,MAAM,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AAC/E;AAcO,SAAS,uBAAuB,WAAgC;AAErE,QAAM,SAAS,UAAU,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAE7D,QAAM,SAAS,SAAS,IAAI,QAAQ,IAAK,OAAO,SAAS,KAAM,CAAC;AAEhE,QAAM,SAAS,KAAK,MAAM;AAC1B,QAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,EAChC;AACA,SAAO,MAAM;AACf;AAgBO,SAAS,2BACd,SACoC;AACpC,SAAO;AAAA,IACL,WAAW,uBAAuB,QAAQ,SAAS;AAAA,IACnD,IAAI,QAAQ;AAAA,IACZ,MAAM;AAAA,MACJ,IAAI,IAAI,YAAY,EAAE,OAAO,QAAQ,KAAK,EAAE;AAAA,MAC5C,MAAM,QAAQ,KAAK;AAAA,MACnB,aAAa,QAAQ,KAAK;AAAA,IAC5B;AAAA,IACA,kBAAkB,QAAQ;AAAA,IAC1B,oBAAoB,QAAQ,mBAAmB,IAAI,CAAC,UAAU;AAAA,MAC5D,IAAI,uBAAuB,KAAK,EAAE;AAAA,MAClC,MAAM,KAAK;AAAA,MACX,YAAY,KAAK;AAAA,IACnB,EAAE;AAAA,IACF,wBAAwB,QAAQ;AAAA,IAChC,SAAS,QAAQ;AAAA,IACjB,aAAa,QAAQ;AAAA,EACvB;AACF;AAgBO,SAAS,6BACd,SACmC;AACnC,SAAO;AAAA,IACL,WAAW,uBAAuB,QAAQ,SAAS;AAAA,IACnD,MAAM,QAAQ;AAAA,IACd,kBAAkB,QAAQ,iBAAiB,IAAI,CAAC,UAAU;AAAA,MACxD,IAAI,uBAAuB,KAAK,EAAE;AAAA,MAClC,MAAM,KAAK;AAAA,MACX,YAAY,KAAK;AAAA,IACnB,EAAE;AAAA,IACF,kBAAkB,QAAQ;AAAA,IAC1B,SAAS,QAAQ;AAAA,EACnB;AACF;AAgBO,SAAS,2BAA2B,YAUzC;AACA,QAAM,WAAW,WAAW;AAE5B,SAAO;AAAA,IACL,IAAI,WAAW;AAAA,IACf,OAAO,uBAAuB,WAAW,KAAK;AAAA,IAC9C,MAAM;AAAA,IACN,UAAU;AAAA,MACR,gBAAgB,uBAAuB,SAAS,cAAc;AAAA,MAC9D,mBAAmB,uBAAuB,SAAS,iBAAiB;AAAA,MACpE,YAAY,SAAS,gBAAgB;AAAA,IACvC;AAAA,IACA,yBAAyB,WAAW;AAAA,EAItC;AACF;AAgBO,SAAS,6BAA6B,YAW3C;AACA,QAAM,WAAW,WAAW;AAE5B,SAAO;AAAA,IACL,IAAI,WAAW;AAAA,IACf,OAAO,uBAAuB,WAAW,KAAK;AAAA,IAC9C,MAAM;AAAA,IACN,UAAU;AAAA,MACR,gBAAgB,uBAAuB,SAAS,cAAc;AAAA,MAC9D,mBAAmB,uBAAuB,SAAS,iBAAiB;AAAA,MACpE,WAAW,uBAAuB,SAAS,SAAS;AAAA,MACpD,YAAY,SAAS,aAAa,uBAAuB,SAAS,UAAU,IAAI;AAAA,IAClF;AAAA,IACA,yBAAyB,WAAW;AAAA,EAItC;AACF;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
OvixaAuthError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5ZWKDQQM.js";
|
|
4
4
|
|
|
5
5
|
// src/middleware/types.ts
|
|
6
6
|
var DEFAULT_COOKIE_OPTIONS = {
|
|
@@ -140,4 +140,4 @@ export {
|
|
|
140
140
|
setAuthCookies,
|
|
141
141
|
clearAuthCookies
|
|
142
142
|
};
|
|
143
|
-
//# sourceMappingURL=chunk-
|
|
143
|
+
//# sourceMappingURL=chunk-WON3EB4B.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -172,6 +172,119 @@ interface ResetPasswordOptions {
|
|
|
172
172
|
* Supported OAuth providers.
|
|
173
173
|
*/
|
|
174
174
|
type OAuthProvider = 'google' | 'github';
|
|
175
|
+
/**
|
|
176
|
+
* Options for getting passkey registration options.
|
|
177
|
+
*/
|
|
178
|
+
interface GetPasskeyRegistrationOptionsOptions {
|
|
179
|
+
/** Access token (user must be logged in to register a passkey) */
|
|
180
|
+
accessToken: string;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Registration options returned from the server.
|
|
184
|
+
* These are passed directly to navigator.credentials.create().
|
|
185
|
+
*/
|
|
186
|
+
interface PasskeyRegistrationOptions {
|
|
187
|
+
challenge: string;
|
|
188
|
+
rp: {
|
|
189
|
+
id: string;
|
|
190
|
+
name: string;
|
|
191
|
+
};
|
|
192
|
+
user: {
|
|
193
|
+
id: string;
|
|
194
|
+
name: string;
|
|
195
|
+
displayName: string;
|
|
196
|
+
};
|
|
197
|
+
pubKeyCredParams: {
|
|
198
|
+
type: 'public-key';
|
|
199
|
+
alg: number;
|
|
200
|
+
}[];
|
|
201
|
+
excludeCredentials: {
|
|
202
|
+
id: string;
|
|
203
|
+
type: 'public-key';
|
|
204
|
+
transports?: string[];
|
|
205
|
+
}[];
|
|
206
|
+
authenticatorSelection: {
|
|
207
|
+
authenticatorAttachment?: 'platform' | 'cross-platform';
|
|
208
|
+
residentKey: 'discouraged' | 'preferred' | 'required';
|
|
209
|
+
userVerification: 'discouraged' | 'preferred' | 'required';
|
|
210
|
+
};
|
|
211
|
+
timeout: number;
|
|
212
|
+
attestation: 'none';
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Options for verifying passkey registration.
|
|
216
|
+
*/
|
|
217
|
+
interface VerifyPasskeyRegistrationOptions {
|
|
218
|
+
/** Access token (user must be logged in) */
|
|
219
|
+
accessToken: string;
|
|
220
|
+
/** The registration response from navigator.credentials.create() */
|
|
221
|
+
registration: {
|
|
222
|
+
id: string;
|
|
223
|
+
rawId: string;
|
|
224
|
+
type: 'public-key';
|
|
225
|
+
response: {
|
|
226
|
+
clientDataJSON: string;
|
|
227
|
+
attestationObject: string;
|
|
228
|
+
authenticatorData?: string;
|
|
229
|
+
transports?: string[];
|
|
230
|
+
publicKeyAlgorithm?: number;
|
|
231
|
+
publicKey?: string;
|
|
232
|
+
};
|
|
233
|
+
authenticatorAttachment?: 'platform' | 'cross-platform';
|
|
234
|
+
clientExtensionResults?: Record<string, unknown>;
|
|
235
|
+
};
|
|
236
|
+
/** Optional friendly name for the passkey */
|
|
237
|
+
deviceName?: string;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Response from passkey registration verification.
|
|
241
|
+
*/
|
|
242
|
+
interface PasskeyRegistrationResponse {
|
|
243
|
+
success: boolean;
|
|
244
|
+
credential_id: string;
|
|
245
|
+
device_name?: string;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Options for getting passkey authentication options.
|
|
249
|
+
*/
|
|
250
|
+
interface GetPasskeyAuthenticationOptionsOptions {
|
|
251
|
+
/** Optional email for allowCredentials hint (for non-discoverable credentials) */
|
|
252
|
+
email?: string;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Authentication options returned from the server.
|
|
256
|
+
* These are passed directly to navigator.credentials.get().
|
|
257
|
+
*/
|
|
258
|
+
interface PasskeyAuthenticationOptions {
|
|
259
|
+
challenge: string;
|
|
260
|
+
rpId: string;
|
|
261
|
+
allowCredentials: {
|
|
262
|
+
id: string;
|
|
263
|
+
type: 'public-key';
|
|
264
|
+
transports?: string[];
|
|
265
|
+
}[];
|
|
266
|
+
userVerification: 'discouraged' | 'preferred' | 'required';
|
|
267
|
+
timeout: number;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Options for verifying passkey authentication.
|
|
271
|
+
*/
|
|
272
|
+
interface VerifyPasskeyAuthenticationOptions {
|
|
273
|
+
/** The authentication response from navigator.credentials.get() */
|
|
274
|
+
authentication: {
|
|
275
|
+
id: string;
|
|
276
|
+
rawId: string;
|
|
277
|
+
type: 'public-key';
|
|
278
|
+
response: {
|
|
279
|
+
clientDataJSON: string;
|
|
280
|
+
authenticatorData: string;
|
|
281
|
+
signature: string;
|
|
282
|
+
userHandle?: string | null;
|
|
283
|
+
};
|
|
284
|
+
authenticatorAttachment?: 'platform' | 'cross-platform';
|
|
285
|
+
clientExtensionResults?: Record<string, unknown>;
|
|
286
|
+
};
|
|
287
|
+
}
|
|
175
288
|
/**
|
|
176
289
|
* Options for generating OAuth URL.
|
|
177
290
|
*/
|
|
@@ -491,6 +604,155 @@ declare class OvixaAuth {
|
|
|
491
604
|
* ```
|
|
492
605
|
*/
|
|
493
606
|
getOAuthUrl(options: GetOAuthUrlOptions): string;
|
|
607
|
+
/**
|
|
608
|
+
* Get registration options for creating a new passkey.
|
|
609
|
+
*
|
|
610
|
+
* The user must be logged in to register a passkey. Use the returned options
|
|
611
|
+
* with navigator.credentials.create() to create the credential.
|
|
612
|
+
*
|
|
613
|
+
* @param options - Registration options with access token
|
|
614
|
+
* @returns Registration options to pass to navigator.credentials.create()
|
|
615
|
+
* @throws {OvixaAuthError} If request fails
|
|
616
|
+
*
|
|
617
|
+
* @example
|
|
618
|
+
* ```typescript
|
|
619
|
+
* // User must be logged in
|
|
620
|
+
* const options = await auth.getPasskeyRegistrationOptions({
|
|
621
|
+
* accessToken: session.accessToken,
|
|
622
|
+
* });
|
|
623
|
+
*
|
|
624
|
+
* // Create the credential using the browser API
|
|
625
|
+
* const credential = await navigator.credentials.create({
|
|
626
|
+
* publicKey: {
|
|
627
|
+
* ...options,
|
|
628
|
+
* challenge: base64UrlToArrayBuffer(options.challenge),
|
|
629
|
+
* user: {
|
|
630
|
+
* ...options.user,
|
|
631
|
+
* id: new TextEncoder().encode(options.user.id),
|
|
632
|
+
* },
|
|
633
|
+
* excludeCredentials: options.excludeCredentials.map(c => ({
|
|
634
|
+
* ...c,
|
|
635
|
+
* id: base64UrlToArrayBuffer(c.id),
|
|
636
|
+
* })),
|
|
637
|
+
* },
|
|
638
|
+
* });
|
|
639
|
+
*
|
|
640
|
+
* // Verify the registration with the server
|
|
641
|
+
* await auth.verifyPasskeyRegistration({
|
|
642
|
+
* accessToken: session.accessToken,
|
|
643
|
+
* registration: formatRegistrationResponse(credential),
|
|
644
|
+
* deviceName: 'My MacBook',
|
|
645
|
+
* });
|
|
646
|
+
* ```
|
|
647
|
+
*/
|
|
648
|
+
getPasskeyRegistrationOptions(options: GetPasskeyRegistrationOptionsOptions): Promise<PasskeyRegistrationOptions>;
|
|
649
|
+
/**
|
|
650
|
+
* Verify a passkey registration and store the credential.
|
|
651
|
+
*
|
|
652
|
+
* Call this after navigator.credentials.create() succeeds to complete the
|
|
653
|
+
* registration on the server.
|
|
654
|
+
*
|
|
655
|
+
* @param options - Verification options with registration response
|
|
656
|
+
* @returns Registration response with credential ID
|
|
657
|
+
* @throws {OvixaAuthError} If verification fails
|
|
658
|
+
*
|
|
659
|
+
* @example
|
|
660
|
+
* ```typescript
|
|
661
|
+
* const result = await auth.verifyPasskeyRegistration({
|
|
662
|
+
* accessToken: session.accessToken,
|
|
663
|
+
* registration: {
|
|
664
|
+
* id: credential.id,
|
|
665
|
+
* rawId: arrayBufferToBase64Url(credential.rawId),
|
|
666
|
+
* type: 'public-key',
|
|
667
|
+
* response: {
|
|
668
|
+
* clientDataJSON: arrayBufferToBase64Url(credential.response.clientDataJSON),
|
|
669
|
+
* attestationObject: arrayBufferToBase64Url(credential.response.attestationObject),
|
|
670
|
+
* transports: credential.response.getTransports?.(),
|
|
671
|
+
* },
|
|
672
|
+
* },
|
|
673
|
+
* deviceName: 'My MacBook',
|
|
674
|
+
* });
|
|
675
|
+
*
|
|
676
|
+
* console.log('Passkey registered:', result.credential_id);
|
|
677
|
+
* ```
|
|
678
|
+
*/
|
|
679
|
+
verifyPasskeyRegistration(options: VerifyPasskeyRegistrationOptions): Promise<PasskeyRegistrationResponse>;
|
|
680
|
+
/**
|
|
681
|
+
* Get authentication options for signing in with a passkey.
|
|
682
|
+
*
|
|
683
|
+
* Use the returned options with navigator.credentials.get() to authenticate.
|
|
684
|
+
*
|
|
685
|
+
* @param options - Optional email for allowCredentials hint
|
|
686
|
+
* @returns Authentication options to pass to navigator.credentials.get()
|
|
687
|
+
* @throws {OvixaAuthError} If request fails
|
|
688
|
+
*
|
|
689
|
+
* @example
|
|
690
|
+
* ```typescript
|
|
691
|
+
* // Option 1: Discoverable credentials (no email needed)
|
|
692
|
+
* const options = await auth.getPasskeyAuthenticationOptions();
|
|
693
|
+
*
|
|
694
|
+
* // Option 2: Provide email for allowCredentials hint
|
|
695
|
+
* const options = await auth.getPasskeyAuthenticationOptions({
|
|
696
|
+
* email: 'user@example.com',
|
|
697
|
+
* });
|
|
698
|
+
*
|
|
699
|
+
* // Authenticate using the browser API
|
|
700
|
+
* const credential = await navigator.credentials.get({
|
|
701
|
+
* publicKey: {
|
|
702
|
+
* ...options,
|
|
703
|
+
* challenge: base64UrlToArrayBuffer(options.challenge),
|
|
704
|
+
* allowCredentials: options.allowCredentials.map(c => ({
|
|
705
|
+
* ...c,
|
|
706
|
+
* id: base64UrlToArrayBuffer(c.id),
|
|
707
|
+
* })),
|
|
708
|
+
* },
|
|
709
|
+
* });
|
|
710
|
+
*
|
|
711
|
+
* // Verify and get tokens
|
|
712
|
+
* const tokens = await auth.verifyPasskeyAuthentication({
|
|
713
|
+
* authentication: formatAuthenticationResponse(credential),
|
|
714
|
+
* });
|
|
715
|
+
* ```
|
|
716
|
+
*/
|
|
717
|
+
getPasskeyAuthenticationOptions(options?: GetPasskeyAuthenticationOptionsOptions): Promise<PasskeyAuthenticationOptions>;
|
|
718
|
+
/**
|
|
719
|
+
* Verify a passkey authentication and get tokens.
|
|
720
|
+
*
|
|
721
|
+
* Call this after navigator.credentials.get() succeeds to complete the
|
|
722
|
+
* authentication and receive access/refresh tokens.
|
|
723
|
+
*
|
|
724
|
+
* @param options - Verification options with authentication response
|
|
725
|
+
* @returns Token response with access and refresh tokens
|
|
726
|
+
* @throws {OvixaAuthError} If verification fails
|
|
727
|
+
*
|
|
728
|
+
* @example
|
|
729
|
+
* ```typescript
|
|
730
|
+
* const tokens = await auth.verifyPasskeyAuthentication({
|
|
731
|
+
* authentication: {
|
|
732
|
+
* id: credential.id,
|
|
733
|
+
* rawId: arrayBufferToBase64Url(credential.rawId),
|
|
734
|
+
* type: 'public-key',
|
|
735
|
+
* response: {
|
|
736
|
+
* clientDataJSON: arrayBufferToBase64Url(credential.response.clientDataJSON),
|
|
737
|
+
* authenticatorData: arrayBufferToBase64Url(credential.response.authenticatorData),
|
|
738
|
+
* signature: arrayBufferToBase64Url(credential.response.signature),
|
|
739
|
+
* userHandle: credential.response.userHandle
|
|
740
|
+
* ? arrayBufferToBase64Url(credential.response.userHandle)
|
|
741
|
+
* : null,
|
|
742
|
+
* },
|
|
743
|
+
* },
|
|
744
|
+
* });
|
|
745
|
+
*
|
|
746
|
+
* // Use the tokens
|
|
747
|
+
* console.log('Logged in!', tokens.access_token);
|
|
748
|
+
* ```
|
|
749
|
+
*/
|
|
750
|
+
verifyPasskeyAuthentication(options: VerifyPasskeyAuthenticationOptions): Promise<TokenResponse>;
|
|
751
|
+
/**
|
|
752
|
+
* Handle error response from the server.
|
|
753
|
+
* @throws {OvixaAuthError} Always throws with appropriate error details
|
|
754
|
+
*/
|
|
755
|
+
private handleErrorResponse;
|
|
494
756
|
/**
|
|
495
757
|
* Exchange an OAuth authorization code for tokens.
|
|
496
758
|
*
|
|
@@ -659,5 +921,112 @@ declare class OvixaAuthAdmin {
|
|
|
659
921
|
*/
|
|
660
922
|
private mapHttpStatusToErrorCode;
|
|
661
923
|
}
|
|
924
|
+
/**
|
|
925
|
+
* Convert an ArrayBuffer to a base64url-encoded string.
|
|
926
|
+
* Used to encode credential IDs and other binary data for API calls.
|
|
927
|
+
*
|
|
928
|
+
* @param buffer - The ArrayBuffer to encode
|
|
929
|
+
* @returns Base64url-encoded string
|
|
930
|
+
*
|
|
931
|
+
* @example
|
|
932
|
+
* ```typescript
|
|
933
|
+
* const encoded = arrayBufferToBase64Url(credential.rawId);
|
|
934
|
+
* ```
|
|
935
|
+
*/
|
|
936
|
+
declare function arrayBufferToBase64Url(buffer: ArrayBuffer): string;
|
|
937
|
+
/**
|
|
938
|
+
* Convert a base64url-encoded string to an ArrayBuffer.
|
|
939
|
+
* Used to decode challenge and credential IDs from API responses.
|
|
940
|
+
*
|
|
941
|
+
* @param base64url - The base64url-encoded string
|
|
942
|
+
* @returns ArrayBuffer
|
|
943
|
+
*
|
|
944
|
+
* @example
|
|
945
|
+
* ```typescript
|
|
946
|
+
* const challenge = base64UrlToArrayBuffer(options.challenge);
|
|
947
|
+
* ```
|
|
948
|
+
*/
|
|
949
|
+
declare function base64UrlToArrayBuffer(base64url: string): ArrayBuffer;
|
|
950
|
+
/**
|
|
951
|
+
* Prepare registration options for navigator.credentials.create().
|
|
952
|
+
* Converts server response to the format expected by the browser API.
|
|
953
|
+
*
|
|
954
|
+
* @param options - Registration options from getPasskeyRegistrationOptions()
|
|
955
|
+
* @returns PublicKeyCredentialCreationOptions for navigator.credentials.create()
|
|
956
|
+
*
|
|
957
|
+
* @example
|
|
958
|
+
* ```typescript
|
|
959
|
+
* const serverOptions = await auth.getPasskeyRegistrationOptions({ accessToken });
|
|
960
|
+
* const browserOptions = prepareRegistrationOptions(serverOptions);
|
|
961
|
+
* const credential = await navigator.credentials.create({ publicKey: browserOptions });
|
|
962
|
+
* ```
|
|
963
|
+
*/
|
|
964
|
+
declare function prepareRegistrationOptions(options: PasskeyRegistrationOptions): PublicKeyCredentialCreationOptions;
|
|
965
|
+
/**
|
|
966
|
+
* Prepare authentication options for navigator.credentials.get().
|
|
967
|
+
* Converts server response to the format expected by the browser API.
|
|
968
|
+
*
|
|
969
|
+
* @param options - Authentication options from getPasskeyAuthenticationOptions()
|
|
970
|
+
* @returns PublicKeyCredentialRequestOptions for navigator.credentials.get()
|
|
971
|
+
*
|
|
972
|
+
* @example
|
|
973
|
+
* ```typescript
|
|
974
|
+
* const serverOptions = await auth.getPasskeyAuthenticationOptions();
|
|
975
|
+
* const browserOptions = prepareAuthenticationOptions(serverOptions);
|
|
976
|
+
* const credential = await navigator.credentials.get({ publicKey: browserOptions });
|
|
977
|
+
* ```
|
|
978
|
+
*/
|
|
979
|
+
declare function prepareAuthenticationOptions(options: PasskeyAuthenticationOptions): PublicKeyCredentialRequestOptions;
|
|
980
|
+
/**
|
|
981
|
+
* Format a registration credential response for the server.
|
|
982
|
+
* Converts browser API response to the format expected by verifyPasskeyRegistration().
|
|
983
|
+
*
|
|
984
|
+
* @param credential - The credential from navigator.credentials.create()
|
|
985
|
+
* @returns Formatted registration object for verifyPasskeyRegistration()
|
|
986
|
+
*
|
|
987
|
+
* @example
|
|
988
|
+
* ```typescript
|
|
989
|
+
* const credential = await navigator.credentials.create({ publicKey: options });
|
|
990
|
+
* const registration = formatRegistrationResponse(credential as PublicKeyCredential);
|
|
991
|
+
* await auth.verifyPasskeyRegistration({ accessToken, registration });
|
|
992
|
+
* ```
|
|
993
|
+
*/
|
|
994
|
+
declare function formatRegistrationResponse(credential: PublicKeyCredential): {
|
|
995
|
+
id: string;
|
|
996
|
+
rawId: string;
|
|
997
|
+
type: 'public-key';
|
|
998
|
+
response: {
|
|
999
|
+
clientDataJSON: string;
|
|
1000
|
+
attestationObject: string;
|
|
1001
|
+
transports?: string[];
|
|
1002
|
+
};
|
|
1003
|
+
authenticatorAttachment?: 'platform' | 'cross-platform';
|
|
1004
|
+
};
|
|
1005
|
+
/**
|
|
1006
|
+
* Format an authentication credential response for the server.
|
|
1007
|
+
* Converts browser API response to the format expected by verifyPasskeyAuthentication().
|
|
1008
|
+
*
|
|
1009
|
+
* @param credential - The credential from navigator.credentials.get()
|
|
1010
|
+
* @returns Formatted authentication object for verifyPasskeyAuthentication()
|
|
1011
|
+
*
|
|
1012
|
+
* @example
|
|
1013
|
+
* ```typescript
|
|
1014
|
+
* const credential = await navigator.credentials.get({ publicKey: options });
|
|
1015
|
+
* const authentication = formatAuthenticationResponse(credential as PublicKeyCredential);
|
|
1016
|
+
* const tokens = await auth.verifyPasskeyAuthentication({ authentication });
|
|
1017
|
+
* ```
|
|
1018
|
+
*/
|
|
1019
|
+
declare function formatAuthenticationResponse(credential: PublicKeyCredential): {
|
|
1020
|
+
id: string;
|
|
1021
|
+
rawId: string;
|
|
1022
|
+
type: 'public-key';
|
|
1023
|
+
response: {
|
|
1024
|
+
clientDataJSON: string;
|
|
1025
|
+
authenticatorData: string;
|
|
1026
|
+
signature: string;
|
|
1027
|
+
userHandle: string | null;
|
|
1028
|
+
};
|
|
1029
|
+
authenticatorAttachment?: 'platform' | 'cross-platform';
|
|
1030
|
+
};
|
|
662
1031
|
|
|
663
|
-
export { type AccessTokenPayload, type AuthClientConfig, type AuthResult, type DeleteMyAccountOptions, type DeleteUserOptions, type ForgotPasswordOptions, type GetOAuthUrlOptions, type LoginOptions, type OAuthProvider, OvixaAuth, OvixaAuthError, type ResendVerificationOptions, type ResetPasswordOptions, type Session, type SignupOptions, type SignupResponse, type SuccessResponse, type TokenResponse, type User, type VerifyEmailOptions, type VerifyResult };
|
|
1032
|
+
export { type AccessTokenPayload, type AuthClientConfig, type AuthResult, type DeleteMyAccountOptions, type DeleteUserOptions, type ForgotPasswordOptions, type GetOAuthUrlOptions, type GetPasskeyAuthenticationOptionsOptions, type GetPasskeyRegistrationOptionsOptions, type LoginOptions, type OAuthProvider, OvixaAuth, OvixaAuthError, type PasskeyAuthenticationOptions, type PasskeyRegistrationOptions, type PasskeyRegistrationResponse, type ResendVerificationOptions, type ResetPasswordOptions, type Session, type SignupOptions, type SignupResponse, type SuccessResponse, type TokenResponse, type User, type VerifyEmailOptions, type VerifyPasskeyAuthenticationOptions, type VerifyPasskeyRegistrationOptions, type VerifyResult, arrayBufferToBase64Url, base64UrlToArrayBuffer, formatAuthenticationResponse, formatRegistrationResponse, prepareAuthenticationOptions, prepareRegistrationOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
OvixaAuth,
|
|
3
|
-
OvixaAuthError
|
|
4
|
-
|
|
3
|
+
OvixaAuthError,
|
|
4
|
+
arrayBufferToBase64Url,
|
|
5
|
+
base64UrlToArrayBuffer,
|
|
6
|
+
formatAuthenticationResponse,
|
|
7
|
+
formatRegistrationResponse,
|
|
8
|
+
prepareAuthenticationOptions,
|
|
9
|
+
prepareRegistrationOptions
|
|
10
|
+
} from "./chunk-5ZWKDQQM.js";
|
|
5
11
|
export {
|
|
6
12
|
OvixaAuth,
|
|
7
|
-
OvixaAuthError
|
|
13
|
+
OvixaAuthError,
|
|
14
|
+
arrayBufferToBase64Url,
|
|
15
|
+
base64UrlToArrayBuffer,
|
|
16
|
+
formatAuthenticationResponse,
|
|
17
|
+
formatRegistrationResponse,
|
|
18
|
+
prepareAuthenticationOptions,
|
|
19
|
+
prepareRegistrationOptions
|
|
8
20
|
};
|
|
9
21
|
//# sourceMappingURL=index.js.map
|
package/dist/middleware/astro.js
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
clearAuthCookies,
|
|
4
4
|
isPublicRoute,
|
|
5
5
|
setAuthCookies
|
|
6
|
-
} from "../chunk-
|
|
7
|
-
import "../chunk-
|
|
6
|
+
} from "../chunk-WON3EB4B.js";
|
|
7
|
+
import "../chunk-5ZWKDQQM.js";
|
|
8
8
|
|
|
9
9
|
// src/middleware/express.ts
|
|
10
10
|
var ExpressCookieAdapter = class {
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @ovixa/auth-client\n *\n * Client SDK for Ovixa Auth service.\n * Provides authentication, token verification, and session management.\n */\n\nimport {\n createRemoteJWKSet,\n jwtVerify,\n type JWTPayload,\n type JWTVerifyResult,\n type JWTHeaderParameters,\n} from 'jose';\n\n/**\n * Configuration options for the OvixaAuth client.\n */\nexport interface AuthClientConfig {\n /** The base URL of the Ovixa Auth service */\n authUrl: string;\n /** The realm ID to authenticate against */\n realmId: string;\n /** Your application's client secret (for server-side use only) */\n clientSecret?: string;\n /** Cache duration for JWKS in milliseconds (defaults to 1 hour) */\n jwksCacheTtl?: number;\n}\n\n/**\n * JWT claims for an Ovixa access token.\n */\nexport interface AccessTokenPayload extends JWTPayload {\n /** Subject (user ID) */\n sub: string;\n /** User's email address */\n email: string;\n /** Whether the user's email has been verified */\n email_verified: boolean;\n /** Issued at timestamp */\n iat: number;\n /** Expiration timestamp */\n exp: number;\n /** Issuer */\n iss: string;\n /** Audience (realm ID) */\n aud: string;\n}\n\n/**\n * Result of a successful token verification.\n */\nexport interface VerifyResult {\n /** The decoded token payload */\n payload: AccessTokenPayload;\n /** The protected header */\n protectedHeader: JWTHeaderParameters;\n}\n\n/**\n * Token response from the auth service.\n */\nexport interface TokenResponse {\n /** The access token (JWT) */\n access_token: string;\n /** The refresh token (JWT) */\n refresh_token: string;\n /** Token type (always \"Bearer\") */\n token_type: 'Bearer';\n /** Access token expiration time in seconds */\n expires_in: number;\n /** Whether this is a new user (only present in OAuth callback) */\n is_new_user?: boolean;\n}\n\n/**\n * User information extracted from token payload.\n */\nexport interface User {\n /** User ID (UUID) */\n id: string;\n /** User's email address */\n email: string;\n /** Whether the email has been verified */\n emailVerified: boolean;\n}\n\n/**\n * Session information containing tokens and expiry.\n */\nexport interface Session {\n /** The access token (JWT) */\n accessToken: string;\n /** The refresh token for obtaining new access tokens */\n refreshToken: string;\n /** When the access token expires */\n expiresAt: Date;\n}\n\n/**\n * Combined authentication result with user and session data.\n */\nexport interface AuthResult {\n /** The authenticated user */\n user: User;\n /** The session tokens */\n session: Session;\n /** Whether this is a new user (only set for OAuth flows) */\n isNewUser?: boolean;\n}\n\n/**\n * Options for signup.\n */\nexport interface SignupOptions {\n /** User's email address */\n email: string;\n /** Password meeting requirements */\n password: string;\n /** Optional redirect URI after email verification */\n redirectUri?: string;\n}\n\n/**\n * Response from signup endpoint.\n */\nexport interface SignupResponse {\n /** Whether the operation succeeded */\n success: boolean;\n /** Status message */\n message: string;\n}\n\n/**\n * Options for login.\n */\nexport interface LoginOptions {\n /** User's email address */\n email: string;\n /** User's password */\n password: string;\n}\n\n/**\n * Options for email verification.\n */\nexport interface VerifyEmailOptions {\n /** Verification token from email */\n token: string;\n}\n\n/**\n * Options for resending verification email.\n */\nexport interface ResendVerificationOptions {\n /** User's email address */\n email: string;\n /** Optional redirect URI after verification */\n redirectUri?: string;\n}\n\n/**\n * Generic success response.\n */\nexport interface SuccessResponse {\n /** Whether the operation succeeded */\n success: boolean;\n /** Optional status message */\n message?: string;\n}\n\n/**\n * Options for forgot password request.\n */\nexport interface ForgotPasswordOptions {\n /** User's email address */\n email: string;\n /** Optional redirect URI for password reset page */\n redirectUri?: string;\n}\n\n/**\n * Options for password reset.\n */\nexport interface ResetPasswordOptions {\n /** Reset token from email */\n token: string;\n /** New password */\n password: string;\n}\n\n/**\n * Supported OAuth providers.\n */\nexport type OAuthProvider = 'google' | 'github';\n\n/**\n * Options for generating OAuth URL.\n */\nexport interface GetOAuthUrlOptions {\n /** OAuth provider */\n provider: OAuthProvider;\n /** Redirect URI after OAuth completes */\n redirectUri: string;\n}\n\n/**\n * Options for deleting a user (admin operation).\n */\nexport interface DeleteUserOptions {\n /** The ID of the user to delete */\n userId: string;\n}\n\n/**\n * Options for deleting the current user's own account.\n */\nexport interface DeleteMyAccountOptions {\n /** The user's current access token */\n accessToken: string;\n /** The user's current password (for re-authentication) */\n password: string;\n}\n\n/**\n * Error thrown by OvixaAuth operations.\n */\nexport class OvixaAuthError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly statusCode?: number\n ) {\n super(message);\n this.name = 'OvixaAuthError';\n }\n}\n\ninterface InternalConfig {\n authUrl: string;\n realmId: string;\n clientSecret?: string;\n jwksCacheTtl: number;\n}\n\n/** Default JWKS cache TTL: 1 hour */\nconst DEFAULT_JWKS_CACHE_TTL = 60 * 60 * 1000;\n\n/**\n * Custom fetch function that adds cache headers for JWKS requests.\n * Used to implement client-side caching behavior.\n */\ntype JWKSFetcher = ReturnType<typeof createRemoteJWKSet>;\n\ninterface JWKSCache {\n fetcher: JWKSFetcher;\n createdAt: number;\n}\n\n/**\n * OvixaAuth client for authenticating with the Ovixa Auth service.\n *\n * @example\n * ```typescript\n * const auth = new OvixaAuth({\n * authUrl: 'https://auth.ovixa.io',\n * realmId: 'your-realm-id',\n * clientSecret: 'your-client-secret', // Optional, for server-side use\n * });\n *\n * // Verify a token\n * const result = await auth.verifyToken(accessToken);\n * console.log(result.payload.email);\n *\n * // Refresh tokens\n * const tokens = await auth.refreshToken(refreshToken);\n * ```\n */\nexport class OvixaAuth {\n private config: InternalConfig;\n private jwksCache: JWKSCache | null = null;\n\n constructor(config: AuthClientConfig) {\n // Normalize authUrl by removing trailing slash\n const authUrl = config.authUrl.replace(/\\/$/, '');\n\n this.config = {\n authUrl,\n realmId: config.realmId,\n clientSecret: config.clientSecret,\n jwksCacheTtl: config.jwksCacheTtl ?? DEFAULT_JWKS_CACHE_TTL,\n };\n }\n\n /** Get the configured auth URL */\n get authUrl(): string {\n return this.config.authUrl;\n }\n\n /** Get the configured realm ID */\n get realmId(): string {\n return this.config.realmId;\n }\n\n /**\n * Get the JWKS URL for the auth service.\n */\n get jwksUrl(): string {\n return `${this.config.authUrl}/.well-known/jwks.json`;\n }\n\n /**\n * Get the JWKS fetcher, creating a new one if necessary.\n * The fetcher is cached based on the configured TTL.\n */\n private getJwksFetcher(): JWKSFetcher {\n const now = Date.now();\n\n // Return cached fetcher if still valid\n if (this.jwksCache && now - this.jwksCache.createdAt < this.config.jwksCacheTtl) {\n return this.jwksCache.fetcher;\n }\n\n // Create new JWKS fetcher\n const jwksUrl = new URL(this.jwksUrl);\n const fetcher = createRemoteJWKSet(jwksUrl, {\n // jose library has its own internal caching, but we add our own TTL layer\n // to control when to refresh the JWKS set\n cacheMaxAge: this.config.jwksCacheTtl,\n });\n\n this.jwksCache = {\n fetcher,\n createdAt: now,\n };\n\n return fetcher;\n }\n\n /**\n * Verify an access token and return the decoded payload.\n *\n * This method fetches the public key from the JWKS endpoint (with caching)\n * and verifies the token's signature and claims.\n *\n * @param token - The access token (JWT) to verify\n * @returns The verified token payload and header\n * @throws {OvixaAuthError} If verification fails\n *\n * @example\n * ```typescript\n * try {\n * const result = await auth.verifyToken(accessToken);\n * console.log('User ID:', result.payload.sub);\n * console.log('Email:', result.payload.email);\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * console.error('Token verification failed:', error.code);\n * }\n * }\n * ```\n */\n async verifyToken(token: string): Promise<VerifyResult> {\n try {\n const jwks = this.getJwksFetcher();\n\n const result: JWTVerifyResult<AccessTokenPayload> = await jwtVerify(token, jwks, {\n algorithms: ['RS256'],\n issuer: this.config.authUrl,\n // Audience is the realm for this application\n audience: this.config.realmId,\n });\n\n // Validate required claims\n const payload = result.payload;\n if (!payload.sub || !payload.email || payload.email_verified === undefined) {\n throw new OvixaAuthError('Token is missing required claims', 'INVALID_CLAIMS');\n }\n\n return {\n payload: payload as AccessTokenPayload,\n protectedHeader: result.protectedHeader,\n };\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n\n // Handle jose library errors\n if (error instanceof Error) {\n const message = error.message;\n\n if (message.includes('expired')) {\n throw new OvixaAuthError('Token has expired', 'TOKEN_EXPIRED');\n }\n if (message.includes('signature')) {\n throw new OvixaAuthError('Invalid token signature', 'INVALID_SIGNATURE');\n }\n if (message.includes('issuer')) {\n throw new OvixaAuthError('Invalid token issuer', 'INVALID_ISSUER');\n }\n if (message.includes('audience')) {\n throw new OvixaAuthError('Invalid token audience', 'INVALID_AUDIENCE');\n }\n if (message.includes('malformed')) {\n throw new OvixaAuthError('Malformed token', 'MALFORMED_TOKEN');\n }\n\n throw new OvixaAuthError(`Token verification failed: ${message}`, 'VERIFICATION_FAILED');\n }\n\n throw new OvixaAuthError('Token verification failed', 'VERIFICATION_FAILED');\n }\n }\n\n /**\n * Refresh an access token using a refresh token.\n *\n * This method exchanges a valid refresh token for a new access token\n * and a new refresh token (token rotation).\n *\n * @param refreshToken - The refresh token to exchange\n * @returns New token response with access and refresh tokens\n * @throws {OvixaAuthError} If the refresh fails\n *\n * @example\n * ```typescript\n * try {\n * const tokens = await auth.refreshToken(currentRefreshToken);\n * // Store the new tokens\n * saveTokens(tokens.access_token, tokens.refresh_token);\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * // Refresh token is invalid or expired - user must re-authenticate\n * redirectToLogin();\n * }\n * }\n * ```\n */\n async refreshToken(refreshToken: string): Promise<TokenResponse> {\n const url = `${this.config.authUrl}/token/refresh`;\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n refresh_token: refreshToken,\n }),\n });\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({}));\n const errorMessage = (errorBody as { error?: string }).error || 'Token refresh failed';\n const errorCode = this.mapHttpStatusToErrorCode(response.status);\n\n throw new OvixaAuthError(errorMessage, errorCode, response.status);\n }\n\n const data = (await response.json()) as TokenResponse;\n return data;\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n\n if (error instanceof Error) {\n throw new OvixaAuthError(`Network error: ${error.message}`, 'NETWORK_ERROR');\n }\n\n throw new OvixaAuthError('Token refresh failed', 'REFRESH_FAILED');\n }\n }\n\n /**\n * Invalidate the cached JWKS fetcher.\n * Call this if you need to force a refresh of the public keys.\n */\n clearJwksCache(): void {\n this.jwksCache = null;\n }\n\n /**\n * Create a new user account.\n *\n * After signup, a verification email is sent. The user must verify their\n * email before they can log in.\n *\n * @param options - Signup options\n * @returns Signup response indicating success\n * @throws {OvixaAuthError} If signup fails\n *\n * @example\n * ```typescript\n * try {\n * await auth.signup({\n * email: 'user@example.com',\n * password: 'SecurePassword123!',\n * redirectUri: 'https://myapp.com/verify-callback',\n * });\n * console.log('Verification email sent!');\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'EMAIL_ALREADY_EXISTS') {\n * console.error('Email is already registered');\n * }\n * }\n * }\n * ```\n */\n async signup(options: SignupOptions): Promise<SignupResponse> {\n const url = `${this.config.authUrl}/signup`;\n\n const body: Record<string, string> = {\n email: options.email,\n password: options.password,\n realm_id: this.config.realmId,\n };\n\n if (options.redirectUri) {\n body.redirect_uri = options.redirectUri;\n }\n\n return this.makeRequest<SignupResponse>(url, body);\n }\n\n /**\n * Authenticate a user with email and password.\n *\n * @param options - Login options\n * @returns Token response with access and refresh tokens\n * @throws {OvixaAuthError} If login fails\n *\n * @example\n * ```typescript\n * try {\n * const tokens = await auth.login({\n * email: 'user@example.com',\n * password: 'SecurePassword123!',\n * });\n * console.log('Logged in!', tokens.access_token);\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'EMAIL_NOT_VERIFIED') {\n * console.error('Please verify your email first');\n * } else if (error.code === 'INVALID_CREDENTIALS') {\n * console.error('Invalid email or password');\n * }\n * }\n * }\n * ```\n */\n async login(options: LoginOptions): Promise<TokenResponse> {\n const url = `${this.config.authUrl}/login`;\n\n const body = {\n email: options.email,\n password: options.password,\n realm_id: this.config.realmId,\n };\n\n return this.makeRequest<TokenResponse>(url, body);\n }\n\n /**\n * Verify an email address using a verification token.\n *\n * This is the API flow that returns tokens. For browser redirect flow,\n * use `GET /verify` directly.\n *\n * @param options - Verification options\n * @returns Token response with access and refresh tokens\n * @throws {OvixaAuthError} If verification fails\n *\n * @example\n * ```typescript\n * // Get token from URL query params after clicking email link\n * const token = new URLSearchParams(window.location.search).get('token');\n *\n * try {\n * const tokens = await auth.verifyEmail({ token });\n * console.log('Email verified! Logged in.');\n * } catch (error) {\n * if (error instanceof OvixaAuthError && error.code === 'INVALID_TOKEN') {\n * console.error('Invalid or expired verification link');\n * }\n * }\n * ```\n */\n async verifyEmail(options: VerifyEmailOptions): Promise<TokenResponse> {\n const url = `${this.config.authUrl}/verify`;\n\n const body = {\n token: options.token,\n realm_id: this.config.realmId,\n type: 'email_verification',\n };\n\n return this.makeRequest<TokenResponse>(url, body);\n }\n\n /**\n * Resend a verification email for an unverified account.\n *\n * @param options - Resend options\n * @returns Success response\n * @throws {OvixaAuthError} If request fails\n *\n * @example\n * ```typescript\n * await auth.resendVerification({\n * email: 'user@example.com',\n * redirectUri: 'https://myapp.com/verify-callback',\n * });\n * console.log('Verification email sent!');\n * ```\n */\n async resendVerification(options: ResendVerificationOptions): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/verify/resend`;\n\n const body: Record<string, string> = {\n email: options.email,\n realm_id: this.config.realmId,\n };\n\n if (options.redirectUri) {\n body.redirect_uri = options.redirectUri;\n }\n\n return this.makeRequest<SuccessResponse>(url, body);\n }\n\n /**\n * Request a password reset email.\n *\n * Note: This endpoint always returns success to prevent email enumeration.\n *\n * @param options - Forgot password options\n * @returns Success response\n * @throws {OvixaAuthError} If request fails\n *\n * @example\n * ```typescript\n * await auth.forgotPassword({\n * email: 'user@example.com',\n * redirectUri: 'https://myapp.com/reset-password',\n * });\n * console.log('If the email exists, a reset link has been sent.');\n * ```\n */\n async forgotPassword(options: ForgotPasswordOptions): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/forgot-password`;\n\n const body: Record<string, string> = {\n email: options.email,\n realm_id: this.config.realmId,\n };\n\n if (options.redirectUri) {\n body.redirect_uri = options.redirectUri;\n }\n\n return this.makeRequest<SuccessResponse>(url, body);\n }\n\n /**\n * Reset password using a reset token.\n *\n * @param options - Reset password options\n * @returns Success response\n * @throws {OvixaAuthError} If reset fails\n *\n * @example\n * ```typescript\n * // Get token from URL query params\n * const token = new URLSearchParams(window.location.search).get('token');\n *\n * try {\n * await auth.resetPassword({\n * token,\n * password: 'NewSecurePassword123!',\n * });\n * console.log('Password reset successfully!');\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'INVALID_TOKEN') {\n * console.error('Invalid or expired reset link');\n * } else if (error.code === 'WEAK_PASSWORD') {\n * console.error('Password does not meet requirements');\n * }\n * }\n * }\n * ```\n */\n async resetPassword(options: ResetPasswordOptions): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/reset-password`;\n\n const body = {\n token: options.token,\n password: options.password,\n realm_id: this.config.realmId,\n };\n\n return this.makeRequest<SuccessResponse>(url, body);\n }\n\n /**\n * Revoke a refresh token (logout).\n *\n * @param refreshToken - The refresh token to revoke\n * @returns Success response\n * @throws {OvixaAuthError} If logout fails\n *\n * @example\n * ```typescript\n * await auth.logout(currentRefreshToken);\n * // Clear local token storage\n * localStorage.removeItem('refresh_token');\n * localStorage.removeItem('access_token');\n * ```\n */\n async logout(refreshToken: string): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/logout`;\n\n const body = {\n refresh_token: refreshToken,\n };\n\n return this.makeRequest<SuccessResponse>(url, body);\n }\n\n /**\n * Generate an OAuth authorization URL.\n *\n * Redirect the user to this URL to start the OAuth flow. After authentication,\n * the user will be redirected back to your `redirectUri` with an authorization code.\n * Use `exchangeOAuthCode()` to exchange the code for tokens.\n *\n * @param options - OAuth URL options\n * @returns The full OAuth authorization URL\n *\n * @example\n * ```typescript\n * const googleAuthUrl = auth.getOAuthUrl({\n * provider: 'google',\n * redirectUri: 'https://myapp.com/auth/callback',\n * });\n *\n * // Redirect user to start OAuth flow\n * window.location.href = googleAuthUrl;\n *\n * // In your callback handler:\n * // const code = new URLSearchParams(window.location.search).get('code');\n * // const tokens = await auth.exchangeOAuthCode(code);\n * ```\n */\n getOAuthUrl(options: GetOAuthUrlOptions): string {\n const url = new URL(`${this.config.authUrl}/oauth/${options.provider}`);\n url.searchParams.set('realm_id', this.config.realmId);\n url.searchParams.set('redirect_uri', options.redirectUri);\n return url.toString();\n }\n\n /**\n * Exchange an OAuth authorization code for tokens.\n *\n * After the OAuth flow completes, the user is redirected back to your app with\n * an authorization code in the URL query params. Use this method to exchange\n * that code for access and refresh tokens.\n *\n * Note: Authorization codes expire after 60 seconds.\n *\n * @param code - The authorization code from the OAuth callback URL\n * @returns Token response with access and refresh tokens\n * @throws {OvixaAuthError} If the exchange fails\n *\n * @example\n * ```typescript\n * // In your OAuth callback handler\n * const code = new URLSearchParams(window.location.search).get('code');\n *\n * if (code) {\n * try {\n * const tokens = await auth.exchangeOAuthCode(code);\n * console.log('OAuth successful!', tokens.access_token);\n *\n * // Check if this is a new user (first-time OAuth login)\n * if (tokens.is_new_user) {\n * // Show onboarding flow\n * }\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'INVALID_CODE') {\n * console.error('Invalid or expired authorization code');\n * }\n * }\n * }\n * }\n * ```\n */\n async exchangeOAuthCode(code: string): Promise<TokenResponse> {\n const url = `${this.config.authUrl}/oauth/token`;\n\n const body = {\n code,\n realm_id: this.config.realmId,\n };\n\n return this.makeRequest<TokenResponse>(url, body);\n }\n\n /**\n * Transform a token response into an AuthResult with user and session data.\n *\n * This method decodes the access token to extract user information and\n * creates a structured result object.\n *\n * @param tokenResponse - The token response from login, verify, or refresh\n * @returns AuthResult with user and session data\n * @throws {OvixaAuthError} If the access token cannot be decoded\n *\n * @example\n * ```typescript\n * const tokens = await auth.login({ email, password });\n * const result = await auth.toAuthResult(tokens);\n *\n * console.log('User ID:', result.user.id);\n * console.log('Email:', result.user.email);\n * console.log('Expires at:', result.session.expiresAt);\n * ```\n */\n async toAuthResult(tokenResponse: TokenResponse): Promise<AuthResult> {\n // Verify and decode the access token\n const verified = await this.verifyToken(tokenResponse.access_token);\n\n const user: User = {\n id: verified.payload.sub,\n email: verified.payload.email,\n emailVerified: verified.payload.email_verified,\n };\n\n const session: Session = {\n accessToken: tokenResponse.access_token,\n refreshToken: tokenResponse.refresh_token,\n expiresAt: new Date(Date.now() + tokenResponse.expires_in * 1000),\n };\n\n const result: AuthResult = { user, session };\n\n if (tokenResponse.is_new_user !== undefined) {\n result.isNewUser = tokenResponse.is_new_user;\n }\n\n return result;\n }\n\n /**\n * Delete the current user's own account.\n *\n * This permanently deletes the user's account and all associated data.\n * Password re-authentication is required to prevent accidental deletion.\n *\n * @param options - Delete account options\n * @returns Success response\n * @throws {OvixaAuthError} If deletion fails\n *\n * @example\n * ```typescript\n * try {\n * await auth.deleteMyAccount({\n * accessToken: session.accessToken,\n * password: 'current-password',\n * });\n * console.log('Account deleted successfully');\n * // Clear local session storage\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'INVALID_CREDENTIALS') {\n * console.error('Incorrect password');\n * } else if (error.code === 'UNAUTHORIZED') {\n * console.error('Invalid or expired access token');\n * }\n * }\n * }\n * ```\n */\n async deleteMyAccount(options: DeleteMyAccountOptions): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/me`;\n\n try {\n const response = await fetch(url, {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${options.accessToken}`,\n },\n body: JSON.stringify({\n password: options.password,\n }),\n });\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({}));\n const errorData = errorBody as { error?: { code?: string; message?: string } | string };\n\n let errorCode: string;\n let errorMessage: string;\n\n if (typeof errorData.error === 'object' && errorData.error) {\n errorCode = errorData.error.code || this.mapHttpStatusToErrorCode(response.status);\n errorMessage = errorData.error.message || 'Request failed';\n } else {\n errorCode = this.mapHttpStatusToErrorCode(response.status);\n errorMessage = typeof errorData.error === 'string' ? errorData.error : 'Request failed';\n }\n\n throw new OvixaAuthError(errorMessage, errorCode, response.status);\n }\n\n return (await response.json()) as SuccessResponse;\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n\n if (error instanceof Error) {\n throw new OvixaAuthError(`Network error: ${error.message}`, 'NETWORK_ERROR');\n }\n\n throw new OvixaAuthError('Request failed', 'REQUEST_FAILED');\n }\n }\n\n /**\n * Make an authenticated POST request to the auth service.\n */\n private async makeRequest<T>(url: string, body: Record<string, unknown>): Promise<T> {\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({}));\n const errorData = errorBody as { error?: { code?: string; message?: string } | string };\n\n let errorCode: string;\n let errorMessage: string;\n\n if (typeof errorData.error === 'object' && errorData.error) {\n errorCode = errorData.error.code || this.mapHttpStatusToErrorCode(response.status);\n errorMessage = errorData.error.message || 'Request failed';\n } else {\n errorCode = this.mapHttpStatusToErrorCode(response.status);\n errorMessage = typeof errorData.error === 'string' ? errorData.error : 'Request failed';\n }\n\n throw new OvixaAuthError(errorMessage, errorCode, response.status);\n }\n\n return (await response.json()) as T;\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n\n if (error instanceof Error) {\n throw new OvixaAuthError(`Network error: ${error.message}`, 'NETWORK_ERROR');\n }\n\n throw new OvixaAuthError('Request failed', 'REQUEST_FAILED');\n }\n }\n\n /**\n * Map HTTP status codes to error codes.\n */\n private mapHttpStatusToErrorCode(status: number): string {\n switch (status) {\n case 400:\n return 'BAD_REQUEST';\n case 401:\n return 'UNAUTHORIZED';\n case 403:\n return 'FORBIDDEN';\n case 404:\n return 'NOT_FOUND';\n case 429:\n return 'RATE_LIMITED';\n case 500:\n case 502:\n case 503:\n return 'SERVER_ERROR';\n default:\n return 'UNKNOWN_ERROR';\n }\n }\n\n /**\n * Get the admin API interface for realm-scoped operations.\n *\n * Admin operations require clientSecret to be configured.\n * These operations allow managing users within the realm boundary.\n *\n * @returns OvixaAuthAdmin interface for admin operations\n * @throws {OvixaAuthError} If clientSecret is not configured\n *\n * @example\n * ```typescript\n * const auth = new OvixaAuth({\n * authUrl: 'https://auth.ovixa.io',\n * realmId: 'your-realm',\n * clientSecret: process.env.OVIXA_CLIENT_SECRET,\n * });\n *\n * // Delete a user from the realm\n * await auth.admin.deleteUser({ userId: 'user-id-to-delete' });\n * ```\n */\n get admin(): OvixaAuthAdmin {\n if (!this.config.clientSecret) {\n throw new OvixaAuthError(\n 'clientSecret is required for admin operations',\n 'CLIENT_SECRET_REQUIRED'\n );\n }\n return new OvixaAuthAdmin(this.config);\n }\n}\n\n/**\n * Admin API interface for realm-scoped operations.\n *\n * This class provides methods for managing users within the realm boundary.\n * All operations are authenticated using the realm's client_secret.\n *\n * @example\n * ```typescript\n * // Access via OvixaAuth.admin\n * await auth.admin.deleteUser({ userId: 'user-id' });\n * ```\n */\nclass OvixaAuthAdmin {\n private config: InternalConfig;\n\n constructor(config: InternalConfig) {\n this.config = config;\n }\n\n /**\n * Delete a user from the realm.\n *\n * This permanently deletes the user and all associated data (tokens, OAuth accounts).\n * The user must belong to this realm.\n *\n * @param options - Delete user options\n * @returns Success response\n * @throws {OvixaAuthError} If deletion fails\n *\n * @example\n * ```typescript\n * try {\n * await auth.admin.deleteUser({ userId: 'user-id-to-delete' });\n * console.log('User deleted successfully');\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'NOT_FOUND') {\n * console.error('User not found');\n * } else if (error.code === 'FORBIDDEN') {\n * console.error('User does not belong to this realm');\n * }\n * }\n * }\n * ```\n */\n async deleteUser(options: DeleteUserOptions): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/realms/${this.config.realmId}/users/${options.userId}`;\n\n try {\n const response = await fetch(url, {\n method: 'DELETE',\n headers: {\n Authorization: `RealmSecret ${this.config.clientSecret}`,\n },\n });\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({}));\n const errorData = errorBody as { error?: { code?: string; message?: string } | string };\n\n let errorCode: string;\n let errorMessage: string;\n\n if (typeof errorData.error === 'object' && errorData.error) {\n errorCode = errorData.error.code || this.mapHttpStatusToErrorCode(response.status);\n errorMessage = errorData.error.message || 'Request failed';\n } else {\n errorCode = this.mapHttpStatusToErrorCode(response.status);\n errorMessage = typeof errorData.error === 'string' ? errorData.error : 'Request failed';\n }\n\n throw new OvixaAuthError(errorMessage, errorCode, response.status);\n }\n\n return (await response.json()) as SuccessResponse;\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n\n if (error instanceof Error) {\n throw new OvixaAuthError(`Network error: ${error.message}`, 'NETWORK_ERROR');\n }\n\n throw new OvixaAuthError('Request failed', 'REQUEST_FAILED');\n }\n }\n\n /**\n * Map HTTP status codes to error codes.\n */\n private mapHttpStatusToErrorCode(status: number): string {\n switch (status) {\n case 400:\n return 'BAD_REQUEST';\n case 401:\n return 'UNAUTHORIZED';\n case 403:\n return 'FORBIDDEN';\n case 404:\n return 'NOT_FOUND';\n case 429:\n return 'RATE_LIMITED';\n case 500:\n case 502:\n case 503:\n return 'SERVER_ERROR';\n default:\n return 'UNKNOWN_ERROR';\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
File without changes
|