@nauth-toolkit/client 0.1.58 → 0.1.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +212 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +318 -56
- package/dist/index.d.ts +318 -56
- package/dist/index.mjs +211 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
AuthAuditEventType: () => AuthAuditEventType,
|
|
26
26
|
AuthChallenge: () => AuthChallenge,
|
|
27
27
|
BrowserStorage: () => BrowserStorage,
|
|
28
|
+
ChallengeRouter: () => ChallengeRouter,
|
|
28
29
|
EventEmitter: () => EventEmitter,
|
|
29
30
|
FetchAdapter: () => FetchAdapter,
|
|
30
31
|
InMemoryStorage: () => InMemoryStorage,
|
|
@@ -157,6 +158,7 @@ var defaultEndpoints = {
|
|
|
157
158
|
requestPasswordChange: "/request-password-change",
|
|
158
159
|
forgotPassword: "/forgot-password",
|
|
159
160
|
confirmForgotPassword: "/forgot-password/confirm",
|
|
161
|
+
confirmAdminResetPassword: "/admin/reset-password/confirm",
|
|
160
162
|
mfaStatus: "/mfa/status",
|
|
161
163
|
mfaDevices: "/mfa/devices",
|
|
162
164
|
mfaSetupData: "/mfa/setup-data",
|
|
@@ -562,6 +564,155 @@ var FetchAdapter = class {
|
|
|
562
564
|
}
|
|
563
565
|
};
|
|
564
566
|
|
|
567
|
+
// src/core/challenge-router.ts
|
|
568
|
+
var ChallengeRouter = class {
|
|
569
|
+
constructor(config) {
|
|
570
|
+
this.config = config;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Handle auth response - either call callback or auto-navigate.
|
|
574
|
+
*
|
|
575
|
+
* @param response - Auth response from backend
|
|
576
|
+
* @param context - Context about the auth operation
|
|
577
|
+
*/
|
|
578
|
+
async handleAuthResponse(response, context) {
|
|
579
|
+
if (this.config.onAuthResponse) {
|
|
580
|
+
await this.config.onAuthResponse(response, context);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (response.challengeName) {
|
|
584
|
+
await this.navigateToChallenge(response);
|
|
585
|
+
} else {
|
|
586
|
+
await this.navigateToSuccess();
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Navigate to appropriate challenge route.
|
|
591
|
+
*
|
|
592
|
+
* @param response - Auth response containing challenge info
|
|
593
|
+
*/
|
|
594
|
+
async navigateToChallenge(response) {
|
|
595
|
+
const url = this.buildChallengeUrl(response);
|
|
596
|
+
await this.navigate(url);
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Navigate to success URL.
|
|
600
|
+
*/
|
|
601
|
+
async navigateToSuccess() {
|
|
602
|
+
const url = this.config.redirects?.success || "/";
|
|
603
|
+
await this.navigate(url);
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Navigate to error URL.
|
|
607
|
+
*
|
|
608
|
+
* @param type - Type of error (oauth or session)
|
|
609
|
+
*/
|
|
610
|
+
async navigateToError(type) {
|
|
611
|
+
const url = type === "oauth" ? this.config.redirects?.oauthError || "/login" : this.config.redirects?.sessionExpired || "/login";
|
|
612
|
+
await this.navigate(url);
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Build challenge URL based on configuration.
|
|
616
|
+
*
|
|
617
|
+
* Priority:
|
|
618
|
+
* 1. Custom route mapping (challengeRoutes)
|
|
619
|
+
* 2. Single route with query param (useSingleChallengeRoute)
|
|
620
|
+
* 3. MFA-specific routes (mfaRoutes) - for MFA_REQUIRED challenge only
|
|
621
|
+
* 4. Default separate routes (challengeBase + kebab-case)
|
|
622
|
+
*
|
|
623
|
+
* @param response - Auth response containing challenge info
|
|
624
|
+
* @returns URL to navigate to
|
|
625
|
+
*/
|
|
626
|
+
buildChallengeUrl(response) {
|
|
627
|
+
const challengeName = response.challengeName;
|
|
628
|
+
if (this.config.redirects?.challengeRoutes?.[challengeName]) {
|
|
629
|
+
return this.config.redirects.challengeRoutes[challengeName];
|
|
630
|
+
}
|
|
631
|
+
const base = this.config.redirects?.challengeBase || "/auth/challenge";
|
|
632
|
+
if (this.config.redirects?.useSingleChallengeRoute) {
|
|
633
|
+
return `${base}?challenge=${challengeName}`;
|
|
634
|
+
}
|
|
635
|
+
if (challengeName === "MFA_REQUIRED" /* MFA_REQUIRED */) {
|
|
636
|
+
const mfaUrl = this.buildMFAUrl(response);
|
|
637
|
+
if (mfaUrl) {
|
|
638
|
+
return mfaUrl;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
const route = this.buildDefaultRouteSegment(challengeName, response);
|
|
642
|
+
return `${base}/${route}`;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Build MFA-specific URL if custom mfaRoutes are configured.
|
|
646
|
+
*
|
|
647
|
+
* @param response - Auth response with MFA challenge parameters
|
|
648
|
+
* @returns Custom MFA URL if configured, null otherwise
|
|
649
|
+
*/
|
|
650
|
+
buildMFAUrl(response) {
|
|
651
|
+
const params = response.challengeParameters;
|
|
652
|
+
const method = params?.["preferredMethod"] || params?.["method"];
|
|
653
|
+
const mfaRoutes = this.config.redirects?.mfaRoutes;
|
|
654
|
+
if (!mfaRoutes) {
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
if (method === "passkey" && mfaRoutes.passkey) {
|
|
658
|
+
return mfaRoutes.passkey;
|
|
659
|
+
}
|
|
660
|
+
if (!method && params?.["availableMethods"] && Array.isArray(params["availableMethods"]) && params["availableMethods"].length > 1) {
|
|
661
|
+
if (mfaRoutes.selector) {
|
|
662
|
+
return mfaRoutes.selector;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (mfaRoutes.default) {
|
|
666
|
+
return mfaRoutes.default;
|
|
667
|
+
}
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Build default route segment for a challenge.
|
|
672
|
+
*
|
|
673
|
+
* @param challengeName - Challenge type
|
|
674
|
+
* @param response - Auth response for extracting challenge parameters (needed for MFA)
|
|
675
|
+
* @returns Route segment (e.g., 'mfa-required/passkey', 'verify-email')
|
|
676
|
+
*/
|
|
677
|
+
buildDefaultRouteSegment(challengeName, response) {
|
|
678
|
+
if (challengeName === "MFA_REQUIRED" /* MFA_REQUIRED */ && response) {
|
|
679
|
+
const params = response.challengeParameters;
|
|
680
|
+
const method = params?.["preferredMethod"] || params?.["method"];
|
|
681
|
+
if (method === "passkey") {
|
|
682
|
+
return "mfa-required/passkey";
|
|
683
|
+
}
|
|
684
|
+
if (!method && params?.["availableMethods"] && Array.isArray(params["availableMethods"]) && params["availableMethods"].length > 1) {
|
|
685
|
+
return "mfa-selector";
|
|
686
|
+
}
|
|
687
|
+
return "mfa-required";
|
|
688
|
+
}
|
|
689
|
+
return challengeName.toLowerCase().replace(/_/g, "-");
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Execute navigation using configured handler or default.
|
|
693
|
+
*
|
|
694
|
+
* @param url - URL to navigate to
|
|
695
|
+
*/
|
|
696
|
+
async navigate(url) {
|
|
697
|
+
if (this.config.navigationHandler) {
|
|
698
|
+
await this.config.navigationHandler(url);
|
|
699
|
+
} else {
|
|
700
|
+
if (typeof window !== "undefined") {
|
|
701
|
+
window.location.replace(url);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Expose URL builder for guards/components that need it.
|
|
707
|
+
*
|
|
708
|
+
* @param response - Auth response containing challenge info
|
|
709
|
+
* @returns URL for the challenge
|
|
710
|
+
*/
|
|
711
|
+
getChallengeUrl(response) {
|
|
712
|
+
return this.buildChallengeUrl(response);
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
|
|
565
716
|
// src/core/client.ts
|
|
566
717
|
var USER_KEY2 = "nauth_user";
|
|
567
718
|
var CHALLENGE_KEY2 = "nauth_challenge_session";
|
|
@@ -582,6 +733,7 @@ var NAuthClient = class {
|
|
|
582
733
|
__publicField(this, "config");
|
|
583
734
|
__publicField(this, "tokenManager");
|
|
584
735
|
__publicField(this, "eventEmitter");
|
|
736
|
+
__publicField(this, "challengeRouter");
|
|
585
737
|
__publicField(this, "currentUser", null);
|
|
586
738
|
/**
|
|
587
739
|
* Handle cross-tab storage updates.
|
|
@@ -600,6 +752,7 @@ var NAuthClient = class {
|
|
|
600
752
|
this.config = resolveConfig({ ...userConfig, storage }, defaultAdapter);
|
|
601
753
|
this.tokenManager = new TokenManager(storage);
|
|
602
754
|
this.eventEmitter = new EventEmitter();
|
|
755
|
+
this.challengeRouter = new ChallengeRouter(this.config);
|
|
603
756
|
if (hasWindow()) {
|
|
604
757
|
window.addEventListener("storage", this.handleStorageEvent);
|
|
605
758
|
}
|
|
@@ -629,6 +782,7 @@ var NAuthClient = class {
|
|
|
629
782
|
const successEvent = { type: "auth:success", data: response, timestamp: Date.now() };
|
|
630
783
|
this.eventEmitter.emit(successEvent);
|
|
631
784
|
}
|
|
785
|
+
await this.challengeRouter.handleAuthResponse(response, { source: "login" });
|
|
632
786
|
return response;
|
|
633
787
|
} catch (error) {
|
|
634
788
|
const authError = error instanceof NAuthClientError ? error : new NAuthClientError("AUTH_INVALID_CREDENTIALS" /* AUTH_INVALID_CREDENTIALS */, error.message || "Login failed");
|
|
@@ -650,6 +804,7 @@ var NAuthClient = class {
|
|
|
650
804
|
} else {
|
|
651
805
|
this.eventEmitter.emit({ type: "auth:success", data: response, timestamp: Date.now() });
|
|
652
806
|
}
|
|
807
|
+
await this.challengeRouter.handleAuthResponse(response, { source: "signup" });
|
|
653
808
|
return response;
|
|
654
809
|
} catch (error) {
|
|
655
810
|
const authError = error instanceof NAuthClientError ? error : new NAuthClientError("AUTH_INVALID_CREDENTIALS" /* AUTH_INVALID_CREDENTIALS */, error.message || "Signup failed");
|
|
@@ -779,6 +934,7 @@ var NAuthClient = class {
|
|
|
779
934
|
const successEvent = { type: "auth:success", data: result, timestamp: Date.now() };
|
|
780
935
|
this.eventEmitter.emit(successEvent);
|
|
781
936
|
}
|
|
937
|
+
await this.challengeRouter.handleAuthResponse(result, { source: "challenge" });
|
|
782
938
|
return result;
|
|
783
939
|
} catch (error) {
|
|
784
940
|
const authError = error instanceof NAuthClientError ? error : new NAuthClientError(
|
|
@@ -866,6 +1022,45 @@ var NAuthClient = class {
|
|
|
866
1022
|
await this.clearAuthState(false);
|
|
867
1023
|
return result;
|
|
868
1024
|
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Reset password with code or token (works for both admin-initiated and user-initiated resets).
|
|
1027
|
+
*
|
|
1028
|
+
* Accepts either:
|
|
1029
|
+
* - code: Short numeric code from email/SMS (6-10 digits)
|
|
1030
|
+
* - token: Long hex token from reset link (64 chars)
|
|
1031
|
+
*
|
|
1032
|
+
* WHY: Generic method that works for both admin-initiated (adminResetPassword) and
|
|
1033
|
+
* user-initiated (forgotPassword) password resets. Uses same backend endpoint.
|
|
1034
|
+
*
|
|
1035
|
+
* @param identifier - User identifier (email, username, phone)
|
|
1036
|
+
* @param codeOrToken - Verification code OR token from link (one required)
|
|
1037
|
+
* @param newPassword - New password
|
|
1038
|
+
* @returns Success response
|
|
1039
|
+
* @throws {NAuthClientError} When reset fails
|
|
1040
|
+
*
|
|
1041
|
+
* @example
|
|
1042
|
+
* ```typescript
|
|
1043
|
+
* // With code from email
|
|
1044
|
+
* await client.resetPasswordWithCode('user@example.com', '123456', 'NewPass123!');
|
|
1045
|
+
*
|
|
1046
|
+
* // With token from link
|
|
1047
|
+
* await client.resetPasswordWithCode('user@example.com', '64-char-token', 'NewPass123!');
|
|
1048
|
+
* ```
|
|
1049
|
+
*/
|
|
1050
|
+
async resetPasswordWithCode(identifier, codeOrToken, newPassword) {
|
|
1051
|
+
const isToken = codeOrToken.length > 10;
|
|
1052
|
+
const payload = {
|
|
1053
|
+
identifier,
|
|
1054
|
+
...isToken ? { token: codeOrToken } : { code: codeOrToken },
|
|
1055
|
+
newPassword
|
|
1056
|
+
};
|
|
1057
|
+
const result = await this.post(
|
|
1058
|
+
this.config.endpoints.confirmAdminResetPassword,
|
|
1059
|
+
payload
|
|
1060
|
+
);
|
|
1061
|
+
await this.clearAuthState(false);
|
|
1062
|
+
return result;
|
|
1063
|
+
}
|
|
869
1064
|
/**
|
|
870
1065
|
* Request password change (must change on next login).
|
|
871
1066
|
*/
|
|
@@ -1026,6 +1221,7 @@ var NAuthClient = class {
|
|
|
1026
1221
|
}
|
|
1027
1222
|
const result = await this.post(this.config.endpoints.socialExchange, { exchangeToken: token });
|
|
1028
1223
|
await this.handleAuthResponse(result);
|
|
1224
|
+
await this.challengeRouter.handleAuthResponse(result, { source: "social" });
|
|
1029
1225
|
return result;
|
|
1030
1226
|
}
|
|
1031
1227
|
/**
|
|
@@ -1379,6 +1575,21 @@ var NAuthClient = class {
|
|
|
1379
1575
|
});
|
|
1380
1576
|
return response.data;
|
|
1381
1577
|
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Get challenge router for manual navigation control.
|
|
1580
|
+
* Useful for guards that need to handle errors or build custom URLs.
|
|
1581
|
+
*
|
|
1582
|
+
* @returns ChallengeRouter instance
|
|
1583
|
+
*
|
|
1584
|
+
* @example
|
|
1585
|
+
* ```typescript
|
|
1586
|
+
* const router = client.getChallengeRouter();
|
|
1587
|
+
* await router.navigateToError('oauth');
|
|
1588
|
+
* ```
|
|
1589
|
+
*/
|
|
1590
|
+
getChallengeRouter() {
|
|
1591
|
+
return this.challengeRouter;
|
|
1592
|
+
}
|
|
1382
1593
|
};
|
|
1383
1594
|
|
|
1384
1595
|
// src/core/challenge-helpers.ts
|
|
@@ -1427,6 +1638,7 @@ function isOTPChallenge(challenge) {
|
|
|
1427
1638
|
AuthAuditEventType,
|
|
1428
1639
|
AuthChallenge,
|
|
1429
1640
|
BrowserStorage,
|
|
1641
|
+
ChallengeRouter,
|
|
1430
1642
|
EventEmitter,
|
|
1431
1643
|
FetchAdapter,
|
|
1432
1644
|
InMemoryStorage,
|