@drmhse/sso-sdk 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -20,10 +20,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ AuthMethod: () => AuthMethod,
23
24
  AuthModule: () => AuthModule,
25
+ BrowserStorage: () => BrowserStorage,
24
26
  InvitationsModule: () => InvitationsModule,
27
+ MagicLinks: () => MagicLinks,
28
+ MemoryStorage: () => MemoryStorage,
25
29
  OrganizationsModule: () => OrganizationsModule,
30
+ PasskeysModule: () => PasskeysModule,
31
+ PermissionsModule: () => PermissionsModule,
26
32
  PlatformModule: () => PlatformModule,
33
+ RiskAction: () => RiskAction,
34
+ RiskEventOutcome: () => RiskEventOutcome,
35
+ RiskFactorType: () => RiskFactorType,
27
36
  ServiceApiModule: () => ServiceApiModule,
28
37
  ServicesModule: () => ServicesModule,
29
38
  SsoApiError: () => SsoApiError,
@@ -83,6 +92,12 @@ var HttpClient = class {
83
92
  timeout: 3e4
84
93
  };
85
94
  }
95
+ /**
96
+ * Allow injecting session manager after construction to avoid circular dep
97
+ */
98
+ setSessionManager(manager) {
99
+ this.sessionManager = manager;
100
+ }
86
101
  /**
87
102
  * Build query string from params object
88
103
  */
@@ -116,6 +131,12 @@ var HttpClient = class {
116
131
  ...this.defaults.headers.common,
117
132
  ...options.headers
118
133
  };
134
+ if (this.sessionManager) {
135
+ const token = await this.sessionManager.getToken();
136
+ if (token) {
137
+ headers["Authorization"] = `Bearer ${token}`;
138
+ }
139
+ }
119
140
  const controller = new AbortController();
120
141
  const timeoutId = setTimeout(() => controller.abort(), timeout);
121
142
  try {
@@ -126,6 +147,17 @@ var HttpClient = class {
126
147
  signal: controller.signal
127
148
  });
128
149
  clearTimeout(timeoutId);
150
+ if (response.status === 401 && this.sessionManager && !options._retry && !path.includes("/auth/login") && !path.includes("/auth/refresh")) {
151
+ try {
152
+ const newToken = await this.sessionManager.refreshSession();
153
+ return this.request(path, {
154
+ ...options,
155
+ _retry: true,
156
+ headers: { ...options.headers, Authorization: `Bearer ${newToken}` }
157
+ });
158
+ } catch (refreshError) {
159
+ }
160
+ }
129
161
  let data;
130
162
  const contentType = response.headers.get("content-type");
131
163
  if (contentType?.includes("application/json")) {
@@ -194,6 +226,16 @@ var HttpClient = class {
194
226
  headers: config?.headers
195
227
  });
196
228
  }
229
+ /**
230
+ * PUT request
231
+ */
232
+ async put(path, data, config) {
233
+ return this.request(path, {
234
+ method: "PUT",
235
+ body: data,
236
+ headers: config?.headers
237
+ });
238
+ }
197
239
  /**
198
240
  * PATCH request
199
241
  */
@@ -218,6 +260,125 @@ function createHttpAgent(baseURL) {
218
260
  return new HttpClient(baseURL);
219
261
  }
220
262
 
263
+ // src/session.ts
264
+ var SessionManager = class {
265
+ constructor(storage, refreshHandler, config = { storageKeyPrefix: "sso_" }) {
266
+ this.storage = storage;
267
+ this.refreshHandler = refreshHandler;
268
+ this.config = config;
269
+ this.accessToken = null;
270
+ this.refreshToken = null;
271
+ this.refreshPromise = null;
272
+ this.listeners = [];
273
+ }
274
+ /**
275
+ * Initialize session from storage
276
+ */
277
+ async loadSession() {
278
+ this.accessToken = await this.storage.getItem(`${this.config.storageKeyPrefix}access_token`);
279
+ this.refreshToken = await this.storage.getItem(`${this.config.storageKeyPrefix}refresh_token`);
280
+ }
281
+ /**
282
+ * Set the session data (used after login/register)
283
+ */
284
+ async setSession(tokens) {
285
+ this.accessToken = tokens.access_token;
286
+ await this.storage.setItem(`${this.config.storageKeyPrefix}access_token`, tokens.access_token);
287
+ if (tokens.refresh_token) {
288
+ this.refreshToken = tokens.refresh_token;
289
+ await this.storage.setItem(`${this.config.storageKeyPrefix}refresh_token`, tokens.refresh_token);
290
+ }
291
+ this.notifyListeners(true);
292
+ }
293
+ /**
294
+ * Clear session (logout)
295
+ */
296
+ async clearSession() {
297
+ this.accessToken = null;
298
+ this.refreshToken = null;
299
+ await this.storage.removeItem(`${this.config.storageKeyPrefix}access_token`);
300
+ await this.storage.removeItem(`${this.config.storageKeyPrefix}refresh_token`);
301
+ this.notifyListeners(false);
302
+ }
303
+ /**
304
+ * Get the current access token, refreshing it if necessary/possible
305
+ */
306
+ async getToken() {
307
+ return this.accessToken;
308
+ }
309
+ /**
310
+ * Handle logic for when a 401 occurs
311
+ */
312
+ async refreshSession() {
313
+ if (!this.refreshToken) {
314
+ throw new Error("No refresh token available");
315
+ }
316
+ if (this.refreshPromise) {
317
+ return this.refreshPromise;
318
+ }
319
+ this.refreshPromise = (async () => {
320
+ try {
321
+ const tokens = await this.refreshHandler(this.refreshToken);
322
+ await this.setSession(tokens);
323
+ return tokens.access_token;
324
+ } catch (err) {
325
+ await this.clearSession();
326
+ throw err;
327
+ } finally {
328
+ this.refreshPromise = null;
329
+ }
330
+ })();
331
+ return this.refreshPromise;
332
+ }
333
+ isAuthenticated() {
334
+ return !!this.accessToken;
335
+ }
336
+ /**
337
+ * Subscribe to auth state changes (useful for UI updates)
338
+ */
339
+ subscribe(listener) {
340
+ this.listeners.push(listener);
341
+ return () => {
342
+ this.listeners = this.listeners.filter((l) => l !== listener);
343
+ };
344
+ }
345
+ notifyListeners(isAuth) {
346
+ this.listeners.forEach((l) => l(isAuth));
347
+ }
348
+ };
349
+
350
+ // src/storage.ts
351
+ var MemoryStorage = class {
352
+ constructor() {
353
+ this.store = /* @__PURE__ */ new Map();
354
+ }
355
+ getItem(key) {
356
+ return this.store.get(key) || null;
357
+ }
358
+ setItem(key, value) {
359
+ this.store.set(key, value);
360
+ }
361
+ removeItem(key) {
362
+ this.store.delete(key);
363
+ }
364
+ };
365
+ var BrowserStorage = class {
366
+ getItem(key) {
367
+ return typeof window !== "undefined" ? window.localStorage.getItem(key) : null;
368
+ }
369
+ setItem(key, value) {
370
+ if (typeof window !== "undefined") window.localStorage.setItem(key, value);
371
+ }
372
+ removeItem(key) {
373
+ if (typeof window !== "undefined") window.localStorage.removeItem(key);
374
+ }
375
+ };
376
+ function resolveStorage(userStorage) {
377
+ if (userStorage) return userStorage;
378
+ if (typeof window !== "undefined" && window.localStorage) return new BrowserStorage();
379
+ return new MemoryStorage();
380
+ }
381
+
221
382
  // src/modules/analytics.ts
