@drmhse/sso-sdk 0.2.0 → 0.2.2

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 CHANGED
@@ -4,14 +4,12 @@ A zero-dependency, strongly-typed TypeScript SDK for interacting with the multi-
4
4
 
5
5
  ## Features
6
6
 
7
- - **Zero Dependencies**: Built on native `fetch` API - no external dependencies
8
- - **Framework Agnostic**: Pure TypeScript - works in any JavaScript environment
9
- - **Strongly Typed**: Complete TypeScript definitions for all API endpoints
10
- - **Stateless Design**: No internal state management - integrates with any state solution
11
- - **Predictable Error Handling**: Custom `SsoApiError` class with structured error information
12
- - **Modular & Tree-Shakable**: Import only what you need
13
- - **Comprehensive Documentation**: Full TSDoc comments for excellent IDE support
14
- - **Modern**: Supports Node.js 18+ and all modern browsers
7
+ - **Zero Dependencies**: Built on native `fetch` API - no external dependencies.
8
+ - **Framework Agnostic**: Pure TypeScript - works in any JavaScript environment.
9
+ - **Strongly Typed**: Complete TypeScript definitions for all API endpoints.
10
+ - **Stateless Design**: No internal state management - integrates with any state solution.
11
+ - **Predictable Error Handling**: Custom `SsoApiError` class with structured error information.
12
+ - **Modern**: Supports Node.js 18+ and all modern browsers.
15
13
 
16
14
  ## Installation
17
15
 
@@ -22,12 +20,12 @@ npm install @drmhse/sso-sdk
22
20
  ## Quick Start
23
21
 
24
22
  ```typescript
25
- import { SsoClient } from '@drmhse/sso-sdk';
23
+ import { SsoClient, SsoApiError } from '@drmhse/sso-sdk';
26
24
 
27
25
  // Initialize the client
28
26
  const sso = new SsoClient({
29
27
  baseURL: 'https://sso.example.com',
30
- token: localStorage.getItem('jwt') // Optional initial token
28
+ token: localStorage.getItem('sso_token') // Optional initial token
31
29
  });
32
30
 
33
31
  // Use the SDK
@@ -53,7 +51,7 @@ async function example() {
53
51
  ### End-User OAuth Login
54
52
 
55
53
  ```typescript
56
- // Redirect user to OAuth provider
54
+ // 1. Redirect user to OAuth provider
57
55
  const loginUrl = sso.auth.getLoginUrl('github', {
58
56
  org: 'acme-corp',
59
57
  service: 'main-app',
@@ -61,11 +59,15 @@ const loginUrl = sso.auth.getLoginUrl('github', {
61
59
  });
62
60
  window.location.href = loginUrl;
63
61
 
64
- // In your callback handler
65
- const token = new URLSearchParams(window.location.search).get('token');
66
- if (token) {
67
- sso.setAuthToken(token);
68
- localStorage.setItem('jwt', token);
62
+ // 2. In your callback handler, extract both tokens from the URL
63
+ const params = new URLSearchParams(window.location.search);
64
+ const accessToken = params.get('access_token');
65
+ const refreshToken = params.get('refresh_token');
66
+
67
+ if (accessToken && refreshToken) {
68
+ sso.setAuthToken(accessToken);
69
+ localStorage.setItem('sso_token', accessToken);
70
+ localStorage.setItem('sso_refresh_token', refreshToken);
69
71
  }
70
72
  ```
71
73
 
@@ -73,43 +75,66 @@ if (token) {
73
75
 
74
76
  ```typescript
75
77
  const adminUrl = sso.auth.getAdminLoginUrl('github', {
76
- org_slug: 'acme-corp' // Optional
78
+ org_slug: 'acme-corp' // Optional: directs user to a specific org dashboard after login
77
79
  });
78
80
  window.location.href = adminUrl;
79
81
  ```
80
82
 
81
83
  ### Device Flow (for CLIs)
82
84
 
85
+ This flow involves both the CLI and a web browser.
86
+
83
87
  ```typescript
84
- // Step 1: Request device code
88
+ // --- In your CLI Application ---
89
+
90
+ // 1. Request device code
85
91
  const deviceAuth = await sso.auth.deviceCode.request({
86
92
  client_id: 'cli-client-id',
87
93
  org: 'acme-corp',
88
94
  service: 'acme-cli'
89
95
  });
90
96
 
91
- console.log(`Visit ${deviceAuth.verification_uri}`);
97
+ console.log(`Visit: ${deviceAuth.verification_uri}`);
92
98
  console.log(`Enter code: ${deviceAuth.user_code}`);
93
99
 
94
- // Step 2: Poll for token
95
- const pollInterval = setInterval(async () => {
96
- try {
97
- const token = await sso.auth.deviceCode.exchangeToken({
98
- grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
99
- device_code: deviceAuth.device_code,
100
- client_id: 'cli-client-id'
101
- });
102
-
103
- clearInterval(pollInterval);
104
- sso.setAuthToken(token.access_token);
105
- console.log('Authenticated!');
106
- } catch (error) {
107
- if (error.errorCode !== 'authorization_pending') {
108
- clearInterval(pollInterval);
109
- throw error;
110
- }
111
- }
112
- }, deviceAuth.interval * 1000);
100
+ // 4. Poll for the token
101
+ const pollForToken = async () => {
102
+ // Polling logic...
103
+ };
104
+ pollForToken();
105
+
106
+
107
+ // --- In your Web Application at /activate ---
108
+
109
+ // 2. After user enters the code, verify it to get context
110
+ const context = await sso.auth.deviceCode.verify(userEnteredCode);
111
+
112
+ // 3. Redirect user to the appropriate login flow, passing the user_code
113
+ // This links the browser session to the device waiting for authorization.
114
+ const loginUrl = sso.auth.getLoginUrl('github', {
115
+ org: context.org_slug,
116
+ service: context.service_slug,
117
+ user_code: userEnteredCode, // CRITICAL: Pass user_code here
118
+ });
119
+ window.location.href = loginUrl; // User logs in, authorizing the device
120
+ ```
121
+
122
+ ### Refreshing Tokens
123
+
124
+ Renew an expired access token using a refresh token. This uses token rotation for enhanced security.
125
+
126
+ ```typescript
127
+ try {
128
+ const tokens = await sso.auth.refreshToken(storedRefreshToken);
129
+
130
+ // Update tokens in your application state and storage
131
+ sso.setAuthToken(tokens.access_token);
132
+ localStorage.setItem('sso_token', tokens.access_token);
133
+ localStorage.setItem('sso_refresh_token', tokens.refresh_token);
134
+ } catch (error) {
135
+ // Refresh failed, user needs to log in again
136
+ console.error("Token refresh failed:", error);
137
+ }
113
138
  ```
114
139
 
115
140
  ### Logout
@@ -117,14 +142,15 @@ const pollInterval = setInterval(async () => {
117
142
  ```typescript
118
143
  await sso.auth.logout();
119
144
  sso.setAuthToken(null);
120
- localStorage.removeItem('jwt');
145
+ localStorage.removeItem('sso_token');
146
+ localStorage.removeItem('sso_refresh_token');
121
147
  ```
122
148
 
123
149
  ## API Reference
124
150
 
125
- ### Analytics
151
+ ### Analytics (`sso.analytics`)
126
152
 
127
- The analytics module provides login tracking and metrics for organizations.
153
+ Provides login tracking and metrics for a specific organization.
128
154
 
129
155
  ```typescript
130
156
  // Get login trends over time
@@ -132,20 +158,21 @@ const trends = await sso.analytics.getLoginTrends('acme-corp', {
132
158
  start_date: '2025-01-01',
133
159
  end_date: '2025-01-31'
134
160
  });
161
+ ```
135
162
 
136
- // Get logins grouped by service
137
- const byService = await sso.analytics.getLoginsByService('acme-corp');
163
+ ### Authentication (`sso.auth`)
138
164
 
139
- // Get logins grouped by OAuth provider
140
- const byProvider = await sso.analytics.getLoginsByProvider('acme-corp');
165
+ Handles all authentication flows, including OAuth, device flow, and token management.
141
166
 
142
- // Get recent login events
143
- const recent = await sso.analytics.getRecentLogins('acme-corp', {
144
- limit: 10
145
- });
167
+ ```typescript
168
+ // Get a fresh OAuth token for an external provider (e.g., GitHub)
169
+ const githubToken = await sso.auth.getProviderToken('github');
170
+ // Use githubToken.access_token to make GitHub API calls
146
171
  ```
147
172
 
148
- ### Organizations
173
+ ### Organizations (`sso.organizations`)
174
+
175
+ Manages organizations, members, and BYOO credentials.
149
176
 
150
177
  ```typescript
151
178
  // Create organization (public endpoint)
@@ -155,35 +182,14 @@ const org = await sso.organizations.createPublic({
155
182
  owner_email: 'founder@acme.com'
156
183
  });
157
184
 
158
- // List user's organizations
159
- const orgs = await sso.organizations.list({ status: 'active' });
160
-
161
- // Get organization details
162
- const details = await sso.organizations.get('acme-corp');
163
-
164
- // Update organization
165
- await sso.organizations.update('acme-corp', {
166
- name: 'Acme Corp Inc.'
167
- });
168
-
169
- // Manage members
170
- const members = await sso.organizations.members.list('acme-corp');
171
- await sso.organizations.members.updateRole('acme-corp', 'user-id', {
172
- role: 'admin'
173
- });
174
- await sso.organizations.members.remove('acme-corp', 'user-id');
175
-
176
185
  // BYOO: Set custom OAuth credentials
177
186
  await sso.organizations.oauthCredentials.set('acme-corp', 'github', {
178
187
  client_id: 'Iv1.abc123',
179
188
  client_secret: 'secret-value'
180
189
  });
181
-
182
- // Get configured OAuth credentials
183
- const creds = await sso.organizations.oauthCredentials.get('acme-corp', 'github');
184
190
  ```
185
191
 
186
- ### End-User Management
192
+ #### End-User Management (`sso.organizations.endUsers`)
187
193
 
188
194
  Manage your organization's customers (end-users with subscriptions).
189
195
 
@@ -194,174 +200,87 @@ const endUsers = await sso.organizations.endUsers.list('acme-corp', {
194
200
  limit: 20
195
201
  });
196
202
 
197
- // Get detailed information about a specific end-user
198
- const endUser = await sso.organizations.endUsers.get('acme-corp', 'user-id');
199
-
200
- // Revoke all active sessions for an end-user
201
- const result = await sso.organizations.endUsers.revokeSessions('acme-corp', 'user-id');
203
+ // Revoke all active sessions for a specific end-user
204
+ await sso.organizations.endUsers.revokeSessions('acme-corp', 'user-id-123');
202
205
  ```
203
206
 
204
- ### Services
207
+ ### Services & Plans (`sso.services`)
208
+
209
+ Manages the applications (services) and subscription plans for an organization.
205
210
 
206
211
  ```typescript
207
- // Create service (returns service with provider grants and default plan)
212
+ // Create a service
208
213
  const result = await sso.services.create('acme-corp', {
209
214
  slug: 'main-app',
210
215
  name: 'Main Application',
211
216
  service_type: 'web',
212
- github_scopes: ['user:email', 'read:org'],
213
- microsoft_scopes: ['User.Read', 'email'],
214
- google_scopes: ['openid', 'email', 'profile'],
215
217
  redirect_uris: ['https://app.acme.com/callback']
216
218
  });
217
- console.log(result.service.client_id);
218
- console.log(result.usage.current_services);
219
-
220
- // List services (returns services with usage metadata)
221
- const result = await sso.services.list('acme-corp');
222
- console.log(`Using ${result.usage.current_services} of ${result.usage.max_services} services`);
223
- result.services.forEach(svc => console.log(svc.name, svc.client_id));
224
-
225
- // Get service details (includes provider grants and plans)
226
- const service = await sso.services.get('acme-corp', 'main-app');
227
- console.log(service.service.redirect_uris);
228
- console.log(service.plans);
229
-
230
- // Update service
231
- const updated = await sso.services.update('acme-corp', 'main-app', {
232
- name: 'Main Application v2',
233
- github_scopes: ['user:email', 'read:org', 'repo'],
234
- microsoft_scopes: ['User.Read', 'email', 'Mail.Read'],
235
- google_scopes: ['openid', 'email', 'profile', 'drive.readonly'],
236
- redirect_uris: ['https://app.acme.com/callback', 'https://app.acme.com/oauth']
237
- });
238
219
 
239
- // Delete service
240
- await sso.services.delete('acme-corp', 'old-service');
241
-
242
- // Manage plans
243
- const plan = await sso.services.plans.create('acme-corp', 'main-app', {
220
+ // Create a subscription plan for that service
221
+ await sso.services.plans.create('acme-corp', 'main-app', {
244
222
  name: 'pro',
245
- description: 'Pro tier with advanced features',
246
- price_monthly: 29.99,
247
- features: ['api-access', 'advanced-analytics', 'priority-support']
223
+ price_cents: 2999, // Note: price is in cents
224
+ currency: 'usd',
225
+ features: ['api-access', 'priority-support']
248
226
  });
249
-
250
- // List all plans for a service
251
- const plans = await sso.services.plans.list('acme-corp', 'main-app');
252
- plans.forEach(plan => console.log(plan.name, plan.price_monthly));
253
227
  ```
254
228
 
255
- ### Invitations
229
+ ### User Profile & Identities (`sso.user`)
256
230
 
257
- ```typescript
258
- // Send invitation
259
- const invitation = await sso.invitations.create('acme-corp', {
260
- invitee_email: 'newuser@example.com',
261
- role: 'member'
262
- });
263
-
264
- // List organization's invitations
265
- const orgInvites = await sso.invitations.listForOrg('acme-corp');
266
-
267
- // List user's invitations
268
- const myInvites = await sso.invitations.listForUser();
269
-
270
- // Accept invitation
271
- await sso.invitations.accept('invitation-token');
272
-
273
- // Decline invitation
274
- await sso.invitations.decline('invitation-token');
275
-
276
- // Cancel invitation
277
- await sso.invitations.cancel('acme-corp', 'invitation-id');
278
- ```
279
-
280
- ### User Profile
231
+ Manages the authenticated user's own profile and linked social accounts.
281
232
 
282
233
  ```typescript
283
234
  // Get profile
284
235
  const profile = await sso.user.getProfile();
285
236
 
286
- // Update profile
287
- await sso.user.updateProfile({ email: 'newemail@example.com' });
288
-
289
- // Get subscription
290
- const subscription = await sso.user.getSubscription();
291
- ```
292
-
293
- ### Social Account Identities
294
-
295
- Manage linked social accounts for the authenticated user.
296
-
297
- ```typescript
298
- // List all linked social accounts
299
- const identities = await sso.user.identities.list();
300
-
301
237
  // Start linking a new social account
302
- const { authorization_url } = await sso.user.identities.startLink('github');
238
+ const { authorization_url } = await sso.user.identities.startLink('google');
303
239
  window.location.href = authorization_url;
304
240
 
305
241
  // Unlink a social account
306
- await sso.user.identities.unlink('google');
242
+ await sso.user.identities.unlink('github');
307
243
  ```
308
244
 
309
- ### Provider Tokens
245
+ ### Invitations (`sso.invitations`)
246
+
247
+ Manages team invitations for an organization.
310
248
 
311
249
  ```typescript
312
- // Get fresh OAuth token for external provider
313
- const githubToken = await sso.auth.getProviderToken('github');
314
- // Use githubToken.access_token to make GitHub API calls
250
+ // Create and send an invitation
251
+ await sso.invitations.create('acme-corp', {
252
+ email: 'new-dev@example.com',
253
+ role: 'member'
254
+ });
255
+
256
+ // List invitations for the current user
257
+ const myInvites = await sso.invitations.listForUser();
315
258
  ```
316
259
 
317
- ### Platform Administration
260
+ ### Platform Administration (`sso.platform`)
318
261
 
319
262
  Platform owner methods require a Platform Owner JWT.
320
263
 
321
264
  ```typescript
322
- // List all organizations
323
- const allOrgs = await sso.platform.organizations.list({
324
- status: 'pending',
325
- page: 1,
326
- limit: 50
265
+ // List all organizations awaiting approval
266
+ const pendingOrgs = await sso.platform.organizations.list({
267
+ status: 'pending'
327
268
  });
328
269
 
329
- // Approve organization
330
- await sso.platform.organizations.approve('org-id', {
270
+ // Approve an organization
271
+ await sso.platform.organizations.approve('org-id-123', {
331
272
  tier_id: 'tier-starter'
332
273
  });
274
+ ```
333
275
 
334
- // Reject organization
335
- await sso.platform.organizations.reject('org-id', {
336
- reason: 'Does not meet requirements'
337
- });
338
-
339
- // Suspend/activate
340
- await sso.platform.organizations.suspend('org-id');
341
- await sso.platform.organizations.activate('org-id');
342
-
343
- // Update tier
344
- await sso.platform.organizations.updateTier('org-id', {
345
- tier_id: 'tier-pro',
346
- max_services: 20
347
- });
348
-
349
- // Promote platform owner
350
- await sso.platform.promoteOwner({
351
- user_id: 'user-uuid-here'
352
- });
353
-
354
- // Demote platform owner to regular user
355
- await sso.platform.demoteOwner('user-uuid-here');
276
+ #### Platform Analytics (`sso.platform.analytics`)
356
277
 
357
- // List available organization tiers
358
- const tiers = await sso.platform.getTiers();
278
+ Provides platform-wide metrics for platform owners.
359
279
 
360
- // Get audit log
361
- const logs = await sso.platform.getAuditLog({
362
- action: 'organization.approved',
363
- limit: 100
364
- });
280
+ ```typescript
281
+ // Get platform-wide analytics overview
282
+ const overview = await sso.platform.analytics.getOverview();
283
+ console.log(`Total Users: ${overview.total_users}`);
365
284
  ```
366
285
 
367
286
  ## Error Handling
@@ -377,98 +296,17 @@ try {
377
296
  if (error instanceof SsoApiError) {
378
297
  console.error(`Error ${error.statusCode}: ${error.message}`);
379
298
  console.error(`Code: ${error.errorCode}`);
380
- console.error(`Timestamp: ${error.timestamp}`);
381
299
 
382
- // Utility methods
383
300
  if (error.isAuthError()) {
384
301
  // Redirect to login
385
302
  }
386
- if (error.isNotFound()) {
387
- // Show 404 page
388
- }
389
- if (error.is('SERVICE_LIMIT_EXCEEDED')) {
390
- // Handle specific error
391
- }
392
- if (error.isForbidden()) {
393
- // Handle permission errors
394
- }
395
- if (error.isAuthError()) {
396
- // Handle authentication errors
397
- }
398
303
  }
399
304
  }
400
305
  ```
401
306
 
402
- ## Framework Integration Examples
403
-
404
- ### Vue 3 + Pinia
405
-
406
- ```typescript
407
- // stores/auth.ts
408
- import { defineStore } from 'pinia';
409
- import { SsoClient } from '@drmhse/sso-sdk';
410
-
411
- export const useAuthStore = defineStore('auth', {
412
- state: () => ({
413
- token: localStorage.getItem('jwt'),
414
- user: null
415
- }),
416
-
417
- actions: {
418
- async login(token: string) {
419
- this.token = token;
420
- localStorage.setItem('jwt', token);
421
- sso.setAuthToken(token);
422
- await this.fetchUser();
423
- },
424
-
425
- async logout() {
426
- await sso.auth.logout();
427
- this.token = null;
428
- this.user = null;
429
- localStorage.removeItem('jwt');
430
- sso.setAuthToken(null);
431
- },
432
-
433
- async fetchUser() {
434
- this.user = await sso.user.getProfile();
435
- }
436
- }
437
- });
438
-
439
- // Global SSO instance
440
- export const sso = new SsoClient({
441
- baseURL: import.meta.env.VITE_SSO_URL,
442
- token: localStorage.getItem('jwt')
443
- });
444
- ```
445
-
446
- ### React + Context
447
-
448
- ```typescript
449
- // SsoContext.tsx
450
- import { createContext, useContext } from 'react';
451
- import { SsoClient } from '@drmhse/sso-sdk';
452
-
453
- const sso = new SsoClient({
454
- baseURL: process.env.REACT_APP_SSO_URL,
455
- token: localStorage.getItem('jwt')
456
- });
457
-
458
- const SsoContext = createContext(sso);
459
-
460
- export const useSso = () => useContext(SsoContext);
461
-
462
- export const SsoProvider = ({ children }) => (
463
- <SsoContext.Provider value={sso}>
464
- {children}
465
- </SsoContext.Provider>
466
- );
467
- ```
468
-
469
307
  ## TypeScript
470
308
 
471
- The SDK is written in TypeScript and includes complete type definitions. All types are exported:
309
+ The SDK is written in TypeScript and includes complete type definitions.
472
310
 
473
311
  ```typescript
474
312
  import type {
@@ -476,30 +314,11 @@ import type {
476
314
  Service,
477
315
  User,
478
316
  JwtClaims,
479
- OAuthProvider,
480
- SsoClientOptions,
481
317
  SsoApiError,
482
- AnalyticsQuery,
483
- LoginTrendPoint,
484
- LoginsByService,
485
- LoginsByProvider,
486
- RecentLogin,
487
- Invitation,
488
- Subscription,
489
- ProviderToken,
490
- UserProfile,
491
- PlatformOrganizationResponse,
492
- AuditLogEntry,
493
318
  // ... and many more types
494
319
  } from '@drmhse/sso-sdk';
495
320
  ```
496
321
 
497
- All API responses, request payloads, and configuration options are fully typed for excellent IDE support and compile-time safety.
498
-
499
322
  ## License
500
323
 
501
324
  MIT
502
-
503
- ## Contributing
504
-
505
- Contributions are welcome! Please open an issue or pull request.
package/dist/index.d.mts CHANGED
@@ -560,7 +560,6 @@ interface Invitation {
560
560
  status: InvitationStatus;
561
561
  expires_at: string;
562
562
  created_at: string;
563
- updated_at: string;
564
563
  }
565
564
  /**
566
565
  * Create invitation payload
package/dist/index.d.ts CHANGED
@@ -560,7 +560,6 @@ interface Invitation {
560
560
  status: InvitationStatus;
561
561
  expires_at: string;
562
562
  created_at: string;
563
- updated_at: string;
564
563
  }
565
564
  /**
566
565
  * Create invitation payload
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drmhse/sso-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Zero-dependency TypeScript SDK for the multi-tenant SSO Platform API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",