@discover-cloud/shared 1.2.9 → 1.3.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.
Files changed (165) hide show
  1. package/dist/authorization/index.d.ts +1 -1
  2. package/dist/authorization/index.js +1 -1
  3. package/dist/authorization/permission-cache.service.d.ts +104 -16
  4. package/dist/authorization/permission-cache.service.js +156 -143
  5. package/dist/authorization/permission-checker.d.ts +30 -0
  6. package/dist/authorization/permission-checker.js +40 -0
  7. package/dist/contracts/auth-service/account.dto.d.ts +30 -0
  8. package/dist/contracts/auth-service/account.dto.js +7 -0
  9. package/dist/contracts/auth-service/auth.dto.d.ts +21 -0
  10. package/dist/contracts/auth-service/auth.dto.js +10 -0
  11. package/dist/dtos/auth-service.dto.d.ts +72 -26
  12. package/dist/dtos/auth-service.dto.js +4 -0
  13. package/dist/dtos/cloud-service.dto.d.ts +51 -27
  14. package/dist/dtos/cloud-service.dto.js +5 -0
  15. package/dist/dtos/insights-service.dto.d.ts +18 -27
  16. package/dist/dtos/insights-service.dto.js +8 -0
  17. package/dist/dtos/response.dto.d.ts +7 -84
  18. package/dist/dtos/response.dto.js +2 -21
  19. package/dist/dtos/user-service.dto.d.ts +118 -13
  20. package/dist/enums/domain.enums.d.ts +246 -75
  21. package/dist/enums/domain.enums.js +305 -102
  22. package/dist/enums/permissions.enums.d.ts +142 -80
  23. package/dist/enums/permissions.enums.js +174 -133
  24. package/dist/errors/app-error.d.ts +13 -18
  25. package/dist/errors/app-error.js +14 -20
  26. package/dist/errors/http-errors.d.ts +105 -17
  27. package/dist/errors/http-errors.js +118 -33
  28. package/dist/http/index.d.ts +1 -0
  29. package/dist/http/index.js +1 -0
  30. package/dist/http/request-context.d.ts +8 -0
  31. package/dist/http/request-context.js +37 -0
  32. package/dist/http/service-client.d.ts +91 -38
  33. package/dist/http/service-client.js +102 -53
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.js +1 -0
  36. package/dist/jwt/index.d.ts +2 -1
  37. package/dist/jwt/index.js +2 -1
  38. package/dist/jwt/machine-jwt-verifier.d.ts +21 -0
  39. package/dist/jwt/machine-jwt-verifier.js +84 -0
  40. package/dist/jwt/machine-token-client.d.ts +35 -4
  41. package/dist/jwt/machine-token-client.js +57 -23
  42. package/dist/jwt/user-jwt-verifier.d.ts +31 -0
  43. package/dist/jwt/user-jwt-verifier.js +101 -0
  44. package/dist/messaging/index.d.ts +1 -0
  45. package/dist/{security → messaging}/index.js +1 -1
  46. package/dist/messaging/rabbitmq.client.d.ts +13 -0
  47. package/dist/messaging/rabbitmq.client.js +127 -0
  48. package/dist/middleware/error-handler.middleware.d.ts +8 -28
  49. package/dist/middleware/error-handler.middleware.js +61 -52
  50. package/dist/middleware/index.d.ts +7 -5
  51. package/dist/middleware/index.js +7 -5
  52. package/dist/middleware/request-context.middleware.d.ts +17 -0
  53. package/dist/middleware/request-context.middleware.js +73 -0
  54. package/dist/middleware/require-auth.middleware.d.ts +17 -70
  55. package/dist/middleware/require-auth.middleware.js +24 -103
  56. package/dist/middleware/require-machine.middleware.d.ts +38 -0
  57. package/dist/middleware/require-machine.middleware.js +123 -0
  58. package/dist/middleware/require-org-permission-from-body.middleware.d.ts +10 -0
  59. package/dist/middleware/require-org-permission-from-body.middleware.js +24 -0
  60. package/dist/middleware/require-org-permission.middleware.d.ts +19 -0
  61. package/dist/middleware/require-org-permission.middleware.js +115 -0
  62. package/dist/middleware/require-platform-permission.middleware.d.ts +19 -0
  63. package/dist/middleware/require-platform-permission.middleware.js +91 -0
  64. package/dist/middleware/require-user.middleware.d.ts +14 -0
  65. package/dist/middleware/require-user.middleware.js +30 -0
  66. package/dist/middleware/require-workspace-permission.middleware.d.ts +20 -0
  67. package/dist/middleware/require-workspace-permission.middleware.js +114 -0
  68. package/dist/middleware/validate.middleware.d.ts +29 -29
  69. package/dist/middleware/validate.middleware.js +30 -32
  70. package/dist/types/express.types.d.ts +83 -122
  71. package/dist/types/express.types.js +28 -47
  72. package/dist/utils/date.util.d.ts +6 -0
  73. package/dist/utils/date.util.js +11 -0
  74. package/dist/utils/env.util.d.ts +6 -0
  75. package/dist/utils/env.util.js +19 -0
  76. package/dist/utils/index.d.ts +5 -4
  77. package/dist/utils/index.js +5 -4
  78. package/dist/utils/logger.util.d.ts +26 -0
  79. package/dist/utils/logger.util.js +53 -0
  80. package/dist/utils/pagination.util.d.ts +6 -0
  81. package/dist/utils/pagination.util.js +20 -0
  82. package/dist/utils/response.util.d.ts +27 -0
  83. package/dist/utils/response.util.js +56 -0
  84. package/dist/utils/slug.util.d.ts +9 -0
  85. package/dist/utils/slug.util.js +32 -0
  86. package/dist/utils/url-safety.util.d.ts +6 -0
  87. package/dist/utils/url-safety.util.js +72 -0
  88. package/package.json +3 -1
  89. package/dist/authorization/permissions.d.ts +0 -78
  90. package/dist/authorization/permissions.js +0 -174
  91. package/dist/context/access-context.d.ts +0 -10
  92. package/dist/context/access-context.js +0 -2
  93. package/dist/context/index.d.ts +0 -1
  94. package/dist/context/index.js +0 -17
  95. package/dist/dto/auth-service.dtos.d.ts +0 -44
  96. package/dist/dto/auth-service.dtos.js +0 -2
  97. package/dist/dto/index.d.ts +0 -3
  98. package/dist/dto/index.js +0 -19
  99. package/dist/dto/response.dtos.d.ts +0 -55
  100. package/dist/dto/response.dtos.js +0 -6
  101. package/dist/dto/user-service.dtos.d.ts +0 -50
  102. package/dist/dto/user-service.dtos.js +0 -2
  103. package/dist/enums/auth-service.enums.d.ts +0 -12
  104. package/dist/enums/auth-service.enums.js +0 -17
  105. package/dist/enums/permissions.types.d.ts +0 -12
  106. package/dist/enums/permissions.types.js +0 -17
  107. package/dist/enums/user-service.enums.d.ts +0 -32
  108. package/dist/enums/user-service.enums.js +0 -41
  109. package/dist/internal/index.d.ts +0 -4
  110. package/dist/internal/index.js +0 -20
  111. package/dist/internal/internal-jwt.service.d.ts +0 -13
  112. package/dist/internal/internal-jwt.service.js +0 -88
  113. package/dist/internal/internal-jwt.types.d.ts +0 -7
  114. package/dist/internal/internal-jwt.types.js +0 -2
  115. package/dist/internal/internal-key-manager.d.ts +0 -16
  116. package/dist/internal/internal-key-manager.js +0 -67
  117. package/dist/internal/registry.d.ts +0 -8
  118. package/dist/internal/registry.js +0 -34
  119. package/dist/internal/service-client.d.ts +0 -9
  120. package/dist/internal/service-client.js +0 -94
  121. package/dist/jwt/internal-jwt-verifier.d.ts +0 -41
  122. package/dist/jwt/internal-jwt-verifier.js +0 -185
  123. package/dist/jwt/jwt-verifier.d.ts +0 -9
  124. package/dist/jwt/jwt-verifier.js +0 -36
  125. package/dist/jwt/service-client.d.ts +0 -7
  126. package/dist/jwt/service-client.js +0 -87
  127. package/dist/middleware/authorize.d.ts +0 -3
  128. package/dist/middleware/authorize.js +0 -24
  129. package/dist/middleware/authorize.middleware.d.ts +0 -54
  130. package/dist/middleware/authorize.middleware.js +0 -104
  131. package/dist/middleware/error-handler.d.ts +0 -4
  132. package/dist/middleware/error-handler.js +0 -23
  133. package/dist/middleware/request-id.d.ts +0 -2
  134. package/dist/middleware/request-id.js +0 -9
  135. package/dist/middleware/request-id.middleware.d.ts +0 -22
  136. package/dist/middleware/request-id.middleware.js +0 -34
  137. package/dist/middleware/require-auth.d.ts +0 -10
  138. package/dist/middleware/require-auth.js +0 -34
  139. package/dist/middleware/require-human.middleware.d.ts +0 -2
  140. package/dist/middleware/require-human.middleware.js +0 -18
  141. package/dist/middleware/require-internal.middleware.d.ts +0 -18
  142. package/dist/middleware/require-internal.middleware.js +0 -183
  143. package/dist/middleware/validate.d.ts +0 -5
  144. package/dist/middleware/validate.js +0 -18
  145. package/dist/middleware/validated-merge.middleware.d.ts +0 -20
  146. package/dist/middleware/validated-merge.middleware.js +0 -33
  147. package/dist/middleware/verify-internal-jwt.d.ts +0 -7
  148. package/dist/middleware/verify-internal-jwt.js +0 -25
  149. package/dist/security/guard.d.ts +0 -10
  150. package/dist/security/guard.js +0 -40
  151. package/dist/security/index.d.ts +0 -1
  152. package/dist/types/express.d.ts +0 -22
  153. package/dist/types/express.js +0 -3
  154. package/dist/utils/date.utils.d.ts +0 -25
  155. package/dist/utils/date.utils.js +0 -30
  156. package/dist/utils/env.d.ts +0 -46
  157. package/dist/utils/env.js +0 -61
  158. package/dist/utils/env.utils.d.ts +0 -46
  159. package/dist/utils/env.utils.js +0 -61
  160. package/dist/utils/logger.utils.d.ts +0 -66
  161. package/dist/utils/logger.utils.js +0 -97
  162. package/dist/utils/response.d.ts +0 -4
  163. package/dist/utils/response.js +0 -35
  164. package/dist/utils/response.utils.d.ts +0 -54
  165. package/dist/utils/response.utils.js +0 -85
