@drmhse/sso-sdk 0.2.1 → 0.2.3

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,12 +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
- - **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.
13
13
 
14
14
  ## Installation
15
15
 
@@ -97,7 +97,7 @@ const deviceAuth = await sso.auth.deviceCode.request({
97
97
  console.log(`Visit: ${deviceAuth.verification_uri}`);
98
98
  console.log(`Enter code: ${deviceAuth.user_code}`);
99
99
 
100
- // 3. Poll for the token
100
+ // 4. Poll for the token
101
101
  const pollForToken = async () => {
102
102
  // Polling logic...
103
103
  };
@@ -109,11 +109,12 @@ pollForToken();
109
109
  // 2. After user enters the code, verify it to get context
110
110
  const context = await sso.auth.deviceCode.verify(userEnteredCode);
111
111
 
112
- // Redirect user to the appropriate login flow with the user_code
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.
113
114
  const loginUrl = sso.auth.getLoginUrl('github', {
114
115
  org: context.org_slug,
115
116
  service: context.service_slug,
116
- user_code: userEnteredCode,
117
+ user_code: userEnteredCode, // CRITICAL: Pass user_code here
117
118
  });
118
119
  window.location.href = loginUrl; // User logs in, authorizing the device
119
120
  ```
@@ -145,11 +146,127 @@ localStorage.removeItem('sso_token');
145
146
  localStorage.removeItem('sso_refresh_token');
146
147
  ```
147
148
 
149
+ ## Validating Tokens in Your Backend
150
+
151
+ The SSO platform uses **RS256** (RSA with SHA-256) asymmetric signing for JWTs. This means your backend services can validate JWT signatures without needing access to any shared secrets.
152
+
153
+ ### How It Works
154
+
155
+ 1. **Fetch the JWKS**: The SSO platform exposes a public JWKS (JSON Web Key Set) endpoint at `/.well-known/jwks.json` containing the public RSA key(s).
156
+ 2. **Cache the Keys**: Fetch and cache the JWKS in your backend to avoid repeated requests.
157
+ 3. **Verify Tokens**: When a client sends a JWT, extract the `kid` (Key ID) from the token header, find the matching key in your cached JWKS, and verify the signature.
158
+ 4. **Validate Claims**: After signature verification, validate token claims like `exp` (expiration), `iss` (issuer), and `aud` (audience).
159
+
160
+ ### Node.js/Express Example
161
+
162
+ Here's a complete example of JWT validation middleware:
163
+
164
+ ```typescript
165
+ import { expressjwt } from 'express-jwt';
166
+ import jwksRsa from 'jwks-rsa';
167
+
168
+ // Configure JWKS client to fetch public keys
169
+ const jwksClient = jwksRsa({
170
+ cache: true,
171
+ rateLimit: true,
172
+ jwksRequestsPerMinute: 5,
173
+ jwksUri: 'https://sso.example.com/.well-known/jwks.json'
174
+ });
175
+
176
+ // Function to get signing key from JWKS
177
+ function getKey(header, callback) {
178
+ jwksClient.getSigningKey(header.kid, (err, key) => {
179
+ if (err) {
180
+ return callback(err);
181
+ }
182
+ const signingKey = key.getPublicKey();
183
+ callback(null, signingKey);
184
+ });
185
+ }
186
+
187
+ // JWT validation middleware
188
+ const requireAuth = expressjwt({
189
+ secret: getKey,
190
+ algorithms: ['RS256'],
191
+ credentialsRequired: true,
192
+ getToken: (req) => {
193
+ if (req.headers.authorization?.startsWith('Bearer ')) {
194
+ return req.headers.authorization.substring(7);
195
+ }
196
+ return null;
197
+ }
198
+ });
199
+
200
+ // Use in your routes
201
+ app.get('/api/protected', requireAuth, (req, res) => {
202
+ // req.auth contains the decoded JWT claims
203
+ const { sub, email, org, service } = req.auth;
204
+ res.json({ message: `Hello ${email}` });
205
+ });
206
+ ```
207
+
208
+ ### Manual Validation (Node.js)
209
+
210
+ If you prefer to validate manually without middleware:
211
+
212
+ ```typescript
213
+ import jwt from 'jsonwebtoken';
214
+ import jwksRsa from 'jwks-rsa';
215
+
216
+ const jwksClient = jwksRsa({
217
+ jwksUri: 'https://sso.example.com/.well-known/jwks.json'
218
+ });
219
+
220
+ async function validateToken(token: string) {
221
+ try {
222
+ // Decode without verifying to get the kid
223
+ const decoded = jwt.decode(token, { complete: true });
224
+ if (!decoded || !decoded.header.kid) {
225
+ throw new Error('Invalid token: missing kid');
226
+ }
227
+
228
+ // Get the public key for this kid
229
+ const key = await jwksClient.getSigningKey(decoded.header.kid);
230
+ const publicKey = key.getPublicKey();
231
+
232
+ // Verify and decode the token
233
+ const verified = jwt.verify(token, publicKey, {
234
+ algorithms: ['RS256']
235
+ });
236
+
237
+ return verified; // Returns the decoded claims
238
+ } catch (error) {
239
+ console.error('Token validation failed:', error);
240
+ throw error;
241
+ }
242
+ }
243
+
244
+ // Usage
245
+ const claims = await validateToken(req.headers.authorization.split(' ')[1]);
246
+ console.log(claims.email, claims.org, claims.service);
247
+ ```
248
+
249
+ ### Other Languages
250
+
251
+ The same approach works in any language:
252
+
253
+ - **Python**: Use `PyJWT` with `python-jose` or `jwcrypto`
254
+ - **Go**: Use `golang-jwt/jwt` with JWKS support
255
+ - **Java**: Use `java-jwt` or Spring Security with JWKS
256
+ - **Ruby**: Use `jwt` gem with `jwks-ruby`
257
+
258
+ The key steps are always the same:
259
+ 1. Fetch JWKS from `/.well-known/jwks.json`
260
+ 2. Extract `kid` from JWT header
261
+ 3. Find matching key in JWKS
262
+ 4. Verify signature using the public key
263
+ 5. Validate token claims (especially `exp`)
264
+
148
265
  ## API Reference
149
266
 
150
- ### Analytics
267
+ ### Analytics (`sso.analytics`)
151
268
 
152
- The analytics module provides login tracking and metrics for organizations.
269
+ Provides login tracking and metrics for a specific organization.
153
270
 
154
271
  ```typescript
155
272
  // Get login trends over time
@@ -159,7 +276,19 @@ const trends = await sso.analytics.getLoginTrends('acme-corp', {
159
276
  });
160
277
  ```
161
278
 
162
- ### Organizations
279
+ ### Authentication (`sso.auth`)
280
+
281
+ Handles all authentication flows, including OAuth, device flow, and token management.
282
+
283
+ ```typescript
284
+ // Get a fresh OAuth token for an external provider (e.g., GitHub)
285
+ const githubToken = await sso.auth.getProviderToken('github');
286
+ // Use githubToken.access_token to make GitHub API calls
287
+ ```
288
+
289
+ ### Organizations (`sso.organizations`)
290
+
291
+ Manages organizations, members, and BYOO credentials.
163
292
 
164
293
  ```typescript
165
294
  // Create organization (public endpoint)
@@ -176,7 +305,7 @@ await sso.organizations.oauthCredentials.set('acme-corp', 'github', {
176
305
  });
177
306
  ```
178
307
 
179
- ### End-User Management
308
+ #### End-User Management (`sso.organizations.endUsers`)
180
309
 
181
310
  Manage your organization's customers (end-users with subscriptions).
182
311
 
@@ -191,7 +320,9 @@ const endUsers = await sso.organizations.endUsers.list('acme-corp', {
191
320
  await sso.organizations.endUsers.revokeSessions('acme-corp', 'user-id-123');
192
321
  ```
193
322
 
194
- ### Services & Plans
323
+ ### Services & Plans (`sso.services`)
324
+
325
+ Manages the applications (services) and subscription plans for an organization.
195
326
 
196
327
  ```typescript
197
328
  // Create a service
@@ -211,9 +342,9 @@ await sso.services.plans.create('acme-corp', 'main-app', {
211
342
  });
212
343
  ```
213
344
 
214
- ### User Profile & Identities
345
+ ### User Profile & Identities (`sso.user`)
215
346
 
216
- Manage the authenticated user's own profile and linked social accounts.
347
+ Manages the authenticated user's own profile and linked social accounts.
217
348
 
218
349
  ```typescript
219
350
  // Get profile
@@ -227,7 +358,22 @@ window.location.href = authorization_url;
227
358
  await sso.user.identities.unlink('github');
228
359
  ```
229
360
 
230
- ### Platform Administration
361
+ ### Invitations (`sso.invitations`)
362
+
363
+ Manages team invitations for an organization.
364
+
365
+ ```typescript
366
+ // Create and send an invitation
367
+ await sso.invitations.create('acme-corp', {
368
+ email: 'new-dev@example.com',
369
+ role: 'member'
370
+ });
371
+
372
+ // List invitations for the current user
373
+ const myInvites = await sso.invitations.listForUser();
374
+ ```
375
+
376
+ ### Platform Administration (`sso.platform`)
231
377
 
232
378
  Platform owner methods require a Platform Owner JWT.
233
379
 
@@ -241,8 +387,14 @@ const pendingOrgs = await sso.platform.organizations.list({
241
387
  await sso.platform.organizations.approve('org-id-123', {
242
388
  tier_id: 'tier-starter'
243
389
  });
390
+ ```
244
391
 
245
- // Get platform-wide analytics
392
+ #### Platform Analytics (`sso.platform.analytics`)
393
+
394
+ Provides platform-wide metrics for platform owners.
395
+
396
+ ```typescript
397
+ // Get platform-wide analytics overview
246
398
  const overview = await sso.platform.analytics.getOverview();
247
399
  console.log(`Total Users: ${overview.total_users}`);
248
400
  ```
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.1",
3
+ "version": "0.2.3",
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",