222
383
  var AnalyticsModule = class {
223
384
  constructor(http) {
@@ -323,8 +484,9 @@ var AnalyticsModule = class {
323
484
 
324
485
  // src/modules/auth.ts
325
486
  var AuthModule = class {
326
- constructor(http) {
487
+ constructor(http, session) {
327
488
  this.http = http;
489
+ this.session = session;
328
490
  /**
329
491
  * Device Flow: Request a device code for CLI/device authentication.
330
492
  *
@@ -371,6 +533,9 @@ var AuthModule = class {
371
533
  /**
372
534
  * Exchange a device code for a JWT token.
373
535
  * This should be polled by the device/CLI after displaying the user code.
536
+ * Note: This returns a TokenResponse (not RefreshTokenResponse) and typically
537
+ * only includes access_token. For device flows that need persistence,
538
+ * manually call sso.session.setSession() if needed.
374
539
  *
375
540
  * @param payload Token request payload
376
541
  * @returns Token response with JWT
@@ -386,7 +551,7 @@ var AuthModule = class {
386
551
  * client_id: 'service-client-id'
387
552
  * });
388
553
  * clearInterval(interval);
389
- * sso.setAuthToken(token.access_token);
554
+ * // Session is automatically configured
390
555
  * } catch (error) {
391
556
  * if (error.errorCode !== 'authorization_pending') {
392
557
  * clearInterval(interval);
@@ -408,17 +573,29 @@ var AuthModule = class {
408
573
  * should redirect the user's browser to this URL.
409
574
  *
410
575
  * @param provider The OAuth provider to use
411
- * @param params Login parameters (org, service, redirect_uri)
576
+ * @param params Login parameters (org, service, redirect_uri, connection_id)
412
577
  * @returns The full URL to redirect the user to
413
578
  *
414
579
  * @example
415
580
  * ```typescript
581
+ * // Standard OAuth login
416
582
  * const url = sso.auth.getLoginUrl('github', {
417
583
  * org: 'acme-corp',
418
584
  * service: 'main-app',
419
585
  * redirect_uri: 'https://app.acme.com/callback'
420
586
  * });
421
587
  * window.location.href = url;
588
+ *
589
+ * // Enterprise IdP login (after HRD lookup)
590
+ * const hrd = await sso.auth.lookupEmail('user@enterprise.com');
591
+ * if (hrd.connection_id) {
592
+ * const url = sso.auth.getLoginUrl('github', {
593
+ * org: 'acme-corp',
594
+ * service: 'main-app',
595
+ * connection_id: hrd.connection_id
596
+ * });
597
+ * window.location.href = url;
598
+ * }
422
599
  * ```
423
600
  */
424
601
  getLoginUrl(provider, params) {
@@ -433,6 +610,9 @@ var AuthModule = class {
433
610
  if (params.user_code) {
434
611
  searchParams.append("user_code", params.user_code);
435
612
  }
613
+ if (params.connection_id) {
614
+ searchParams.append("connection_id", params.connection_id);
615
+ }
436
616
  return `${baseURL}/auth/${provider}?${searchParams.toString()}`;
437
617
  }
438
618
  /**
@@ -465,19 +645,20 @@ var AuthModule = class {
465
645
  }
466
646
  /**
467
647
  * Logout the current user by revoking their JWT.
468
- * After calling this, you should clear the token from storage
469
- * and call `sso.setAuthToken(null)`.
648
+ * Automatically clears the session and tokens from storage.
470
649
  *
471
650
  * @example
472
651
  * ```typescript
473
652
  * await sso.auth.logout();
474
- * sso.setAuthToken(null);
475
- * localStorage.removeItem('sso_access_token');
476
- * localStorage.removeItem('sso_refresh_token');
653
+ * // Session is automatically cleared - no need for manual cleanup
477
654
  * ```
478
655
  */
479
656
  async logout() {
480
- await this.http.post("/api/auth/logout");
657
+ try {
658
+ await this.http.post("/api/auth/logout");
659
+ } finally {
660
+ await this.session.clearSession();
661
+ }
481
662
  }
482
663
  /**
483
664
  * Refresh an expired JWT access token using a refresh token.
@@ -550,10 +731,26 @@ var AuthModule = class {
550
731
  const response = await this.http.post("/api/auth/register", payload);
551
732
  return response.data;
552
733
  }
734
+ /**
735
+ * Verify an email address using the token from the verification email.
736
+ *
737
+ * @param token Verification token
738
+ * @returns HTML success page string
739
+ *
740
+ * @example
741
+ * ```typescript
742
+ * const html = await sso.auth.verifyEmail('token-from-email');
743
+ * ```
744
+ */
745
+ async verifyEmail(token) {
746
+ const response = await this.http.get("/auth/verify-email", {
747
+ params: { token }
748
+ });
749
+ return response.data;
750
+ }
553
751
  /**
554
752
  * Login with email and password.
555
- * Returns access token and refresh token on successful authentication.
556
- * The user's email must be verified before login.
753
+ * Automatically persists the session and configures the client.
557
754
  *
558
755
  * @param payload Login credentials (email and password)
559
756
  * @returns Access token, refresh token, and expiration info
@@ -564,13 +761,15 @@ var AuthModule = class {
564
761
  * email: 'user@example.com',
565
762
  * password: 'SecurePassword123!'
566
763
  * });
567
- * sso.setAuthToken(tokens.access_token);
568
- * localStorage.setItem('sso_access_token', tokens.access_token);
569
- * localStorage.setItem('sso_refresh_token', tokens.refresh_token);
764
+ * // Session is automatically saved - no need for manual token management
570
765
  * ```
571
766
  */
572
767
  async login(payload) {
573
768
  const response = await this.http.post("/api/auth/login", payload);
769
+ await this.session.setSession({
770
+ access_token: response.data.access_token,
771
+ refresh_token: response.data.refresh_token
772
+ });
574
773
  return response.data;
575
774
  }
576
775
  /**
@@ -578,6 +777,7 @@ var AuthModule = class {
578
777
  * This method should be called after login when the user has MFA enabled.
579
778
  * The login will return a pre-auth token with a short expiration (5 minutes).
580
779
  * Exchange the pre-auth token and TOTP code for a full session.
780
+ * Automatically persists the session after successful MFA verification.
581
781
  *
582
782
  * @param preauthToken The pre-authentication token received from login
583
783
  * @param code The TOTP code from the user's authenticator app or a backup code
@@ -596,9 +796,7 @@ var AuthModule = class {
596
796
  * // User needs to provide MFA code
597
797
  * const mfaCode = prompt('Enter your 6-digit code from authenticator app');
598
798
  * const tokens = await sso.auth.verifyMfa(loginResponse.access_token, mfaCode);
599
- * sso.setAuthToken(tokens.access_token);
600
- * localStorage.setItem('sso_access_token', tokens.access_token);
601
- * localStorage.setItem('sso_refresh_token', tokens.refresh_token);
799
+ * // Session is automatically saved - no need for manual token management
602
800
  * }
603
801
  * ```
604
802
  */
@@ -608,6 +806,10 @@ var AuthModule = class {
608
806
  code,
609
807
  ...deviceCodeId && { device_code_id: deviceCodeId }
610
808
  });
809
+ await this.session.setSession({
810
+ access_token: response.data.access_token,
811
+ refresh_token: response.data.refresh_token
812
+ });
611
813
  return response.data;
612
814
  }
613
815
  /**
@@ -650,6 +852,52 @@ var AuthModule = class {
650
852
  const response = await this.http.post("/api/auth/reset-password", payload);
651
853
  return response.data;
652
854
  }
855
+ // ============================================================================
856
+ // HOME REALM DISCOVERY (HRD)
857
+ // ============================================================================
858
+ /**
859
+ * Lookup an email address to determine which authentication method to use.
860
+ * This implements Home Realm Discovery (HRD), allowing users to simply enter
861
+ * their email address and be automatically routed to the correct identity provider.
862
+ *
863
+ * The system will:
864
+ * 1. Extract the domain from the email address
865
+ * 2. Check if the domain is verified and mapped to an enterprise IdP
866
+ * 3. Return routing information (connection_id) if found
867
+ * 4. Otherwise, indicate to use default authentication (password or OAuth)
868
+ *
869
+ * @param email The user's email address
870
+ * @returns HRD response with routing information
871
+ *
872
+ * @example
873
+ * ```typescript
874
+ * // Lookup email to determine authentication flow
875
+ * const result = await sso.auth.lookupEmail('john@acmecorp.com');
876
+ *
877
+ * if (result.auth_method === 'upstream' && result.connection_id) {
878
+ * // Route to enterprise IdP
879
+ * console.log(`Redirecting to ${result.provider_name}`);
880
+ * const url = sso.auth.getLoginUrl('github', {
881
+ * org: 'acme-corp',
882
+ * service: 'main-app',
883
+ * connection_id: result.connection_id
884
+ * });
885
+ * window.location.href = url;
886
+ * } else if (result.auth_method === 'password') {
887
+ * // Show password login form
888
+ * showPasswordForm();
889
+ * } else {
890
+ * // Show default OAuth provider buttons (GitHub, Google, Microsoft)
891
+ * showOAuthButtons();
892
+ * }
893
+ * ```
894
+ */
895
+ async lookupEmail(email) {
896
+ const response = await this.http.post("/api/auth/lookup-email", {
897
+ email
898
+ });
899
+ return response.data;
900
+ }
653
901
  };
654
902
 
655
903
  // src/modules/user.ts
@@ -786,11 +1034,127 @@ var MfaModule = class {
786
1034
  return response.data;
787
1035
  }
788
1036
  };
1037
+ var DevicesModule = class {
1038
+ constructor(http) {
1039
+ this.http = http;
1040
+ }
1041
+ /**
1042
+ * List all devices associated with the authenticated user.
1043
+ *
1044
+ * @param options Optional query parameters for pagination
1045
+ * @returns Array of user devices
1046
+ *
1047
+ * @example
1048
+ * ```typescript
1049
+ * const { devices, total } = await sso.user.devices.list();
1050
+ * console.log(devices); // Array of trusted devices
1051
+ * ```
1052
+ */
1053
+ async list(options) {
1054
+ const params = new URLSearchParams();
1055
+ if (options?.page) params.append("page", options.page.toString());
1056
+ if (options?.limit) params.append("limit", options.limit.toString());
1057
+ if (options?.sort_by) params.append("sort_by", options.sort_by);
1058
+ if (options?.sort_order) params.append("sort_order", options.sort_order);
1059
+ const query = params.toString();
1060
+ const url = `/api/user/devices${query ? `?${query}` : ""}`;
1061
+ const response = await this.http.get(url);
1062
+ return response.data;
1063
+ }
1064
+ /**
1065
+ * Get details for a specific device.
1066
+ *
1067
+ * @param deviceId The device ID to retrieve
1068
+ * @returns Device details
1069
+ *
1070
+ * @example
1071
+ * ```typescript
1072
+ * const device = await sso.user.devices.get('device-123');
1073
+ * console.log(device.device_name, device.is_trusted);
1074
+ * ```
1075
+ */
1076
+ async get(deviceId) {
1077
+ const response = await this.http.get(`/api/user/devices/${deviceId}`);
1078
+ return response.data;
1079
+ }
1080
+ /**
1081
+ * Revoke access for a specific device.
1082
+ * This will remove the device's trust and require re-authentication.
1083
+ *
1084
+ * @param deviceId The device ID to revoke
1085
+ * @param reason Optional reason for revocation
1086
+ * @returns Confirmation message
1087
+ *
1088
+ * @example
1089
+ * ```typescript
1090
+ * const result = await sso.user.devices.revoke('device-123', 'Device lost');
1091
+ * console.log(result.message);
1092
+ * ```
1093
+ */
1094
+ async revoke(deviceId, reason) {
1095
+ const payload = reason ? { reason } : {};
1096
+ const response = await this.http.post(`/api/user/devices/${deviceId}/revoke`, payload);
1097
+ return response.data;
1098
+ }
1099
+ /**
1100
+ * Revoke all devices except the current one.
1101
+ * This is useful when you suspect account compromise or want to force re-authentication on all devices.
1102
+ *
1103
+ * @returns Confirmation message
1104
+ *
1105
+ * @example
1106
+ * ```typescript
1107
+ * const result = await sso.user.devices.revokeAll();
1108
+ * console.log(result.message); // "All other devices have been revoked"
1109
+ * ```
1110
+ */
1111
+ async revokeAll() {
1112
+ const response = await this.http.post("/api/user/devices/revoke-all", {});
1113
+ return response.data;
1114
+ }
1115
+ /**
1116
+ * Update the name of a device.
1117
+ *
1118
+ * @param deviceId The device ID to update
1119
+ * @param deviceName New device name
1120
+ * @returns Updated device information
1121
+ *
1122
+ * @example
1123
+ * ```typescript
1124
+ * const device = await sso.user.devices.updateName('device-123', 'My Laptop');
1125
+ * console.log(device.device_name); // "My Laptop"
1126
+ * ```
1127
+ */
1128
+ async updateName(deviceId, deviceName) {
1129
+ const response = await this.http.patch(`/api/user/devices/${deviceId}`, {
1130
+ device_name: deviceName
1131
+ });
1132
+ return response.data;
1133
+ }
1134
+ /**
1135
+ * Mark a device as trusted manually.
1136
+ * This is useful for devices that you want to explicitly trust regardless of risk assessment.
1137
+ *
1138
+ * @param deviceId The device ID to trust
1139
+ * @returns Updated device information
1140
+ *
1141
+ * @example
1142
+ * ```typescript
1143
+ * const device = await sso.user.devices.trust('device-123');
1144
+ * console.log(device.is_trusted); // true
1145
+ * ```
1146
+ */
1147
+ async trust(deviceId) {
1148
+ const response = await this.http.post(`/api/user/devices/${deviceId}/trust`, {});
1149
+ return response.data;
1150
+ }
1151
+ };
789
1152
  var UserModule = class {
790
1153
  constructor(http) {
791
1154
  this.http = http;
792
1155
  this.identities = new IdentitiesModule(http);
793
1156
  this.mfa = new MfaModule(http);
1157
+ this.devices = new DevicesModule(http);
794
1158
  }
795
1159
  /**
796
1160
  * Get the profile of the currently authenticated user.
@@ -1143,6 +1507,43 @@ var WebhooksModule = class {
1143
1507
  var OrganizationsModule = class {
1144
1508
  constructor(http) {
1145
1509
  this.http = http;
1510
+ /**
1511
+ * SCIM token management methods
1512
+ */
1513
+ this.scim = {
1514
+ /**
1515
+ * Create a new SCIM token.
1516
+ * The token is only returned once upon creation.
1517
+ */
1518
+ createToken: async (orgSlug, payload) => {
1519
+ const response = await this.http.post(
1520
+ `/api/organizations/${orgSlug}/scim-tokens`,
1521
+ payload
1522
+ );
1523
+ return response.data;
1524
+ },
1525
+ /**
1526
+ * List all SCIM tokens.
1527
+ */
1528
+ listTokens: async (orgSlug) => {
1529
+ const response = await this.http.get(
1530
+ `/api/organizations/${orgSlug}/scim-tokens`
1531
+ );
1532
+ return response.data;
1533
+ },
1534
+ /**
1535
+ * Revoke a SCIM token.
1536
+ */
1537
+ revokeToken: async (orgSlug, tokenId) => {
1538
+ await this.http.post(`/api/organizations/${orgSlug}/scim-tokens/${tokenId}/revoke`);
1539
+ },
1540
+ /**
1541
+ * Delete a SCIM token.
1542
+ */
1543
+ deleteToken: async (orgSlug, tokenId) => {
1544
+ await this.http.delete(`/api/organizations/${orgSlug}/scim-tokens/${tokenId}`);
1545
+ }
1546
+ };
1146
1547
  /**
1147
1548
  * Member management methods
1148
1549
  */
@@ -1166,6 +1567,24 @@ var OrganizationsModule = class {
1166
1567
  );
1167
1568
  return response.data;
1168
1569
  },
1570
+ /**
1571
+ * Add a member to the organization (Invite + Accept).
1572
+ * This is a convenience method that creates an invitation and immediately accepts it.
1573
+ * Useful for testing and admin operations.
1574
+ *
1575
+ * @param orgSlug Organization slug
1576
+ * @param payload Member details (email, role)
1577
+ * @returns The created invitation
1578
+ */
1579
+ add: async (orgSlug, payload) => {
1580
+ const response = await this.http.post(
1581
+ `/api/organizations/${orgSlug}/invitations`,
1582
+ payload
1583
+ );
1584
+ const invitation = response.data;
1585
+ await this.http.post("/api/invitations/accept", { token: invitation.token });
1586
+ return invitation;
1587
+ },
1169
1588
  /**
1170
1589
  * Update a member's role.
1171
1590
  * Requires 'owner' role.
@@ -1352,81 +1771,296 @@ var OrganizationsModule = class {
1352
1771
  return response.data;
1353
1772
  }
1354
1773
  };
1355
- this.auditLogs = new AuditLogsModule(http);
1356
- this.webhooks = new WebhooksModule(http);
1357
- }
1358
- /**
1359
- * Create a new organization (requires authentication).
1360
- * The authenticated user becomes the organization owner.
1361
- * Returns JWT tokens with organization context, eliminating the need to re-authenticate.
1362
- *
1363
- * @param payload Organization creation payload
1364
- * @returns Created organization with owner, membership, and JWT tokens
1365
- *
1366
- * @example
1367
- * ```typescript
1368
- * const result = await sso.organizations.create({
1369
- * slug: 'acme-corp',
1370
- * name: 'Acme Corporation'
1371
- * });
1372
- * // Store the new tokens with org context
1373
- * authStore.setTokens(result.access_token, result.refresh_token);
1374
- * ```
1375
- */
1376
- async create(payload) {
1377
- const response = await this.http.post("/api/organizations", payload);
1378
- return response.data;
1379
- }
1380
- /**
1381
- * List all organizations the authenticated user is a member of.
1382
- *
1383
- * @param params Optional query parameters for filtering and pagination
1384
- * @returns Array of organization responses
1385
- *
1386
- * @example
1387
- * ```typescript
1388
- * const orgs = await sso.organizations.list({
1389
- * status: 'active',
1390
- * page: 1,
1391
- * limit: 20
1392
- * });
1393
- * ```
1394
- */
1395
- async list(params) {
1396
- const response = await this.http.get("/api/organizations", { params });
1397
- return response.data;
1398
- }
1399
- /**
1400
- * Get detailed information for a specific organization.
1401
- *
1402
- * @param orgSlug Organization slug
1403
- * @returns Organization details
1404
- *
1405
- * @example
1406
- * ```typescript
1407
- * const org = await sso.organizations.get('acme-corp');
1408
- * console.log(org.organization.name, org.membership_count);
1409
- * ```
1410
- */
1411
- async get(orgSlug) {
1412
- const response = await this.http.get(`/api/organizations/${orgSlug}`);
1413
- return response.data;
1414
- }
1415
- /**
1416
- * Update organization details.
1417
- * Requires 'owner' or 'admin' role.
1418
- *
1419
- * @param orgSlug Organization slug
1420
- * @param payload Update payload
1421
- * @returns Updated organization details
1422
- *
1423
- * @example
1424
- * ```typescript
1425
- * const updated = await sso.organizations.update('acme-corp', {
1426
- * name: 'Acme Corporation Inc.',
1427
- * max_services: 20
1428
- * });
1429
- * ```
1774
+ // ============================================================================
1775
+ // RISK SETTINGS
1776
+ // ============================================================================
1777
+ /**
1778
+ * Risk settings management methods
1779
+ */
1780
+ this.riskSettings = {
1781
+ /**
1782
+ * Get risk settings for an organization.
1783
+ * Requires 'owner' or 'admin' role.
1784
+ *
1785
+ * @param orgSlug Organization slug
1786
+ * @returns Risk settings configuration
1787
+ *
1788
+ * @example
1789
+ * ```typescript
1790
+ * const settings = await sso.organizations.riskSettings.get('acme-corp');
1791
+ * console.log('Enforcement mode:', settings.enforcement_mode);
1792
+ * console.log('Low threshold:', settings.low_threshold);
1793
+ * ```
1794
+ */
1795
+ get: async (orgSlug) => {
1796
+ const response = await this.http.get(
1797
+ `/api/organizations/${orgSlug}/risk-settings`
1798
+ );
1799
+ return response.data;
1800
+ },
1801
+ /**
1802
+ * Update risk settings for an organization.
1803
+ * Requires 'owner' or 'admin' role.
1804
+ *
1805
+ * @param orgSlug Organization slug
1806
+ * @param payload Risk settings update payload
1807
+ * @returns Updated risk settings
1808
+ *
1809
+ * @example
1810
+ * ```typescript
1811
+ * const result = await sso.organizations.riskSettings.update('acme-corp', {
1812
+ * enforcement_mode: 'challenge',
1813
+ * low_threshold: 30,
1814
+ * medium_threshold: 70,
1815
+ * new_device_score: 20,
1816
+ * impossible_travel_score: 50
1817
+ * });
1818
+ * console.log(result.message);
1819
+ * ```
1820
+ */
1821
+ update: async (orgSlug, payload) => {
1822
+ const response = await this.http.put(
1823
+ `/api/organizations/${orgSlug}/risk-settings`,
1824
+ payload
1825
+ );
1826
+ return response.data;
1827
+ },
1828
+ /**
1829
+ * Reset risk settings to default values.
1830
+ * Requires 'owner' or 'admin' role.
1831
+ *
1832
+ * @param orgSlug Organization slug
1833
+ * @returns Reset confirmation with default values
1834
+ *
1835
+ * @example
1836
+ * ```typescript
1837
+ * const result = await sso.organizations.riskSettings.reset('acme-corp');
1838
+ * console.log('Risk settings reset to defaults');
1839
+ * ```
1840
+ */
1841
+ reset: async (orgSlug) => {
1842
+ const response = await this.http.post(
1843
+ `/api/organizations/${orgSlug}/risk-settings/reset`
1844
+ );
1845
+ return response.data;
1846
+ }
1847
+ };
1848
+ // ============================================================================
1849
+ // SIEM CONFIGURATIONS
1850
+ // ============================================================================
1851
+ /**
1852
+ * SIEM (Security Information and Event Management) configuration methods
1853
+ */
1854
+ this.siem = {
1855
+ /**
1856
+ * Create a new SIEM configuration.
1857
+ * Requires 'owner' or 'admin' role.
1858
+ *
1859
+ * @param orgSlug Organization slug
1860
+ * @param payload SIEM configuration payload
1861
+ * @returns Created SIEM configuration
1862
+ *
1863
+ * @example
1864
+ * ```typescript
1865
+ * const config = await sso.organizations.siem.create('acme-corp', {
1866
+ * name: 'Datadog Integration',
1867
+ * provider_type: 'Datadog',
1868
+ * endpoint_url: 'https://http-intake.logs.datadoghq.com/v1/input',
1869
+ * api_key: 'dd-api-key',
1870
+ * batch_size: 100
1871
+ * });
1872
+ * ```
1873
+ */
1874
+ create: async (orgSlug, payload) => {
1875
+ const response = await this.http.post(
1876
+ `/api/organizations/${orgSlug}/siem-configs`,
1877
+ payload
1878
+ );
1879
+ return response.data;
1880
+ },
1881
+ /**
1882
+ * List all SIEM configurations for an organization.
1883
+ * Requires 'owner' or 'admin' role.
1884
+ *
1885
+ * @param orgSlug Organization slug
1886
+ * @returns List of SIEM configurations
1887
+ *
1888
+ * @example
1889
+ * ```typescript
1890
+ * const result = await sso.organizations.siem.list('acme-corp');
1891
+ * console.log(`Total SIEM configs: ${result.total}`);
1892
+ * result.siem_configs.forEach(config => {
1893
+ * console.log(config.name, config.provider_type, config.enabled);
1894
+ * });
1895
+ * ```
1896
+ */
1897
+ list: async (orgSlug) => {
1898
+ const response = await this.http.get(
1899
+ `/api/organizations/${orgSlug}/siem-configs`
1900
+ );
1901
+ return response.data;
1902
+ },
1903
+ /**
1904
+ * Get a specific SIEM configuration.
1905
+ * Requires 'owner' or 'admin' role.
1906
+ *
1907
+ * @param orgSlug Organization slug
1908
+ * @param configId SIEM configuration ID
1909
+ * @returns SIEM configuration
1910
+ *
1911
+ * @example
1912
+ * ```typescript
1913
+ * const config = await sso.organizations.siem.get('acme-corp', 'config-id');
1914
+ * console.log(config.name, config.endpoint_url);
1915
+ * ```
1916
+ */
1917
+ get: async (orgSlug, configId) => {
1918
+ const response = await this.http.get(
1919
+ `/api/organizations/${orgSlug}/siem-configs/${configId}`
1920
+ );
1921
+ return response.data;
1922
+ },
1923
+ /**
1924
+ * Update a SIEM configuration.
1925
+ * Requires 'owner' or 'admin' role.
1926
+ *
1927
+ * @param orgSlug Organization slug
1928
+ * @param configId SIEM configuration ID
1929
+ * @param payload Update payload
1930
+ * @returns Updated SIEM configuration
1931
+ *
1932
+ * @example
1933
+ * ```typescript
1934
+ * const updated = await sso.organizations.siem.update('acme-corp', 'config-id', {
1935
+ * enabled: false,
1936
+ * batch_size: 200
1937
+ * });
1938
+ * ```
1939
+ */
1940
+ update: async (orgSlug, configId, payload) => {
1941
+ const response = await this.http.patch(
1942
+ `/api/organizations/${orgSlug}/siem-configs/${configId}`,
1943
+ payload
1944
+ );
1945
+ return response.data;
1946
+ },
1947
+ /**
1948
+ * Delete a SIEM configuration.
1949
+ * Requires 'owner' or 'admin' role.
1950
+ *
1951
+ * @param orgSlug Organization slug
1952
+ * @param configId SIEM configuration ID
1953
+ *
1954
+ * @example
1955
+ * ```typescript
1956
+ * await sso.organizations.siem.delete('acme-corp', 'config-id');
1957
+ * console.log('SIEM configuration deleted');
1958
+ * ```
1959
+ */
1960
+ delete: async (orgSlug, configId) => {
1961
+ await this.http.delete(`/api/organizations/${orgSlug}/siem-configs/${configId}`);
1962
+ },
1963
+ /**
1964
+ * Test connection to a SIEM endpoint.
1965
+ * Sends a test event to verify the configuration.
1966
+ * Requires 'owner' or 'admin' role.
1967
+ *
1968
+ * @param orgSlug Organization slug
1969
+ * @param configId SIEM configuration ID
1970
+ * @returns Test result
1971
+ *
1972
+ * @example
1973
+ * ```typescript
1974
+ * const result = await sso.organizations.siem.test('acme-corp', 'config-id');
1975
+ * if (result.success) {
1976
+ * console.log('Connection successful:', result.message);
1977
+ * } else {
1978
+ * console.error('Connection failed:', result.message);
1979
+ * }
1980
+ * ```
1981
+ */
1982
+ test: async (orgSlug, configId) => {
1983
+ const response = await this.http.post(
1984
+ `/api/organizations/${orgSlug}/siem-configs/${configId}/test`
1985
+ );
1986
+ return response.data;
1987
+ }
1988
+ };
1989
+ this.auditLogs = new AuditLogsModule(http);
1990
+ this.webhooks = new WebhooksModule(http);
1991
+ }
1992
+ /**
1993
+ * Create a new organization (requires authentication).
1994
+ * The authenticated user becomes the organization owner.
1995
+ * Returns JWT tokens with organization context, eliminating the need to re-authenticate.
1996
+ *
1997
+ * @param payload Organization creation payload
1998
+ * @returns Created organization with owner, membership, and JWT tokens
1999
+ *
2000
+ * @example
2001
+ * ```typescript
2002
+ * const result = await sso.organizations.create({
2003
+ * slug: 'acme-corp',
2004
+ * name: 'Acme Corporation'
2005
+ * });
2006
+ * // Store the new tokens with org context
2007
+ * authStore.setTokens(result.access_token, result.refresh_token);
2008
+ * ```
2009
+ */
2010
+ async create(payload) {
2011
+ const response = await this.http.post("/api/organizations", payload);
2012
+ return response.data;
2013
+ }
2014
+ /**
2015
+ * List all organizations the authenticated user is a member of.
2016
+ *
2017
+ * @param params Optional query parameters for filtering and pagination
2018
+ * @returns Array of organization responses
2019
+ *
2020
+ * @example
2021
+ * ```typescript
2022
+ * const orgs = await sso.organizations.list({
2023
+ * status: 'active',
2024
+ * page: 1,
2025
+ * limit: 20
2026
+ * });
2027
+ * ```
2028
+ */
2029
+ async list(params) {
2030
+ const response = await this.http.get("/api/organizations", { params });
2031
+ return response.data;
2032
+ }
2033
+ /**
2034
+ * Get detailed information for a specific organization.
2035
+ *
2036
+ * @param orgSlug Organization slug
2037
+ * @returns Organization details
2038
+ *
2039
+ * @example
2040
+ * ```typescript
2041
+ * const org = await sso.organizations.get('acme-corp');
2042
+ * console.log(org.organization.name, org.membership_count);
2043
+ * ```
2044
+ */
2045
+ async get(orgSlug) {
2046
+ const response = await this.http.get(`/api/organizations/${orgSlug}`);
2047
+ return response.data;
2048
+ }
2049
+ /**
2050
+ * Update organization details.
2051
+ * Requires 'owner' or 'admin' role.
2052
+ *
2053
+ * @param orgSlug Organization slug
2054
+ * @param payload Update payload
2055
+ * @returns Updated organization details
2056
+ *
2057
+ * @example
2058
+ * ```typescript
2059
+ * const updated = await sso.organizations.update('acme-corp', {
2060
+ * name: 'Acme Corporation Inc.',
2061
+ * max_services: 20
2062
+ * });
2063
+ * ```
1430
2064
  */
1431
2065
  async update(orgSlug, payload) {
1432
2066
  const response = await this.http.patch(
@@ -1992,6 +2626,26 @@ var ServicesModule = class {
1992
2626
  );
1993
2627
  return response.data;
1994
2628
  },
2629
+ /**
2630
+ * Initiate an IdP-initiated SAML login.
2631
+ * Returns an HTML page with an auto-submitting form that POSTs the SAML assertion to the Service Provider.
2632
+ *
2633
+ * @param orgSlug Organization slug
2634
+ * @param serviceSlug Service slug
2635
+ * @returns HTML page with auto-submitting form
2636
+ *
2637
+ * @example
2638
+ * ```typescript
2639
+ * const html = await sso.services.saml.initiateLogin('acme-corp', 'salesforce');
2640
+ * document.body.innerHTML = html; // Auto-submits
2641
+ * ```
2642
+ */
2643
+ initiateLogin: async (orgSlug, serviceSlug) => {
2644
+ const response = await this.http.get(
2645
+ `/api/organizations/${orgSlug}/services/${serviceSlug}/saml/login`
2646
+ );
2647
+ return response.data;
2648
+ },
1995
2649
  /**
1996
2650
  * Generate a new SAML signing certificate for the IdP.
1997
2651
  * Requires 'owner' or 'admin' role.
@@ -2088,6 +2742,41 @@ var ServicesModule = class {
2088
2742
  return `${baseURL}/saml/${orgSlug}/${serviceSlug}/sso`;
2089
2743
  }
2090
2744
  };
2745
+ /**
2746
+ * Stripe billing and checkout methods
2747
+ */
2748
+ this.billing = {
2749
+ /**
2750
+ * Create a Stripe checkout session for the authenticated user to subscribe to a plan.
2751
+ * Requires organization membership.
2752
+ *
2753
+ * IMPORTANT: The plan must have a `stripe_price_id` configured to be available for purchase.
2754
+ *
2755
+ * @param orgSlug Organization slug
2756
+ * @param serviceSlug Service slug
2757
+ * @param payload Checkout payload containing plan_id and redirect URLs
2758
+ * @returns Checkout session with URL to redirect user to
2759
+ *
2760
+ * @example
2761
+ * ```typescript
2762
+ * const checkout = await sso.services.billing.createCheckout('acme-corp', 'main-app', {
2763
+ * plan_id: 'plan_pro',
2764
+ * success_url: 'https://app.acme.com/billing/success?session_id={CHECKOUT_SESSION_ID}',
2765
+ * cancel_url: 'https://app.acme.com/billing/cancel'
2766
+ * });
2767
+ *
2768
+ * // Redirect user to Stripe checkout
2769
+ * window.location.href = checkout.checkout_url;
2770
+ * ```
2771
+ */
2772
+ createCheckout: async (orgSlug, serviceSlug, payload) => {
2773
+ const response = await this.http.post(
2774
+ `/api/organizations/${orgSlug}/services/${serviceSlug}/checkout`,
2775
+ payload
2776
+ );
2777
+ return response.data;
2778
+ }
2779
+ };
2091
2780
  }
2092
2781
  /**
2093
2782
  * Create a new service for an organization.
@@ -2210,7 +2899,7 @@ var InvitationsModule = class {
2210
2899
  * @example
2211
2900
  * ```typescript
2212
2901
  * const invitation = await sso.invitations.create('acme-corp', {
2213
- * invitee_email: 'newuser@example.com',
2902
+ * email: 'newuser@example.com',
2214
2903
  * role: 'member'
2215
2904
  * });
2216
2905
  * ```
@@ -2704,6 +3393,41 @@ var PlatformModule = class {
2704
3393
  const response = await this.http.get("/api/platform/audit-log", { params });
2705
3394
  return response.data;
2706
3395
  }
3396
+ /**
3397
+ * Impersonate a user (Platform Owner or Org Admin only).
3398
+ * Returns a short-lived JWT (15 minutes) that allows acting as the target user.
3399
+ *
3400
+ * Security:
3401
+ * - Platform Owners can impersonate any user
3402
+ * - Organization Admins can only impersonate users within their organization
3403
+ * - All impersonation actions are logged to the platform audit log with HIGH severity
3404
+ * - Tokens contain RFC 8693 actor claim for full audit trail
3405
+ *
3406
+ * @param payload Impersonation details (user_id and reason)
3407
+ * @returns Impersonation token and user context
3408
+ *
3409
+ * @example
3410
+ * ```typescript
3411
+ * const result = await sso.platform.impersonateUser({
3412
+ * user_id: 'user-uuid-123',
3413
+ * reason: 'Investigating support ticket #456'
3414
+ * });
3415
+ *
3416
+ * // Use the returned token to create a new client acting as the user
3417
+ * const userClient = new SsoClient({
3418
+ * baseURL: 'https://sso.example.com',
3419
+ * token: result.token
3420
+ * });
3421
+ *
3422
+ * // Now all requests with userClient are made as the target user
3423
+ * const profile = await userClient.user.getProfile();
3424
+ * console.log('Acting as:', result.target_user.email);
3425
+ * ```
3426
+ */
3427
+ async impersonateUser(payload) {
3428
+ const response = await this.http.post("/api/platform/impersonate", payload);
3429
+ return response.data;
3430
+ }
2707
3431
  };
2708
3432
 
2709
3433
  // src/modules/serviceApi.ts
@@ -2712,14 +3436,86 @@ var ServiceApiModule = class {
2712
3436
  this.http = http;
2713
3437
  }
2714
3438
  /**
2715
- * Create a new user
2716
- * Requires 'write:users' permission on the API key
3439
+ * List all users for the service
3440
+ * Requires 'read:users' permission on the API key
2717
3441
  *
2718
- * @param request User creation request
2719
- * @returns Created user
3442
+ * @param params Optional pagination parameters
3443
+ * @returns List of users with total count
2720
3444
  */
2721
- async createUser(request) {
2722
- const response = await this.http.post("/api/service/users", request);
3445
+ async listUsers(params) {
3446
+ const queryParams = new URLSearchParams();
3447
+ if (params?.limit) queryParams.set("limit", params.limit.toString());
3448
+ if (params?.offset) queryParams.set("offset", params.offset.toString());
3449
+ const query = queryParams.toString() ? `?${queryParams.toString()}` : "";
3450
+ const response = await this.http.get(`/api/service/users${query}`);
3451
+ return response.data;
3452
+ }
3453
+ /**
3454
+ * Get a specific user by ID
3455
+ * Requires 'read:users' permission on the API key
3456
+ *
3457
+ * @param userId User ID to retrieve
3458
+ * @returns User details
3459
+ */
3460
+ async getUser(userId) {
3461
+ const response = await this.http.get(`/api/service/users/${userId}`);
3462
+ return response.data;
3463
+ }
3464
+ /**
3465
+ * List all subscriptions for the service
3466
+ * Requires 'read:subscriptions' permission on the API key
3467
+ *
3468
+ * @param params Optional pagination parameters
3469
+ * @returns List of subscriptions with total count
3470
+ */
3471
+ async listSubscriptions(params) {
3472
+ const queryParams = new URLSearchParams();
3473
+ if (params?.limit) queryParams.set("limit", params.limit.toString());
3474
+ if (params?.offset) queryParams.set("offset", params.offset.toString());
3475
+ const query = queryParams.toString() ? `?${queryParams.toString()}` : "";
3476
+ const response = await this.http.get(`/api/service/subscriptions${query}`);
3477
+ return response.data;
3478
+ }
3479
+ /**
3480
+ * Get subscription for a specific user
3481
+ * Requires 'read:subscriptions' permission on the API key
3482
+ *
3483
+ * @param userId User ID whose subscription to retrieve
3484
+ * @returns User's subscription
3485
+ */
3486
+ async getSubscription(userId) {
3487
+ const response = await this.http.get(`/api/service/subscriptions/${userId}`);
3488
+ return response.data;
3489
+ }
3490
+ /**
3491
+ * Get analytics for the service
3492
+ * Requires 'read:analytics' permission on the API key
3493
+ *
3494
+ * @returns Service analytics data
3495
+ */
3496
+ async getAnalytics() {
3497
+ const response = await this.http.get("/api/service/analytics");
3498
+ return response.data;
3499
+ }
3500
+ /**
3501
+ * Get service information
3502
+ * Requires 'read:service' permission on the API key
3503
+ *
3504
+ * @returns Service information
3505
+ */
3506
+ async getServiceInfo() {
3507
+ const response = await this.http.get("/api/service/info");
3508
+ return response.data;
3509
+ }
3510
+ /**
3511
+ * Create a new user
3512
+ * Requires 'write:users' permission on the API key
3513
+ *
3514
+ * @param request User creation request
3515
+ * @returns Created user
3516
+ */
3517
+ async createUser(request) {
3518
+ const response = await this.http.post("/api/service/users", request);
2723
3519
  return response.data;
2724
3520
  }
2725
3521
  /**
@@ -2788,24 +3584,676 @@ var ServiceApiModule = class {
2788
3584
  }
2789
3585
  };
2790
3586
 
3587
+ // src/modules/permissions.ts
3588
+ var PermissionsModule = class {
3589
+ constructor(http) {
3590
+ this.http = http;
3591
+ }
3592
+ /**
3593
+ * Check if user has a specific permission.
3594
+ * Fetches from user profile API (which uses cached permissions).
3595
+ *
3596
+ * @param permission Permission in format "namespace:object_id#relation"
3597
+ * @returns true if the permission is present
3598
+ *
3599
+ * @example
3600
+ * ```typescript
3601
+ * const hasAccess = await sso.permissions.hasPermission('organization:acme#owner');
3602
+ * ```
3603
+ */
3604
+ async hasPermission(permission) {
3605
+ const response = await this.http.get("/api/user");
3606
+ return response.data.permissions.includes(permission);
3607
+ }
3608
+ /**
3609
+ * Get all user permissions.
3610
+ * Fetches from user profile API (which uses cached permissions).
3611
+ *
3612
+ * @returns Array of permission strings
3613
+ *
3614
+ * @example
3615
+ * ```typescript
3616
+ * const permissions = await sso.permissions.listPermissions();
3617
+ * // ["organization:acme#owner", "service:api#admin"]
3618
+ * ```
3619
+ */
3620
+ async listPermissions() {
3621
+ const response = await this.http.get("/api/user");
3622
+ return response.data.permissions;
3623
+ }
3624
+ /**
3625
+ * Check if user has access to a feature.
3626
+ *
3627
+ * @param feature Feature name to check
3628
+ * @returns true if the feature is available
3629
+ *
3630
+ * @example
3631
+ * ```typescript
3632
+ * const canExport = await sso.permissions.hasFeature('advanced-export');
3633
+ * ```
3634
+ */
3635
+ async hasFeature(feature) {
3636
+ const response = await this.http.get("/api/user");
3637
+ return response.data.features?.includes(feature) ?? false;
3638
+ }
3639
+ /**
3640
+ * Get current plan name.
3641
+ *
3642
+ * @returns Current plan name or null if not in org/service context
3643
+ *
3644
+ * @example
3645
+ * ```typescript
3646
+ * const plan = await sso.permissions.getPlan();
3647
+ * console.log(plan); // "pro", "enterprise", etc.
3648
+ * ```
3649
+ */
3650
+ async getPlan() {
3651
+ const response = await this.http.get("/api/user");
3652
+ return response.data.plan;
3653
+ }
3654
+ /**
3655
+ * Check if user has a specific permission on a resource.
3656
+ *
3657
+ * @param namespace The permission namespace (e.g., "organization", "service")
3658
+ * @param objectId The object ID (e.g., organization slug, service slug)
3659
+ * @param relation The relation type (e.g., "owner", "admin", "member")
3660
+ * @returns true if the user has the permission
3661
+ *
3662
+ * @example
3663
+ * ```typescript
3664
+ * const isOwner = await sso.permissions.can('organization', 'acme-corp', 'owner');
3665
+ * ```
3666
+ */
3667
+ async can(namespace, objectId, relation) {
3668
+ return this.hasPermission(`${namespace}:${objectId}#${relation}`);
3669
+ }
3670
+ /**
3671
+ * Check if user is a member of an organization.
3672
+ *
3673
+ * @param orgId The organization ID or slug
3674
+ * @returns true if the user is a member
3675
+ *
3676
+ * @example
3677
+ * ```typescript
3678
+ * if (await sso.permissions.isOrgMember('acme-corp')) {
3679
+ * // User is a member
3680
+ * }
3681
+ * ```
3682
+ */
3683
+ async isOrgMember(orgId) {
3684
+ return this.hasPermission(`organization:${orgId}#member`);
3685
+ }
3686
+ /**
3687
+ * Check if user is an admin of an organization.
3688
+ *
3689
+ * @param orgId The organization ID or slug
3690
+ * @returns true if the user is an admin
3691
+ *
3692
+ * @example
3693
+ * ```typescript
3694
+ * if (await sso.permissions.isOrgAdmin('acme-corp')) {
3695
+ * // User is an admin
3696
+ * }
3697
+ * ```
3698
+ */
3699
+ async isOrgAdmin(orgId) {
3700
+ return this.hasPermission(`organization:${orgId}#admin`);
3701
+ }
3702
+ /**
3703
+ * Check if user is an owner of an organization.
3704
+ *
3705
+ * @param orgId The organization ID or slug
3706
+ * @returns true if the user is an owner
3707
+ *
3708
+ * @example
3709
+ * ```typescript
3710
+ * if (await sso.permissions.isOrgOwner('acme-corp')) {
3711
+ * // User is an owner
3712
+ * }
3713
+ * ```
3714
+ */
3715
+ async isOrgOwner(orgId) {
3716
+ return this.hasPermission(`organization:${orgId}#owner`);
3717
+ }
3718
+ /**
3719
+ * Check if user has access to a service.
3720
+ *
3721
+ * @param serviceId The service ID or slug
3722
+ * @returns true if the user has access
3723
+ *
3724
+ * @example
3725
+ * ```typescript
3726
+ * if (await sso.permissions.hasServiceAccess('api-service')) {
3727
+ * // User has access to the service
3728
+ * }
3729
+ * ```
3730
+ */
3731
+ async hasServiceAccess(serviceId) {
3732
+ return this.hasPermission(`service:${serviceId}#member`);
3733
+ }
3734
+ /**
3735
+ * Filter permissions by namespace.
3736
+ *
3737
+ * @param namespace The namespace to filter by (e.g., "organization", "service")
3738
+ * @returns Array of permissions matching the namespace
3739
+ *
3740
+ * @example
3741
+ * ```typescript
3742
+ * const orgPermissions = await sso.permissions.getPermissionsByNamespace('organization');
3743
+ * ```
3744
+ */
3745
+ async getPermissionsByNamespace(namespace) {
3746
+ const allPermissions = await this.listPermissions();
3747
+ return allPermissions.filter((p) => p.startsWith(`${namespace}:`));
3748
+ }
3749
+ // ============================================================================
3750
+ // DEPRECATED METHODS - Token-based permission checking (legacy)
3751
+ // ============================================================================
3752
+ /**
3753
+ * @deprecated Use `hasPermission()` instead (without token parameter)
3754
+ * Decode a JWT token to extract claims (including permissions)
3755
+ * Note: This does NOT verify the signature - it only decodes the payload
3756
+ *
3757
+ * @param token The JWT access token
3758
+ * @returns The decoded JWT claims
3759
+ * @throws Error if the token is malformed
3760
+ */
3761
+ decodeToken(token) {
3762
+ try {
3763
+ const parts = token.split(".");
3764
+ if (parts.length !== 3) {
3765
+ throw new Error("Invalid JWT format");
3766
+ }
3767
+ const payload = parts[1];
3768
+ const decoded = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
3769
+ return JSON.parse(decoded);
3770
+ } catch (error) {
3771
+ throw new Error(`Failed to decode JWT: ${error instanceof Error ? error.message : "Unknown error"}`);
3772
+ }
3773
+ }
3774
+ /**
3775
+ * @deprecated JWT tokens no longer contain permissions. Use `hasPermission(permission)` instead.
3776
+ * Check if a JWT token contains a specific permission
3777
+ *
3778
+ * @param token The JWT access token (ignored)
3779
+ * @param permission Permission in format "namespace:object_id#relation"
3780
+ * @returns true if the permission is present in the token
3781
+ */
3782
+ hasPermissionFromToken(token, permission) {
3783
+ const claims = this.decodeToken(token);
3784
+ return claims.permissions?.includes(permission) ?? false;
3785
+ }
3786
+ /**
3787
+ * @deprecated JWT tokens no longer contain permissions. Use `can(namespace, objectId, relation)` instead.
3788
+ * Check if a user has a specific permission on a resource
3789
+ *
3790
+ * @param token The JWT access token (ignored)
3791
+ * @param namespace The permission namespace (e.g., "organization", "service")
3792
+ * @param objectId The object ID (e.g., organization slug, service slug)
3793
+ * @param relation The relation type (e.g., "owner", "admin", "member")
3794
+ * @returns true if the user has the permission
3795
+ */
3796
+ canFromToken(token, namespace, objectId, relation) {
3797
+ return this.hasPermissionFromToken(token, `${namespace}:${objectId}#${relation}`);
3798
+ }
3799
+ /**
3800
+ * @deprecated JWT tokens no longer contain permissions. Use `isOrgMember(orgId)` instead.
3801
+ * Check if user is a member of an organization
3802
+ *
3803
+ * @param token The JWT access token (ignored)
3804
+ * @param orgId The organization ID or slug
3805
+ * @returns true if the user is a member
3806
+ */
3807
+ isOrgMemberFromToken(token, orgId) {
3808
+ return this.hasPermissionFromToken(token, `organization:${orgId}#member`);
3809
+ }
3810
+ /**
3811
+ * @deprecated JWT tokens no longer contain permissions. Use `isOrgAdmin(orgId)` instead.
3812
+ * Check if user is an admin of an organization
3813
+ *
3814
+ * @param token The JWT access token (ignored)
3815
+ * @param orgId The organization ID or slug
3816
+ * @returns true if the user is an admin
3817
+ */
3818
+ isOrgAdminFromToken(token, orgId) {
3819
+ return this.hasPermissionFromToken(token, `organization:${orgId}#admin`);
3820
+ }
3821
+ /**
3822
+ * @deprecated JWT tokens no longer contain permissions. Use `isOrgOwner(orgId)` instead.
3823
+ * Check if user is an owner of an organization
3824
+ *
3825
+ * @param token The JWT access token (ignored)
3826
+ * @param orgId The organization ID or slug
3827
+ * @returns true if the user is an owner
3828
+ */
3829
+ isOrgOwnerFromToken(token, orgId) {
3830
+ return this.hasPermissionFromToken(token, `organization:${orgId}#owner`);
3831
+ }
3832
+ /**
3833
+ * @deprecated JWT tokens no longer contain permissions. Use `hasServiceAccess(serviceId)` instead.
3834
+ * Check if user has access to a service
3835
+ *
3836
+ * @param token The JWT access token (ignored)
3837
+ * @param serviceId The service ID or slug
3838
+ * @returns true if the user has access
3839
+ */
3840
+ hasServiceAccessFromToken(token, serviceId) {
3841
+ return this.hasPermissionFromToken(token, `service:${serviceId}#member`);
3842
+ }
3843
+ /**
3844
+ * @deprecated JWT tokens no longer contain permissions. Use `listPermissions()` instead.
3845
+ * Get all permissions from a JWT token
3846
+ *
3847
+ * @param token The JWT access token
3848
+ * @returns Array of permission strings, or empty array if none
3849
+ */
3850
+ getAllPermissionsFromToken(token) {
3851
+ const claims = this.decodeToken(token);
3852
+ return claims.permissions ?? [];
3853
+ }
3854
+ /**
3855
+ * Parse a permission string into its components
3856
+ *
3857
+ * @param permission Permission string in format "namespace:object_id#relation"
3858
+ * @returns Parsed permission components or null if invalid format
3859
+ *
3860
+ * @example
3861
+ * ```typescript
3862
+ * const parsed = sso.permissions.parsePermission('organization:acme#owner');
3863
+ * // { namespace: 'organization', objectId: 'acme', relation: 'owner' }
3864
+ * ```
3865
+ */
3866
+ parsePermission(permission) {
3867
+ const match = permission.match(/^([^:]+):([^#]+)#(.+)$/);
3868
+ if (!match) {
3869
+ return null;
3870
+ }
3871
+ return {
3872
+ namespace: match[1],
3873
+ objectId: match[2],
3874
+ relation: match[3]
3875
+ };
3876
+ }
3877
+ /**
3878
+ * @deprecated JWT tokens no longer contain permissions. Use `getPermissionsByNamespace(namespace)` instead.
3879
+ * Filter permissions by namespace
3880
+ *
3881
+ * @param token The JWT access token (ignored)
3882
+ * @param namespace The namespace to filter by (e.g., "organization", "service")
3883
+ * @returns Array of permissions matching the namespace
3884
+ */
3885
+ getPermissionsByNamespaceFromToken(token, namespace) {
3886
+ const allPermissions = this.getAllPermissionsFromToken(token);
3887
+ return allPermissions.filter((p) => p.startsWith(`${namespace}:`));
3888
+ }
3889
+ };
3890
+
3891
+ // src/modules/passkeys.ts
3892
+ var PasskeysModule = class {
3893
+ constructor(http) {
3894
+ this.http = http;
3895
+ }
3896
+ /**
3897
+ * Check if WebAuthn is supported in the current browser
3898
+ */
3899
+ isSupported() {
3900
+ return typeof window !== "undefined" && window.PublicKeyCredential !== void 0 && typeof window.PublicKeyCredential === "function";
3901
+ }
3902
+ /**
3903
+ * Check if platform authenticator (like Touch ID, Face ID, Windows Hello) is available
3904
+ */
3905
+ async isPlatformAuthenticatorAvailable() {
3906
+ if (!this.isSupported()) {
3907
+ return false;
3908
+ }
3909
+ return PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
3910
+ }
3911
+ /**
3912
+ * Register a new passkey for the authenticated user
3913
+ *
3914
+ * This method requires an authenticated session (JWT token must be set).
3915
+ * It starts the WebAuthn registration ceremony, prompts the user to create
3916
+ * a passkey using their device's authenticator (e.g., Touch ID, Face ID,
3917
+ * Windows Hello, or hardware security key), and stores the credential.
3918
+ *
3919
+ * @param displayName Optional display name for the passkey
3920
+ * @returns Promise resolving to the registered passkey ID
3921
+ * @throws {Error} If WebAuthn is not supported or registration fails
3922
+ *
3923
+ * @example
3924
+ * ```typescript
3925
+ * try {
3926
+ * const passkeyId = await sso.passkeys.register('My MacBook Pro');
3927
+ * console.log('Passkey registered:', passkeyId);
3928
+ * } catch (error) {
3929
+ * console.error('Passkey registration failed:', error);
3930
+ * }
3931
+ * ```
3932
+ */
3933
+ /**
3934
+ * Start the passkey registration ceremony.
3935
+ * returns the options required to create credentials in the browser.
3936
+ */
3937
+ async registerStart(displayName) {
3938
+ const response = await this.http.post(
3939
+ "/api/auth/passkeys/register/start",
3940
+ { name: displayName }
3941
+ );
3942
+ return response.data;
3943
+ }
3944
+ /**
3945
+ * Finish the passkey registration ceremony.
3946
+ * Verifies the credential created by the browser.
3947
+ */
3948
+ async registerFinish(challengeId, credential) {
3949
+ const response = await this.http.post(
3950
+ "/api/auth/passkeys/register/finish",
3951
+ {
3952
+ challenge_id: challengeId,
3953
+ credential
3954
+ }
3955
+ );
3956
+ return response.data;
3957
+ }
3958
+ /**
3959
+ * Register a new passkey for the authenticated user
3960
+ * ...
3961
+ */
3962
+ async register(displayName) {
3963
+ if (!this.isSupported()) {
3964
+ throw new Error("WebAuthn is not supported in this browser");
3965
+ }
3966
+ const startData = await this.registerStart(displayName);
3967
+ const createOptions = {
3968
+ publicKey: {
3969
+ ...startData.options,
3970
+ challenge: this.base64UrlToUint8Array(startData.options.challenge),
3971
+ user: {
3972
+ ...startData.options.user,
3973
+ id: this.base64UrlToUint8Array(startData.options.user.id)
3974
+ },
3975
+ excludeCredentials: startData.options.excludeCredentials?.map((cred) => ({
3976
+ ...cred,
3977
+ id: this.base64UrlToUint8Array(cred.id)
3978
+ }))
3979
+ }
3980
+ };
3981
+ const credential = await navigator.credentials.create(createOptions);
3982
+ if (!credential || !(credential instanceof PublicKeyCredential)) {
3983
+ throw new Error("Failed to create passkey");
3984
+ }
3985
+ if (!(credential.response instanceof AuthenticatorAttestationResponse)) {
3986
+ throw new Error("Invalid credential response type");
3987
+ }
3988
+ const credentialJSON = {
3989
+ id: credential.id,
3990
+ rawId: this.uint8ArrayToBase64Url(new Uint8Array(credential.rawId)),
3991
+ response: {
3992
+ clientDataJSON: this.uint8ArrayToBase64Url(new Uint8Array(credential.response.clientDataJSON)),
3993
+ attestationObject: this.uint8ArrayToBase64Url(new Uint8Array(credential.response.attestationObject)),
3994
+ transports: credential.response.getTransports?.()
3995
+ },
3996
+ authenticatorAttachment: credential.authenticatorAttachment,
3997
+ clientExtensionResults: credential.getClientExtensionResults(),
3998
+ type: credential.type
3999
+ };
4000
+ const finishResponse = await this.registerFinish(startData.challenge_id, credentialJSON);
4001
+ return finishResponse.passkey_id;
4002
+ }
4003
+ /**
4004
+ * Authenticate with a passkey and obtain a JWT token
4005
+ *
4006
+ * This method prompts the user to authenticate using their passkey.
4007
+ * Upon successful authentication, a JWT token is returned which can
4008
+ * be used to make authenticated API requests.
4009
+ *
4010
+ * @param email User's email address
4011
+ * @returns Promise resolving to authentication response with JWT token
4012
+ * @throws {Error} If WebAuthn is not supported or authentication fails
4013
+ *
4014
+ * @example
4015
+ * ```typescript
4016
+ * try {
4017
+ * const { token, user_id } = await sso.passkeys.login('user@example.com');
4018
+ * sso.setToken(token);
4019
+ * console.log('Logged in as:', user_id);
4020
+ * } catch (error) {
4021
+ * console.error('Passkey login failed:', error);
4022
+ * }
4023
+ * ```
4024
+ */
4025
+ /**
4026
+ * Start the passkey authentication ceremony.
4027
+ * Returns the options required to get credentials from the browser.
4028
+ */
4029
+ async authenticateStart(email) {
4030
+ const response = await this.http.post(
4031
+ "/api/auth/passkeys/authenticate/start",
4032
+ { email }
4033
+ );
4034
+ return response.data;
4035
+ }
4036
+ /**
4037
+ * Finish the passkey authentication ceremony.
4038
+ * Verifies the assertion returned by the browser.
4039
+ */
4040
+ async authenticateFinish(challengeId, credential) {
4041
+ const response = await this.http.post(
4042
+ "/api/auth/passkeys/authenticate/finish",
4043
+ {
4044
+ challenge_id: challengeId,
4045
+ credential
4046
+ }
4047
+ );
4048
+ return response.data;
4049
+ }
4050
+ /**
4051
+ * Authenticate with a passkey and obtain a JWT token
4052
+ * ...
4053
+ */
4054
+ async login(email) {
4055
+ if (!this.isSupported()) {
4056
+ throw new Error("WebAuthn is not supported in this browser");
4057
+ }
4058
+ const startData = await this.authenticateStart(email);
4059
+ const getOptions = {
4060
+ publicKey: {
4061
+ ...startData.options,
4062
+ challenge: this.base64UrlToUint8Array(startData.options.challenge),
4063
+ allowCredentials: startData.options.allowCredentials?.map((cred) => ({
4064
+ ...cred,
4065
+ id: this.base64UrlToUint8Array(cred.id)
4066
+ }))
4067
+ }
4068
+ };
4069
+ const credential = await navigator.credentials.get(getOptions);
4070
+ if (!credential || !(credential instanceof PublicKeyCredential)) {
4071
+ throw new Error("Failed to get passkey");
4072
+ }
4073
+ if (!(credential.response instanceof AuthenticatorAssertionResponse)) {
4074
+ throw new Error("Invalid credential response type");
4075
+ }
4076
+ const credentialJSON = {
4077
+ id: credential.id,
4078
+ rawId: this.uint8ArrayToBase64Url(new Uint8Array(credential.rawId)),
4079
+ response: {
4080
+ clientDataJSON: this.uint8ArrayToBase64Url(new Uint8Array(credential.response.clientDataJSON)),
4081
+ authenticatorData: this.uint8ArrayToBase64Url(new Uint8Array(credential.response.authenticatorData)),
4082
+ signature: this.uint8ArrayToBase64Url(new Uint8Array(credential.response.signature)),
4083
+ userHandle: credential.response.userHandle ? this.uint8ArrayToBase64Url(new Uint8Array(credential.response.userHandle)) : void 0
4084
+ },
4085
+ authenticatorAttachment: credential.authenticatorAttachment,
4086
+ clientExtensionResults: credential.getClientExtensionResults(),
4087
+ type: credential.type
4088
+ };
4089
+ return this.authenticateFinish(startData.challenge_id, credentialJSON);
4090
+ }
4091
+ /**
4092
+ * Convert Base64URL string to Uint8Array
4093
+ */
4094
+ base64UrlToUint8Array(base64url) {
4095
+ const padding = "=".repeat((4 - base64url.length % 4) % 4);
4096
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/") + padding;
4097
+ const rawData = atob(base64);
4098
+ const outputArray = new Uint8Array(rawData.length);
4099
+ for (let i = 0; i < rawData.length; ++i) {
4100
+ outputArray[i] = rawData.charCodeAt(i);
4101
+ }
4102
+ return outputArray;
4103
+ }
4104
+ /**
4105
+ * Convert Uint8Array to Base64URL string
4106
+ */
4107
+ uint8ArrayToBase64Url(array) {
4108
+ let binary = "";
4109
+ for (let i = 0; i < array.byteLength; i++) {
4110
+ binary += String.fromCharCode(array[i]);
4111
+ }
4112
+ const base64 = btoa(binary);
4113
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
4114
+ }
4115
+ };
4116
+
4117
+ // src/modules/magic.ts
4118
+ var MagicLinks = class {
4119
+ constructor(http) {
4120
+ this.http = http;
4121
+ }
4122
+ /**
4123
+ * Request a magic link to be sent to the user's email
4124
+ *
4125
+ * @param data Magic link request data
4126
+ * @returns Promise resolving to magic link response
4127
+ */
4128
+ async request(data) {
4129
+ const response = await this.http.post("/api/auth/magic-link/request", data);
4130
+ return response.data;
4131
+ }
4132
+ /**
4133
+ * Verify a magic link token and complete authentication
4134
+ * Note: This is typically handled by redirecting to the magic link URL
4135
+ * The backend will handle verification and either redirect or return tokens
4136
+ *
4137
+ * @param token The magic link token to verify
4138
+ * @param redirectUri Optional where to redirect after success
4139
+ * @returns URL to redirect to for verification
4140
+ */
4141
+ getVerificationUrl(token, redirectUri) {
4142
+ const params = new URLSearchParams({ token });
4143
+ if (redirectUri) {
4144
+ params.append("redirect_uri", redirectUri);
4145
+ }
4146
+ return `/api/auth/magic-link/verify?${params.toString()}`;
4147
+ }
4148
+ /**
4149
+ * Verify a magic link token via API call
4150
+ * This is an alternative to redirect-based verification
4151
+ *
4152
+ * @param token The magic link token
4153
+ * @param redirectUri Optional redirect URI
4154
+ * @returns Promise resolving to authentication response
4155
+ */
4156
+ async verify(token, redirectUri) {
4157
+ const params = new URLSearchParams({ token });
4158
+ if (redirectUri) {
4159
+ params.append("redirect_uri", redirectUri);
4160
+ }
4161
+ return this.http.get(`/api/auth/magic-link/verify?${params.toString()}`);
4162
+ }
4163
+ /**
4164
+ * Construct the complete magic link URL that would be sent via email
4165
+ *
4166
+ * @param token The magic link token
4167
+ * @param redirectUri Optional redirect URI
4168
+ * @returns Complete magic link URL
4169
+ */
4170
+ constructMagicLink(token, redirectUri) {
4171
+ return this.getVerificationUrl(token, redirectUri);
4172
+ }
4173
+ };
4174
+
4175
+ // src/modules/privacy.ts
4176
+ var PrivacyModule = class {
4177
+ constructor(http) {
4178
+ this.http = http;
4179
+ }
4180
+ /**
4181
+ * Export all user data (GDPR Right to Access).
4182
+ * Users can export their own data, or organization owners can export their members' data.
4183
+ *
4184
+ * @param userId User ID to export data for
4185
+ * @returns Complete user data export including memberships, login events, identities, MFA events, and passkeys
4186
+ *
4187
+ * @example
4188
+ * ```typescript
4189
+ * const userData = await sso.privacy.exportData('user-id');
4190
+ * console.log(`Exported ${userData.login_events_count} login events`);
4191
+ * console.log(`User has ${userData.memberships.length} organization memberships`);
4192
+ * ```
4193
+ */
4194
+ async exportData(userId) {
4195
+ const response = await this.http.get(`/api/privacy/export/${userId}`);
4196
+ return response.data;
4197
+ }
4198
+ /**
4199
+ * Anonymize user data (GDPR Right to be Forgotten).
4200
+ * Requires organization owner permission for all organizations the user is a member of.
4201
+ * Platform owners cannot be anonymized.
4202
+ *
4203
+ * This operation:
4204
+ * - Soft-deletes the user account
4205
+ * - Hard-deletes PII from identities and passkeys tables
4206
+ * - Preserves audit logs for compliance
4207
+ *
4208
+ * @param userId User ID to anonymize
4209
+ * @returns Anonymization confirmation response
4210
+ *
4211
+ * @example
4212
+ * ```typescript
4213
+ * const result = await sso.privacy.forgetUser('user-id');
4214
+ * console.log(result.message);
4215
+ * // "User data has been anonymized. PII has been removed while preserving audit logs."
4216
+ * ```
4217
+ */
4218
+ async forgetUser(userId) {
4219
+ const response = await this.http.delete(`/api/privacy/forget/${userId}`);
4220
+ return response.data;
4221
+ }
4222
+ };
4223
+
2791
4224
  // src/client.ts
2792
4225
  var SsoClient = class {
2793
4226
  constructor(options) {
2794
4227
  this.http = createHttpAgent(options.baseURL);
2795
- if (options.token) {
2796
- this.setAuthToken(options.token);
2797
- }
2798
- if (options.apiKey) {
2799
- this.setApiKey(options.apiKey);
2800
- }
4228
+ this.session = new SessionManager(
4229
+ resolveStorage(options.storage),
4230
+ async (refreshToken) => {
4231
+ const res = await this.http.post("/api/auth/refresh", { refresh_token: refreshToken });
4232
+ return res.data;
4233
+ },
4234
+ { storageKeyPrefix: options.storagePrefix || "sso_" }
4235
+ );
4236
+ this.http.setSessionManager(this.session);
2801
4237
  this.analytics = new AnalyticsModule(this.http);
2802
- this.auth = new AuthModule(this.http);
4238
+ this.auth = new AuthModule(this.http, this.session);
2803
4239
  this.user = new UserModule(this.http);
2804
4240
  this.organizations = new OrganizationsModule(this.http);
2805
4241
  this.services = new ServicesModule(this.http);
2806
4242
  this.invitations = new InvitationsModule(this.http);
2807
4243
  this.platform = new PlatformModule(this.http);
2808
4244
  this.serviceApi = new ServiceApiModule(this.http);
4245
+ this.permissions = new PermissionsModule(this.http);
4246
+ this.passkeys = new PasskeysModule(this.http);
4247
+ this.magicLinks = new MagicLinks(this.http);
4248
+ this.privacy = new PrivacyModule(this.http);
4249
+ if (options.apiKey) {
4250
+ this.setApiKey(options.apiKey);
4251
+ }
4252
+ if (options.token) {
4253
+ this.session.setSession({ access_token: options.token });
4254
+ } else {
4255
+ this.session.loadSession().catch(console.error);
4256
+ }
2809
4257
  }
2810
4258
  /**
2811
4259
  * Sets the JWT for all subsequent authenticated requests.
@@ -2857,13 +4305,96 @@ var SsoClient = class {
2857
4305
  getBaseURL() {
2858
4306
  return this.http.defaults.baseURL || "";
2859
4307
  }
4308
+ /**
4309
+ * Check if the user is currently authenticated
4310
+ */
4311
+ isAuthenticated() {
4312
+ return this.session.isAuthenticated();
4313
+ }
4314
+ /**
4315
+ * Subscribe to authentication state changes.
4316
+ * Useful for updating UI when login/logout/expiration occurs.
4317
+ *
4318
+ * @param listener Callback function that receives the authentication state
4319
+ * @returns Unsubscribe function
4320
+ *
4321
+ * @example
4322
+ * ```typescript
4323
+ * const unsubscribe = sso.onAuthStateChange((isAuth) => {
4324
+ * console.log(isAuth ? 'User is logged in' : 'User is logged out');
4325
+ * });
4326
+ *
4327
+ * // Later, to stop listening
4328
+ * unsubscribe();
4329
+ * ```
4330
+ */
4331
+ onAuthStateChange(listener) {
4332
+ return this.session.subscribe(listener);
4333
+ }
4334
+ /**
4335
+ * Manually retrieve the current access token
4336
+ *
4337
+ * @returns The current access token, or null if not authenticated
4338
+ */
4339
+ async getToken() {
4340
+ return this.session.getToken();
4341
+ }
2860
4342
  };
4343
+
4344
+ // src/types/risk.ts
4345
+ var RiskAction = /* @__PURE__ */ ((RiskAction2) => {
4346
+ RiskAction2["ALLOW"] = "allow";
4347
+ RiskAction2["LOG_ONLY"] = "log_only";
4348
+ RiskAction2["CHALLENGE_MFA"] = "challenge_mfa";
4349
+ RiskAction2["BLOCK"] = "block";
4350
+ return RiskAction2;
4351
+ })(RiskAction || {});
4352
+ var RiskFactorType = /* @__PURE__ */ ((RiskFactorType2) => {
4353
+ RiskFactorType2["NEW_IP"] = "new_ip";
4354
+ RiskFactorType2["HIGH_RISK_LOCATION"] = "high_risk_location";
4355
+ RiskFactorType2["IMPOSSIBLE_TRAVEL"] = "impossible_travel";
4356
+ RiskFactorType2["NEW_DEVICE"] = "new_device";
4357
+ RiskFactorType2["FAILED_ATTEMPTS"] = "failed_attempts";
4358
+ RiskFactorType2["UNUSUAL_TIME"] = "unusual_time";
4359
+ RiskFactorType2["SUSPICIOUS_USER_AGENT"] = "suspicious_user_agent";
4360
+ RiskFactorType2["ANONYMOUS_NETWORK"] = "anonymous_network";
4361
+ RiskFactorType2["NEW_ACCOUNT"] = "new_account";
4362
+ RiskFactorType2["SUSPICIOUS_HISTORY"] = "suspicious_history";
4363
+ RiskFactorType2["HIGH_VELOCITY"] = "high_velocity";
4364
+ RiskFactorType2["CUSTOM_RULE"] = "custom_rule";
4365
+ return RiskFactorType2;
4366
+ })(RiskFactorType || {});
4367
+ var AuthMethod = /* @__PURE__ */ ((AuthMethod2) => {
4368
+ AuthMethod2["PASSWORD"] = "password";
4369
+ AuthMethod2["OAUTH"] = "oauth";
4370
+ AuthMethod2["PASSKEY"] = "passkey";
4371
+ AuthMethod2["MAGIC_LINK"] = "magic_link";
4372
+ AuthMethod2["MFA"] = "mfa";
4373
+ AuthMethod2["SAML"] = "saml";
4374
+ return AuthMethod2;
4375
+ })(AuthMethod || {});
4376
+ var RiskEventOutcome = /* @__PURE__ */ ((RiskEventOutcome2) => {
4377
+ RiskEventOutcome2["ALLOWED"] = "allowed";
4378
+ RiskEventOutcome2["BLOCKED"] = "blocked";
4379
+ RiskEventOutcome2["CHALLENGED"] = "challenged";
4380
+ RiskEventOutcome2["LOGGED"] = "logged";
4381
+ return RiskEventOutcome2;
4382
+ })(RiskEventOutcome || {});
2861
4383
  // Annotate the CommonJS export names for ESM import in node:
2862
4384
  0 && (module.exports = {
4385
+ AuthMethod,
2863
4386
  AuthModule,
4387
+ BrowserStorage,
2864
4388
  InvitationsModule,
4389
+ MagicLinks,
4390
+ MemoryStorage,
2865
4391
  OrganizationsModule,
4392
+ PasskeysModule,
4393
+ PermissionsModule,
2866
4394
  PlatformModule,
4395
+ RiskAction,
4396
+ RiskEventOutcome,
4397
+ RiskFactorType,
2867
4398
  ServiceApiModule,
2868
4399
  ServicesModule,
2869
4400
  SsoApiError,