@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.
- package/dist/authorization/index.d.ts +1 -1
- package/dist/authorization/index.js +1 -1
- package/dist/authorization/permission-cache.service.d.ts +104 -16
- package/dist/authorization/permission-cache.service.js +156 -143
- package/dist/authorization/permission-checker.d.ts +30 -0
- package/dist/authorization/permission-checker.js +40 -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/dtos/auth-service.dto.d.ts +72 -26
- package/dist/dtos/auth-service.dto.js +4 -0
- package/dist/dtos/cloud-service.dto.d.ts +51 -27
- package/dist/dtos/cloud-service.dto.js +5 -0
- package/dist/dtos/insights-service.dto.d.ts +18 -27
- package/dist/dtos/insights-service.dto.js +8 -0
- package/dist/dtos/response.dto.d.ts +7 -84
- package/dist/dtos/response.dto.js +2 -21
- package/dist/dtos/user-service.dto.d.ts +118 -13
- package/dist/enums/domain.enums.d.ts +246 -75
- package/dist/enums/domain.enums.js +305 -102
- 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 +14 -20
- package/dist/errors/http-errors.d.ts +105 -17
- package/dist/errors/http-errors.js +118 -33
- package/dist/http/index.d.ts +1 -0
- 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 +91 -38
- package/dist/http/service-client.js +102 -53
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/jwt/index.d.ts +2 -1
- package/dist/jwt/index.js +2 -1
- package/dist/jwt/machine-jwt-verifier.d.ts +21 -0
- package/dist/jwt/machine-jwt-verifier.js +84 -0
- package/dist/jwt/machine-token-client.d.ts +35 -4
- package/dist/jwt/machine-token-client.js +57 -23
- package/dist/jwt/user-jwt-verifier.d.ts +31 -0
- package/dist/jwt/user-jwt-verifier.js +101 -0
- package/dist/messaging/index.d.ts +1 -0
- package/dist/{security → messaging}/index.js +1 -1
- package/dist/messaging/rabbitmq.client.d.ts +13 -0
- package/dist/messaging/rabbitmq.client.js +127 -0
- package/dist/middleware/error-handler.middleware.d.ts +8 -28
- package/dist/middleware/error-handler.middleware.js +61 -52
- package/dist/middleware/index.d.ts +7 -5
- package/dist/middleware/index.js +7 -5
- package/dist/middleware/request-context.middleware.d.ts +17 -0
- package/dist/middleware/request-context.middleware.js +73 -0
- package/dist/middleware/require-auth.middleware.d.ts +17 -70
- package/dist/middleware/require-auth.middleware.js +24 -103
- package/dist/middleware/require-machine.middleware.d.ts +38 -0
- package/dist/middleware/require-machine.middleware.js +123 -0
- package/dist/middleware/require-org-permission-from-body.middleware.d.ts +10 -0
- package/dist/middleware/require-org-permission-from-body.middleware.js +24 -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 +29 -29
- package/dist/middleware/validate.middleware.js +30 -32
- package/dist/types/express.types.d.ts +83 -122
- package/dist/types/express.types.js +28 -47
- 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 +26 -0
- package/dist/utils/logger.util.js +53 -0
- package/dist/utils/pagination.util.d.ts +6 -0
- package/dist/utils/pagination.util.js +20 -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 +72 -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/context/index.d.ts +0 -1
- package/dist/context/index.js +0 -17
- 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/index.js +0 -19
- 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
|
@@ -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,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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
23
|
-
* - Role change → invalidate(accountId)
|
|
24
|
-
* - Suspension → invalidate(accountId)
|
|
25
|
-
* - Account deletion → invalidate(accountId)
|
|
18
|
+
* Source of truth:
|
|
26
19
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
20
|
+
* User / Account
|
|
21
|
+
* ↓
|
|
22
|
+
* RoleAssignment
|
|
23
|
+
* ↓
|
|
24
|
+
* Role
|
|
25
|
+
* ↓
|
|
26
|
+
* Permission
|
|
33
27
|
*
|
|
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.
|
|
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
|
|
39
|
+
console.error("[PermissionCache] Redis error", err);
|
|
54
40
|
});
|
|
55
41
|
this.client.on("ready", () => {
|
|
56
|
-
console.info("[PermissionCache] Redis
|
|
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
|
|
48
|
+
console.error("[PermissionCache] Redis connection failed", err);
|
|
65
49
|
});
|
|
66
50
|
}
|
|
67
|
-
/*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
async
|
|
91
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
*
|
|
148
|
-
* Authoritative liveness check using the client's own state machine.
|
|
98
|
+
* Invalidates organization permission cache.
|
|
149
99
|
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
*
|
|
156
|
-
*
|
|
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
|
-
|
|
159
|
-
|
|
118
|
+
async disconnect() {
|
|
119
|
+
await this.client.quit();
|
|
160
120
|
}
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
188
|
-
|
|
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(
|
|
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
|
|
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,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 });
|