@@ -1,2 +1,2 @@
1
- export * from "./permissions";
2
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
- import { GlobalPermission, AccountRole } from "../enums";
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
- * isReady
11
- * Authoritative liveness check using the client's own state machine.
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
- * Replaces a manually tracked `connected` boolean, which could drift
14
- * after reconnect cycles — the node-redis client reconnects automatically
15
- * after a transient failure, but a boolean set only on the first "ready"
16
- * event would not reflect subsequent disconnects and reconnects.
100
+ * @param {string} key - Cache key.
101
+ * @param {string[]} permissions - Permission keys.
102
+ */
103
+ private set;
104
+ /**
105
+ * Deletes permission cache.
17
106
  *
18
- * `client.isReady` is kept current by the client's internal state machine
19
- * and is the correct primitive for this guard.
107
+ * @param {string} key - Cache key.
20
108
  */
21
- private isReady;
22
- private key;
23
- private safeGet;
24
- private safeSet;
109
+ private delete;
110
+ private platformKey;
111
+ private organizationKey;
112
+ private workspaceKey;
25
113
  }
@@ -1,200 +1,213 @@
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 permissions_1 = require("./permissions");
11
+ const CACHE_TTL_SECONDS = 24 * 60 * 60; // 24 hours
12
+ const PLATFORM_PREFIX = "perms:platform";
13
+ const ORG_PREFIX = "perms:org";
14
+ const WORKSPACE_PREFIX = "perms:workspace";
6
15
  /**
7
- * PERMISSION CACHE SERVICE (@discover-cloud/shared)
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.
16
+ * Redis-backed permission cache.
21
17
  *
22
- * Invalidation triggers:
23
- * - Role change → invalidate(accountId)
24
- * - Suspension → invalidate(accountId)
25
- * - Account deletion → invalidate(accountId)
18
+ * Source of truth:
26
19
  *
27
- * Connection lifecycle:
28
- * - isReady() is the authoritative liveness check — it reflects the actual
29
- * client state (ready / connecting / reconnecting / closing) rather than a
30
- * manually tracked boolean that can drift after automatic reconnects.
31
- * - Call disconnect() in your SIGTERM handler to drain in-flight commands
32
- * and release the TCP socket before the process exits.
20
+ * User / Account
21
+ *
22
+ * RoleAssignment
23
+ *
24
+ * Role
25
+ *
26
+ * Permission
33
27
  *
34
- * Fault tolerance:
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.
28
+ * This service only caches the resolved permission keys.
44
29
  */
