@nauth-toolkit/client 0.1.88 → 0.1.90

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 CHANGED
@@ -22,6 +22,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
22
22
  // src/index.ts
23
23
  var index_exports = {};
24
24
  __export(index_exports, {
25
+ AdminOperations: () => AdminOperations,
25
26
  AuthAuditEventType: () => AuthAuditEventType,
26
27
  AuthChallenge: () => AuthChallenge,
27
28
  BrowserStorage: () => BrowserStorage,
@@ -32,6 +33,7 @@ __export(index_exports, {
32
33
  NAuthClient: () => NAuthClient,
33
34
  NAuthClientError: () => NAuthClientError,
34
35
  NAuthErrorCode: () => NAuthErrorCode,
36
+ defaultAdminEndpoints: () => defaultAdminEndpoints,
35
37
  defaultEndpoints: () => defaultEndpoints,
36
38
  getChallengeInstructions: () => getChallengeInstructions,
37
39
  getMFAMethod: () => getMFAMethod,
@@ -176,11 +178,45 @@ var defaultEndpoints = {
176
178
  auditHistory: "/audit/history",
177
179
  updateProfile: "/profile"
178
180
  };
181
+ var defaultAdminEndpoints = {
182
+ signup: "/signup",
183
+ signupSocial: "/signup-social",
184
+ getUsers: "/users",
185
+ getUser: "/users/:sub",
186
+ deleteUser: "/users/:sub",
187
+ disableUser: "/users/:sub/disable",
188
+ enableUser: "/users/:sub/enable",
189
+ forcePasswordChange: "/users/:sub/force-password-change",
190
+ setPassword: "/set-password",
191
+ resetPasswordInitiate: "/reset-password/initiate",
192
+ getUserSessions: "/users/:sub/sessions",
193
+ logoutAll: "/users/:sub/logout-all",
194
+ getMfaStatus: "/users/:sub/mfa/status",
195
+ setPreferredMfaMethod: "/mfa/preferred-method",
196
+ removeMfaDevices: "/mfa/remove-devices",
197
+ setMfaExemption: "/mfa/exemption",
198
+ getAuditHistory: "/audit/history"
199
+ };
179
200
  var resolveConfig = (config, defaultAdapter) => {
180
201
  const resolvedEndpoints = {
181
202
  ...defaultEndpoints,
182
203
  ...config.endpoints ?? {}
183
204
  };
205
+ let resolvedAdmin;
206
+ if (config.admin) {
207
+ const resolvedAdminEndpoints = {
208
+ ...defaultAdminEndpoints,
209
+ ...config.admin.endpoints ?? {}
210
+ };
211
+ resolvedAdmin = {
212
+ pathPrefix: config.admin.pathPrefix ?? "/admin",
213
+ endpoints: resolvedAdminEndpoints,
214
+ headers: {
215
+ ...config.headers,
216
+ ...config.admin.headers ?? {}
217
+ }
218
+ };
219
+ }
184
220
  return {
185
221
  ...config,
186
222
  csrf: {
@@ -195,7 +231,8 @@ var resolveConfig = (config, defaultAdapter) => {
195
231
  timeout: config.timeout ?? 3e4,
196
232
  endpoints: resolvedEndpoints,
197
233
  storage: config.storage,
198
- httpAdapter: config.httpAdapter ?? defaultAdapter
234
+ httpAdapter: config.httpAdapter ?? defaultAdapter,
235
+ admin: resolvedAdmin
199
236
  };
200
237
  };
201
238
 
@@ -568,8 +605,9 @@ var FetchAdapter = class {
568
605
  // src/core/challenge-router.ts
569
606
  var OAUTH_STATE_KEY = "nauth_oauth_state";
570
607
  var ChallengeRouter = class {
571
- constructor(config) {
608
+ constructor(config, oauthStorage) {
572
609
  this.config = config;
610
+ this.oauthStorage = oauthStorage;
573
611
  }
574
612
  /**
575
613
  * Handle auth response - either call callback or auto-navigate.
@@ -585,10 +623,39 @@ var ChallengeRouter = class {
585
623
  if (response.challengeName) {
586
624
  await this.navigateToChallenge(response);
587
625
  } else {
588
- const queryParams = await this.getStoredOauthState();
589
- await this.navigateToSuccess(queryParams);
626
+ const queryParams = context.appState ? { appState: context.appState } : await this.getStoredOauthState();
627
+ await this.navigateToSuccess(queryParams, context);
590
628
  }
591
629
  }
630
+ /**
631
+ * Resolve the configured success URL based on context and explicit override keys.
632
+ *
633
+ * IMPORTANT:
634
+ * - `null` explicitly disables auto-navigation (caller should rely on framework events / custom routing).
635
+ * - `undefined` / missing keys fall back to other configured values or defaults.
636
+ * - Falls back to legacy `redirects.success` when new keys are not set.
637
+ *
638
+ * @param context - Auth response context
639
+ * @returns URL string, or null when auto-navigation is disabled
640
+ */
641
+ resolveSuccessUrl(context) {
642
+ const redirects = this.config.redirects;
643
+ if (!redirects) {
644
+ return "/";
645
+ }
646
+ const isSignup = context?.source === "signup";
647
+ if (isSignup) {
648
+ if (redirects.signupSuccess === null) return null;
649
+ if (typeof redirects.signupSuccess === "string") return redirects.signupSuccess;
650
+ }
651
+ if (!isSignup) {
652
+ if (redirects.loginSuccess === null) return null;
653
+ if (typeof redirects.loginSuccess === "string") return redirects.loginSuccess;
654
+ }
655
+ if (redirects.success === null) return null;
656
+ if (typeof redirects.success === "string") return redirects.success;
657
+ return "/";
658
+ }
592
659
  /**
593
660
  * Navigate to appropriate challenge route.
594
661
  *
@@ -602,14 +669,19 @@ var ChallengeRouter = class {
602
669
  * Navigate to success URL.
603
670
  *
604
671
  * @param queryParams - Optional query parameters to append to the success URL
672
+ * @param context - Optional auth context for selecting the success route
605
673
  *
606
674
  * @example
607
675
  * ```typescript
608
676
  * await router.navigateToSuccess({ appState: 'invite-code-123' });
609
677
  * ```
610
678
  */
611
- async navigateToSuccess(queryParams) {
612
- let url = this.config.redirects?.success || "/";
679
+ async navigateToSuccess(queryParams, context) {
680
+ const resolved = this.resolveSuccessUrl(context);
681
+ if (resolved === null) {
682
+ return;
683
+ }
684
+ let url = resolved;
613
685
  if (queryParams && Object.keys(queryParams).length > 0) {
614
686
  const searchParams = new URLSearchParams();
615
687
  Object.entries(queryParams).forEach(([key, value]) => {
@@ -623,15 +695,18 @@ var ChallengeRouter = class {
623
695
  await this.navigate(url);
624
696
  }
625
697
  /**
626
- * Retrieve stored OAuth appState from storage.
698
+ * Retrieve stored OAuth appState from sessionStorage.
699
+ *
700
+ * NOTE: This method does NOT clear the storage. Only the public getLastOauthState() API clears it.
701
+ * This allows the SDK to read appState for auto-navigation while still making it available to
702
+ * consumers via the public API.
627
703
  *
628
704
  * @returns Query params object with appState if present, undefined otherwise
629
705
  */
630
706
  async getStoredOauthState() {
631
707
  try {
632
- const stored = await this.config.storage.getItem(OAUTH_STATE_KEY);
708
+ const stored = await this.oauthStorage.getItem(OAUTH_STATE_KEY);
633
709
  if (stored) {
634
- await this.config.storage.removeItem(OAUTH_STATE_KEY);
635
710
  return { appState: stored };
636
711
  }
637
712
  } catch {
@@ -644,7 +719,12 @@ var ChallengeRouter = class {
644
719
  * @param type - Type of error (oauth or session)
645
720
  */
646
721
  async navigateToError(type) {
647
- const url = type === "oauth" ? this.config.redirects?.oauthError || "/login" : this.config.redirects?.sessionExpired || "/login";
722
+ const redirects = this.config.redirects;
723
+ const raw = type === "oauth" ? redirects?.oauthError : redirects?.sessionExpired;
724
+ if (raw === null) {
725
+ return;
726
+ }
727
+ const url = raw ?? "/login";
648
728
  await this.navigate(url);
649
729
  }
650
730
  /**
@@ -749,13 +829,625 @@ var ChallengeRouter = class {
749
829
  }
750
830
  };
751
831
 
832
+ // src/core/admin-operations.ts
833
+ var hasWindow = () => typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
834
+ var AdminOperations = class {
835
+ /**
836
+ * Create admin operations instance.
837
+ *
838
+ * @param config - Resolved client configuration
839
+ */
840
+ constructor(config) {
841
+ __publicField(this, "config");
842
+ __publicField(this, "adminEndpoints");
843
+ __publicField(this, "adminPathPrefix");
844
+ __publicField(this, "adminHeaders");
845
+ if (!config.admin) {
846
+ throw new Error("Admin operations require admin configuration");
847
+ }
848
+ this.config = config;
849
+ this.adminEndpoints = config.admin.endpoints;
850
+ this.adminPathPrefix = config.admin.pathPrefix;
851
+ this.adminHeaders = config.admin.headers;
852
+ }
853
+ // ============================================================================
854
+ // User Management
855
+ // ============================================================================
856
+ /**
857
+ * Create a new user (admin operation)
858
+ *
859
+ * Allows creating users with:
860
+ * - Pre-verified email/phone
861
+ * - Auto-generated passwords
862
+ * - Force password change flag
863
+ *
864
+ * @param request - User creation request
865
+ * @returns Created user and optional generated password
866
+ * @throws {NAuthClientError} If creation fails
867
+ *
868
+ * @example
869
+ * ```typescript
870
+ * const result = await client.admin.createUser({
871
+ * email: 'user@example.com',
872
+ * password: 'SecurePass123!',
873
+ * isEmailVerified: true,
874
+ * });
875
+ *
876
+ * // With auto-generated password
877
+ * const result = await client.admin.createUser({
878
+ * email: 'user@example.com',
879
+ * generatePassword: true,
880
+ * mustChangePassword: true,
881
+ * });
882
+ * console.log('Generated password:', result.generatedPassword);
883
+ * ```
884
+ */
885
+ async createUser(request) {
886
+ const path = this.buildAdminUrl(this.adminEndpoints.signup);
887
+ return this.post(path, request);
888
+ }
889
+ /**
890
+ * Import social user (admin operation)
891
+ *
892
+ * Imports existing social users from external platforms (e.g., Cognito, Auth0)
893
+ * with social account linkage.
894
+ *
895
+ * @param request - Social user import request
896
+ * @returns Created user and social account info
897
+ * @throws {NAuthClientError} If import fails
898
+ *
899
+ * @example
900
+ * ```typescript
901
+ * const result = await client.admin.importSocialUser({
902
+ * email: 'user@example.com',
903
+ * provider: 'google',
904
+ * providerId: 'google_12345',
905
+ * providerEmail: 'user@gmail.com',
906
+ * });
907
+ * ```
908
+ */
909
+ async importSocialUser(request) {
910
+ const path = this.buildAdminUrl(this.adminEndpoints.signupSocial);
911
+ return this.post(path, request);
912
+ }
913
+ /**
914
+ * Get users with filters and pagination
915
+ *
916
+ * @param params - Filter and pagination params
917
+ * @returns Paginated user list
918
+ * @throws {NAuthClientError} If request fails
919
+ *
920
+ * @example
921
+ * ```typescript
922
+ * const result = await client.admin.getUsers({
923
+ * page: 1,
924
+ * limit: 20,
925
+ * isEmailVerified: true,
926
+ * mfaEnabled: false,
927
+ * sortBy: 'createdAt',
928
+ * sortOrder: 'DESC',
929
+ * });
930
+ * ```
931
+ */
932
+ async getUsers(params = {}) {
933
+ const path = this.buildAdminUrl(this.adminEndpoints.getUsers);
934
+ const queryString = this.buildQueryString(params);
935
+ return this.get(`${path}${queryString}`);
936
+ }
937
+ /**
938
+ * Get user by sub (UUID)
939
+ *
940
+ * @param sub - User UUID
941
+ * @returns User object
942
+ * @throws {NAuthClientError} If user not found
943
+ *
944
+ * @example
945
+ * ```typescript
946
+ * const user = await client.admin.getUser('a21b654c-2746-4168-acee-c175083a65cd');
947
+ * ```
948
+ */
949
+ async getUser(sub) {
950
+ const path = this.buildAdminUrl(this.adminEndpoints.getUser, { sub });
951
+ return this.get(path);
952
+ }
953
+ /**
954
+ * Delete user with cascade cleanup
955
+ *
956
+ * @param sub - User UUID
957
+ * @returns Deletion confirmation with cascade counts
958
+ * @throws {NAuthClientError} If deletion fails
959
+ *
960
+ * @example
961
+ * ```typescript
962
+ * const result = await client.admin.deleteUser('user-uuid');
963
+ * console.log('Deleted records:', result.deletedRecords);
964
+ * ```
965
+ */
966
+ async deleteUser(sub) {
967
+ const path = this.buildAdminUrl(this.adminEndpoints.deleteUser, { sub });
968
+ return this.delete(path);
969
+ }
970
+ /**
971
+ * Disable user account (permanent lock)
972
+ *
973
+ * @param sub - User UUID
974
+ * @param reason - Optional reason for disabling
975
+ * @returns Disable confirmation with revoked session count
976
+ * @throws {NAuthClientError} If operation fails
977
+ *
978
+ * @example
979
+ * ```typescript
980
+ * const result = await client.admin.disableUser(
981
+ * 'user-uuid',
982
+ * 'Account compromised'
983
+ * );
984
+ * console.log('Revoked sessions:', result.revokedSessions);
985
+ * ```
986
+ */
987
+ async disableUser(sub, reason) {
988
+ const path = this.buildAdminUrl(this.adminEndpoints.disableUser, { sub });
989
+ return this.post(path, { reason });
990
+ }
991
+ /**
992
+ * Enable (unlock) user account
993
+ *
994
+ * @param sub - User UUID
995
+ * @returns Enable confirmation with updated user
996
+ * @throws {NAuthClientError} If operation fails
997
+ *
998
+ * @example
999
+ * ```typescript
1000
+ * const result = await client.admin.enableUser('user-uuid');
1001
+ * console.log('User enabled:', result.user);
1002
+ * ```
1003
+ */
1004
+ async enableUser(sub) {
1005
+ const path = this.buildAdminUrl(this.adminEndpoints.enableUser, { sub });
1006
+ return this.post(path, {});
1007
+ }
1008
+ /**
1009
+ * Force password change on next login
1010
+ *
1011
+ * @param sub - User UUID
1012
+ * @returns Success confirmation
1013
+ * @throws {NAuthClientError} If operation fails
1014
+ *
1015
+ * @example
1016
+ * ```typescript
1017
+ * await client.admin.forcePasswordChange('user-uuid');
1018
+ * ```
1019
+ */
1020
+ async forcePasswordChange(sub) {
1021
+ const path = this.buildAdminUrl(this.adminEndpoints.forcePasswordChange, { sub });
1022
+ return this.post(path, {});
1023
+ }
1024
+ // ============================================================================
1025
+ // Password Management
1026
+ // ============================================================================
1027
+ /**
1028
+ * Set password for any user (admin operation)
1029
+ *
1030
+ * @param identifier - User email, username, or phone
1031
+ * @param newPassword - New password
1032
+ * @returns Success confirmation
1033
+ * @throws {NAuthClientError} If operation fails
1034
+ *
1035
+ * @example
1036
+ * ```typescript
1037
+ * await client.admin.setPassword('user@example.com', 'NewSecurePass123!');
1038
+ * ```
1039
+ */
1040
+ async setPassword(identifier, newPassword) {
1041
+ const path = this.buildAdminUrl(this.adminEndpoints.setPassword);
1042
+ return this.post(path, { identifier, newPassword });
1043
+ }
1044
+ /**
1045
+ * Initiate password reset workflow (sends code/link to user)
1046
+ *
1047
+ * @param request - Password reset request
1048
+ * @returns Reset confirmation with delivery details
1049
+ * @throws {NAuthClientError} If operation fails
1050
+ *
1051
+ * @example
1052
+ * ```typescript
1053
+ * const result = await client.admin.initiatePasswordReset({
1054
+ * sub: 'user-uuid',
1055
+ * deliveryMethod: 'email',
1056
+ * baseUrl: 'https://myapp.com/reset-password',
1057
+ * reason: 'User requested password reset',
1058
+ * });
1059
+ * console.log('Code sent to:', result.destination);
1060
+ * ```
1061
+ */
1062
+ async initiatePasswordReset(request) {
1063
+ const path = this.buildAdminUrl(this.adminEndpoints.resetPasswordInitiate);
1064
+ return this.post(path, request);
1065
+ }
1066
+ // ============================================================================
1067
+ // Session Management
1068
+ // ============================================================================
1069
+ /**
1070
+ * Get all sessions for a user
1071
+ *
1072
+ * @param sub - User UUID
1073
+ * @returns User sessions
1074
+ * @throws {NAuthClientError} If request fails
1075
+ *
1076
+ * @example
1077
+ * ```typescript
1078
+ * const result = await client.admin.getUserSessions('user-uuid');
1079
+ * console.log('Active sessions:', result.sessions);
1080
+ * ```
1081
+ */
1082
+ async getUserSessions(sub) {
1083
+ const path = this.buildAdminUrl(this.adminEndpoints.getUserSessions, { sub });
1084
+ return this.get(path);
1085
+ }
1086
+ /**
1087
+ * Logout all sessions for a user (admin-initiated)
1088
+ *
1089
+ * @param sub - User UUID
1090
+ * @param forgetDevices - If true, also revokes all trusted devices
1091
+ * @returns Number of sessions revoked
1092
+ * @throws {NAuthClientError} If operation fails
1093
+ *
1094
+ * @example
1095
+ * ```typescript
1096
+ * const result = await client.admin.logoutAllSessions('user-uuid', true);
1097
+ * console.log(`Revoked ${result.revokedCount} sessions`);
1098
+ * ```
1099
+ */
1100
+ async logoutAllSessions(sub, forgetDevices = false) {
1101
+ const path = this.buildAdminUrl(this.adminEndpoints.logoutAll, { sub });
1102
+ return this.post(path, { forgetDevices });
1103
+ }
1104
+ // ============================================================================
1105
+ // MFA Management
1106
+ // ============================================================================
1107
+ /**
1108
+ * Get MFA status for a user
1109
+ *
1110
+ * @param sub - User UUID
1111
+ * @returns MFA status
1112
+ * @throws {NAuthClientError} If request fails
1113
+ *
1114
+ * @example
1115
+ * ```typescript
1116
+ * const status = await client.admin.getMfaStatus('user-uuid');
1117
+ * console.log('MFA enabled:', status.enabled);
1118
+ * ```
1119
+ */
1120
+ async getMfaStatus(sub) {
1121
+ const path = this.buildAdminUrl(this.adminEndpoints.getMfaStatus, { sub });
1122
+ return this.get(path);
1123
+ }
1124
+ /**
1125
+ * Set preferred MFA method for a user
1126
+ *
1127
+ * @param sub - User UUID
1128
+ * @param method - MFA method to set as preferred
1129
+ * @returns Success message
1130
+ * @throws {NAuthClientError} If operation fails
1131
+ *
1132
+ * @example
1133
+ * ```typescript
1134
+ * await client.admin.setPreferredMfaMethod('user-uuid', 'totp');
1135
+ * ```
1136
+ */
1137
+ async setPreferredMfaMethod(sub, method) {
1138
+ const path = this.buildAdminUrl(this.adminEndpoints.setPreferredMfaMethod);
1139
+ return this.post(path, { sub, method });
1140
+ }
1141
+ /**
1142
+ * Remove MFA devices for a user
1143
+ *
1144
+ * @param sub - User UUID
1145
+ * @param method - MFA method to remove
1146
+ * @returns Success message
1147
+ * @throws {NAuthClientError} If operation fails
1148
+ *
1149
+ * @example
1150
+ * ```typescript
1151
+ * await client.admin.removeMfaDevices('user-uuid', 'sms');
1152
+ * ```
1153
+ */
1154
+ async removeMfaDevices(sub, method) {
1155
+ const path = this.buildAdminUrl(this.adminEndpoints.removeMfaDevices);
1156
+ return this.post(path, { sub, method });
1157
+ }
1158
+ /**
1159
+ * Grant or revoke MFA exemption for a user
1160
+ *
1161
+ * @param sub - User UUID
1162
+ * @param exempt - True to exempt from MFA, false to require
1163
+ * @param reason - Optional reason for exemption
1164
+ * @returns Success message
1165
+ * @throws {NAuthClientError} If operation fails
1166
+ *
1167
+ * @example
1168
+ * ```typescript
1169
+ * await client.admin.setMfaExemption('user-uuid', true, 'Service account');
1170
+ * ```
1171
+ */
1172
+ async setMfaExemption(sub, exempt, reason) {
1173
+ const path = this.buildAdminUrl(this.adminEndpoints.setMfaExemption);
1174
+ return this.post(path, { sub, exempt, reason });
1175
+ }
1176
+ // ============================================================================
1177
+ // Audit
1178
+ // ============================================================================
1179
+ /**
1180
+ * Get audit history for a user
1181
+ *
1182
+ * @param params - Audit history request params
1183
+ * @returns Paginated audit events
1184
+ * @throws {NAuthClientError} If request fails
1185
+ *
1186
+ * @example
1187
+ * ```typescript
1188
+ * const history = await client.admin.getAuditHistory({
1189
+ * sub: 'user-uuid',
1190
+ * page: 1,
1191
+ * limit: 50,
1192
+ * eventType: 'LOGIN_SUCCESS',
1193
+ * });
1194
+ * ```
1195
+ */
1196
+ async getAuditHistory(params) {
1197
+ const path = this.buildAdminUrl(this.adminEndpoints.getAuditHistory);
1198
+ const queryString = this.buildQueryString(params);
1199
+ return this.get(`${path}${queryString}`);
1200
+ }
1201
+ // ============================================================================
1202
+ // Private Helper Methods
1203
+ // ============================================================================
1204
+ /**
1205
+ * Build admin endpoint URL with path parameter replacement
1206
+ *
1207
+ * Path construction order:
1208
+ * 1. Start with endpoint: '/users/:sub'
1209
+ * 2. Replace params: '/users/uuid-123'
1210
+ * 3. Apply adminPathPrefix: '/admin/users/uuid-123'
1211
+ * 4. Apply authPathPrefix if exists: '/auth/admin/users/uuid-123'
1212
+ * 5. Combine with baseUrl: 'https://api.example.com/auth/admin/users/uuid-123'
1213
+ *
1214
+ * @param endpointPath - Endpoint path (may contain :sub, :provider, etc.)
1215
+ * @param pathParams - Path parameters to replace
1216
+ * @returns Full URL
1217
+ * @private
1218
+ */
1219
+ buildAdminUrl(endpointPath, pathParams) {
1220
+ let path = endpointPath;
1221
+ if (pathParams) {
1222
+ Object.entries(pathParams).forEach(([key, value]) => {
1223
+ path = path.replace(`:${key}`, encodeURIComponent(value));
1224
+ });
1225
+ }
1226
+ const prefix = this.adminPathPrefix.startsWith("/") ? this.adminPathPrefix : `/${this.adminPathPrefix}`;
1227
+ path = `${prefix}${path.startsWith("/") ? "" : "/"}${path}`;
1228
+ if (this.config.authPathPrefix) {
1229
+ const authPrefix = this.config.authPathPrefix.startsWith("/") ? this.config.authPathPrefix : `/${this.config.authPathPrefix}`;
1230
+ path = `${authPrefix}${path}`;
1231
+ }
1232
+ const normalizedBase = this.config.baseUrl.endsWith("/") ? this.config.baseUrl.slice(0, -1) : this.config.baseUrl;
1233
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1234
+ return `${normalizedBase}${normalizedPath}`;
1235
+ }
1236
+ /**
1237
+ * Build query string from params object
1238
+ *
1239
+ * Handles:
1240
+ * - Simple values (string, number, boolean)
1241
+ * - Arrays (multiple values for same key)
1242
+ * - Nested objects (e.g., createdAt[operator], createdAt[value])
1243
+ * - Dates (converted to ISO string)
1244
+ *
1245
+ * @param params - Query parameters
1246
+ * @returns Query string (e.g., '?page=1&limit=20')
1247
+ * @private
1248
+ */
1249
+ buildQueryString(params) {
1250
+ const searchParams = new URLSearchParams();
1251
+ for (const [key, rawValue] of Object.entries(params)) {
1252
+ if (rawValue === void 0 || rawValue === null) {
1253
+ continue;
1254
+ }
1255
+ if (Array.isArray(rawValue)) {
1256
+ for (const item of rawValue) {
1257
+ searchParams.append(key, String(item));
1258
+ }
1259
+ continue;
1260
+ }
1261
+ if (typeof rawValue === "object" && rawValue !== null && !(rawValue instanceof Date)) {
1262
+ const nestedObj = rawValue;
1263
+ for (const [nestedKey, nestedValue] of Object.entries(nestedObj)) {
1264
+ const nestedParamKey = `${key}[${nestedKey}]`;
1265
+ const valueToAppend = nestedValue instanceof Date ? nestedValue.toISOString() : String(nestedValue);
1266
+ searchParams.append(nestedParamKey, valueToAppend);
1267
+ }
1268
+ continue;
1269
+ }
1270
+ if (rawValue instanceof Date) {
1271
+ searchParams.append(key, rawValue.toISOString());
1272
+ continue;
1273
+ }
1274
+ searchParams.append(key, String(rawValue));
1275
+ }
1276
+ const query = searchParams.toString();
1277
+ return query ? `?${query}` : "";
1278
+ }
1279
+ /**
1280
+ * Build request headers for authentication
1281
+ *
1282
+ * @param auth - Whether to include authentication headers
1283
+ * @param method - HTTP method
1284
+ * @returns Headers object
1285
+ * @private
1286
+ */
1287
+ async buildHeaders(auth, method = "GET") {
1288
+ const headers = {
1289
+ ...this.config.headers,
1290
+ ...this.adminHeaders
1291
+ };
1292
+ if (method !== "GET") {
1293
+ headers["Content-Type"] = "application/json";
1294
+ }
1295
+ if (auth && this.config.tokenDelivery === "json") {
1296
+ try {
1297
+ const ACCESS_TOKEN_KEY2 = "nauth_access_token";
1298
+ const token = await this.config.storage.getItem(ACCESS_TOKEN_KEY2);
1299
+ if (token) {
1300
+ headers["Authorization"] = `Bearer ${token}`;
1301
+ }
1302
+ } catch {
1303
+ }
1304
+ }
1305
+ if (this.config.tokenDelivery === "json") {
1306
+ try {
1307
+ const deviceToken = await this.config.storage.getItem(this.config.deviceTrust.storageKey);
1308
+ if (deviceToken) {
1309
+ headers[this.config.deviceTrust.headerName] = deviceToken;
1310
+ }
1311
+ } catch {
1312
+ }
1313
+ }
1314
+ const mutatingMethods = [
1315
+ "POST",
1316
+ "PUT",
1317
+ "PATCH",
1318
+ "DELETE"
1319
+ ];
1320
+ if (this.config.tokenDelivery === "cookies" && hasWindow() && mutatingMethods.includes(method)) {
1321
+ const csrfToken = this.getCsrfToken();
1322
+ if (csrfToken) {
1323
+ headers[this.config.csrf.headerName] = csrfToken;
1324
+ }
1325
+ }
1326
+ return headers;
1327
+ }
1328
+ /**
1329
+ * Get CSRF token from cookie (browser only)
1330
+ *
1331
+ * @returns CSRF token or null
1332
+ * @private
1333
+ */
1334
+ getCsrfToken() {
1335
+ if (!hasWindow() || typeof document === "undefined") return null;
1336
+ const match = document.cookie.match(
1337
+ new RegExp(`(^| )${this.config.csrf.cookieName}=([^;]+)`)
1338
+ );
1339
+ return match ? decodeURIComponent(match[2]) : null;
1340
+ }
1341
+ /**
1342
+ * Execute GET request
1343
+ *
1344
+ * @param path - Full URL path
1345
+ * @returns Response data
1346
+ * @private
1347
+ */
1348
+ async get(path) {
1349
+ const headers = await this.buildHeaders(true, "GET");
1350
+ const credentials = this.config.tokenDelivery === "cookies" ? "include" : "omit";
1351
+ try {
1352
+ const response = await this.config.httpAdapter.request({
1353
+ method: "GET",
1354
+ url: path,
1355
+ headers,
1356
+ credentials
1357
+ });
1358
+ return response.data;
1359
+ } catch (error) {
1360
+ throw this.handleError(error);
1361
+ }
1362
+ }
1363
+ /**
1364
+ * Execute POST request
1365
+ *
1366
+ * @param path - Full URL path
1367
+ * @param body - Request body
1368
+ * @returns Response data
1369
+ * @private
1370
+ */
1371
+ async post(path, body) {
1372
+ const headers = await this.buildHeaders(true, "POST");
1373
+ const credentials = this.config.tokenDelivery === "cookies" ? "include" : "omit";
1374
+ try {
1375
+ const response = await this.config.httpAdapter.request({
1376
+ method: "POST",
1377
+ url: path,
1378
+ headers,
1379
+ body,
1380
+ credentials
1381
+ });
1382
+ return response.data;
1383
+ } catch (error) {
1384
+ throw this.handleError(error);
1385
+ }
1386
+ }
1387
+ /**
1388
+ * Execute DELETE request
1389
+ *
1390
+ * @param path - Full URL path
1391
+ * @returns Response data
1392
+ * @private
1393
+ */
1394
+ async delete(path) {
1395
+ const headers = await this.buildHeaders(true, "DELETE");
1396
+ const credentials = this.config.tokenDelivery === "cookies" ? "include" : "omit";
1397
+ try {
1398
+ const response = await this.config.httpAdapter.request({
1399
+ method: "DELETE",
1400
+ url: path,
1401
+ headers,
1402
+ credentials
1403
+ });
1404
+ return response.data;
1405
+ } catch (error) {
1406
+ throw this.handleError(error);
1407
+ }
1408
+ }
1409
+ /**
1410
+ * Handle HTTP errors and convert to NAuthClientError
1411
+ *
1412
+ * @param error - Error from HTTP adapter
1413
+ * @returns NAuthClientError
1414
+ * @private
1415
+ */
1416
+ handleError(error) {
1417
+ if (error instanceof NAuthClientError) {
1418
+ return error;
1419
+ }
1420
+ if (error && typeof error === "object" && "response" in error) {
1421
+ const httpError = error;
1422
+ const status = httpError.response?.status ?? 500;
1423
+ const errorData = typeof httpError.response?.data === "object" && httpError.response.data !== null ? httpError.response.data : {};
1424
+ const code = typeof errorData["code"] === "string" ? errorData["code"] : "INTERNAL_ERROR" /* INTERNAL_ERROR */;
1425
+ const message = typeof errorData["message"] === "string" ? errorData["message"] : `Request failed with status ${status}`;
1426
+ return new NAuthClientError(code, message, {
1427
+ statusCode: status,
1428
+ details: errorData
1429
+ });
1430
+ }
1431
+ return new NAuthClientError(
1432
+ "INTERNAL_ERROR" /* INTERNAL_ERROR */,
1433
+ error instanceof Error ? error.message : "Unknown error"
1434
+ );
1435
+ }
1436
+ };
1437
+
752
1438
  // src/core/client.ts
753
1439
  var USER_KEY2 = "nauth_user";
754
1440
  var CHALLENGE_KEY2 = "nauth_challenge_session";
755
1441
  var OAUTH_STATE_KEY2 = "nauth_oauth_state";
756
- var hasWindow = () => typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
1442
+ var hasWindow2 = () => typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
1443
+ var getOauthStorage = (mainStorage) => {
1444
+ if (hasWindow2() && typeof window.sessionStorage !== "undefined") {
1445
+ return new BrowserStorage(window.sessionStorage);
1446
+ }
1447
+ return mainStorage;
1448
+ };
757
1449
  var defaultStorage = () => {
758
- if (hasWindow() && typeof window.localStorage !== "undefined") {
1450
+ if (hasWindow2() && typeof window.localStorage !== "undefined") {
759
1451
  return new BrowserStorage();
760
1452
  }
761
1453
  return new InMemoryStorage();
@@ -771,7 +1463,34 @@ var NAuthClient = class {
771
1463
  __publicField(this, "tokenManager");
772
1464
  __publicField(this, "eventEmitter");
773
1465
  __publicField(this, "challengeRouter");
1466
+ __publicField(this, "oauthStorage");
774
1467
  __publicField(this, "currentUser", null);
1468
+ /**
1469
+ * Admin operations (available if admin config provided).
1470
+ *
1471
+ * Provides admin-level user management methods:
1472
+ * - User CRUD operations
1473
+ * - Password management
1474
+ * - Session management
1475
+ * - MFA management
1476
+ * - Audit history
1477
+ *
1478
+ * @example
1479
+ * ```typescript
1480
+ * const client = new NAuthClient({
1481
+ * baseUrl: 'https://api.example.com/auth',
1482
+ * tokenDelivery: 'cookies',
1483
+ * admin: {
1484
+ * pathPrefix: '/admin',
1485
+ * },
1486
+ * });
1487
+ *
1488
+ * // Use admin operations
1489
+ * const users = await client.admin.getUsers({ page: 1 });
1490
+ * await client.admin.deleteUser('user-uuid');
1491
+ * ```
1492
+ */
1493
+ __publicField(this, "admin");
775
1494
  /**
776
1495
  * Handle cross-tab storage updates.
777
1496
  */
@@ -789,8 +1508,12 @@ var NAuthClient = class {
789
1508
  this.config = resolveConfig({ ...userConfig, storage }, defaultAdapter);
790
1509
  this.tokenManager = new TokenManager(storage);
791
1510
  this.eventEmitter = new EventEmitter();
792
- this.challengeRouter = new ChallengeRouter(this.config);
793
- if (hasWindow()) {
1511
+ this.oauthStorage = getOauthStorage(storage);
1512
+ this.challengeRouter = new ChallengeRouter(this.config, this.oauthStorage);
1513
+ if (this.config.admin) {
1514
+ this.admin = new AdminOperations(this.config);
1515
+ }
1516
+ if (hasWindow2()) {
794
1517
  window.addEventListener("storage", this.handleStorageEvent);
795
1518
  }
796
1519
  }
@@ -798,7 +1521,7 @@ var NAuthClient = class {
798
1521
  * Clean up resources.
799
1522
  */
800
1523
  dispose() {
801
- if (hasWindow()) {
1524
+ if (hasWindow2()) {
802
1525
  window.removeEventListener("storage", this.handleStorageEvent);
803
1526
  }
804
1527
  }
@@ -1246,11 +1969,12 @@ var NAuthClient = class {
1246
1969
  */
1247
1970
  async loginWithSocial(provider, options) {
1248
1971
  this.eventEmitter.emit({ type: "oauth:started", data: { provider }, timestamp: Date.now() });
1249
- if (hasWindow()) {
1972
+ if (hasWindow2()) {
1250
1973
  const startPath = this.config.endpoints.socialRedirectStart.replace(":provider", provider);
1251
1974
  const fullUrl = this.buildUrl(startPath);
1252
1975
  const startUrl = new URL(fullUrl);
1253
- const returnTo = options?.returnTo ?? this.config.redirects?.success ?? "/";
1976
+ const redirects = this.config.redirects;
1977
+ const returnTo = options?.returnTo ?? redirects?.loginSuccess ?? redirects?.success ?? "/";
1254
1978
  startUrl.searchParams.set("returnTo", returnTo);
1255
1979
  if (options?.action === "link") {
1256
1980
  startUrl.searchParams.set("action", "link");
@@ -1277,7 +2001,11 @@ var NAuthClient = class {
1277
2001
  }
1278
2002
  const result = await this.post(this.config.endpoints.socialExchange, { exchangeToken: token });
1279
2003
  await this.handleAuthResponse(result);
1280
- await this.challengeRouter.handleAuthResponse(result, { source: "social" });
2004
+ const appState = await this.oauthStorage.getItem(OAUTH_STATE_KEY2);
2005
+ await this.challengeRouter.handleAuthResponse(result, {
2006
+ source: "social",
2007
+ appState: appState ?? void 0
2008
+ });
1281
2009
  return result;
1282
2010
  }
1283
2011
  /**
@@ -1563,7 +2291,7 @@ var NAuthClient = class {
1563
2291
  }
1564
2292
  }
1565
2293
  const mutatingMethods = ["POST", "PUT", "PATCH", "DELETE"];
1566
- if (this.config.tokenDelivery === "cookies" && hasWindow() && mutatingMethods.includes(method)) {
2294
+ if (this.config.tokenDelivery === "cookies" && hasWindow2() && mutatingMethods.includes(method)) {
1567
2295
  const csrfToken = this.getCsrfToken();
1568
2296
  if (csrfToken) {
1569
2297
  headers[this.config.csrf.headerName] = csrfToken;
@@ -1576,7 +2304,7 @@ var NAuthClient = class {
1576
2304
  * @private
1577
2305
  */
1578
2306
  getCsrfToken() {
1579
- if (!hasWindow() || typeof document === "undefined") return null;
2307
+ if (!hasWindow2() || typeof document === "undefined") return null;
1580
2308
  const match = document.cookie.match(new RegExp(`(^| )${this.config.csrf.cookieName}=([^;]+)`));
1581
2309
  return match ? decodeURIComponent(match[2]) : null;
1582
2310
  }
@@ -1668,6 +2396,8 @@ var NAuthClient = class {
1668
2396
  * when appState is present in the callback URL. The stored state can
1669
2397
  * be retrieved using getLastOauthState().
1670
2398
  *
2399
+ * Stores in sessionStorage (ephemeral) for better security.
2400
+ *
1671
2401
  * @param appState - OAuth appState value from callback URL
1672
2402
  *
1673
2403
  * @example
@@ -1677,7 +2407,7 @@ var NAuthClient = class {
1677
2407
  */
1678
2408
  async storeOauthState(appState) {
1679
2409
  if (appState && appState.trim() !== "") {
1680
- await this.config.storage.setItem(OAUTH_STATE_KEY2, appState);
2410
+ await this.oauthStorage.setItem(OAUTH_STATE_KEY2, appState);
1681
2411
  }
1682
2412
  }
1683
2413
  /**
@@ -1688,6 +2418,7 @@ var NAuthClient = class {
1688
2418
  * applying invite codes, or tracking referral information.
1689
2419
  *
1690
2420
  * The state is automatically cleared after retrieval to prevent reuse.
2421
+ * Stored in sessionStorage (ephemeral) for better security.
1691
2422
  *
1692
2423
  * @returns The stored appState, or null if none exists
1693
2424
  *
@@ -1701,9 +2432,9 @@ var NAuthClient = class {
1701
2432
  * ```
1702
2433
  */
1703
2434
  async getLastOauthState() {
1704
- const stored = await this.config.storage.getItem(OAUTH_STATE_KEY2);
2435
+ const stored = await this.oauthStorage.getItem(OAUTH_STATE_KEY2);
1705
2436
  if (stored) {
1706
- await this.config.storage.removeItem(OAUTH_STATE_KEY2);
2437
+ await this.oauthStorage.removeItem(OAUTH_STATE_KEY2);
1707
2438
  return stored;
1708
2439
  }
1709
2440
  return null;
@@ -1753,6 +2484,7 @@ function isOTPChallenge(challenge) {
1753
2484
  }
1754
2485
  // Annotate the CommonJS export names for ESM import in node:
1755
2486
  0 && (module.exports = {
2487
+ AdminOperations,
1756
2488
  AuthAuditEventType,
1757
2489
  AuthChallenge,
1758
2490
  BrowserStorage,
@@ -1763,6 +2495,7 @@ function isOTPChallenge(challenge) {
1763
2495
  NAuthClient,
1764
2496
  NAuthClientError,
1765
2497
  NAuthErrorCode,
2498
+ defaultAdminEndpoints,
1766
2499
  defaultEndpoints,
1767
2500
  getChallengeInstructions,
1768
2501
  getMFAMethod,