@drmhse/authos-node 0.1.3 → 0.1.5

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
@@ -192,6 +192,78 @@ app.get('/org/:slug/data',
192
192
  );
193
193
  ```
194
194
 
195
+ ### requireService(slug, options?)
196
+
197
+ Requires the user's JWT to have a matching service claim. Use this for service-scoped endpoints.
198
+
199
+ ```ts
200
+ app.get('/services/:slug/data',
201
+ requireAuth(),
202
+ requireService((req) => req.params.slug),
203
+ (req, res) => { ... }
204
+ );
205
+ ```
206
+
207
+ ### requireTenant(orgSlug, serviceSlug, options?)
208
+
209
+ Requires the user to belong to a specific org AND service. Provides complete tenant isolation.
210
+
211
+ ```ts
212
+ app.get('/orgs/:org/services/:service/data',
213
+ requireAuth(),
214
+ requireTenant(
215
+ (req) => req.params.org,
216
+ (req) => req.params.service
217
+ ),
218
+ (req, res) => { ... }
219
+ );
220
+ ```
221
+
222
+ ## Understanding JWT Context
223
+
224
+ The middleware functions check claims embedded in the JWT by the client SDK during login:
225
+
226
+ | SDK Initialization | JWT Claims | Middleware to Use |
227
+ |-------------------|-----------|------------------|
228
+ | Platform-level (`baseURL` only) | `is_platform_owner: true` | `requirePlatformOwner()` |
229
+ | Org-level (`baseURL` + `org`) | `org: 'slug'` | `requireOrganization()` |
230
+ | Service-level (`baseURL` + `org` + `service`) | `org: 'slug'`, `service: 'app'` | `requireTenant()` or `requireService()` |
231
+ | With permissions | `permissions: ['users:write', ...]` | `requirePermission()` |
232
+
233
+ ### How It Works
234
+
235
+ 1. **Client-side**: User logs in via `@drmhse/sso-sdk` or `@drmhse/authos-react`
236
+ 2. **JWT issued**: AuthOS embeds context (`org`, `service`, `is_platform_owner`) in claims
237
+ 3. **Server-side**: This package verifies the JWT and middleware checks the claims
238
+
239
+ ```ts
240
+ // Example: Route for tenant-specific data
241
+ app.get('/orgs/:org/services/:service/data',
242
+ requireAuth(), // 1. Verify JWT signature
243
+ requireTenant( // 2. Check org + service claims
244
+ (req) => req.params.org,
245
+ (req) => req.params.service
246
+ ),
247
+ (req, res) => {
248
+ // User is authenticated AND belongs to this org+service
249
+ res.json({
250
+ org: req.auth?.claims.org,
251
+ service: req.auth?.claims.service
252
+ });
253
+ }
254
+ );
255
+
256
+ // Example: Route for platform owners only
257
+ app.get('/platform/analytics',
258
+ requireAuth(), // 1. Verify JWT signature
259
+ requirePlatformOwner(), // 2. Check is_platform_owner: true
260
+ (req, res) => {
261
+ // User is a platform owner
262
+ res.json({ data: '...' });
263
+ }
264
+ );
265
+ ```
266
+
195
267
  ## Webhook Verification
196
268
 
197
269
  Verify webhooks from AuthOS:
@@ -19,6 +19,8 @@ declare function createAuthMiddleware(options: AuthOSNodeOptions): {
19
19
  requireAllPermissions: (permissions: string[], permOptions?: RequirePermissionOptions) => RequestHandler;
20
20
  requirePlatformOwner: (permOptions?: RequirePermissionOptions) => RequestHandler;
21
21
  requireOrganization: (getOrgSlug: string | ((req: Request) => string), permOptions?: RequirePermissionOptions) => RequestHandler;
22
+ requireService: (getServiceSlug: string | ((req: Request) => string), permOptions?: RequirePermissionOptions) => RequestHandler;
23
+ requireTenant: (getOrgSlug: string | ((req: Request) => string), getServiceSlug: string | ((req: Request) => string), permOptions?: RequirePermissionOptions) => RequestHandler;
22
24
  };
23
25
 
24
26
  export { createAuthMiddleware };
package/dist/express.d.ts CHANGED
@@ -19,6 +19,8 @@ declare function createAuthMiddleware(options: AuthOSNodeOptions): {
19
19
  requireAllPermissions: (permissions: string[], permOptions?: RequirePermissionOptions) => RequestHandler;
20
20
  requirePlatformOwner: (permOptions?: RequirePermissionOptions) => RequestHandler;
21
21
  requireOrganization: (getOrgSlug: string | ((req: Request) => string), permOptions?: RequirePermissionOptions) => RequestHandler;
22
+ requireService: (getServiceSlug: string | ((req: Request) => string), permOptions?: RequirePermissionOptions) => RequestHandler;
23
+ requireTenant: (getOrgSlug: string | ((req: Request) => string), getServiceSlug: string | ((req: Request) => string), permOptions?: RequirePermissionOptions) => RequestHandler;
22
24
  };
23
25
 
24
26
  export { createAuthMiddleware };
package/dist/express.js CHANGED
@@ -382,13 +382,76 @@ function createAuthMiddleware(options) {
382
382
  next();
383
383
  };
384
384
  }
385
+ function requireService(getServiceSlug, permOptions = {}) {
386
+ const { message = "Service access required" } = permOptions;
387
+ return (req, res, next) => {
388
+ if (!req.auth) {
389
+ res.status(401).json({
390
+ error: "Unauthorized",
391
+ message: "Authentication required",
392
+ code: "NOT_AUTHENTICATED"
393
+ });
394
+ return;
395
+ }
396
+ const requiredService = typeof getServiceSlug === "function" ? getServiceSlug(req) : getServiceSlug;
397
+ const userService = req.auth.claims.service;
398
+ if (!userService || userService !== requiredService) {
399
+ res.status(403).json({
400
+ error: "Forbidden",
401
+ message,
402
+ code: "WRONG_SERVICE",
403
+ required: requiredService
404
+ });
405
+ return;
406
+ }
407
+ next();
408
+ };
409
+ }
410
+ function requireTenant(getOrgSlug, getServiceSlug, permOptions = {}) {
411
+ const { message = "Tenant access required" } = permOptions;
412
+ return (req, res, next) => {
413
+ if (!req.auth) {
414
+ res.status(401).json({
415
+ error: "Unauthorized",
416
+ message: "Authentication required",
417
+ code: "NOT_AUTHENTICATED"
418
+ });
419
+ return;
420
+ }
421
+ const requiredOrg = typeof getOrgSlug === "function" ? getOrgSlug(req) : getOrgSlug;
422
+ const requiredService = typeof getServiceSlug === "function" ? getServiceSlug(req) : getServiceSlug;
423
+ const userOrg = req.auth.claims.org;
424
+ const userService = req.auth.claims.service;
425
+ if (!userOrg || userOrg !== requiredOrg) {
426
+ res.status(403).json({
427
+ error: "Forbidden",
428
+ message,
429
+ code: "WRONG_ORGANIZATION",
430
+ required: { org: requiredOrg, service: requiredService }
431
+ });
432
+ return;
433
+ }
434
+ if (!userService || userService !== requiredService) {
435
+ res.status(403).json({
436
+ error: "Forbidden",
437
+ message,
438
+ code: "WRONG_SERVICE",
439
+ required: { org: requiredOrg, service: requiredService }
440
+ });
441
+ return;
442
+ }
443
+ next();
444
+ };
445
+ }
385
446
  return {
386
447
  requireAuth,
387
448
  requirePermission,
388
449
  requireAnyPermission,
389
450
  requireAllPermissions,
390
451
  requirePlatformOwner,
391
- requireOrganization
452
+ requireOrganization,
453
+ requireService,
454
+ requireTenant
392
455
  };
393
456
  }
394
457
 
package/dist/express.mjs CHANGED
@@ -360,13 +360,76 @@ function createAuthMiddleware(options) {
360
360
  next();
361
361
  };
362
362
  }
363
+ function requireService(getServiceSlug, permOptions = {}) {
364
+ const { message = "Service access required" } = permOptions;
365
+ return (req, res, next) => {
366
+ if (!req.auth) {
367
+ res.status(401).json({
368
+ error: "Unauthorized",
369
+ message: "Authentication required",
370
+ code: "NOT_AUTHENTICATED"
371
+ });
372
+ return;
373
+ }
374
+ const requiredService = typeof getServiceSlug === "function" ? getServiceSlug(req) : getServiceSlug;
375
+ const userService = req.auth.claims.service;
376
+ if (!userService || userService !== requiredService) {
377
+ res.status(403).json({
378
+ error: "Forbidden",
379
+ message,
380
+ code: "WRONG_SERVICE",
381
+ required: requiredService
382
+ });
383
+ return;
384
+ }
385
+ next();
386
+ };
387
+ }
388
+ function requireTenant(getOrgSlug, getServiceSlug, permOptions = {}) {
389
+ const { message = "Tenant access required" } = permOptions;
390
+ return (req, res, next) => {
391
+ if (!req.auth) {
392
+ res.status(401).json({
393
+ error: "Unauthorized",
394
+ message: "Authentication required",
395
+ code: "NOT_AUTHENTICATED"
396
+ });
397
+ return;
398
+ }
399
+ const requiredOrg = typeof getOrgSlug === "function" ? getOrgSlug(req) : getOrgSlug;
400
+ const requiredService = typeof getServiceSlug === "function" ? getServiceSlug(req) : getServiceSlug;
401
+ const userOrg = req.auth.claims.org;
402
+ const userService = req.auth.claims.service;
403
+ if (!userOrg || userOrg !== requiredOrg) {
404
+ res.status(403).json({
405
+ error: "Forbidden",
406
+ message,
407
+ code: "WRONG_ORGANIZATION",
408
+ required: { org: requiredOrg, service: requiredService }
409
+ });
410
+ return;
411
+ }
412
+ if (!userService || userService !== requiredService) {
413
+ res.status(403).json({
414
+ error: "Forbidden",
415
+ message,
416
+ code: "WRONG_SERVICE",
417
+ required: { org: requiredOrg, service: requiredService }
418
+ });
419
+ return;
420
+ }
421
+ next();
422
+ };
423
+ }
363
424
  return {
364
425
  requireAuth,
365
426
  requirePermission,
366
427
  requireAnyPermission,
367
428
  requireAllPermissions,
368
429
  requirePlatformOwner,
369
- requireOrganization
430
+ requireOrganization,
431
+ requireService,
432
+ requireTenant
370
433
  };
371
434
  }
372
435
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drmhse/authos-node",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Node.js server adapter for AuthOS authentication - Express middleware and token verification",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -58,7 +58,7 @@
58
58
  }
59
59
  },
60
60
  "dependencies": {
61
- "@drmhse/sso-sdk": "^0.3.8"
61
+ "@drmhse/sso-sdk": "^0.3.10"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@types/express": "^5.0.0",