45
- const CACHE_TTL_SECONDS = 24 * 60 * 60; // 24 hours
46
- const KEY_PREFIX = "perms";
47
30
  class PermissionCacheService {
31
+ /**
32
+ * Initializes Redis client and connection listeners.
33
+ */
48
34
  constructor() {
49
35
  this.client = (0, redis_1.createClient)({
50
36
  url: process.env["REDIS_URL"] ?? "redis://localhost:6379",
51
37
  });
52
38
  this.client.on("error", (err) => {
53
- console.error("[PermissionCache] Redis client error", err);
39
+ console.error("[PermissionCache] Redis error", err);
54
40
  });
55
41
  this.client.on("ready", () => {
56
- console.info("[PermissionCache] Redis connected and ready");
42
+ console.info("[PermissionCache] Redis ready");
57
43
  });
58
44
  this.client.on("reconnecting", () => {
59
45
  console.warn("[PermissionCache] Redis reconnecting...");
60
46
  });
61
- // Connect asynchronously — all read/write methods guard via isReady()
62
- // and fall back gracefully, so startup is non-blocking.
63
47
  this.client.connect().catch((err) => {
64
- console.error("[PermissionCache] Redis initial connection failed", err);
48
+ console.error("[PermissionCache] Redis connection failed", err);
65
49
  });
66
50
  }
67
- /* ----------------------------------------------------------------
68
- disconnect
69
- Gracefully closes the Redis connection. Call during service shutdown
70
- (SIGTERM handler) to drain in-flight commands and release the TCP
71
- socket before the process exits.
72
- ---------------------------------------------------------------- */
73
- async disconnect() {
74
- try {
75
- await this.client.quit();
76
- console.info("[PermissionCache] Redis connection closed");
77
- }
78
- catch (err) {
79
- console.error("[PermissionCache] Error during disconnect", err);
80
- }
51
+ /* ---------------------------------------------------------------------------
52
+ Permission Resolution
53
+ --------------------------------------------------------------------------- */
54
+ /**
55
+ * Resolves platform-level permissions.
56
+ *
57
+ * @param {string} accountId - Account ID.
58
+ * @param {Function} resolver - Database permission resolver.
59
+ * @returns {Promise<string[]>} Permission keys.
60
+ */
61
+ async resolvePlatformPermissions(accountId, resolver) {
62
+ return this.resolve(this.platformKey(accountId), resolver);
81
63
  }
82
- /* ----------------------------------------------------------------
83
- resolve
84
- Returns permissions for an account, loading from cache or deriving
85
- from the role map on a miss.
86
-
87
- Never throws on any Redis failure it falls back to deriving
88
- from the role map directly (degraded but functional).
89
- ---------------------------------------------------------------- */
90
- async resolve(accountId, accountRole) {
91
- const cached = await this.safeGet(accountId);
92
- if (cached !== null)
93
- return cached;
94
- // Cache miss — derive from role map (no DB call needed; the role is
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;
64
+ /**
65
+ * Resolves organization-scoped permissions.
66
+ *
67
+ * @param {string} userId - User ID.
68
+ * @param {string} organizationId - Organization ID.
69
+ * @param {Function} resolver - Database permission resolver.
70
+ * @returns {Promise<string[]>} Permission keys.
71
+ */
72
+ async resolveOrganizationPermissions(userId, organizationId, resolver) {
73
+ return this.resolve(this.organizationKey(userId, organizationId), resolver);
101
74
  }
102
- /* ----------------------------------------------------------------
103
- invalidate
104
- Removes a specific account's permission cache entry.
105
- Call on: role change, suspension, account deletion.
106
- ---------------------------------------------------------------- */
107
- async invalidate(accountId) {
108
- if (!this.isReady()) {
109
- // Skip deletion — the stale entry will expire via TTL at worst.
110
- console.warn(`[PermissionCache] Skipping invalidation for ${accountId} — Redis not ready`);
111
- return;
112
- }
113
- try {
114
- await this.client.del(this.key(accountId));
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
- }
75
+ /**
76
+ * Resolves workspace-scoped permissions.
77
+ *
78
+ * @param {string} userId - User ID.
79
+ * @param {string} workspaceId - Workspace ID.
80
+ * @param {Function} resolver - Database permission resolver.
81
+ * @returns {Promise<string[]>} Permission keys.
82
+ */
83
+ async resolveWorkspacePermissions(userId, workspaceId, resolver) {
84
+ return this.resolve(this.workspaceKey(userId, workspaceId), resolver);
121
85
  }
122
- /* ----------------------------------------------------------------
123
- invalidateMany
124
- Batch invalidation — use when a role definition changes globally.
125
- Sends a single DEL with multiple keys (O(n) in Redis, one round-trip).
126
- ---------------------------------------------------------------- */
127
- async invalidateMany(accountIds) {
128
- if (accountIds.length === 0)
129
- return;
130
- if (!this.isReady()) {
131
- console.warn(`[PermissionCache] Skipping batch invalidation (${accountIds.length} entries) — Redis not ready`);
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
- }
86
+ /* ---------------------------------------------------------------------------
87
+ Cache Invalidation
88
+ --------------------------------------------------------------------------- */
89
+ /**
90
+ * Invalidates platform permission cache.
91
+ *
92
+ * @param {string} accountId - Account ID.
93
+ */
94
+ async invalidatePlatform(accountId) {
95
+ await this.delete(this.platformKey(accountId));
142
96
  }
143
- /* ----------------------------------------------------------------
144
- Private helpers
145
- ---------------------------------------------------------------- */
146
97
  /**
147
- * isReady
148
- * Authoritative liveness check using the client's own state machine.
98
+ * Invalidates organization permission cache.
149
99
  *
150
- * Replaces a manually tracked `connected` boolean, which could drift
151
- * after reconnect cycles — the node-redis client reconnects automatically
152
- * after a transient failure, but a boolean set only on the first "ready"
153
- * event would not reflect subsequent disconnects and reconnects.
100
+ * @param {string} userId - User ID.
101
+ * @param {string} organizationId - Organization ID.
102
+ */
103
+ async invalidateOrganization(userId, organizationId) {
104
+ await this.delete(this.organizationKey(userId, organizationId));
105
+ }
106
+ /**
107
+ * Invalidates workspace permission cache.
154
108
  *
155
- * `client.isReady` is kept current by the client's internal state machine
156
- * and is the correct primitive for this guard.
109
+ * @param {string} userId - User ID.
110
+ * @param {string} workspaceId - Workspace ID.
111
+ */
112
+ async invalidateWorkspace(userId, workspaceId) {
113
+ await this.delete(this.workspaceKey(userId, workspaceId));
114
+ }
115
+ /**
116
+ * Gracefully closes Redis connection.
157
117
  */
158
- isReady() {
159
- return this.client.isReady;
118
+ async disconnect() {
119
+ await this.client.quit();
160
120
  }
161
- key(accountId) {
162
- return `${KEY_PREFIX}:${accountId}`;
121
+ /* ---------------------------------------------------------------------------
122
+ Internal Cache Helpers
123
+ --------------------------------------------------------------------------- */
124
+ /**
125
+ * Resolves permissions using cache-aside pattern.
126
+ *
127
+ * Redis hit:
128
+ * cache → return
129
+ *
130
+ * Redis miss:
131
+ * database → cache → return
132
+ *
133
+ * @param {string} key - Cache key.
134
+ * @param {Function} resolver - Database resolver.
135
+ */
136
+ async resolve(key, resolver) {
137
+ const cached = await this.get(key);
138
+ if (cached !== null) {
139
+ return cached;
140
+ }
141
+ const permissions = await resolver();
142
+ await this.set(key, permissions);
143
+ return permissions;
163
144
  }
164
- async safeGet(accountId) {
165
- if (!this.isReady())
145
+ /**
146
+ * Reads permission list from Redis.
147
+ *
148
+ * @param {string} key - Cache key.
149
+ */
150
+ async get(key) {
151
+ if (!this.client.isReady) {
166
152
  return null;
153
+ }
167
154
  try {
168
- const raw = await this.client.get(this.key(accountId));
169
- if (!raw)
155
+ const raw = await this.client.get(key);
156
+ if (!raw) {
170
157
  return null;
158
+ }
171
159
  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
160
  if (!Array.isArray(parsed)) {
176
- console.warn(`[PermissionCache] Unexpected cache shape for ${accountId}, treating as miss`);
177
161
  return null;
178
162
  }
179
163
  return parsed;
180
164
  }
181
165
  catch (err) {
182
- // JSON.parse failure or Redis error — bypass the cache gracefully.
183
- console.warn(`[PermissionCache] Cache get failed for ${accountId}, bypassing`, err);
166
+ console.warn(`[PermissionCache] Cache read failed: ${key}`, err);
184
167
  return null;
185
168
  }
186
169
  }
187
- async safeSet(accountId, perms) {
188
- if (!this.isReady())
170
+ /**
171
+ * Stores permission list in Redis.
172
+ *
173
+ * @param {string} key - Cache key.
174
+ * @param {string[]} permissions - Permission keys.
175
+ */
176
+ async set(key, permissions) {
177
+ if (!this.client.isReady) {
189
178
  return;
179
+ }
190
180
  try {
191
- await this.client.set(this.key(accountId), JSON.stringify(perms), {
181
+ await this.client.set(key, JSON.stringify(permissions), {
192
182
  EX: CACHE_TTL_SECONDS,
193
183
  });
194
184
  }
195
185
  catch (err) {
196
- console.warn(`[PermissionCache] Cache set failed for ${accountId}`, err);
186
+ console.warn(`[PermissionCache] Cache write failed: ${key}`, err);
197
187
  }
198
188
  }
189
+ /**
190
+ * Deletes permission cache.
191
+ *
192
+ * @param {string} key - Cache key.
193
+ */
194
+ async delete(key) {
195
+ if (!this.client.isReady) {
196
+ return;
197
+ }
198
+ await this.client.del(key);
199
+ }
200
+ /* ---------------------------------------------------------------------------
201
+ Cache Key Builders
202
+ --------------------------------------------------------------------------- */
203
+ platformKey(accountId) {
204
+ return `${PLATFORM_PREFIX}:${accountId}`;
205
+ }
206
+ organizationKey(userId, organizationId) {
207
+ return `${ORG_PREFIX}:${userId}:${organizationId}`;
208
+ }
209
+ workspaceKey(userId, workspaceId) {
210
+ return `${WORKSPACE_PREFIX}:${userId}:${workspaceId}`;
211
+ }
199
212
  }
200
213
  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;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @file account.dto.ts
3
+ * @description Data transfer objects (DTOs) for account management resources.
4
+ * Public DTOs never expose credentials, hashes, or encrypted secrets.
5
+ */
6
+ import { AccountStatus } from "../../enums";
7
+ /** Public account representation. */
8
+ export interface AccountDto {
9
+ id: string;
10
+ email: string;
11
+ isVerified: boolean;
12
+ status: AccountStatus;
13
+ lastLoginAt: string | null;
14
+ createdAt: string;
15
+ updatedAt: string;
16
+ }
17
+ /** OAuth provider identity metadata. */
18
+ export interface OAuthIdentityDto {
19
+ id: string;
20
+ accountId: string;
21
+ provider: string;
22
+ providerId: string;
23
+ createdAt: string;
24
+ updatedAt: string;
25
+ }
26
+ /** Standard action tracking and confirmation signature message payload. */
27
+ export interface ActionResponseDto {
28
+ action: string;
29
+ message: string;
30
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * @file account.dto.ts
4
+ * @description Data transfer objects (DTOs) for account management resources.
5
+ * Public DTOs never expose credentials, hashes, or encrypted secrets.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @file auth.dto.ts
3
+ * @description Data transfer objects (DTOs) for authentication resources.
4
+ * Public DTOs never expose credentials, hashes, or encrypted secrets.
5
+ *
6
+ * Refresh tokens are never returned in the public HTTP body.
7
+ * They are transported securely using HttpOnly cookies.
8
+ */
9
+ /** Tokens returned upon successful authentication. */
10
+ export interface AuthTokenDto {
11
+ accessToken: string;
12
+ }
13
+ /** Internal auth-service resolution block mapping backend tokens to transport layers. */
14
+ export interface AuthSessionResultDto {
15
+ accessToken: string;
16
+ refreshToken: string;
17
+ }
18
+ /** Generic message response payload contract. */
19
+ export interface MessageResponseDto {
20
+ message: string;
21
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**
3
+ * @file auth.dto.ts
4
+ * @description Data transfer objects (DTOs) for authentication resources.
5
+ * Public DTOs never expose credentials, hashes, or encrypted secrets.
6
+ *
7
+ * Refresh tokens are never returned in the public HTTP body.
8
+ * They are transported securely using HttpOnly cookies.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });