@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/README.md +72 -31
- package/dist/index.d.mts +2035 -156
- package/dist/index.d.ts +2035 -156
- package/dist/index.js +1637 -106
- package/dist/index.mjs +1628 -106
- package/package.json +2 -2
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2716
|
-
* Requires '
|
|
3439
|
+
* List all users for the service
|
|
3440
|
+
* Requires 'read:users' permission on the API key
|
|
2717
3441
|
*
|
|
2718
|
-
* @param
|
|
2719
|
-
* @returns
|
|
3442
|
+
* @param params Optional pagination parameters
|
|
3443
|
+
* @returns List of users with total count
|
|
2720
3444
|
*/
|
|
2721
|
-
async
|
|
2722
|
-
const
|
|
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
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
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,
|