@nauth-toolkit/client 0.1.18 → 0.1.22

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.mjs CHANGED
@@ -126,12 +126,12 @@ var defaultEndpoints = {
126
126
  mfaPreferred: "/mfa/preferred-method",
127
127
  mfaBackupCodes: "/mfa/backup-codes/generate",
128
128
  mfaExemption: "/mfa/exemption",
129
- socialAuthUrl: "/social/auth-url",
130
- socialCallback: "/social/callback",
131
129
  socialLinked: "/social/linked",
132
130
  socialLink: "/social/link",
133
131
  socialUnlink: "/social/unlink",
134
132
  socialVerify: "/social/:provider/verify",
133
+ socialRedirectStart: "/social/:provider/redirect",
134
+ socialExchange: "/social/exchange",
135
135
  trustDevice: "/trust-device",
136
136
  isTrustedDevice: "/is-trusted-device",
137
137
  auditHistory: "/audit/history",
@@ -367,6 +367,9 @@ var BrowserStorage = class {
367
367
  async removeItem(key) {
368
368
  this.storage.removeItem(key);
369
369
  }
370
+ async clear() {
371
+ this.storage.clear();
372
+ }
370
373
  };
371
374
 
372
375
  // src/storage/memory.ts
@@ -383,6 +386,9 @@ var InMemoryStorage = class {
383
386
  async removeItem(key) {
384
387
  this.store.delete(key);
385
388
  }
389
+ async clear() {
390
+ this.store.clear();
391
+ }
386
392
  };
387
393
 
388
394
  // src/core/events.ts
@@ -503,9 +509,9 @@ var FetchAdapter = class {
503
509
  });
504
510
  if (!response.ok) {
505
511
  const errorData = typeof data === "object" && data !== null ? data : {};
506
- const code = typeof errorData["code"] === "string" ? errorData.code : "INTERNAL_ERROR" /* INTERNAL_ERROR */;
507
- const message = typeof errorData["message"] === "string" ? errorData.message : `Request failed with status ${status}`;
508
- const timestamp = typeof errorData["timestamp"] === "string" ? errorData.timestamp : void 0;
512
+ const code = typeof errorData["code"] === "string" ? errorData["code"] : "INTERNAL_ERROR" /* INTERNAL_ERROR */;
513
+ const message = typeof errorData["message"] === "string" ? errorData["message"] : `Request failed with status ${status}`;
514
+ const timestamp = typeof errorData["timestamp"] === "string" ? errorData["timestamp"] : void 0;
509
515
  const details = errorData["details"];
510
516
  throw new NAuthClientError(code, message, {
511
517
  statusCode: status,
@@ -817,7 +823,9 @@ var NAuthClient = class {
817
823
  */
818
824
  async confirmForgotPassword(identifier, code, newPassword) {
819
825
  const payload = { identifier, code, newPassword };
820
- return this.post(this.config.endpoints.confirmForgotPassword, payload);
826
+ const result = await this.post(this.config.endpoints.confirmForgotPassword, payload);
827
+ await this.clearAuthState(false);
828
+ return result;
821
829
  }
822
830
  /**
823
831
  * Request password change (must change on next login).
@@ -927,126 +935,57 @@ var NAuthClient = class {
927
935
  // Social Authentication
928
936
  // ============================================================================
929
937
  /**
930
- * Start social OAuth flow with automatic state management.
938
+ * Start redirect-first social OAuth flow (web).
931
939
  *
932
- * Generates a secure state token, stores OAuth context, and redirects to the OAuth provider.
933
- * After OAuth callback, use `handleOAuthCallback()` to complete authentication.
940
+ * This performs a browser navigation to:
941
+ * `GET {baseUrl}/social/:provider/redirect?returnTo=...&appState=...`
942
+ *
943
+ * The backend:
944
+ * - generates and stores CSRF state (cluster-safe)
945
+ * - redirects the user to the provider
946
+ * - completes OAuth on callback and sets cookies (or issues an exchange token)
947
+ * - redirects back to `returnTo` with `appState` (and `exchangeToken` for json/hybrid)
934
948
  *
935
949
  * @param provider - OAuth provider ('google', 'apple', 'facebook')
936
- * @param options - Optional configuration
950
+ * @param options - Optional redirect options
937
951
  *
938
952
  * @example
939
953
  * ```typescript
940
- * // Simple usage
941
- * await client.loginWithSocial('google');
942
- *
943
- * // With custom redirect URI
944
- * await client.loginWithSocial('apple', {
945
- * redirectUri: 'https://example.com/auth/callback'
946
- * });
954
+ * await client.loginWithSocial('google', { returnTo: '/auth/callback', appState: '12345' });
947
955
  * ```
948
956
  */
949
- async loginWithSocial(provider, _options) {
957
+ async loginWithSocial(provider, options) {
950
958
  this.eventEmitter.emit({ type: "oauth:started", data: { provider }, timestamp: Date.now() });
951
- const { url } = await this.getSocialAuthUrl({ provider });
952
959
  if (hasWindow()) {
953
- window.location.href = url;
954
- }
955
- }
956
- /**
957
- * Auto-detect and handle OAuth callback.
958
- *
959
- * Call this on app initialization or in callback route.
960
- * Returns null if not an OAuth callback (no provider/code params).
961
- *
962
- * The SDK validates the state token, completes authentication via backend,
963
- * and emits appropriate events.
964
- *
965
- * @param urlOrParams - Optional URL string or URLSearchParams (auto-detects from window.location if not provided)
966
- * @returns AuthResponse if OAuth callback detected, null otherwise
967
- *
968
- * @example
969
- * ```typescript
970
- * // Auto-detect on app init
971
- * const response = await client.handleOAuthCallback();
972
- * if (response) {
973
- * if (response.challengeName) {
974
- * router.navigate(['/challenge', response.challengeName]);
975
- * } else {
976
- * router.navigate(['/']); // Navigate to your app's home route
977
- * }
978
- * }
979
- *
980
- * // In callback route
981
- * const response = await client.handleOAuthCallback(window.location.search);
982
- * ```
983
- */
984
- async handleOAuthCallback(urlOrParams) {
985
- let params;
986
- if (urlOrParams instanceof URLSearchParams) {
987
- params = urlOrParams;
988
- } else if (typeof urlOrParams === "string") {
989
- params = new URLSearchParams(urlOrParams);
990
- } else if (hasWindow()) {
991
- params = new URLSearchParams(window.location.search);
992
- } else {
993
- return null;
994
- }
995
- const provider = params.get("provider");
996
- const code = params.get("code");
997
- const state = params.get("state");
998
- const error = params.get("error");
999
- if (!provider || !code && !error) {
1000
- return null;
1001
- }
1002
- this.eventEmitter.emit({ type: "oauth:callback", data: { provider }, timestamp: Date.now() });
1003
- try {
1004
- if (error) {
1005
- const authError = new NAuthClientError(
1006
- "SOCIAL_TOKEN_INVALID" /* SOCIAL_TOKEN_INVALID */,
1007
- params.get("error_description") || error,
1008
- { details: { error, provider } }
1009
- );
1010
- this.eventEmitter.emit({ type: "oauth:error", data: authError, timestamp: Date.now() });
1011
- throw authError;
960
+ const startPath = this.config.endpoints.socialRedirectStart.replace(":provider", provider);
961
+ const base = this.config.baseUrl.replace(/\/$/, "");
962
+ const startUrl = new URL(`${base}${startPath}`);
963
+ const returnTo = options?.returnTo ?? this.config.redirects?.success ?? "/";
964
+ startUrl.searchParams.set("returnTo", returnTo);
965
+ if (options?.action === "link") {
966
+ startUrl.searchParams.set("action", "link");
1012
967
  }
1013
- if (!state) {
1014
- throw new NAuthClientError("CHALLENGE_INVALID" /* CHALLENGE_INVALID */, "Missing OAuth state parameter");
968
+ if (typeof options?.appState === "string" && options.appState.trim() !== "") {
969
+ startUrl.searchParams.set("appState", options.appState);
1015
970
  }
1016
- const response = await this.handleSocialCallback({
1017
- provider,
1018
- code,
1019
- state
1020
- });
1021
- if (response.challengeName) {
1022
- this.eventEmitter.emit({ type: "auth:challenge", data: response, timestamp: Date.now() });
1023
- } else {
1024
- this.eventEmitter.emit({ type: "auth:success", data: response, timestamp: Date.now() });
1025
- }
1026
- this.eventEmitter.emit({ type: "oauth:completed", data: response, timestamp: Date.now() });
1027
- return response;
1028
- } catch (error2) {
1029
- const authError = error2 instanceof NAuthClientError ? error2 : new NAuthClientError(
1030
- "SOCIAL_TOKEN_INVALID" /* SOCIAL_TOKEN_INVALID */,
1031
- error2.message || "OAuth callback failed"
1032
- );
1033
- this.eventEmitter.emit({ type: "oauth:error", data: authError, timestamp: Date.now() });
1034
- throw authError;
971
+ window.location.href = startUrl.toString();
1035
972
  }
1036
973
  }
1037
974
  /**
1038
- * Get social auth URL (low-level API).
975
+ * Exchange an `exchangeToken` (from redirect callback URL) into an AuthResponse.
1039
976
  *
1040
- * For most cases, use `loginWithSocial()` which handles state management automatically.
1041
- */
1042
- async getSocialAuthUrl(request) {
1043
- return this.post(this.config.endpoints.socialAuthUrl, request);
1044
- }
1045
- /**
1046
- * Handle social callback.
977
+ * Used for `tokenDelivery: 'json'` or hybrid flows where the backend redirects back
978
+ * with `exchangeToken` instead of setting cookies.
979
+ *
980
+ * @param exchangeToken - One-time exchange token from the callback URL
981
+ * @returns AuthResponse
1047
982
  */
1048
- async handleSocialCallback(request) {
1049
- const result = await this.post(this.config.endpoints.socialCallback, request);
983
+ async exchangeSocialRedirect(exchangeToken) {
984
+ const token = exchangeToken?.trim();
985
+ if (!token) {
986
+ throw new NAuthClientError("CHALLENGE_INVALID" /* CHALLENGE_INVALID */, "Missing exchangeToken");
987
+ }
988
+ const result = await this.post(this.config.endpoints.socialExchange, { exchangeToken: token });
1050
989
  await this.handleAuthResponse(result);
1051
990
  return result;
1052
991
  }
@@ -1235,7 +1174,9 @@ var NAuthClient = class {
1235
1174
  await this.setDeviceToken(response.deviceToken);
1236
1175
  }
1237
1176
  if (response.user) {
1238
- await this.setUser(response.user);
1177
+ const user = response.user;
1178
+ user.sessionAuthMethod = response.authMethod ?? null;
1179
+ await this.setUser(user);
1239
1180
  }
1240
1181
  await this.clearChallenge();
1241
1182
  }
@@ -1307,6 +1248,15 @@ var NAuthClient = class {
1307
1248
  headers["Authorization"] = `Bearer ${accessToken}`;
1308
1249
  }
1309
1250
  }
1251
+ if (this.config.tokenDelivery === "json") {
1252
+ try {
1253
+ const deviceToken = await this.config.storage.getItem(this.config.deviceTrust.storageKey);
1254
+ if (deviceToken) {
1255
+ headers[this.config.deviceTrust.headerName] = deviceToken;
1256
+ }
1257
+ } catch {
1258
+ }
1259
+ }
1310
1260
  if (this.config.tokenDelivery === "cookies" && hasWindow()) {
1311
1261
  const csrfToken = this.getCsrfToken();
1312
1262
  if (csrfToken) {