@nauth-toolkit/client 0.1.89 → 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.mjs CHANGED
@@ -136,11 +136,45 @@ var defaultEndpoints = {
136
136
  auditHistory: "/audit/history",
137
137
  updateProfile: "/profile"
138
138
  };
139
+ var defaultAdminEndpoints = {
140
+ signup: "/signup",
141
+ signupSocial: "/signup-social",
142
+ getUsers: "/users",
143
+ getUser: "/users/:sub",
144
+ deleteUser: "/users/:sub",
145
+ disableUser: "/users/:sub/disable",
146
+ enableUser: "/users/:sub/enable",
147
+ forcePasswordChange: "/users/:sub/force-password-change",
148
+ setPassword: "/set-password",
149
+ resetPasswordInitiate: "/reset-password/initiate",
150
+ getUserSessions: "/users/:sub/sessions",
151
+ logoutAll: "/users/:sub/logout-all",
152
+ getMfaStatus: "/users/:sub/mfa/status",
153
+ setPreferredMfaMethod: "/mfa/preferred-method",
154
+ removeMfaDevices: "/mfa/remove-devices",
155
+ setMfaExemption: "/mfa/exemption",
156
+ getAuditHistory: "/audit/history"
157
+ };
139
158
  var resolveConfig = (config, defaultAdapter) => {
140
159
  const resolvedEndpoints = {
141
160
  ...defaultEndpoints,
142
161
  ...config.endpoints ?? {}
143
162
  };
163
+ let resolvedAdmin;
164
+ if (config.admin) {
165
+ const resolvedAdminEndpoints = {
166
+ ...defaultAdminEndpoints,
167
+ ...config.admin.endpoints ?? {}
168
+ };
169
+ resolvedAdmin = {
170
+ pathPrefix: config.admin.pathPrefix ?? "/admin",
171
+ endpoints: resolvedAdminEndpoints,
172
+ headers: {
173
+ ...config.headers,
174
+ ...config.admin.headers ?? {}
175
+ }
176
+ };
177
+ }
144
178
  return {
145
179
  ...config,
146
180
  csrf: {
@@ -155,7 +189,8 @@ var resolveConfig = (config, defaultAdapter) => {
155
189
  timeout: config.timeout ?? 3e4,
156
190
  endpoints: resolvedEndpoints,
157
191
  storage: config.storage,
158
- httpAdapter: config.httpAdapter ?? defaultAdapter
192
+ httpAdapter: config.httpAdapter ?? defaultAdapter,
193
+ admin: resolvedAdmin
159
194
  };
160
195
  };
161
196
 
@@ -528,8 +563,9 @@ var FetchAdapter = class {
528
563
  // src/core/challenge-router.ts
529
564
  var OAUTH_STATE_KEY = "nauth_oauth_state";
530
565
  var ChallengeRouter = class {
531
- constructor(config) {
566
+ constructor(config, oauthStorage) {
532
567
  this.config = config;
568
+ this.oauthStorage = oauthStorage;
533
569
  }
534
570
  /**
535
571
  * Handle auth response - either call callback or auto-navigate.
@@ -545,10 +581,39 @@ var ChallengeRouter = class {
545
581
  if (response.challengeName) {
546
582
  await this.navigateToChallenge(response);
547
583
  } else {
548
- const queryParams = await this.getStoredOauthState();
549
- await this.navigateToSuccess(queryParams);
584
+ const queryParams = context.appState ? { appState: context.appState } : await this.getStoredOauthState();
585
+ await this.navigateToSuccess(queryParams, context);
550
586
  }
551
587
  }
588
+ /**
589
+ * Resolve the configured success URL based on context and explicit override keys.
590
+ *
591
+ * IMPORTANT:
592
+ * - `null` explicitly disables auto-navigation (caller should rely on framework events / custom routing).
593
+ * - `undefined` / missing keys fall back to other configured values or defaults.
594
+ * - Falls back to legacy `redirects.success` when new keys are not set.
595
+ *
596
+ * @param context - Auth response context
597
+ * @returns URL string, or null when auto-navigation is disabled
598
+ */
599
+ resolveSuccessUrl(context) {
600
+ const redirects = this.config.redirects;
601
+ if (!redirects) {
602
+ return "/";
603
+ }
604
+ const isSignup = context?.source === "signup";
605
+ if (isSignup) {
606
+ if (redirects.signupSuccess === null) return null;
607
+ if (typeof redirects.signupSuccess === "string") return redirects.signupSuccess;
608
+ }
609
+ if (!isSignup) {
610
+ if (redirects.loginSuccess === null) return null;
611
+ if (typeof redirects.loginSuccess === "string") return redirects.loginSuccess;
612
+ }
613
+ if (redirects.success === null) return null;
614
+ if (typeof redirects.success === "string") return redirects.success;
615
+ return "/";
616
+ }
552
617
  /**
553
618
  * Navigate to appropriate challenge route.
554
619
  *
@@ -562,14 +627,19 @@ var ChallengeRouter = class {
562
627
  * Navigate to success URL.
563
628
  *
564
629
  * @param queryParams - Optional query parameters to append to the success URL
630
+ * @param context - Optional auth context for selecting the success route
565
631
  *
566
632
  * @example
567
633
  * ```typescript
568
634
  * await router.navigateToSuccess({ appState: 'invite-code-123' });
569
635
  * ```
570
636
  */
571
- async navigateToSuccess(queryParams) {
572
- let url = this.config.redirects?.success || "/";
637
+ async navigateToSuccess(queryParams, context) {
638
+ const resolved = this.resolveSuccessUrl(context);
639
+ if (resolved === null) {
640
+ return;
641
+ }
642
+ let url = resolved;
573
643
  if (queryParams && Object.keys(queryParams).length > 0) {
574
644
  const searchParams = new URLSearchParams();
575
645
  Object.entries(queryParams).forEach(([key, value]) => {
@@ -583,15 +653,18 @@ var ChallengeRouter = class {
583
653
  await this.navigate(url);
584
654
  }
585
655
  /**
586
- * Retrieve stored OAuth appState from storage.
656
+ * Retrieve stored OAuth appState from sessionStorage.
657
+ *
658
+ * NOTE: This method does NOT clear the storage. Only the public getLastOauthState() API clears it.
659
+ * This allows the SDK to read appState for auto-navigation while still making it available to
660
+ * consumers via the public API.
587
661
  *
588
662
  * @returns Query params object with appState if present, undefined otherwise
589
663
  */
590
664
  async getStoredOauthState() {
591
665
  try {
592
- const stored = await this.config.storage.getItem(OAUTH_STATE_KEY);
666
+ const stored = await this.oauthStorage.getItem(OAUTH_STATE_KEY);
593
667
  if (stored) {
594
- await this.config.storage.removeItem(OAUTH_STATE_KEY);
595
668
  return { appState: stored };
596
669
  }
597
670
  } catch {
@@ -604,7 +677,12 @@ var ChallengeRouter = class {
604
677
  * @param type - Type of error (oauth or session)
605
678
  */
606
679
  async navigateToError(type) {
607
- const url = type === "oauth" ? this.config.redirects?.oauthError || "/login" : this.config.redirects?.sessionExpired || "/login";
680
+ const redirects = this.config.redirects;
681
+ const raw = type === "oauth" ? redirects?.oauthError : redirects?.sessionExpired;
682
+ if (raw === null) {
683
+ return;
684
+ }
685
+ const url = raw ?? "/login";
608
686
  await this.navigate(url);
609
687
  }
610
688
  /**
@@ -709,13 +787,625 @@ var ChallengeRouter = class {
709
787
  }
710
788
  };
711
789
 
790
+ // src/core/admin-operations.ts
791
+ var hasWindow = () => typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
792
+ var AdminOperations = class {
793
+ /**
794
+ * Create admin operations instance.
795
+ *
796
+ * @param config - Resolved client configuration
797
+ */
798
+ constructor(config) {
799
+ __publicField(this, "config");
800
+ __publicField(this, "adminEndpoints");
801
+ __publicField(this, "adminPathPrefix");
802
+ __publicField(this, "adminHeaders");
803
+ if (!config.admin) {
804
+ throw new Error("Admin operations require admin configuration");
805
+ }
806
+ this.config = config;
807
+ this.adminEndpoints = config.admin.endpoints;
808
+ this.adminPathPrefix = config.admin.pathPrefix;
809
+ this.adminHeaders = config.admin.headers;
810
+ }
811
+ // ============================================================================
812
+ // User Management
813
+ // ============================================================================
814
+ /**
815
+ * Create a new user (admin operation)
816
+ *
817
+ * Allows creating users with:
818
+ * - Pre-verified email/phone
819
+ * - Auto-generated passwords
820
+ * - Force password change flag
821
+ *
822
+ * @param request - User creation request
823
+ * @returns Created user and optional generated password
824
+ * @throws {NAuthClientError} If creation fails
825
+ *
826
+ * @example
827
+ * ```typescript
828
+ * const result = await client.admin.createUser({
829
+ * email: 'user@example.com',
830
+ * password: 'SecurePass123!',
831
+ * isEmailVerified: true,
832
+ * });
833
+ *
834
+ * // With auto-generated password
835
+ * const result = await client.admin.createUser({
836
+ * email: 'user@example.com',
837
+ * generatePassword: true,
838
+ * mustChangePassword: true,
839
+ * });
840
+ * console.log('Generated password:', result.generatedPassword);
841
+ * ```
842
+ */
843
+ async createUser(request) {
844
+ const path = this.buildAdminUrl(this.adminEndpoints.signup);
845
+ return this.post(path, request);
846
+ }
847
+ /**
848
+ * Import social user (admin operation)
849
+ *
850
+ * Imports existing social users from external platforms (e.g., Cognito, Auth0)
851
+ * with social account linkage.
852
+ *
853
+ * @param request - Social user import request
854
+ * @returns Created user and social account info
855
+ * @throws {NAuthClientError} If import fails
856
+ *
857
+ * @example
858
+ * ```typescript
859
+ * const result = await client.admin.importSocialUser({
860
+ * email: 'user@example.com',
861
+ * provider: 'google',
862
+ * providerId: 'google_12345',
863
+ * providerEmail: 'user@gmail.com',
864
+ * });
865
+ * ```
866
+ */
867
+ async importSocialUser(request) {
868
+ const path = this.buildAdminUrl(this.adminEndpoints.signupSocial);
869
+ return this.post(path, request);
870
+ }
871
+ /**
872
+ * Get users with filters and pagination
873
+ *
874
+ * @param params - Filter and pagination params
875
+ * @returns Paginated user list
876
+ * @throws {NAuthClientError} If request fails
877
+ *
878
+ * @example
879
+ * ```typescript
880
+ * const result = await client.admin.getUsers({
881
+ * page: 1,
882
+ * limit: 20,
883
+ * isEmailVerified: true,
884
+ * mfaEnabled: false,
885
+ * sortBy: 'createdAt',
886
+ * sortOrder: 'DESC',
887
+ * });
888
+ * ```
889
+ */
890
+ async getUsers(params = {}) {
891
+ const path = this.buildAdminUrl(this.adminEndpoints.getUsers);
892
+ const queryString = this.buildQueryString(params);
893
+ return this.get(`${path}${queryString}`);
894
+ }
895
+ /**
896
+ * Get user by sub (UUID)
897
+ *
898
+ * @param sub - User UUID
899
+ * @returns User object
900
+ * @throws {NAuthClientError} If user not found
901
+ *
902
+ * @example
903
+ * ```typescript
904
+ * const user = await client.admin.getUser('a21b654c-2746-4168-acee-c175083a65cd');
905
+ * ```
906
+ */
907
+ async getUser(sub) {
908
+ const path = this.buildAdminUrl(this.adminEndpoints.getUser, { sub });
909
+ return this.get(path);
910
+ }
911
+ /**
912
+ * Delete user with cascade cleanup
913
+ *
914
+ * @param sub - User UUID
915
+ * @returns Deletion confirmation with cascade counts
916
+ * @throws {NAuthClientError} If deletion fails
917
+ *
918
+ * @example
919
+ * ```typescript
920
+ * const result = await client.admin.deleteUser('user-uuid');
921
+ * console.log('Deleted records:', result.deletedRecords);
922
+ * ```
923
+ */
924
+ async deleteUser(sub) {
925
+ const path = this.buildAdminUrl(this.adminEndpoints.deleteUser, { sub });
926
+ return this.delete(path);
927
+ }
928
+ /**
929
+ * Disable user account (permanent lock)
930
+ *
931
+ * @param sub - User UUID
932
+ * @param reason - Optional reason for disabling
933
+ * @returns Disable confirmation with revoked session count
934
+ * @throws {NAuthClientError} If operation fails
935
+ *
936
+ * @example
937
+ * ```typescript
938
+ * const result = await client.admin.disableUser(
939
+ * 'user-uuid',
940
+ * 'Account compromised'
941
+ * );
942
+ * console.log('Revoked sessions:', result.revokedSessions);
943
+ * ```
944
+ */
945
+ async disableUser(sub, reason) {
946
+ const path = this.buildAdminUrl(this.adminEndpoints.disableUser, { sub });
947
+ return this.post(path, { reason });
948
+ }
949
+ /**
950
+ * Enable (unlock) user account
951
+ *
952
+ * @param sub - User UUID
953
+ * @returns Enable confirmation with updated user
954
+ * @throws {NAuthClientError} If operation fails
955
+ *
956
+ * @example
957
+ * ```typescript
958
+ * const result = await client.admin.enableUser('user-uuid');
959
+ * console.log('User enabled:', result.user);
960
+ * ```
961
+ */
962
+ async enableUser(sub) {
963
+ const path = this.buildAdminUrl(this.adminEndpoints.enableUser, { sub });
964
+ return this.post(path, {});
965
+ }
966
+ /**
967
+ * Force password change on next login
968
+ *
969
+ * @param sub - User UUID
970
+ * @returns Success confirmation
971
+ * @throws {NAuthClientError} If operation fails
972
+ *
973
+ * @example
974
+ * ```typescript
975
+ * await client.admin.forcePasswordChange('user-uuid');
976
+ * ```
977
+ */
978
+ async forcePasswordChange(sub) {
979
+ const path = this.buildAdminUrl(this.adminEndpoints.forcePasswordChange, { sub });
980
+ return this.post(path, {});
981
+ }
982
+ // ============================================================================
983
+ // Password Management
984
+ // ============================================================================
985
+ /**
986
+ * Set password for any user (admin operation)
987
+ *
988
+ * @param identifier - User email, username, or phone
989
+ * @param newPassword - New password
990
+ * @returns Success confirmation
991
+ * @throws {NAuthClientError} If operation fails
992
+ *
993
+ * @example
994
+ * ```typescript
995
+ * await client.admin.setPassword('user@example.com', 'NewSecurePass123!');
996
+ * ```
997
+ */
998
+ async setPassword(identifier, newPassword) {
999
+ const path = this.buildAdminUrl(this.adminEndpoints.setPassword);
1000
+ return this.post(path, { identifier, newPassword });
1001
+ }
1002
+ /**
1003
+ * Initiate password reset workflow (sends code/link to user)
1004
+ *
1005
+ * @param request - Password reset request
1006
+ * @returns Reset confirmation with delivery details
1007
+ * @throws {NAuthClientError} If operation fails
1008
+ *
1009
+ * @example
1010
+ * ```typescript
1011
+ * const result = await client.admin.initiatePasswordReset({
1012
+ * sub: 'user-uuid',
1013
+ * deliveryMethod: 'email',
1014
+ * baseUrl: 'https://myapp.com/reset-password',
1015
+ * reason: 'User requested password reset',
1016
+ * });
1017
+ * console.log('Code sent to:', result.destination);
1018
+ * ```
1019
+ */
1020
+ async initiatePasswordReset(request) {
1021
+ const path = this.buildAdminUrl(this.adminEndpoints.resetPasswordInitiate);
1022
+ return this.post(path, request);
1023
+ }
1024
+ // ============================================================================
1025
+ // Session Management
1026
+ // ============================================================================
1027
+ /**
1028
+ * Get all sessions for a user
1029
+ *
1030
+ * @param sub - User UUID
1031
+ * @returns User sessions
1032
+ * @throws {NAuthClientError} If request fails
1033
+ *
1034
+ * @example
1035
+ * ```typescript
1036
+ * const result = await client.admin.getUserSessions('user-uuid');
1037
+ * console.log('Active sessions:', result.sessions);
1038
+ * ```
1039
+ */
1040
+ async getUserSessions(sub) {
1041
+ const path = this.buildAdminUrl(this.adminEndpoints.getUserSessions, { sub });
1042
+ return this.get(path);
1043
+ }
1044
+ /**
1045
+ * Logout all sessions for a user (admin-initiated)
1046
+ *
1047
+ * @param sub - User UUID
1048
+ * @param forgetDevices - If true, also revokes all trusted devices
1049
+ * @returns Number of sessions revoked
1050
+ * @throws {NAuthClientError} If operation fails
1051
+ *
1052
+ * @example
1053
+ * ```typescript
1054
+ * const result = await client.admin.logoutAllSessions('user-uuid', true);
1055
+ * console.log(`Revoked ${result.revokedCount} sessions`);
1056
+ * ```
1057
+ */
1058
+ async logoutAllSessions(sub, forgetDevices = false) {
1059
+ const path = this.buildAdminUrl(this.adminEndpoints.logoutAll, { sub });
1060
+ return this.post(path, { forgetDevices });
1061
+ }
1062
+ // ============================================================================
1063
+ // MFA Management
1064
+ // ============================================================================
1065
+ /**
1066
+ * Get MFA status for a user
1067
+ *
1068
+ * @param sub - User UUID
1069
+ * @returns MFA status
1070
+ * @throws {NAuthClientError} If request fails
1071
+ *
1072
+ * @example
1073
+ * ```typescript
1074
+ * const status = await client.admin.getMfaStatus('user-uuid');
1075
+ * console.log('MFA enabled:', status.enabled);
1076
+ * ```
1077
+ */
1078
+ async getMfaStatus(sub) {
1079
+ const path = this.buildAdminUrl(this.adminEndpoints.getMfaStatus, { sub });
1080
+ return this.get(path);
1081
+ }
1082
+ /**
1083
+ * Set preferred MFA method for a user
1084
+ *
1085
+ * @param sub - User UUID
1086
+ * @param method - MFA method to set as preferred
1087
+ * @returns Success message
1088
+ * @throws {NAuthClientError} If operation fails
1089
+ *
1090
+ * @example
1091
+ * ```typescript
1092
+ * await client.admin.setPreferredMfaMethod('user-uuid', 'totp');
1093
+ * ```
1094
+ */
1095
+ async setPreferredMfaMethod(sub, method) {
1096
+ const path = this.buildAdminUrl(this.adminEndpoints.setPreferredMfaMethod);
1097
+ return this.post(path, { sub, method });
1098
+ }
1099
+ /**
1100
+ * Remove MFA devices for a user
1101
+ *
1102
+ * @param sub - User UUID
1103
+ * @param method - MFA method to remove
1104
+ * @returns Success message
1105
+ * @throws {NAuthClientError} If operation fails
1106
+ *
1107
+ * @example
1108
+ * ```typescript
1109
+ * await client.admin.removeMfaDevices('user-uuid', 'sms');
1110
+ * ```
1111
+ */
1112
+ async removeMfaDevices(sub, method) {
1113
+ const path = this.buildAdminUrl(this.adminEndpoints.removeMfaDevices);
1114
+ return this.post(path, { sub, method });
1115
+ }
1116
+ /**
1117
+ * Grant or revoke MFA exemption for a user
1118
+ *
1119
+ * @param sub - User UUID
1120
+ * @param exempt - True to exempt from MFA, false to require
1121
+ * @param reason - Optional reason for exemption
1122
+ * @returns Success message
1123
+ * @throws {NAuthClientError} If operation fails
1124
+ *
1125
+ * @example
1126
+ * ```typescript
1127
+ * await client.admin.setMfaExemption('user-uuid', true, 'Service account');
1128
+ * ```
1129
+ */
1130
+ async setMfaExemption(sub, exempt, reason) {
1131
+ const path = this.buildAdminUrl(this.adminEndpoints.setMfaExemption);
1132
+ return this.post(path, { sub, exempt, reason });
1133
+ }
1134
+ // ============================================================================
1135
+ // Audit
1136
+ // ============================================================================
1137
+ /**
1138
+ * Get audit history for a user
1139
+ *
1140
+ * @param params - Audit history request params
1141
+ * @returns Paginated audit events
1142
+ * @throws {NAuthClientError} If request fails
1143
+ *
1144
+ * @example
1145
+ * ```typescript
1146
+ * const history = await client.admin.getAuditHistory({
1147
+ * sub: 'user-uuid',
1148
+ * page: 1,
1149
+ * limit: 50,
1150
+ * eventType: 'LOGIN_SUCCESS',
1151
+ * });
1152
+ * ```
1153
+ */
1154
+ async getAuditHistory(params) {
1155
+ const path = this.buildAdminUrl(this.adminEndpoints.getAuditHistory);
1156
+ const queryString = this.buildQueryString(params);
1157
+ return this.get(`${path}${queryString}`);
1158
+ }
1159
+ // ============================================================================
1160
+ // Private Helper Methods
1161
+ // ============================================================================
1162
+ /**
1163
+ * Build admin endpoint URL with path parameter replacement
1164
+ *
1165
+ * Path construction order:
1166
+ * 1. Start with endpoint: '/users/:sub'
1167
+ * 2. Replace params: '/users/uuid-123'
1168
+ * 3. Apply adminPathPrefix: '/admin/users/uuid-123'
1169
+ * 4. Apply authPathPrefix if exists: '/auth/admin/users/uuid-123'
1170
+ * 5. Combine with baseUrl: 'https://api.example.com/auth/admin/users/uuid-123'
1171
+ *
1172
+ * @param endpointPath - Endpoint path (may contain :sub, :provider, etc.)
1173
+ * @param pathParams - Path parameters to replace
1174
+ * @returns Full URL
1175
+ * @private
1176
+ */
1177
+ buildAdminUrl(endpointPath, pathParams) {
1178
+ let path = endpointPath;
1179
+ if (pathParams) {
1180
+ Object.entries(pathParams).forEach(([key, value]) => {
1181
+ path = path.replace(`:${key}`, encodeURIComponent(value));
1182
+ });
1183
+ }
1184
+ const prefix = this.adminPathPrefix.startsWith("/") ? this.adminPathPrefix : `/${this.adminPathPrefix}`;
1185
+ path = `${prefix}${path.startsWith("/") ? "" : "/"}${path}`;
1186
+ if (this.config.authPathPrefix) {
1187
+ const authPrefix = this.config.authPathPrefix.startsWith("/") ? this.config.authPathPrefix : `/${this.config.authPathPrefix}`;
1188
+ path = `${authPrefix}${path}`;
1189
+ }
1190
+ const normalizedBase = this.config.baseUrl.endsWith("/") ? this.config.baseUrl.slice(0, -1) : this.config.baseUrl;
1191
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1192
+ return `${normalizedBase}${normalizedPath}`;
1193
+ }
1194
+ /**
1195
+ * Build query string from params object
1196
+ *
1197
+ * Handles:
1198
+ * - Simple values (string, number, boolean)
1199
+ * - Arrays (multiple values for same key)
1200
+ * - Nested objects (e.g., createdAt[operator], createdAt[value])
1201
+ * - Dates (converted to ISO string)
1202
+ *
1203
+ * @param params - Query parameters
1204
+ * @returns Query string (e.g., '?page=1&limit=20')
1205
+ * @private
1206
+ */
1207
+ buildQueryString(params) {
1208
+ const searchParams = new URLSearchParams();
1209
+ for (const [key, rawValue] of Object.entries(params)) {
1210
+ if (rawValue === void 0 || rawValue === null) {
1211
+ continue;
1212
+ }
1213
+ if (Array.isArray(rawValue)) {
1214
+ for (const item of rawValue) {
1215
+ searchParams.append(key, String(item));
1216
+ }
1217
+ continue;
1218
+ }
1219
+ if (typeof rawValue === "object" && rawValue !== null && !(rawValue instanceof Date)) {
1220
+ const nestedObj = rawValue;
1221
+ for (const [nestedKey, nestedValue] of Object.entries(nestedObj)) {
1222
+ const nestedParamKey = `${key}[${nestedKey}]`;
1223
+ const valueToAppend = nestedValue instanceof Date ? nestedValue.toISOString() : String(nestedValue);
1224
+ searchParams.append(nestedParamKey, valueToAppend);
1225
+ }
1226
+ continue;
1227
+ }
1228
+ if (rawValue instanceof Date) {
1229
+ searchParams.append(key, rawValue.toISOString());
1230
+ continue;
1231
+ }
1232
+ searchParams.append(key, String(rawValue));
1233
+ }
1234
+ const query = searchParams.toString();
1235
+ return query ? `?${query}` : "";
1236
+ }
1237
+ /**
1238
+ * Build request headers for authentication
1239
+ *
1240
+ * @param auth - Whether to include authentication headers
1241
+ * @param method - HTTP method
1242
+ * @returns Headers object
1243
+ * @private
1244
+ */
1245
+ async buildHeaders(auth, method = "GET") {
1246
+ const headers = {
1247
+ ...this.config.headers,
1248
+ ...this.adminHeaders
1249
+ };
1250
+ if (method !== "GET") {
1251
+ headers["Content-Type"] = "application/json";
1252
+ }
1253
+ if (auth && this.config.tokenDelivery === "json") {
1254
+ try {
1255
+ const ACCESS_TOKEN_KEY2 = "nauth_access_token";
1256
+ const token = await this.config.storage.getItem(ACCESS_TOKEN_KEY2);
1257
+ if (token) {
1258
+ headers["Authorization"] = `Bearer ${token}`;
1259
+ }
1260
+ } catch {
1261
+ }
1262
+ }
1263
+ if (this.config.tokenDelivery === "json") {
1264
+ try {
1265
+ const deviceToken = await this.config.storage.getItem(this.config.deviceTrust.storageKey);
1266
+ if (deviceToken) {
1267
+ headers[this.config.deviceTrust.headerName] = deviceToken;
1268
+ }
1269
+ } catch {
1270
+ }
1271
+ }
1272
+ const mutatingMethods = [
1273
+ "POST",
1274
+ "PUT",
1275
+ "PATCH",
1276
+ "DELETE"
1277
+ ];
1278
+ if (this.config.tokenDelivery === "cookies" && hasWindow() && mutatingMethods.includes(method)) {
1279
+ const csrfToken = this.getCsrfToken();
1280
+ if (csrfToken) {
1281
+ headers[this.config.csrf.headerName] = csrfToken;
1282
+ }
1283
+ }
1284
+ return headers;
1285
+ }
1286
+ /**
1287
+ * Get CSRF token from cookie (browser only)
1288
+ *
1289
+ * @returns CSRF token or null
1290
+ * @private
1291
+ */
1292
+ getCsrfToken() {
1293
+ if (!hasWindow() || typeof document === "undefined") return null;
1294
+ const match = document.cookie.match(
1295
+ new RegExp(`(^| )${this.config.csrf.cookieName}=([^;]+)`)
1296
+ );
1297
+ return match ? decodeURIComponent(match[2]) : null;
1298
+ }
1299
+ /**
1300
+ * Execute GET request
1301
+ *
1302
+ * @param path - Full URL path
1303
+ * @returns Response data
1304
+ * @private
1305
+ */
1306
+ async get(path) {
1307
+ const headers = await this.buildHeaders(true, "GET");
1308
+ const credentials = this.config.tokenDelivery === "cookies" ? "include" : "omit";
1309
+ try {
1310
+ const response = await this.config.httpAdapter.request({
1311
+ method: "GET",
1312
+ url: path,
1313
+ headers,
1314
+ credentials
1315
+ });
1316
+ return response.data;
1317
+ } catch (error) {
1318
+ throw this.handleError(error);
1319
+ }
1320
+ }
1321
+ /**
1322
+ * Execute POST request
1323
+ *
1324
+ * @param path - Full URL path
1325
+ * @param body - Request body
1326
+ * @returns Response data
1327
+ * @private
1328
+ */
1329
+ async post(path, body) {
1330
+ const headers = await this.buildHeaders(true, "POST");
1331
+ const credentials = this.config.tokenDelivery === "cookies" ? "include" : "omit";
1332
+ try {
1333
+ const response = await this.config.httpAdapter.request({
1334
+ method: "POST",
1335
+ url: path,
1336
+ headers,
1337
+ body,
1338
+ credentials
1339
+ });
1340
+ return response.data;
1341
+ } catch (error) {
1342
+ throw this.handleError(error);
1343
+ }
1344
+ }
1345
+ /**
1346
+ * Execute DELETE request
1347
+ *
1348
+ * @param path - Full URL path
1349
+ * @returns Response data
1350
+ * @private
1351
+ */
1352
+ async delete(path) {
1353
+ const headers = await this.buildHeaders(true, "DELETE");
1354
+ const credentials = this.config.tokenDelivery === "cookies" ? "include" : "omit";
1355
+ try {
1356
+ const response = await this.config.httpAdapter.request({
1357
+ method: "DELETE",
1358
+ url: path,
1359
+ headers,
1360
+ credentials
1361
+ });
1362
+ return response.data;
1363
+ } catch (error) {
1364
+ throw this.handleError(error);
1365
+ }
1366
+ }
1367
+ /**
1368
+ * Handle HTTP errors and convert to NAuthClientError
1369
+ *
1370
+ * @param error - Error from HTTP adapter
1371
+ * @returns NAuthClientError
1372
+ * @private
1373
+ */
1374
+ handleError(error) {
1375
+ if (error instanceof NAuthClientError) {
1376
+ return error;
1377
+ }
1378
+ if (error && typeof error === "object" && "response" in error) {
1379
+ const httpError = error;
1380
+ const status = httpError.response?.status ?? 500;
1381
+ const errorData = typeof httpError.response?.data === "object" && httpError.response.data !== null ? httpError.response.data : {};
1382
+ const code = typeof errorData["code"] === "string" ? errorData["code"] : "INTERNAL_ERROR" /* INTERNAL_ERROR */;
1383
+ const message = typeof errorData["message"] === "string" ? errorData["message"] : `Request failed with status ${status}`;
1384
+ return new NAuthClientError(code, message, {
1385
+ statusCode: status,
1386
+ details: errorData
1387
+ });
1388
+ }
1389
+ return new NAuthClientError(
1390
+ "INTERNAL_ERROR" /* INTERNAL_ERROR */,
1391
+ error instanceof Error ? error.message : "Unknown error"
1392
+ );
1393
+ }
1394
+ };
1395
+
712
1396
  // src/core/client.ts
713
1397
  var USER_KEY2 = "nauth_user";
714
1398
  var CHALLENGE_KEY2 = "nauth_challenge_session";
715
1399
  var OAUTH_STATE_KEY2 = "nauth_oauth_state";
716
- var hasWindow = () => typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
1400
+ var hasWindow2 = () => typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
1401
+ var getOauthStorage = (mainStorage) => {
1402
+ if (hasWindow2() && typeof window.sessionStorage !== "undefined") {
1403
+ return new BrowserStorage(window.sessionStorage);
1404
+ }
1405
+ return mainStorage;
1406
+ };
717
1407
  var defaultStorage = () => {
718
- if (hasWindow() && typeof window.localStorage !== "undefined") {
1408
+ if (hasWindow2() && typeof window.localStorage !== "undefined") {
719
1409
  return new BrowserStorage();
720
1410
  }
721
1411
  return new InMemoryStorage();
@@ -731,7 +1421,34 @@ var NAuthClient = class {
731
1421
  __publicField(this, "tokenManager");
732
1422
  __publicField(this, "eventEmitter");
733
1423
  __publicField(this, "challengeRouter");
1424
+ __publicField(this, "oauthStorage");
734
1425
  __publicField(this, "currentUser", null);
1426
+ /**
1427
+ * Admin operations (available if admin config provided).
1428
+ *
1429
+ * Provides admin-level user management methods:
1430
+ * - User CRUD operations
1431
+ * - Password management
1432
+ * - Session management
1433
+ * - MFA management
1434
+ * - Audit history
1435
+ *
1436
+ * @example
1437
+ * ```typescript
1438
+ * const client = new NAuthClient({
1439
+ * baseUrl: 'https://api.example.com/auth',
1440
+ * tokenDelivery: 'cookies',
1441
+ * admin: {
1442
+ * pathPrefix: '/admin',
1443
+ * },
1444
+ * });
1445
+ *
1446
+ * // Use admin operations
1447
+ * const users = await client.admin.getUsers({ page: 1 });
1448
+ * await client.admin.deleteUser('user-uuid');
1449
+ * ```
1450
+ */
1451
+ __publicField(this, "admin");
735
1452
  /**
736
1453
  * Handle cross-tab storage updates.
737
1454
  */
@@ -749,8 +1466,12 @@ var NAuthClient = class {
749
1466
  this.config = resolveConfig({ ...userConfig, storage }, defaultAdapter);
750
1467
  this.tokenManager = new TokenManager(storage);
751
1468
  this.eventEmitter = new EventEmitter();
752
- this.challengeRouter = new ChallengeRouter(this.config);
753
- if (hasWindow()) {
1469
+ this.oauthStorage = getOauthStorage(storage);
1470
+ this.challengeRouter = new ChallengeRouter(this.config, this.oauthStorage);
1471
+ if (this.config.admin) {
1472
+ this.admin = new AdminOperations(this.config);
1473
+ }
1474
+ if (hasWindow2()) {
754
1475
  window.addEventListener("storage", this.handleStorageEvent);
755
1476
  }
756
1477
  }
@@ -758,7 +1479,7 @@ var NAuthClient = class {
758
1479
  * Clean up resources.
759
1480
  */
760
1481
  dispose() {
761
- if (hasWindow()) {
1482
+ if (hasWindow2()) {
762
1483
  window.removeEventListener("storage", this.handleStorageEvent);
763
1484
  }
764
1485
  }
@@ -1206,11 +1927,12 @@ var NAuthClient = class {
1206
1927
  */
1207
1928
  async loginWithSocial(provider, options) {
1208
1929
  this.eventEmitter.emit({ type: "oauth:started", data: { provider }, timestamp: Date.now() });
1209
- if (hasWindow()) {
1930
+ if (hasWindow2()) {
1210
1931
  const startPath = this.config.endpoints.socialRedirectStart.replace(":provider", provider);
1211
1932
  const fullUrl = this.buildUrl(startPath);
1212
1933
  const startUrl = new URL(fullUrl);
1213
- const returnTo = options?.returnTo ?? this.config.redirects?.success ?? "/";
1934
+ const redirects = this.config.redirects;
1935
+ const returnTo = options?.returnTo ?? redirects?.loginSuccess ?? redirects?.success ?? "/";
1214
1936
  startUrl.searchParams.set("returnTo", returnTo);
1215
1937
  if (options?.action === "link") {
1216
1938
  startUrl.searchParams.set("action", "link");
@@ -1237,7 +1959,11 @@ var NAuthClient = class {
1237
1959
  }
1238
1960
  const result = await this.post(this.config.endpoints.socialExchange, { exchangeToken: token });
1239
1961
  await this.handleAuthResponse(result);
1240
- await this.challengeRouter.handleAuthResponse(result, { source: "social" });
1962
+ const appState = await this.oauthStorage.getItem(OAUTH_STATE_KEY2);
1963
+ await this.challengeRouter.handleAuthResponse(result, {
1964
+ source: "social",
1965
+ appState: appState ?? void 0
1966
+ });
1241
1967
  return result;
1242
1968
  }
1243
1969
  /**
@@ -1523,7 +2249,7 @@ var NAuthClient = class {
1523
2249
  }
1524
2250
  }
1525
2251
  const mutatingMethods = ["POST", "PUT", "PATCH", "DELETE"];
1526
- if (this.config.tokenDelivery === "cookies" && hasWindow() && mutatingMethods.includes(method)) {
2252
+ if (this.config.tokenDelivery === "cookies" && hasWindow2() && mutatingMethods.includes(method)) {
1527
2253
  const csrfToken = this.getCsrfToken();
1528
2254
  if (csrfToken) {
1529
2255
  headers[this.config.csrf.headerName] = csrfToken;
@@ -1536,7 +2262,7 @@ var NAuthClient = class {
1536
2262
  * @private
1537
2263
  */
1538
2264
  getCsrfToken() {
1539
- if (!hasWindow() || typeof document === "undefined") return null;
2265
+ if (!hasWindow2() || typeof document === "undefined") return null;
1540
2266
  const match = document.cookie.match(new RegExp(`(^| )${this.config.csrf.cookieName}=([^;]+)`));
1541
2267
  return match ? decodeURIComponent(match[2]) : null;
1542
2268
  }
@@ -1628,6 +2354,8 @@ var NAuthClient = class {
1628
2354
  * when appState is present in the callback URL. The stored state can
1629
2355
  * be retrieved using getLastOauthState().
1630
2356
  *
2357
+ * Stores in sessionStorage (ephemeral) for better security.
2358
+ *
1631
2359
  * @param appState - OAuth appState value from callback URL
1632
2360
  *
1633
2361
  * @example
@@ -1637,7 +2365,7 @@ var NAuthClient = class {
1637
2365
  */
1638
2366
  async storeOauthState(appState) {
1639
2367
  if (appState && appState.trim() !== "") {
1640
- await this.config.storage.setItem(OAUTH_STATE_KEY2, appState);
2368
+ await this.oauthStorage.setItem(OAUTH_STATE_KEY2, appState);
1641
2369
  }
1642
2370
  }
1643
2371
  /**
@@ -1648,6 +2376,7 @@ var NAuthClient = class {
1648
2376
  * applying invite codes, or tracking referral information.
1649
2377
  *
1650
2378
  * The state is automatically cleared after retrieval to prevent reuse.
2379
+ * Stored in sessionStorage (ephemeral) for better security.
1651
2380
  *
1652
2381
  * @returns The stored appState, or null if none exists
1653
2382
  *
@@ -1661,9 +2390,9 @@ var NAuthClient = class {
1661
2390
  * ```
1662
2391
  */
1663
2392
  async getLastOauthState() {
1664
- const stored = await this.config.storage.getItem(OAUTH_STATE_KEY2);
2393
+ const stored = await this.oauthStorage.getItem(OAUTH_STATE_KEY2);
1665
2394
  if (stored) {
1666
- await this.config.storage.removeItem(OAUTH_STATE_KEY2);
2395
+ await this.oauthStorage.removeItem(OAUTH_STATE_KEY2);
1667
2396
  return stored;
1668
2397
  }
1669
2398
  return null;
@@ -1712,6 +2441,7 @@ function isOTPChallenge(challenge) {
1712
2441
  return challengeName === "VERIFY_EMAIL" /* VERIFY_EMAIL */ || challengeName === "VERIFY_PHONE" /* VERIFY_PHONE */ || challengeName === "MFA_REQUIRED" /* MFA_REQUIRED */;
1713
2442
  }
1714
2443
  export {
2444
+ AdminOperations,
1715
2445
  AuthAuditEventType,
1716
2446
  AuthChallenge,
1717
2447
  BrowserStorage,
@@ -1722,6 +2452,7 @@ export {
1722
2452
  NAuthClient,
1723
2453
  NAuthClientError,
1724
2454
  NAuthErrorCode,
2455
+ defaultAdminEndpoints,
1725
2456
  defaultEndpoints,
1726
2457
  getChallengeInstructions,
1727
2458
  getMFAMethod,