@drmhse/sso-sdk 0.2.0 → 0.2.1

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.
Files changed (2) hide show
  1. package/README.md +86 -303
  2. package/package.json +1 -1
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,65 @@ 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
+ // 3. 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
+ // Redirect user to the appropriate login flow with the user_code
113
+ const loginUrl = sso.auth.getLoginUrl('github', {
114
+ org: context.org_slug,
115
+ service: context.service_slug,
116
+ user_code: userEnteredCode,
117
+ });
118
+ window.location.href = loginUrl; // User logs in, authorizing the device
119
+ ```
120
+
121
+ ### Refreshing Tokens
122
+
123
+ Renew an expired access token using a refresh token. This uses token rotation for enhanced security.
124
+
125
+ ```typescript
126
+ try {
127
+ const tokens = await sso.auth.refreshToken(storedRefreshToken);
128
+
129
+ // Update tokens in your application state and storage
130
+ sso.setAuthToken(tokens.access_token);
131
+ localStorage.setItem('sso_token', tokens.access_token);
132
+ localStorage.setItem('sso_refresh_token', tokens.refresh_token);
133
+ } catch (error) {
134
+ // Refresh failed, user needs to log in again
135
+ console.error("Token refresh failed:", error);
136
+ }
113
137
  ```
114
138
 
115
139
  ### Logout
@@ -117,7 +141,8 @@ const pollInterval = setInterval(async () => {
117
141
  ```typescript
118
142
  await sso.auth.logout();
119
143
  sso.setAuthToken(null);
120
- localStorage.removeItem('jwt');
144
+ localStorage.removeItem('sso_token');
145
+ localStorage.removeItem('sso_refresh_token');
121
146
  ```
122
147
 
123
148
  ## API Reference
@@ -132,17 +157,6 @@ const trends = await sso.analytics.getLoginTrends('acme-corp', {
132
157
  start_date: '2025-01-01',
133
158
  end_date: '2025-01-31'
134
159
  });
135
-
136
- // Get logins grouped by service
137
- const byService = await sso.analytics.getLoginsByService('acme-corp');
138
-
139
- // Get logins grouped by OAuth provider
140
- const byProvider = await sso.analytics.getLoginsByProvider('acme-corp');
141
-
142
- // Get recent login events
143
- const recent = await sso.analytics.getRecentLogins('acme-corp', {
144
- limit: 10
145
- });
146
160
  ```
147
161
 
148
162
  ### Organizations
@@ -155,32 +169,11 @@ const org = await sso.organizations.createPublic({
155
169
  owner_email: 'founder@acme.com'
156
170
  });
157
171
 
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
172
  // BYOO: Set custom OAuth credentials
177
173
  await sso.organizations.oauthCredentials.set('acme-corp', 'github', {
178
174
  client_id: 'Iv1.abc123',
179
175
  client_secret: 'secret-value'
180
176
  });
181
-
182
- // Get configured OAuth credentials
183
- const creds = await sso.organizations.oauthCredentials.get('acme-corp', 'github');
184
177
  ```
185
178
 
186
179
  ### End-User Management
@@ -194,124 +187,44 @@ const endUsers = await sso.organizations.endUsers.list('acme-corp', {
194
187
  limit: 20
195
188
  });
196
189
 
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');
190
+ // Revoke all active sessions for a specific end-user
191
+ await sso.organizations.endUsers.revokeSessions('acme-corp', 'user-id-123');
202
192
  ```
203
193
 
204
- ### Services
194
+ ### Services & Plans
205
195
 
206
196
  ```typescript
207
- // Create service (returns service with provider grants and default plan)
197
+ // Create a service
208
198
  const result = await sso.services.create('acme-corp', {
209
199
  slug: 'main-app',
210
200
  name: 'Main Application',
211
201
  service_type: 'web',
212
- github_scopes: ['user:email', 'read:org'],
213
- microsoft_scopes: ['User.Read', 'email'],
214
- google_scopes: ['openid', 'email', 'profile'],
215
202
  redirect_uris: ['https://app.acme.com/callback']
216
203
  });
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
204
 
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', {
205
+ // Create a subscription plan for that service
206
+ await sso.services.plans.create('acme-corp', 'main-app', {
244
207
  name: 'pro',
245
- description: 'Pro tier with advanced features',
246
- price_monthly: 29.99,
247
- features: ['api-access', 'advanced-analytics', 'priority-support']
208
+ price_cents: 2999, // Note: price is in cents
209
+ currency: 'usd',
210
+ features: ['api-access', 'priority-support']
248
211
  });
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
212
  ```
