@heavybit/pendoadmin-shared-lib 1.5.0 → 1.6.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.
@@ -4,4 +4,5 @@ export * from './password.utils.js';
4
4
  export * from './uuid.utils.js';
5
5
  export * from './response.utils.js';
6
6
  export * from './timezone.utils.js';
7
+ export * from './permission.utils.js';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC"}
@@ -4,4 +4,5 @@ export * from './password.utils.js';
4
4
  export * from './uuid.utils.js';
5
5
  export * from './response.utils.js';
6
6
  export * from './timezone.utils.js';
7
+ export * from './permission.utils.js';
7
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { IAuthUser } from '../types/index.js';
2
+ /** The API-key scope that grants every permission the key's company has. */
3
+ export declare const ALL_PERMISSIONS_SCOPE = "*";
4
+ export type PermissionHolder = Pick<IAuthUser, 'isSuperAdmin' | 'permissions'>;
5
+ /**
6
+ * True when the user may act with ANY of the listed permissions: super admins always,
7
+ * API keys holding the '*' scope always, otherwise when one of the permissions is granted.
8
+ * This is the rule `requirePermission` enforces; use it for in-service checks so the two never drift.
9
+ */
10
+ export declare function hasPermission(user: PermissionHolder | null | undefined, ...permissions: string[]): boolean;
11
+ //# sourceMappingURL=permission.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission.utils.d.ts","sourceRoot":"","sources":["../../src/common/permission.utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,SAAS,EAAE,cAAc,GAAG,aAAa,CAAC,CAAC;AAE/E;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAK1G"}
@@ -0,0 +1,16 @@
1
+ /** The API-key scope that grants every permission the key's company has. */
2
+ export const ALL_PERMISSIONS_SCOPE = '*';
3
+ /**
4
+ * True when the user may act with ANY of the listed permissions: super admins always,
5
+ * API keys holding the '*' scope always, otherwise when one of the permissions is granted.
6
+ * This is the rule `requirePermission` enforces; use it for in-service checks so the two never drift.
7
+ */
8
+ export function hasPermission(user, ...permissions) {
9
+ if (!user)
10
+ return false;
11
+ if (user.isSuperAdmin)
12
+ return true;
13
+ const granted = user.permissions ?? [];
14
+ return granted.includes(ALL_PERMISSIONS_SCOPE) || permissions.some(p => granted.includes(p));
15
+ }
16
+ //# sourceMappingURL=permission.utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission.utils.js","sourceRoot":"","sources":["../../src/common/permission.utils.ts"],"names":[],"mappings":"AAEA,4EAA4E;AAC5E,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAIzC;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAyC,EAAE,GAAG,WAAqB;IAC/F,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;IACvC,OAAO,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/F,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"permission.guard.d.ts","sourceRoot":"","sources":["../../src/express/permission.guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE/D;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,WAAW,EAAE,MAAM,EAAE,IAChD,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAAI,CAoB/D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,WAAW,EAAE,MAAM,EAAE,IACpD,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAAI,CAmB/D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,IACpC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAAI,CAe/D"}
1
+ {"version":3,"file":"permission.guard.d.ts","sourceRoot":"","sources":["../../src/express/permission.guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG/D;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,WAAW,EAAE,MAAM,EAAE,IAChD,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAAI,CAgB/D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,WAAW,EAAE,MAAM,EAAE,IACpD,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAAI,CAmB/D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,IACpC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAAI,CAe/D"}
@@ -1,3 +1,4 @@
1
+ import { hasPermission } from '../common/permission.utils.js';
1
2
  /**
2
3
  * Require a specific permission. Superadmins bypass all checks.
3
4
  */
@@ -7,11 +8,7 @@ export function requirePermission(...permissions) {
7
8
  res.status(401).json({ success: false, message: 'Authentication required' });
8
9
  return;
9
10
  }
10
- if (req.user.isSuperAdmin)
11
- return next();
12
- // API keys may hold the '*' scope meaning all permissions the key's company has.
13
- const hasPermission = req.user.permissions.includes('*') || permissions.some(p => req.user.permissions.includes(p));
14
- if (!hasPermission) {
11
+ if (!hasPermission(req.user, ...permissions)) {
15
12
  res.status(403).json({
16
13
  success: false,
17
14
  message: 'Insufficient permissions',
@@ -1 +1 @@
1
- {"version":3,"file":"permission.guard.js","sourceRoot":"","sources":["../../src/express/permission.guard.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"permission.guard.js","sourceRoot":"","sources":["../../src/express/permission.guard.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAE9D;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAG,WAAqB;IACxD,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;QAC/D,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACd,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAC;YAC7E,OAAO;QACT,CAAC;QAED,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC;YAC7C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,0BAA0B;gBACnC,MAAM,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE;aAClC,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAG,WAAqB;IAC5D,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;QAC/D,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACd,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAC;YAC7E,OAAO;QACT,CAAC;QAED,IAAI,GAAG,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,EAAE,CAAC;QAEzC,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,IAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACzE,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,0BAA0B;gBACnC,MAAM,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE;aAClC,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,GAAG,KAAe;IAC5C,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;QAC/D,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACd,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAC;YAC7E,OAAO;QACT,CAAC;QAED,IAAI,GAAG,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,EAAE,CAAC;QAEzC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,IAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC,CAAC;YACvE,OAAO;QACT,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heavybit/pendoadmin-shared-lib",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Shared library for PendoAdmin microservices",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -4,3 +4,4 @@ export * from './password.utils.js';
4
4
  export * from './uuid.utils.js';
5
5
  export * from './response.utils.js';
6
6
  export * from './timezone.utils.js';
7
+ export * from './permission.utils.js';
@@ -0,0 +1,18 @@
1
+ import type { IAuthUser } from '../types/index.js';
2
+
3
+ /** The API-key scope that grants every permission the key's company has. */
4
+ export const ALL_PERMISSIONS_SCOPE = '*';
5
+
6
+ export type PermissionHolder = Pick<IAuthUser, 'isSuperAdmin' | 'permissions'>;
7
+
8
+ /**
9
+ * True when the user may act with ANY of the listed permissions: super admins always,
10
+ * API keys holding the '*' scope always, otherwise when one of the permissions is granted.
11
+ * This is the rule `requirePermission` enforces; use it for in-service checks so the two never drift.
12
+ */
13
+ export function hasPermission(user: PermissionHolder | null | undefined, ...permissions: string[]): boolean {
14
+ if (!user) return false;
15
+ if (user.isSuperAdmin) return true;
16
+ const granted = user.permissions ?? [];
17
+ return granted.includes(ALL_PERMISSIONS_SCOPE) || permissions.some(p => granted.includes(p));
18
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Request, Response, NextFunction } from 'express';
2
+ import { hasPermission } from '../common/permission.utils.js';
2
3
 
3
4
  /**
4
5
  * Require a specific permission. Superadmins bypass all checks.
@@ -10,11 +11,7 @@ export function requirePermission(...permissions: string[]) {
10
11
  return;
11
12
  }
12
13
 
13
- if (req.user.isSuperAdmin) return next();
14
-
15
- // API keys may hold the '*' scope meaning all permissions the key's company has.
16
- const hasPermission = req.user!.permissions.includes('*') || permissions.some(p => req.user!.permissions.includes(p));
17
- if (!hasPermission) {
14
+ if (!hasPermission(req.user, ...permissions)) {
18
15
  res.status(403).json({
19
16
  success: false,
20
17
  message: 'Insufficient permissions',
@@ -0,0 +1,59 @@
1
+ import { test, describe } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { hasPermission, ALL_PERMISSIONS_SCOPE } from '../dist/common/index.js';
4
+ import { requirePermission } from '../dist/express/index.js';
5
+
6
+ const user = (permissions, isSuperAdmin = false) => ({ permissions, isSuperAdmin });
7
+
8
+ describe('hasPermission', () => {
9
+ test('grants when any listed permission is held', () => {
10
+ assert.equal(hasPermission(user(['users:read']), 'users.security:read', 'users:read'), true);
11
+ });
12
+
13
+ test('denies when none of the permissions is held', () => {
14
+ assert.equal(hasPermission(user(['users:read']), 'users.security:read'), false);
15
+ assert.equal(hasPermission(user([]), 'users:read'), false);
16
+ });
17
+
18
+ test('super admins always pass', () => {
19
+ assert.equal(hasPermission(user([], true), 'anything'), true);
20
+ });
21
+
22
+ test("the '*' API-key scope grants every permission", () => {
23
+ assert.equal(ALL_PERMISSIONS_SCOPE, '*');
24
+ assert.equal(hasPermission(user(['*']), 'users.security:read'), true);
25
+ });
26
+
27
+ test('no user, or missing permissions array, is denied', () => {
28
+ assert.equal(hasPermission(null, 'users:read'), false);
29
+ assert.equal(hasPermission(undefined, 'users:read'), false);
30
+ assert.equal(hasPermission({ isSuperAdmin: false }, 'users:read'), false);
31
+ });
32
+
33
+ test('no permissions listed is denied for a normal user', () => {
34
+ assert.equal(hasPermission(user(['users:read'])), false);
35
+ });
36
+ });
37
+
38
+ describe('requirePermission uses the same rule', () => {
39
+ function run(reqUser, ...perms) {
40
+ const res = { code: 200, status(c) { this.code = c; return this; }, json() { return this; } };
41
+ let nextCalled = false;
42
+ requirePermission(...perms)({ user: reqUser }, res, () => { nextCalled = true; });
43
+ return { nextCalled, code: res.code };
44
+ }
45
+
46
+ test('401 without a user', () => {
47
+ assert.deepEqual(run(undefined, 'x'), { nextCalled: false, code: 401 });
48
+ });
49
+
50
+ test('403 without the permission', () => {
51
+ assert.deepEqual(run(user(['y']), 'x'), { nextCalled: false, code: 403 });
52
+ });
53
+
54
+ test('passes with the permission, the * scope, or as super admin', () => {
55
+ assert.equal(run(user(['x']), 'x').nextCalled, true);
56
+ assert.equal(run(user(['*']), 'x').nextCalled, true);
57
+ assert.equal(run(user([], true), 'x').nextCalled, true);
58
+ });
59
+ });