@discover-cloud/shared 1.2.9 → 1.4.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.
- package/dist/authorization/index.d.ts +2 -2
- package/dist/authorization/index.js +1 -1
- package/dist/authorization/permission-cache.service.d.ts +104 -16
- package/dist/authorization/permission-cache.service.js +177 -148
- package/dist/authorization/permission-checker.d.ts +30 -0
- package/dist/authorization/permission-checker.js +40 -0
- package/dist/context/index.d.ts +1 -1
- package/dist/context/index.js +1 -1
- package/dist/context/request-context.storage.d.ts +21 -0
- package/dist/context/request-context.storage.js +37 -0
- package/dist/contracts/auth-service/account.dto.d.ts +30 -0
- package/dist/contracts/auth-service/account.dto.js +7 -0
- package/dist/contracts/auth-service/auth.dto.d.ts +21 -0
- package/dist/contracts/auth-service/auth.dto.js +10 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/{security → contracts}/index.js +1 -1
- package/dist/contracts/visibility.d.ts +63 -0
- package/dist/contracts/visibility.js +10 -0
- package/dist/dtos/audit-service.types.d.ts +42 -0
- package/dist/dtos/audit-service.types.js +31 -0
- package/dist/dtos/auth-service.dto.d.ts +87 -26
- package/dist/dtos/auth-service.dto.js +4 -0
- package/dist/dtos/cloud-service.dto.d.ts +57 -27
- package/dist/dtos/cloud-service.dto.js +5 -0
- package/dist/dtos/index.d.ts +6 -5
- package/dist/dtos/index.js +1 -0
- package/dist/dtos/insights-service.dto.d.ts +184 -32
- package/dist/dtos/insights-service.dto.js +8 -0
- package/dist/dtos/response.dto.d.ts +12 -83
- package/dist/dtos/response.dto.js +2 -21
- package/dist/dtos/user-service.dto.d.ts +163 -13
- package/dist/enums/domain.enums.d.ts +272 -75
- package/dist/enums/domain.enums.js +329 -102
- package/dist/enums/index.d.ts +2 -2
- package/dist/enums/permissions.enums.d.ts +142 -80
- package/dist/enums/permissions.enums.js +174 -133
- package/dist/errors/app-error.d.ts +13 -18
- package/dist/errors/app-error.js +15 -21
- package/dist/errors/http-errors.d.ts +106 -18
- package/dist/errors/http-errors.js +136 -51
- package/dist/errors/index.d.ts +2 -2
- package/dist/http/index.d.ts +2 -1
- package/dist/http/index.js +1 -0
- package/dist/http/request-context.d.ts +8 -0
- package/dist/http/request-context.js +37 -0
- package/dist/http/service-client.d.ts +93 -40
- package/dist/http/service-client.js +104 -55
- package/dist/index.d.ts +11 -9
- package/dist/index.js +2 -0
- package/dist/jwt/index.d.ts +3 -2
- package/dist/jwt/index.js +2 -1
- package/dist/jwt/machine-jwt-verifier.d.ts +21 -0
- package/dist/jwt/machine-jwt-verifier.js +83 -0
- package/dist/jwt/machine-token-client.d.ts +36 -5
- package/dist/jwt/machine-token-client.js +61 -27
- package/dist/jwt/user-jwt-verifier.d.ts +31 -0
- package/dist/jwt/user-jwt-verifier.js +102 -0
- package/dist/messaging/audit.publisher.d.ts +14 -0
- package/dist/messaging/audit.publisher.js +49 -0
- package/dist/messaging/index.d.ts +2 -0
- package/dist/{dto → messaging}/index.js +2 -3
- package/dist/messaging/rabbitmq.client.d.ts +15 -0
- package/dist/messaging/rabbitmq.client.js +133 -0
- package/dist/middleware/error-handler.middleware.d.ts +8 -28
- package/dist/middleware/error-handler.middleware.js +63 -54
- package/dist/middleware/index.d.ts +10 -8
- package/dist/middleware/index.js +7 -5
- package/dist/middleware/request-context.middleware.d.ts +6 -0
- package/dist/middleware/request-context.middleware.js +48 -0
- package/dist/middleware/require-auth.middleware.d.ts +7 -76
- package/dist/middleware/require-auth.middleware.js +29 -117
- package/dist/middleware/require-machine.middleware.d.ts +13 -0
- package/dist/middleware/require-machine.middleware.js +128 -0
- package/dist/middleware/require-org-permission-from-body.middleware.d.ts +10 -0
- package/dist/middleware/require-org-permission-from-body.middleware.js +23 -0
- package/dist/middleware/require-org-permission.middleware.d.ts +19 -0
- package/dist/middleware/require-org-permission.middleware.js +115 -0
- package/dist/middleware/require-platform-permission.middleware.d.ts +19 -0
- package/dist/middleware/require-platform-permission.middleware.js +91 -0
- package/dist/middleware/require-user.middleware.d.ts +14 -0
- package/dist/middleware/require-user.middleware.js +30 -0
- package/dist/middleware/require-workspace-permission.middleware.d.ts +20 -0
- package/dist/middleware/require-workspace-permission.middleware.js +114 -0
- package/dist/middleware/validate.middleware.d.ts +31 -31
- package/dist/middleware/validate.middleware.js +31 -33
- package/dist/types/audit.types.d.ts +39 -0
- package/dist/types/audit.types.js +30 -0
- package/dist/types/express.types.d.ts +92 -124
- package/dist/types/express.types.js +30 -49
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/date.util.d.ts +6 -0
- package/dist/utils/date.util.js +11 -0
- package/dist/utils/env.util.d.ts +6 -0
- package/dist/utils/env.util.js +19 -0
- package/dist/utils/index.d.ts +5 -4
- package/dist/utils/index.js +5 -4
- package/dist/utils/logger.util.d.ts +31 -0
- package/dist/utils/logger.util.js +91 -0
- package/dist/utils/pagination.util.d.ts +6 -0
- package/dist/utils/pagination.util.js +20 -0
- package/dist/utils/request-context.als.d.ts +10 -0
- package/dist/utils/request-context.als.js +5 -0
- package/dist/utils/response.util.d.ts +27 -0
- package/dist/utils/response.util.js +56 -0
- package/dist/utils/slug.util.d.ts +9 -0
- package/dist/utils/slug.util.js +32 -0
- package/dist/utils/url-safety.util.d.ts +6 -0
- package/dist/utils/url-safety.util.js +75 -0
- package/package.json +3 -1
- package/dist/authorization/permissions.d.ts +0 -78
- package/dist/authorization/permissions.js +0 -174
- package/dist/context/access-context.d.ts +0 -10
- package/dist/context/access-context.js +0 -2
- package/dist/dto/auth-service.dtos.d.ts +0 -44
- package/dist/dto/auth-service.dtos.js +0 -2
- package/dist/dto/index.d.ts +0 -3
- package/dist/dto/response.dtos.d.ts +0 -55
- package/dist/dto/response.dtos.js +0 -6
- package/dist/dto/user-service.dtos.d.ts +0 -50
- package/dist/dto/user-service.dtos.js +0 -2
- package/dist/enums/auth-service.enums.d.ts +0 -12
- package/dist/enums/auth-service.enums.js +0 -17
- package/dist/enums/permissions.types.d.ts +0 -12
- package/dist/enums/permissions.types.js +0 -17
- package/dist/enums/user-service.enums.d.ts +0 -32
- package/dist/enums/user-service.enums.js +0 -41
- package/dist/internal/index.d.ts +0 -4
- package/dist/internal/index.js +0 -20
- package/dist/internal/internal-jwt.service.d.ts +0 -13
- package/dist/internal/internal-jwt.service.js +0 -88
- package/dist/internal/internal-jwt.types.d.ts +0 -7
- package/dist/internal/internal-jwt.types.js +0 -2
- package/dist/internal/internal-key-manager.d.ts +0 -16
- package/dist/internal/internal-key-manager.js +0 -67
- package/dist/internal/registry.d.ts +0 -8
- package/dist/internal/registry.js +0 -34
- package/dist/internal/service-client.d.ts +0 -9
- package/dist/internal/service-client.js +0 -94
- package/dist/jwt/internal-jwt-verifier.d.ts +0 -41
- package/dist/jwt/internal-jwt-verifier.js +0 -185
- package/dist/jwt/jwt-verifier.d.ts +0 -9
- package/dist/jwt/jwt-verifier.js +0 -36
- package/dist/jwt/service-client.d.ts +0 -7
- package/dist/jwt/service-client.js +0 -87
- package/dist/middleware/authorize.d.ts +0 -3
- package/dist/middleware/authorize.js +0 -24
- package/dist/middleware/authorize.middleware.d.ts +0 -54
- package/dist/middleware/authorize.middleware.js +0 -104
- package/dist/middleware/error-handler.d.ts +0 -4
- package/dist/middleware/error-handler.js +0 -23
- package/dist/middleware/request-id.d.ts +0 -2
- package/dist/middleware/request-id.js +0 -9
- package/dist/middleware/request-id.middleware.d.ts +0 -22
- package/dist/middleware/request-id.middleware.js +0 -34
- package/dist/middleware/require-auth.d.ts +0 -10
- package/dist/middleware/require-auth.js +0 -34
- package/dist/middleware/require-human.middleware.d.ts +0 -2
- package/dist/middleware/require-human.middleware.js +0 -18
- package/dist/middleware/require-internal.middleware.d.ts +0 -18
- package/dist/middleware/require-internal.middleware.js +0 -183
- package/dist/middleware/validate.d.ts +0 -5
- package/dist/middleware/validate.js +0 -18
- package/dist/middleware/validated-merge.middleware.d.ts +0 -20
- package/dist/middleware/validated-merge.middleware.js +0 -33
- package/dist/middleware/verify-internal-jwt.d.ts +0 -7
- package/dist/middleware/verify-internal-jwt.js +0 -25
- package/dist/security/guard.d.ts +0 -10
- package/dist/security/guard.js +0 -40
- package/dist/security/index.d.ts +0 -1
- package/dist/types/express.d.ts +0 -22
- package/dist/types/express.js +0 -3
- package/dist/utils/date.utils.d.ts +0 -25
- package/dist/utils/date.utils.js +0 -30
- package/dist/utils/env.d.ts +0 -46
- package/dist/utils/env.js +0 -61
- package/dist/utils/env.utils.d.ts +0 -46
- package/dist/utils/env.utils.js +0 -61
- package/dist/utils/logger.utils.d.ts +0 -66
- package/dist/utils/logger.utils.js +0 -97
- package/dist/utils/response.d.ts +0 -4
- package/dist/utils/response.js +0 -35
- package/dist/utils/response.utils.d.ts +0 -54
- package/dist/utils/response.utils.js +0 -85
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from './permission-cache.service';
|
|
2
|
+
export * from './permission-checker';
|
|
@@ -14,5 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./permissions"), exports);
|
|
18
17
|
__exportStar(require("./permission-cache.service"), exports);
|
|
18
|
+
__exportStar(require("./permission-checker"), exports);
|
|
@@ -1,25 +1,113 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file permission-cache.service.ts
|
|
3
|
+
* @description Redis-backed permission cache for RBAC authorization.
|
|
4
|
+
* Stores resolved permission sets for platform, organization, and workspace scopes.
|
|
5
|
+
* Does not calculate permissions — RBAC resolution is database-driven.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Redis-backed permission cache.
|
|
9
|
+
*
|
|
10
|
+
* Source of truth:
|
|
11
|
+
*
|
|
12
|
+
* User / Account
|
|
13
|
+
* ↓
|
|
14
|
+
* RoleAssignment
|
|
15
|
+
* ↓
|
|
16
|
+
* Role
|
|
17
|
+
* ↓
|
|
18
|
+
* Permission
|
|
19
|
+
*
|
|
20
|
+
* This service only caches the resolved permission keys.
|
|
21
|
+
*/
|
|
2
22
|
export declare class PermissionCacheService {
|
|
3
23
|
private readonly client;
|
|
24
|
+
/**
|
|
25
|
+
* Initializes Redis client and connection listeners.
|
|
26
|
+
*/
|
|
4
27
|
constructor();
|
|
28
|
+
/**
|
|
29
|
+
* Resolves platform-level permissions.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} accountId - Account ID.
|
|
32
|
+
* @param {Function} resolver - Database permission resolver.
|
|
33
|
+
* @returns {Promise<string[]>} Permission keys.
|
|
34
|
+
*/
|
|
35
|
+
resolvePlatformPermissions(accountId: string, resolver: () => Promise<string[]>): Promise<string[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Resolves organization-scoped permissions.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} userId - User ID.
|
|
40
|
+
* @param {string} organizationId - Organization ID.
|
|
41
|
+
* @param {Function} resolver - Database permission resolver.
|
|
42
|
+
* @returns {Promise<string[]>} Permission keys.
|
|
43
|
+
*/
|
|
44
|
+
resolveOrganizationPermissions(userId: string, organizationId: string, resolver: () => Promise<string[]>): Promise<string[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Resolves workspace-scoped permissions.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} userId - User ID.
|
|
49
|
+
* @param {string} workspaceId - Workspace ID.
|
|
50
|
+
* @param {Function} resolver - Database permission resolver.
|
|
51
|
+
* @returns {Promise<string[]>} Permission keys.
|
|
52
|
+
*/
|
|
53
|
+
resolveWorkspacePermissions(userId: string, workspaceId: string, resolver: () => Promise<string[]>): Promise<string[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Invalidates platform permission cache.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} accountId - Account ID.
|
|
58
|
+
*/
|
|
59
|
+
invalidatePlatform(accountId: string): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Invalidates organization permission cache.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} userId - User ID.
|
|
64
|
+
* @param {string} organizationId - Organization ID.
|
|
65
|
+
*/
|
|
66
|
+
invalidateOrganization(userId: string, organizationId: string): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Invalidates workspace permission cache.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} userId - User ID.
|
|
71
|
+
* @param {string} workspaceId - Workspace ID.
|
|
72
|
+
*/
|
|
73
|
+
invalidateWorkspace(userId: string, workspaceId: string): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Gracefully closes Redis connection.
|
|
76
|
+
*/
|
|
5
77
|
disconnect(): Promise<void>;
|
|
6
|
-
resolve(accountId: string, accountRole: AccountRole): Promise<GlobalPermission[]>;
|
|
7
|
-
invalidate(accountId: string): Promise<void>;
|
|
8
|
-
invalidateMany(accountIds: string[]): Promise<void>;
|
|
9
78
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
79
|
+
* Resolves permissions using cache-aside pattern.
|
|
80
|
+
*
|
|
81
|
+
* Redis hit:
|
|
82
|
+
* cache → return
|
|
83
|
+
*
|
|
84
|
+
* Redis miss:
|
|
85
|
+
* database → cache → return
|
|
86
|
+
*
|
|
87
|
+
* @param {string} key - Cache key.
|
|
88
|
+
* @param {Function} resolver - Database resolver.
|
|
89
|
+
*/
|
|
90
|
+
private resolve;
|
|
91
|
+
/**
|
|
92
|
+
* Reads permission list from Redis.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} key - Cache key.
|
|
95
|
+
*/
|
|
96
|
+
private get;
|
|
97
|
+
/**
|
|
98
|
+
* Stores permission list in Redis.
|
|
12
99
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
100
|
+
* @param {string} key - Cache key.
|
|
101
|
+
* @param {string[]} permissions - Permission keys.
|
|
102
|
+
*/
|
|
103
|
+
private set;
|
|
104
|
+
/**
|
|
105
|
+
* Deletes permission cache.
|
|
17
106
|
*
|
|
18
|
-
*
|
|
19
|
-
* and is the correct primitive for this guard.
|
|
107
|
+
* @param {string} key - Cache key.
|
|
20
108
|
*/
|
|
21
|
-
private
|
|
22
|
-
private
|
|
23
|
-
private
|
|
24
|
-
private
|
|
109
|
+
private delete;
|
|
110
|
+
private platformKey;
|
|
111
|
+
private organizationKey;
|
|
112
|
+
private workspaceKey;
|
|
25
113
|
}
|
|
@@ -1,200 +1,229 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file permission-cache.service.ts
|
|
4
|
+
* @description Redis-backed permission cache for RBAC authorization.
|
|
5
|
+
* Stores resolved permission sets for platform, organization, and workspace scopes.
|
|
6
|
+
* Does not calculate permissions — RBAC resolution is database-driven.
|
|
7
|
+
*/
|
|
2
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
9
|
exports.PermissionCacheService = void 0;
|
|
4
10
|
const redis_1 = require("redis");
|
|
5
|
-
const
|
|
11
|
+
const env_util_1 = require("../utils/env.util");
|
|
12
|
+
const CACHE_TTL_SECONDS = 24 * 60 * 60; // 24 hours
|
|
13
|
+
const PLATFORM_PREFIX = 'perms:platform';
|
|
14
|
+
const ORG_PREFIX = 'perms:org';
|
|
15
|
+
const WORKSPACE_PREFIX = 'perms:workspace';
|
|
6
16
|
/**
|
|
7
|
-
*
|
|
8
|
-
* ─────────────────────────────────────────────────────
|
|
9
|
-
* Redis-backed permission cache. Sits between JWT verification and route
|
|
10
|
-
* handlers — loads permissions for a verified accountId + role, caches
|
|
11
|
-
* them, and exposes invalidation for role changes and account lifecycle events.
|
|
12
|
-
*
|
|
13
|
-
* Cache key: perms:{accountId}
|
|
14
|
-
* Cache value: JSON array of GlobalPermission
|
|
15
|
-
* TTL: 24 hours (auto-evicted; role changes trigger explicit invalidation)
|
|
16
|
-
*
|
|
17
|
-
* Cold cache strategy:
|
|
18
|
-
* Derive from globalRolePermissions (role → perms map).
|
|
19
|
-
* No DB call — the role is already verified in the JWT, and all permissions
|
|
20
|
-
* are statically derived from it.
|
|
17
|
+
* Redis-backed permission cache.
|
|
21
18
|
*
|
|
22
|
-
*
|
|
23
|
-
* - Role change → invalidate(accountId)
|
|
24
|
-
* - Suspension → invalidate(accountId)
|
|
25
|
-
* - Account deletion → invalidate(accountId)
|
|
19
|
+
* Source of truth:
|
|
26
20
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
21
|
+
* User / Account
|
|
22
|
+
* ↓
|
|
23
|
+
* RoleAssignment
|
|
24
|
+
* ↓
|
|
25
|
+
* Role
|
|
26
|
+
* ↓
|
|
27
|
+
* Permission
|
|
33
28
|
*
|
|
34
|
-
*
|
|
35
|
-
* - All read/write paths guard via isReady() and fall back gracefully.
|
|
36
|
-
* - On a cold-cache miss during Redis unavailability, permissions are derived
|
|
37
|
-
* from the static role map (degraded but functional — no request is blocked).
|
|
38
|
-
* - safeGet validates the parsed cache value shape before returning it;
|
|
39
|
-
* malformed entries are treated as cache misses rather than runtime errors.
|
|
40
|
-
*
|
|
41
|
-
* NOTE: Uses console.* for logging — shared cannot depend on a service-specific
|
|
42
|
-
* logger (pino, winston, etc.). Each service's own logger will capture these
|
|
43
|
-
* via stdout in production.
|
|
29
|
+
* This service only caches the resolved permission keys.
|
|
44
30
|
*/
|
|
45
|
-
const CACHE_TTL_SECONDS = 24 * 60 * 60; // 24 hours
|
|
46
|
-
const KEY_PREFIX = "perms";
|
|
47
31
|
class PermissionCacheService {
|
|
32
|
+
/**
|
|
33
|
+
* Initializes Redis client and connection listeners.
|
|
34
|
+
*/
|
|
48
35
|
constructor() {
|
|
49
36
|
this.client = (0, redis_1.createClient)({
|
|
50
|
-
url:
|
|
37
|
+
url: (0, env_util_1.getEnvOptional)('REDIS_URL') ?? 'redis://localhost:6379',
|
|
51
38
|
});
|
|
52
|
-
this.client.on(
|
|
53
|
-
console.error(
|
|
39
|
+
this.client.on('error', (err) => {
|
|
40
|
+
console.error('[PermissionCache] Redis error', err);
|
|
54
41
|
});
|
|
55
|
-
this.client.on(
|
|
56
|
-
console.info(
|
|
42
|
+
this.client.on('ready', () => {
|
|
43
|
+
console.info('[PermissionCache] Redis ready');
|
|
57
44
|
});
|
|
58
|
-
this.client.on(
|
|
59
|
-
console.warn(
|
|
45
|
+
this.client.on('reconnecting', () => {
|
|
46
|
+
console.warn('[PermissionCache] Redis reconnecting...');
|
|
60
47
|
});
|
|
61
|
-
// Connect asynchronously — all read/write methods guard via isReady()
|
|
62
|
-
// and fall back gracefully, so startup is non-blocking.
|
|
63
48
|
this.client.connect().catch((err) => {
|
|
64
|
-
console.error(
|
|
49
|
+
console.error('[PermissionCache] Redis connection failed', err);
|
|
65
50
|
});
|
|
66
51
|
}
|
|
67
|
-
/*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
console.error("[PermissionCache] Error during disconnect", err);
|
|
80
|
-
}
|
|
52
|
+
/* ---------------------------------------------------------------------------
|
|
53
|
+
Permission Resolution
|
|
54
|
+
--------------------------------------------------------------------------- */
|
|
55
|
+
/**
|
|
56
|
+
* Resolves platform-level permissions.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} accountId - Account ID.
|
|
59
|
+
* @param {Function} resolver - Database permission resolver.
|
|
60
|
+
* @returns {Promise<string[]>} Permission keys.
|
|
61
|
+
*/
|
|
62
|
+
async resolvePlatformPermissions(accountId, resolver) {
|
|
63
|
+
return this.resolve(this.platformKey(accountId), resolver);
|
|
81
64
|
}
|
|
82
|
-
/*
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// already verified in the JWT payload by the time we get here).
|
|
96
|
-
const perms = [...(permissions_1.globalRolePermissions[accountRole] ?? [])];
|
|
97
|
-
// Write back fire-and-forget — safeSet swallows errors internally,
|
|
98
|
-
// so we don't await or attach a catch here.
|
|
99
|
-
void this.safeSet(accountId, perms);
|
|
100
|
-
return perms;
|
|
65
|
+
/* --------------------------------------------------------------------------
|
|
66
|
+
resolveOrganizationPermissions
|
|
67
|
+
-------------------------------------------------------------------------- */
|
|
68
|
+
/**
|
|
69
|
+
* Resolves organization-scoped permissions.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} userId - User ID.
|
|
72
|
+
* @param {string} organizationId - Organization ID.
|
|
73
|
+
* @param {Function} resolver - Database permission resolver.
|
|
74
|
+
* @returns {Promise<string[]>} Permission keys.
|
|
75
|
+
*/
|
|
76
|
+
async resolveOrganizationPermissions(userId, organizationId, resolver) {
|
|
77
|
+
return this.resolve(this.organizationKey(userId, organizationId), resolver);
|
|
101
78
|
}
|
|
102
|
-
/*
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
console.info(`[PermissionCache] Invalidated: ${accountId}`);
|
|
116
|
-
}
|
|
117
|
-
catch (err) {
|
|
118
|
-
// Log but don't throw — stale entry expires via TTL at worst.
|
|
119
|
-
console.error(`[PermissionCache] Failed to invalidate: ${accountId}`, err);
|
|
120
|
-
}
|
|
79
|
+
/* --------------------------------------------------------------------------
|
|
80
|
+
resolveWorkspacePermissions
|
|
81
|
+
-------------------------------------------------------------------------- */
|
|
82
|
+
/**
|
|
83
|
+
* Resolves workspace-scoped permissions.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} userId - User ID.
|
|
86
|
+
* @param {string} workspaceId - Workspace ID.
|
|
87
|
+
* @param {Function} resolver - Database permission resolver.
|
|
88
|
+
* @returns {Promise<string[]>} Permission keys.
|
|
89
|
+
*/
|
|
90
|
+
async resolveWorkspacePermissions(userId, workspaceId, resolver) {
|
|
91
|
+
return this.resolve(this.workspaceKey(userId, workspaceId), resolver);
|
|
121
92
|
}
|
|
122
|
-
/*
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
try {
|
|
135
|
-
const keys = accountIds.map((id) => this.key(id));
|
|
136
|
-
await this.client.del(keys);
|
|
137
|
-
console.info(`[PermissionCache] Batch invalidated ${accountIds.length} entries`);
|
|
138
|
-
}
|
|
139
|
-
catch (err) {
|
|
140
|
-
console.error("[PermissionCache] Batch invalidation failed", err);
|
|
141
|
-
}
|
|
93
|
+
/* ---------------------------------------------------------------------------
|
|
94
|
+
Cache Invalidation
|
|
95
|
+
--------------------------------------------------------------------------- */
|
|
96
|
+
/**
|
|
97
|
+
* Invalidates platform permission cache.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} accountId - Account ID.
|
|
100
|
+
*/
|
|
101
|
+
async invalidatePlatform(accountId) {
|
|
102
|
+
await this.delete(this.platformKey(accountId));
|
|
142
103
|
}
|
|
143
|
-
/*
|
|
144
|
-
|
|
145
|
-
|
|
104
|
+
/* --------------------------------------------------------------------------
|
|
105
|
+
invalidateOrganization
|
|
106
|
+
-------------------------------------------------------------------------- */
|
|
146
107
|
/**
|
|
147
|
-
*
|
|
148
|
-
* Authoritative liveness check using the client's own state machine.
|
|
108
|
+
* Invalidates organization permission cache.
|
|
149
109
|
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
|
|
153
|
-
|
|
110
|
+
* @param {string} userId - User ID.
|
|
111
|
+
* @param {string} organizationId - Organization ID.
|
|
112
|
+
*/
|
|
113
|
+
async invalidateOrganization(userId, organizationId) {
|
|
114
|
+
await this.delete(this.organizationKey(userId, organizationId));
|
|
115
|
+
}
|
|
116
|
+
/* --------------------------------------------------------------------------
|
|
117
|
+
invalidateWorkspace
|
|
118
|
+
-------------------------------------------------------------------------- */
|
|
119
|
+
/**
|
|
120
|
+
* Invalidates workspace permission cache.
|
|
154
121
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
122
|
+
* @param {string} userId - User ID.
|
|
123
|
+
* @param {string} workspaceId - Workspace ID.
|
|
124
|
+
*/
|
|
125
|
+
async invalidateWorkspace(userId, workspaceId) {
|
|
126
|
+
await this.delete(this.workspaceKey(userId, workspaceId));
|
|
127
|
+
}
|
|
128
|
+
/* --------------------------------------------------------------------------
|
|
129
|
+
disconnect
|
|
130
|
+
-------------------------------------------------------------------------- */
|
|
131
|
+
/**
|
|
132
|
+
* Gracefully closes Redis connection.
|
|
157
133
|
*/
|
|
158
|
-
|
|
159
|
-
|
|
134
|
+
async disconnect() {
|
|
135
|
+
await this.client.quit();
|
|
160
136
|
}
|
|
161
|
-
|
|
162
|
-
|
|
137
|
+
/* ---------------------------------------------------------------------------
|
|
138
|
+
Internal Cache Helpers
|
|
139
|
+
--------------------------------------------------------------------------- */
|
|
140
|
+
/**
|
|
141
|
+
* Resolves permissions using cache-aside pattern.
|
|
142
|
+
*
|
|
143
|
+
* Redis hit:
|
|
144
|
+
* cache → return
|
|
145
|
+
*
|
|
146
|
+
* Redis miss:
|
|
147
|
+
* database → cache → return
|
|
148
|
+
*
|
|
149
|
+
* @param {string} key - Cache key.
|
|
150
|
+
* @param {Function} resolver - Database resolver.
|
|
151
|
+
*/
|
|
152
|
+
async resolve(key, resolver) {
|
|
153
|
+
const cached = await this.get(key);
|
|
154
|
+
if (cached !== null) {
|
|
155
|
+
return cached;
|
|
156
|
+
}
|
|
157
|
+
const permissions = await resolver();
|
|
158
|
+
await this.set(key, permissions);
|
|
159
|
+
return permissions;
|
|
163
160
|
}
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Reads permission list from Redis.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} key - Cache key.
|
|
165
|
+
*/
|
|
166
|
+
async get(key) {
|
|
167
|
+
if (!this.client.isReady) {
|
|
166
168
|
return null;
|
|
169
|
+
}
|
|
167
170
|
try {
|
|
168
|
-
const raw = await this.client.get(
|
|
169
|
-
if (!raw)
|
|
171
|
+
const raw = await this.client.get(key);
|
|
172
|
+
if (!raw) {
|
|
170
173
|
return null;
|
|
174
|
+
}
|
|
171
175
|
const parsed = JSON.parse(raw);
|
|
172
|
-
// Validate shape before trusting the cached value. A malformed entry
|
|
173
|
-
// (e.g. from a prior schema change or a manual key mutation) should
|
|
174
|
-
// produce a cache miss, not a silent type error downstream.
|
|
175
176
|
if (!Array.isArray(parsed)) {
|
|
176
|
-
console.warn(`[PermissionCache] Unexpected cache shape for ${accountId}, treating as miss`);
|
|
177
177
|
return null;
|
|
178
178
|
}
|
|
179
179
|
return parsed;
|
|
180
180
|
}
|
|
181
181
|
catch (err) {
|
|
182
|
-
|
|
183
|
-
console.warn(`[PermissionCache] Cache get failed for ${accountId}, bypassing`, err);
|
|
182
|
+
console.warn(`[PermissionCache] Cache read failed: ${key}`, err);
|
|
184
183
|
return null;
|
|
185
184
|
}
|
|
186
185
|
}
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Stores permission list in Redis.
|
|
188
|
+
*
|
|
189
|
+
* @param {string} key - Cache key.
|
|
190
|
+
* @param {string[]} permissions - Permission keys.
|
|
191
|
+
*/
|
|
192
|
+
async set(key, permissions) {
|
|
193
|
+
if (!this.client.isReady) {
|
|
189
194
|
return;
|
|
195
|
+
}
|
|
190
196
|
try {
|
|
191
|
-
await this.client.set(
|
|
197
|
+
await this.client.set(key, JSON.stringify(permissions), {
|
|
192
198
|
EX: CACHE_TTL_SECONDS,
|
|
193
199
|
});
|
|
194
200
|
}
|
|
195
201
|
catch (err) {
|
|
196
|
-
console.warn(`[PermissionCache] Cache
|
|
202
|
+
console.warn(`[PermissionCache] Cache write failed: ${key}`, err);
|
|
197
203
|
}
|
|
198
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Deletes permission cache.
|
|
207
|
+
*
|
|
208
|
+
* @param {string} key - Cache key.
|
|
209
|
+
*/
|
|
210
|
+
async delete(key) {
|
|
211
|
+
if (!this.client.isReady) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
await this.client.del(key);
|
|
215
|
+
}
|
|
216
|
+
/* ---------------------------------------------------------------------------
|
|
217
|
+
Cache Key Builders
|
|
218
|
+
--------------------------------------------------------------------------- */
|
|
219
|
+
platformKey(accountId) {
|
|
220
|
+
return `${PLATFORM_PREFIX}:${accountId}`;
|
|
221
|
+
}
|
|
222
|
+
organizationKey(userId, organizationId) {
|
|
223
|
+
return `${ORG_PREFIX}:${userId}:${organizationId}`;
|
|
224
|
+
}
|
|
225
|
+
workspaceKey(userId, workspaceId) {
|
|
226
|
+
return `${WORKSPACE_PREFIX}:${userId}:${workspaceId}`;
|
|
227
|
+
}
|
|
199
228
|
}
|
|
200
229
|
exports.PermissionCacheService = PermissionCacheService;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file permission-checker.ts
|
|
3
|
+
* @description Centralized utility for validating resolved permission string arrays against required permissions.
|
|
4
|
+
*/
|
|
5
|
+
export declare class PermissionChecker {
|
|
6
|
+
/**
|
|
7
|
+
* Checks whether the resolved list has the specific permission.
|
|
8
|
+
*
|
|
9
|
+
* @param {readonly string[]} permissions - Active resolved permissions.
|
|
10
|
+
* @param {string} permission - The required permission key.
|
|
11
|
+
* @returns {boolean} True if the permission exists in the array.
|
|
12
|
+
*/
|
|
13
|
+
static has(permissions: readonly string[], permission: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Checks whether the resolved list contains at least one of the required permissions.
|
|
16
|
+
*
|
|
17
|
+
* @param {readonly string[]} permissions - Active resolved permissions.
|
|
18
|
+
* @param {readonly string[]} required - Allowed keys.
|
|
19
|
+
* @returns {boolean} True if at least one matches.
|
|
20
|
+
*/
|
|
21
|
+
static hasAny(permissions: readonly string[], required: readonly string[]): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Checks whether the resolved list contains all of the required permissions.
|
|
24
|
+
*
|
|
25
|
+
* @param {readonly string[]} permissions - Active resolved permissions.
|
|
26
|
+
* @param {readonly string[]} required - Required keys.
|
|
27
|
+
* @returns {boolean} True if all match.
|
|
28
|
+
*/
|
|
29
|
+
static hasAll(permissions: readonly string[], required: readonly string[]): boolean;
|
|
30
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file permission-checker.ts
|
|
4
|
+
* @description Centralized utility for validating resolved permission string arrays against required permissions.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.PermissionChecker = void 0;
|
|
8
|
+
class PermissionChecker {
|
|
9
|
+
/**
|
|
10
|
+
* Checks whether the resolved list has the specific permission.
|
|
11
|
+
*
|
|
12
|
+
* @param {readonly string[]} permissions - Active resolved permissions.
|
|
13
|
+
* @param {string} permission - The required permission key.
|
|
14
|
+
* @returns {boolean} True if the permission exists in the array.
|
|
15
|
+
*/
|
|
16
|
+
static has(permissions, permission) {
|
|
17
|
+
return permissions.includes(permission);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Checks whether the resolved list contains at least one of the required permissions.
|
|
21
|
+
*
|
|
22
|
+
* @param {readonly string[]} permissions - Active resolved permissions.
|
|
23
|
+
* @param {readonly string[]} required - Allowed keys.
|
|
24
|
+
* @returns {boolean} True if at least one matches.
|
|
25
|
+
*/
|
|
26
|
+
static hasAny(permissions, required) {
|
|
27
|
+
return required.some((permission) => permissions.includes(permission));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Checks whether the resolved list contains all of the required permissions.
|
|
31
|
+
*
|
|
32
|
+
* @param {readonly string[]} permissions - Active resolved permissions.
|
|
33
|
+
* @param {readonly string[]} required - Required keys.
|
|
34
|
+
* @returns {boolean} True if all match.
|
|
35
|
+
*/
|
|
36
|
+
static hasAll(permissions, required) {
|
|
37
|
+
return required.every((permission) => permissions.includes(permission));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.PermissionChecker = PermissionChecker;
|
package/dist/context/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from './request-context.storage';
|
package/dist/context/index.js
CHANGED
|
@@ -14,4 +14,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./
|
|
17
|
+
__exportStar(require("./request-context.storage"), exports);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
+
import { RequestMetadataContext, UserContext, MachineContext } from '../types';
|
|
3
|
+
export type AuditRequestContext = RequestMetadataContext & {
|
|
4
|
+
actor?: UserContext | MachineContext;
|
|
5
|
+
};
|
|
6
|
+
export declare const requestContextStorage: AsyncLocalStorage<AuditRequestContext>;
|
|
7
|
+
export declare class RequestContextProvider {
|
|
8
|
+
/**
|
|
9
|
+
* Retrieves the current context if it exists.
|
|
10
|
+
*/
|
|
11
|
+
static get(): AuditRequestContext | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Retrieves the current context or throws an error.
|
|
14
|
+
* Use this in deeper repositories where context is strictly required.
|
|
15
|
+
*/
|
|
16
|
+
static getOrThrow(): AuditRequestContext;
|
|
17
|
+
/**
|
|
18
|
+
* Attaches the authenticated actor to the current request context.
|
|
19
|
+
*/
|
|
20
|
+
static setActor(actor: UserContext | MachineContext): void;
|
|
21
|
+
}
|