254
213
 
255
- ### Invitations
214
+ ### User Profile & Identities
256
215
 
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
216
+ Manage the authenticated user's own profile and linked social accounts.
281
217
 
282
218
  ```typescript
283
219
  // Get profile
284
220
  const profile = await sso.user.getProfile();
285
221
 
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
222
  // Start linking a new social account
302
- const { authorization_url } = await sso.user.identities.startLink('github');
223
+ const { authorization_url } = await sso.user.identities.startLink('google');
303
224
  window.location.href = authorization_url;
304
225
 
305
226
  // Unlink a social account
306
- await sso.user.identities.unlink('google');
307
- ```
308
-
309
- ### Provider Tokens
310
-
311
- ```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
227
+ await sso.user.identities.unlink('github');
315
228
  ```
316
229
 
317
230
  ### Platform Administration
@@ -319,49 +232,19 @@ const githubToken = await sso.auth.getProviderToken('github');
319
232
  Platform owner methods require a Platform Owner JWT.
320
233
 
321
234
  ```typescript
322
- // List all organizations
323
- const allOrgs = await sso.platform.organizations.list({
324
- status: 'pending',
325
- page: 1,
326
- limit: 50
235
+ // List all organizations awaiting approval
236
+ const pendingOrgs = await sso.platform.organizations.list({
237
+ status: 'pending'
327
238
  });
328
239
 
329
- // Approve organization
330
- await sso.platform.organizations.approve('org-id', {
240
+ // Approve an organization
241
+ await sso.platform.organizations.approve('org-id-123', {
331
242
  tier_id: 'tier-starter'
332
243
  });
333
244
 
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');
356
-
357
- // List available organization tiers
358
- const tiers = await sso.platform.getTiers();
359
-
360
- // Get audit log
361
- const logs = await sso.platform.getAuditLog({
362
- action: 'organization.approved',
363
- limit: 100
364
- });
245
+ // Get platform-wide analytics
246
+ const overview = await sso.platform.analytics.getOverview();
247
+ console.log(`Total Users: ${overview.total_users}`);
365
248
  ```
366
249
 
367
250
  ## Error Handling
@@ -377,98 +260,17 @@ try {
377
260
  if (error instanceof SsoApiError) {
378
261
  console.error(`Error ${error.statusCode}: ${error.message}`);
379
262
  console.error(`Code: ${error.errorCode}`);
380
- console.error(`Timestamp: ${error.timestamp}`);
381
263
 
382
- // Utility methods
383
264
  if (error.isAuthError()) {
384
265
  // Redirect to login
385
266
  }
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
267
  }
399
268
  }
400
269
  ```
401
270
 
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
271
  ## TypeScript
470
272
 
471
- The SDK is written in TypeScript and includes complete type definitions. All types are exported:
273
+ The SDK is written in TypeScript and includes complete type definitions.
472
274
 
473
275
  ```typescript
474
276
  import type {
@@ -476,30 +278,11 @@ import type {
476
278
  Service,
477
279
  User,
478
280
  JwtClaims,
479
- OAuthProvider,
480
- SsoClientOptions,
481
281
  SsoApiError,
482
- AnalyticsQuery,
483
- LoginTrendPoint,
484
- LoginsByService,
485
- LoginsByProvider,
486
- RecentLogin,
487
- Invitation,
488
- Subscription,
489
- ProviderToken,
490
- UserProfile,
491
- PlatformOrganizationResponse,
492
- AuditLogEntry,
493
282
  // ... and many more types
494
283
  } from '@drmhse/sso-sdk';
495
284
  ```
496
285
 
497
- All API responses, request payloads, and configuration options are fully typed for excellent IDE support and compile-time safety.
498
-
499
286
  ## License
500
287
 
501
288
  MIT
502
-
503
- ## Contributing
504
-
505
- Contributions are welcome! Please open an issue or pull request.
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.1",
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",