@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
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file response.utils.ts
|
|
4
|
+
* @description Helper functions to construct and send standardized API success and error response envelopes.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.failure = exports.success = void 0;
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
9
|
+
/**
|
|
10
|
+
* Sends a successful standardized API response containing the data payload.
|
|
11
|
+
*
|
|
12
|
+
* @param {Response} res - Express response object.
|
|
13
|
+
* @param {Request} req - Express request object.
|
|
14
|
+
* @param {T} data - The payload data.
|
|
15
|
+
* @param {number} [statusCode=200] - HTTP status code.
|
|
16
|
+
* @returns {void}
|
|
17
|
+
*/
|
|
18
|
+
const success = (res, req, data, statusCode = 200) => {
|
|
19
|
+
const response = {
|
|
20
|
+
success: true,
|
|
21
|
+
data,
|
|
22
|
+
meta: {
|
|
23
|
+
requestId: req.id ?? (0, crypto_1.randomUUID)(),
|
|
24
|
+
timestamp: new Date().toISOString(),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
res.status(statusCode).json(response);
|
|
28
|
+
};
|
|
29
|
+
exports.success = success;
|
|
30
|
+
/**
|
|
31
|
+
* Sends a failed standardized API response containing error details.
|
|
32
|
+
*
|
|
33
|
+
* @param {Response} res - Express response object.
|
|
34
|
+
* @param {Request} req - Express request object.
|
|
35
|
+
* @param {string} message - Human-readable error message.
|
|
36
|
+
* @param {string} code - Error code mapping.
|
|
37
|
+
* @param {number} [statusCode=400] - HTTP status code.
|
|
38
|
+
* @param {unknown} [details] - Optional error metadata details.
|
|
39
|
+
* @returns {void}
|
|
40
|
+
*/
|
|
41
|
+
const failure = (res, req, message, code, statusCode = 400, details) => {
|
|
42
|
+
const response = {
|
|
43
|
+
success: false,
|
|
44
|
+
error: {
|
|
45
|
+
code,
|
|
46
|
+
message,
|
|
47
|
+
...(details !== undefined ? { details } : {}),
|
|
48
|
+
},
|
|
49
|
+
meta: {
|
|
50
|
+
requestId: req.id ?? (0, crypto_1.randomUUID)(),
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
res.status(statusCode).json(response);
|
|
55
|
+
};
|
|
56
|
+
exports.failure = failure;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file slug.utils.ts
|
|
3
|
+
* @description Slug normalization and soft-delete suffixing helpers.
|
|
4
|
+
*/
|
|
5
|
+
export declare function normalizeSlug(input: string): string;
|
|
6
|
+
export declare function isValidSlug(slug: string): boolean;
|
|
7
|
+
export declare function appendDeletedSuffix(slug: string, timestamp?: number): string;
|
|
8
|
+
export declare function stripDeletedSuffix(slug: string): string;
|
|
9
|
+
export declare function appendDisambiguationSuffix(slug: string, timestamp?: number): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file slug.utils.ts
|
|
4
|
+
* @description Slug normalization and soft-delete suffixing helpers.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.normalizeSlug = normalizeSlug;
|
|
8
|
+
exports.isValidSlug = isValidSlug;
|
|
9
|
+
exports.appendDeletedSuffix = appendDeletedSuffix;
|
|
10
|
+
exports.stripDeletedSuffix = stripDeletedSuffix;
|
|
11
|
+
exports.appendDisambiguationSuffix = appendDisambiguationSuffix;
|
|
12
|
+
const DELETED_SUFFIX_MARKER = '__deleted_';
|
|
13
|
+
function normalizeSlug(input) {
|
|
14
|
+
return input
|
|
15
|
+
.trim()
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
18
|
+
.replace(/-+/g, '-')
|
|
19
|
+
.replace(/^-|-$/g, '');
|
|
20
|
+
}
|
|
21
|
+
function isValidSlug(slug) {
|
|
22
|
+
return /^[a-z0-9]+(-[a-z0-9]+)*$/.test(slug);
|
|
23
|
+
}
|
|
24
|
+
function appendDeletedSuffix(slug, timestamp = Date.now()) {
|
|
25
|
+
return `${stripDeletedSuffix(slug)}${DELETED_SUFFIX_MARKER}${timestamp}`;
|
|
26
|
+
}
|
|
27
|
+
function stripDeletedSuffix(slug) {
|
|
28
|
+
return slug.split(DELETED_SUFFIX_MARKER)[0];
|
|
29
|
+
}
|
|
30
|
+
function appendDisambiguationSuffix(slug, timestamp = Date.now()) {
|
|
31
|
+
return `${slug}-${timestamp}`;
|
|
32
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file url-safety.utils.ts
|
|
3
|
+
* @description Validation helpers to mitigate SSRF risk for user-supplied destination URLs.
|
|
4
|
+
*/
|
|
5
|
+
export declare function isPrivateOrBlockedHost(hostname: string): boolean;
|
|
6
|
+
export declare function isSafeWebhookUrl(value: string): boolean;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file url-safety.utils.ts
|
|
4
|
+
* @description Validation helpers to mitigate SSRF risk for user-supplied destination URLs.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.isPrivateOrBlockedHost = isPrivateOrBlockedHost;
|
|
8
|
+
exports.isSafeWebhookUrl = isSafeWebhookUrl;
|
|
9
|
+
const BLOCKED_HOSTNAMES = new Set(['localhost', '0.0.0.0', '::1', '::']);
|
|
10
|
+
const BLOCKED_EXACT_IPS = new Set([
|
|
11
|
+
'169.254.169.254', // AWS, GCP, Azure IMDS
|
|
12
|
+
'169.254.170.2', // AWS ECS task metadata
|
|
13
|
+
'fd00:ec2::254', // AWS IMDSv6
|
|
14
|
+
]);
|
|
15
|
+
const PRIVATE_IPV4_RANGES = [
|
|
16
|
+
{ test: (p) => p[0] === 10 },
|
|
17
|
+
{ test: (p) => p[0] === 127 },
|
|
18
|
+
{ test: (p) => p[0] === 172 && p[1] >= 16 && p[1] <= 31 },
|
|
19
|
+
{ test: (p) => p[0] === 192 && p[1] === 168 },
|
|
20
|
+
{ test: (p) => p[0] === 169 && p[1] === 254 },
|
|
21
|
+
{ test: (p) => p[0] === 0 },
|
|
22
|
+
{ test: (p) => p[0] >= 224 },
|
|
23
|
+
];
|
|
24
|
+
const ALLOWED_PROTOCOLS = new Set(['http:', 'https:']);
|
|
25
|
+
function parseIpv4(hostname) {
|
|
26
|
+
const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
|
|
27
|
+
if (!match)
|
|
28
|
+
return null;
|
|
29
|
+
const parts = match.slice(1, 5).map(Number);
|
|
30
|
+
if (parts.some((p) => p > 255))
|
|
31
|
+
return null;
|
|
32
|
+
return parts;
|
|
33
|
+
}
|
|
34
|
+
function isPrivateOrBlockedHost(hostname) {
|
|
35
|
+
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
36
|
+
if (BLOCKED_HOSTNAMES.has(normalized))
|
|
37
|
+
return true;
|
|
38
|
+
if (BLOCKED_EXACT_IPS.has(normalized))
|
|
39
|
+
return true;
|
|
40
|
+
if (normalized.endsWith('.local') || normalized.endsWith('.internal'))
|
|
41
|
+
return true;
|
|
42
|
+
if (normalized.startsWith('::ffff:')) {
|
|
43
|
+
const unwrappedIpv4 = normalized.substring(7);
|
|
44
|
+
return isPrivateOrBlockedHost(unwrappedIpv4);
|
|
45
|
+
}
|
|
46
|
+
const ipv4 = parseIpv4(normalized);
|
|
47
|
+
if (ipv4) {
|
|
48
|
+
return PRIVATE_IPV4_RANGES.some(({ test }) => test(ipv4));
|
|
49
|
+
}
|
|
50
|
+
if (normalized === '::1' ||
|
|
51
|
+
normalized.startsWith('fc') ||
|
|
52
|
+
normalized.startsWith('fd') ||
|
|
53
|
+
normalized.startsWith('fe80')) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
function isSafeWebhookUrl(value) {
|
|
59
|
+
let url;
|
|
60
|
+
try {
|
|
61
|
+
url = new URL(value);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
if (!ALLOWED_PROTOCOLS.has(url.protocol))
|
|
67
|
+
return false;
|
|
68
|
+
if (!url.hostname)
|
|
69
|
+
return false;
|
|
70
|
+
if (isPrivateOrBlockedHost(url.hostname))
|
|
71
|
+
return false;
|
|
72
|
+
if (url.username || url.password)
|
|
73
|
+
return false;
|
|
74
|
+
return true;
|
|
75
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@discover-cloud/shared",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"amqplib": "^2.0.1",
|
|
19
20
|
"axios-retry": "^4.5.0",
|
|
20
21
|
"jose": "^6.1.3",
|
|
21
22
|
"redis": "^5.11.0"
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
"zod": "^4.3.6"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
30
|
+
"@types/amqplib": "^0.10.8",
|
|
29
31
|
"@types/express": "^5.0.6",
|
|
30
32
|
"@types/node": "^25.3.0",
|
|
31
33
|
"axios": "^1.13.5",
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { AccountRole, GlobalPermission, OrganizationRole, OrgPermission } from "../enums";
|
|
2
|
-
import { AccessContext } from "../types";
|
|
3
|
-
/**
|
|
4
|
-
* GLOBAL ROLE PERMISSION MAP
|
|
5
|
-
* ───────────────────────────
|
|
6
|
-
* Defines which GlobalPermissions each AccountRole carries.
|
|
7
|
-
* This is the single source of truth — the Redis permission cache
|
|
8
|
-
* derives from this map on a cold cache miss.
|
|
9
|
-
*
|
|
10
|
-
* Design principles:
|
|
11
|
-
* - SUPERADMIN gets all permissions (dynamically via Object.values, not a
|
|
12
|
-
* hardcoded list — so new permissions are automatically granted on addition)
|
|
13
|
-
* - Each role has only the minimum permissions it needs (least privilege)
|
|
14
|
-
* - Permissions are additive — no role inherits from another at runtime
|
|
15
|
-
* - Adding a new permission: add to GlobalPermission enum, then grant it here
|
|
16
|
-
*
|
|
17
|
-
* ┌─────────────┬──────────────────────────────────────────────────────┐
|
|
18
|
-
* │ Role │ Key permissions │
|
|
19
|
-
* ├─────────────┼──────────────────────────────────────────────────────┤
|
|
20
|
-
* │ SUPERADMIN │ Everything — full platform control │
|
|
21
|
-
* │ ADMIN │ User mgmt, cost data access, logs, health, support │
|
|
22
|
-
* │ SUPPORT │ Read-only user + cost data for support work │
|
|
23
|
-
* │ MODERATOR │ Content moderation only (future) │
|
|
24
|
-
* │ USER │ No global permissions — service layer gates own data │
|
|
25
|
-
* └─────────────┴──────────────────────────────────────────────────────┘
|
|
26
|
-
*/
|
|
27
|
-
export declare const globalRolePermissions: Record<AccountRole, readonly GlobalPermission[]>;
|
|
28
|
-
/**
|
|
29
|
-
* ORG ROLE PERMISSION MAP (future work)
|
|
30
|
-
* ────────────────────────────────────────
|
|
31
|
-
* Org permissions are NOT embedded in the JWT — they're fetched from the DB
|
|
32
|
-
* by tenant middleware within each service because:
|
|
33
|
-
* 1. Org membership changes frequently (invites, role changes, removal)
|
|
34
|
-
* 2. A user can be in multiple orgs with different roles
|
|
35
|
-
* 3. Embedding them in the JWT would create stale permission windows
|
|
36
|
-
*
|
|
37
|
-
* This map is used by tenant middleware for the DB-free fast path:
|
|
38
|
-
* if the org role is already known (e.g. from a prior DB fetch cached in
|
|
39
|
-
* Redis), derive permissions here rather than re-querying the DB.
|
|
40
|
-
*
|
|
41
|
-
* NOTE: All entries use OrgPermission values only — no cross-namespace mixing
|
|
42
|
-
* with GlobalPermission. Org admin membership management is expressed via
|
|
43
|
-
* the dedicated OrgPermission.* values below.
|
|
44
|
-
*/
|
|
45
|
-
export declare const orgRolePermissions: Record<OrganizationRole, readonly OrgPermission[]>;
|
|
46
|
-
/**
|
|
47
|
-
* hasGlobalPermission
|
|
48
|
-
* Checks if a GlobalPermission is present in the AccessContext's perms array.
|
|
49
|
-
* Perms are populated from the Redis permission cache (derived from the role
|
|
50
|
-
* map on a miss) — never read directly from the JWT.
|
|
51
|
-
*
|
|
52
|
-
* Machine contexts always return false — they carry no user permissions.
|
|
53
|
-
*/
|
|
54
|
-
export declare const hasGlobalPermission: (ctx: AccessContext, permission: GlobalPermission) => boolean;
|
|
55
|
-
/**
|
|
56
|
-
* isAllowed
|
|
57
|
-
* Unified permission check for both GlobalPermission and OrgPermission.
|
|
58
|
-
*
|
|
59
|
-
* GlobalPermission → checked against ctx.perms (Redis cache, derived from role)
|
|
60
|
-
* OrgPermission → intentionally returns false here; handled by tenant
|
|
61
|
-
* middleware in each service via DB lookup. This function
|
|
62
|
-
* is not the place for org permission checks — the boundary
|
|
63
|
-
* is explicit by design.
|
|
64
|
-
*/
|
|
65
|
-
export declare const isAllowed: (ctx: AccessContext, permission: GlobalPermission | OrgPermission) => boolean;
|
|
66
|
-
/**
|
|
67
|
-
* isSuperAdmin
|
|
68
|
-
* Convenience guard for cases where superadmin-only logic is needed
|
|
69
|
-
* beyond a single permission check (e.g. bypassing org scoping entirely).
|
|
70
|
-
*/
|
|
71
|
-
export declare const isSuperAdmin: (ctx: AccessContext) => boolean;
|
|
72
|
-
/**
|
|
73
|
-
* deriveGlobalPermissions
|
|
74
|
-
* Derives permissions for a role from the static map.
|
|
75
|
-
* Called by PermissionCacheService on a cold cache miss.
|
|
76
|
-
* Returns a mutable copy — callers can extend it if needed.
|
|
77
|
-
*/
|
|
78
|
-
export declare const deriveGlobalPermissions: (role: AccountRole) => GlobalPermission[];
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.deriveGlobalPermissions = exports.isSuperAdmin = exports.isAllowed = exports.hasGlobalPermission = exports.orgRolePermissions = exports.globalRolePermissions = void 0;
|
|
4
|
-
const enums_1 = require("../enums");
|
|
5
|
-
/**
|
|
6
|
-
* GLOBAL ROLE PERMISSION MAP
|
|
7
|
-
* ───────────────────────────
|
|
8
|
-
* Defines which GlobalPermissions each AccountRole carries.
|
|
9
|
-
* This is the single source of truth — the Redis permission cache
|
|
10
|
-
* derives from this map on a cold cache miss.
|
|
11
|
-
*
|
|
12
|
-
* Design principles:
|
|
13
|
-
* - SUPERADMIN gets all permissions (dynamically via Object.values, not a
|
|
14
|
-
* hardcoded list — so new permissions are automatically granted on addition)
|
|
15
|
-
* - Each role has only the minimum permissions it needs (least privilege)
|
|
16
|
-
* - Permissions are additive — no role inherits from another at runtime
|
|
17
|
-
* - Adding a new permission: add to GlobalPermission enum, then grant it here
|
|
18
|
-
*
|
|
19
|
-
* ┌─────────────┬──────────────────────────────────────────────────────┐
|
|
20
|
-
* │ Role │ Key permissions │
|
|
21
|
-
* ├─────────────┼──────────────────────────────────────────────────────┤
|
|
22
|
-
* │ SUPERADMIN │ Everything — full platform control │
|
|
23
|
-
* │ ADMIN │ User mgmt, cost data access, logs, health, support │
|
|
24
|
-
* │ SUPPORT │ Read-only user + cost data for support work │
|
|
25
|
-
* │ MODERATOR │ Content moderation only (future) │
|
|
26
|
-
* │ USER │ No global permissions — service layer gates own data │
|
|
27
|
-
* └─────────────┴──────────────────────────────────────────────────────┘
|
|
28
|
-
*/
|
|
29
|
-
exports.globalRolePermissions = {
|
|
30
|
-
[enums_1.AccountRole.SUPERADMIN]: Object.values(enums_1.GlobalPermission),
|
|
31
|
-
[enums_1.AccountRole.ADMIN]: [
|
|
32
|
-
// Account management (no impersonate, no hard delete)
|
|
33
|
-
enums_1.GlobalPermission.MANAGE_ACCOUNTS,
|
|
34
|
-
enums_1.GlobalPermission.VIEW_ANY_ACCOUNT,
|
|
35
|
-
enums_1.GlobalPermission.SUSPEND_ACCOUNT,
|
|
36
|
-
// Cloud cost data
|
|
37
|
-
enums_1.GlobalPermission.VIEW_ANY_COST_DATA,
|
|
38
|
-
enums_1.GlobalPermission.EXPORT_ANY_COST_DATA,
|
|
39
|
-
enums_1.GlobalPermission.MANAGE_COST_ALERTS,
|
|
40
|
-
// Platform operations
|
|
41
|
-
enums_1.GlobalPermission.VIEW_SYSTEM_LOGS,
|
|
42
|
-
enums_1.GlobalPermission.VIEW_SYSTEM_HEALTH,
|
|
43
|
-
// Support tooling
|
|
44
|
-
enums_1.GlobalPermission.SUPPORT_VIEW,
|
|
45
|
-
enums_1.GlobalPermission.SUPPORT_WRITE,
|
|
46
|
-
],
|
|
47
|
-
[enums_1.AccountRole.SUPPORT]: [
|
|
48
|
-
// Read-only access to user data and cost data for support purposes.
|
|
49
|
-
// Cannot write, cannot manage accounts, cannot access system internals.
|
|
50
|
-
enums_1.GlobalPermission.VIEW_ANY_ACCOUNT,
|
|
51
|
-
enums_1.GlobalPermission.VIEW_ANY_COST_DATA,
|
|
52
|
-
enums_1.GlobalPermission.SUPPORT_VIEW,
|
|
53
|
-
],
|
|
54
|
-
[enums_1.AccountRole.MODERATOR]: [
|
|
55
|
-
// Content moderation only — no access to user accounts or cost data.
|
|
56
|
-
enums_1.GlobalPermission.MODERATE_CONTENT,
|
|
57
|
-
],
|
|
58
|
-
// Standard users carry zero global permissions.
|
|
59
|
-
// All their data access is gated at the service layer
|
|
60
|
-
// (they can only see their own cost data, their own account, etc.)
|
|
61
|
-
[enums_1.AccountRole.USER]: [],
|
|
62
|
-
};
|
|
63
|
-
/**
|
|
64
|
-
* ORG ROLE PERMISSION MAP (future work)
|
|
65
|
-
* ────────────────────────────────────────
|
|
66
|
-
* Org permissions are NOT embedded in the JWT — they're fetched from the DB
|
|
67
|
-
* by tenant middleware within each service because:
|
|
68
|
-
* 1. Org membership changes frequently (invites, role changes, removal)
|
|
69
|
-
* 2. A user can be in multiple orgs with different roles
|
|
70
|
-
* 3. Embedding them in the JWT would create stale permission windows
|
|
71
|
-
*
|
|
72
|
-
* This map is used by tenant middleware for the DB-free fast path:
|
|
73
|
-
* if the org role is already known (e.g. from a prior DB fetch cached in
|
|
74
|
-
* Redis), derive permissions here rather than re-querying the DB.
|
|
75
|
-
*
|
|
76
|
-
* NOTE: All entries use OrgPermission values only — no cross-namespace mixing
|
|
77
|
-
* with GlobalPermission. Org admin membership management is expressed via
|
|
78
|
-
* the dedicated OrgPermission.* values below.
|
|
79
|
-
*/
|
|
80
|
-
exports.orgRolePermissions = {
|
|
81
|
-
[enums_1.OrganizationRole.OWNER]: Object.values(enums_1.OrgPermission), // Full org control
|
|
82
|
-
[enums_1.OrganizationRole.ADMIN]: [
|
|
83
|
-
enums_1.OrgPermission.MANAGE_ORG_SETTINGS,
|
|
84
|
-
enums_1.OrgPermission.INVITE_MEMBERS,
|
|
85
|
-
enums_1.OrgPermission.REMOVE_MEMBERS,
|
|
86
|
-
enums_1.OrgPermission.CHANGE_MEMBER_ROLES,
|
|
87
|
-
enums_1.OrgPermission.VIEW_MEMBERS,
|
|
88
|
-
enums_1.OrgPermission.VIEW_COST_DATA,
|
|
89
|
-
enums_1.OrgPermission.EXPORT_COST_DATA,
|
|
90
|
-
enums_1.OrgPermission.MANAGE_COST_REPORTS,
|
|
91
|
-
enums_1.OrgPermission.MANAGE_ALERTS,
|
|
92
|
-
enums_1.OrgPermission.MANAGE_CLOUD_ACCOUNTS,
|
|
93
|
-
enums_1.OrgPermission.VIEW_CLOUD_ACCOUNTS,
|
|
94
|
-
enums_1.OrgPermission.VIEW_BILLING,
|
|
95
|
-
],
|
|
96
|
-
[enums_1.OrganizationRole.EDITOR]: [
|
|
97
|
-
enums_1.OrgPermission.VIEW_MEMBERS,
|
|
98
|
-
enums_1.OrgPermission.VIEW_COST_DATA,
|
|
99
|
-
enums_1.OrgPermission.EXPORT_COST_DATA,
|
|
100
|
-
enums_1.OrgPermission.MANAGE_COST_REPORTS,
|
|
101
|
-
enums_1.OrgPermission.MANAGE_ALERTS,
|
|
102
|
-
enums_1.OrgPermission.VIEW_CLOUD_ACCOUNTS,
|
|
103
|
-
],
|
|
104
|
-
[enums_1.OrganizationRole.VIEWER]: [
|
|
105
|
-
enums_1.OrgPermission.VIEW_MEMBERS,
|
|
106
|
-
enums_1.OrgPermission.VIEW_COST_DATA,
|
|
107
|
-
enums_1.OrgPermission.VIEW_CLOUD_ACCOUNTS,
|
|
108
|
-
],
|
|
109
|
-
[enums_1.OrganizationRole.BILLING]: [
|
|
110
|
-
enums_1.OrgPermission.VIEW_BILLING,
|
|
111
|
-
enums_1.OrgPermission.MANAGE_BILLING,
|
|
112
|
-
enums_1.OrgPermission.VIEW_COST_DATA,
|
|
113
|
-
enums_1.OrgPermission.EXPORT_COST_DATA,
|
|
114
|
-
],
|
|
115
|
-
};
|
|
116
|
-
/* ====================================================================
|
|
117
|
-
PERMISSION CHECK HELPERS
|
|
118
|
-
==================================================================== */
|
|
119
|
-
/**
|
|
120
|
-
* hasGlobalPermission
|
|
121
|
-
* Checks if a GlobalPermission is present in the AccessContext's perms array.
|
|
122
|
-
* Perms are populated from the Redis permission cache (derived from the role
|
|
123
|
-
* map on a miss) — never read directly from the JWT.
|
|
124
|
-
*
|
|
125
|
-
* Machine contexts always return false — they carry no user permissions.
|
|
126
|
-
*/
|
|
127
|
-
const hasGlobalPermission = (ctx, permission) => {
|
|
128
|
-
if (ctx.kind !== "human")
|
|
129
|
-
return false;
|
|
130
|
-
return ctx.perms.includes(permission);
|
|
131
|
-
};
|
|
132
|
-
exports.hasGlobalPermission = hasGlobalPermission;
|
|
133
|
-
/**
|
|
134
|
-
* isAllowed
|
|
135
|
-
* Unified permission check for both GlobalPermission and OrgPermission.
|
|
136
|
-
*
|
|
137
|
-
* GlobalPermission → checked against ctx.perms (Redis cache, derived from role)
|
|
138
|
-
* OrgPermission → intentionally returns false here; handled by tenant
|
|
139
|
-
* middleware in each service via DB lookup. This function
|
|
140
|
-
* is not the place for org permission checks — the boundary
|
|
141
|
-
* is explicit by design.
|
|
142
|
-
*/
|
|
143
|
-
const isAllowed = (ctx, permission) => {
|
|
144
|
-
if (ctx.kind !== "human")
|
|
145
|
-
return false;
|
|
146
|
-
if (Object.values(enums_1.GlobalPermission).includes(permission)) {
|
|
147
|
-
return (0, exports.hasGlobalPermission)(ctx, permission);
|
|
148
|
-
}
|
|
149
|
-
// OrgPermission — not handled here by design.
|
|
150
|
-
// Use tenant middleware (DB-backed) within the relevant service.
|
|
151
|
-
return false;
|
|
152
|
-
};
|
|
153
|
-
exports.isAllowed = isAllowed;
|
|
154
|
-
/**
|
|
155
|
-
* isSuperAdmin
|
|
156
|
-
* Convenience guard for cases where superadmin-only logic is needed
|
|
157
|
-
* beyond a single permission check (e.g. bypassing org scoping entirely).
|
|
158
|
-
*/
|
|
159
|
-
const isSuperAdmin = (ctx) => {
|
|
160
|
-
if (ctx.kind !== "human")
|
|
161
|
-
return false;
|
|
162
|
-
return ctx.accountRole === enums_1.AccountRole.SUPERADMIN;
|
|
163
|
-
};
|
|
164
|
-
exports.isSuperAdmin = isSuperAdmin;
|
|
165
|
-
/**
|
|
166
|
-
* deriveGlobalPermissions
|
|
167
|
-
* Derives permissions for a role from the static map.
|
|
168
|
-
* Called by PermissionCacheService on a cold cache miss.
|
|
169
|
-
* Returns a mutable copy — callers can extend it if needed.
|
|
170
|
-
*/
|
|
171
|
-
const deriveGlobalPermissions = (role) => {
|
|
172
|
-
return [...(exports.globalRolePermissions[role] ?? [])];
|
|
173
|
-
};
|
|
174
|
-
exports.deriveGlobalPermissions = deriveGlobalPermissions;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { AccountRole, OrganizationRole, MembershipStatus } from "../enums";
|
|
2
|
-
export interface AccessContext {
|
|
3
|
-
accountId: string;
|
|
4
|
-
accountRole: AccountRole;
|
|
5
|
-
membership?: {
|
|
6
|
-
organizationId: string;
|
|
7
|
-
role: OrganizationRole;
|
|
8
|
-
status: MembershipStatus;
|
|
9
|
-
};
|
|
10
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { AccountStatus } from "../enums";
|
|
2
|
-
export interface AccountDto {
|
|
3
|
-
id: string;
|
|
4
|
-
email: string;
|
|
5
|
-
isVerified: boolean;
|
|
6
|
-
status: AccountStatus;
|
|
7
|
-
createdAt: Date;
|
|
8
|
-
updatedAt: Date;
|
|
9
|
-
}
|
|
10
|
-
export interface EmailVerificationDto {
|
|
11
|
-
id: string;
|
|
12
|
-
accountId: string;
|
|
13
|
-
token: string;
|
|
14
|
-
expiresAt: Date;
|
|
15
|
-
isUsed: boolean;
|
|
16
|
-
createdAt: Date;
|
|
17
|
-
}
|
|
18
|
-
export interface OAuthIdentityDto {
|
|
19
|
-
id: string;
|
|
20
|
-
accountId: string;
|
|
21
|
-
provider: string;
|
|
22
|
-
providerId: string;
|
|
23
|
-
createdAt: Date;
|
|
24
|
-
updatedAt: Date;
|
|
25
|
-
}
|
|
26
|
-
export interface PasswordResetDto {
|
|
27
|
-
id: string;
|
|
28
|
-
accountId: string;
|
|
29
|
-
token: string;
|
|
30
|
-
expiresAt: Date;
|
|
31
|
-
isUsed: boolean;
|
|
32
|
-
createdAt: Date;
|
|
33
|
-
}
|
|
34
|
-
export interface SessionDto {
|
|
35
|
-
id: string;
|
|
36
|
-
accountId: string;
|
|
37
|
-
expiresAt: Date;
|
|
38
|
-
revokedAt: Date | null;
|
|
39
|
-
ipAddress: string | null;
|
|
40
|
-
userAgent: string | null;
|
|
41
|
-
device: string | null;
|
|
42
|
-
createdAt: Date;
|
|
43
|
-
updatedAt: Date;
|
|
44
|
-
}
|
package/dist/dto/index.d.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
export interface ApiMeta {
|
|
2
|
-
requestId: string;
|
|
3
|
-
timestamp: string;
|
|
4
|
-
}
|
|
5
|
-
export interface ApiSuccessResponse<T> {
|
|
6
|
-
success: true;
|
|
7
|
-
data: T;
|
|
8
|
-
meta: ApiMeta;
|
|
9
|
-
}
|
|
10
|
-
export interface ApiErrorResponse {
|
|
11
|
-
success: false;
|
|
12
|
-
error: {
|
|
13
|
-
code: string;
|
|
14
|
-
message: string;
|
|
15
|
-
details?: unknown;
|
|
16
|
-
};
|
|
17
|
-
meta: ApiMeta;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Returned on login / token refresh.
|
|
21
|
-
* accessToken goes in the response body.
|
|
22
|
-
* refreshToken goes in an HttpOnly cookie — not in the body.
|
|
23
|
-
* Only include refreshToken here if your client explicitly needs it
|
|
24
|
-
* (e.g. mobile clients that can't use cookies).
|
|
25
|
-
*/
|
|
26
|
-
export interface TokensResponseDto {
|
|
27
|
-
accessToken: string;
|
|
28
|
-
}
|
|
29
|
-
/** Generic message — use for simple confirmations */
|
|
30
|
-
export interface MessageResponseDto {
|
|
31
|
-
message: string;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Generic action confirmation — use when the client needs to know
|
|
35
|
-
* which action was performed (e.g. in a polling or event-driven context).
|
|
36
|
-
*
|
|
37
|
-
* action: machine-readable string, e.g. "account.suspended", "session.revoked"
|
|
38
|
-
* message: human-readable description
|
|
39
|
-
*/
|
|
40
|
-
export interface ActionResponseDto {
|
|
41
|
-
action: string;
|
|
42
|
-
message: string;
|
|
43
|
-
}
|
|
44
|
-
export interface PaginationMeta {
|
|
45
|
-
page: number;
|
|
46
|
-
pageSize: number;
|
|
47
|
-
totalItems: number;
|
|
48
|
-
totalPages: number;
|
|
49
|
-
hasNext: boolean;
|
|
50
|
-
hasPrev: boolean;
|
|
51
|
-
}
|
|
52
|
-
export interface PaginatedResponseDto<T> {
|
|
53
|
-
items: T[];
|
|
54
|
-
pagination: PaginationMeta;
|
|
55
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/* ====================================================================
|
|
3
|
-
RESPONSE DTOs
|
|
4
|
-
Shared HTTP response shapes used across all services.
|
|
5
|
-
==================================================================== */
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { AccountStatus, OrganizationRole, MembershipStatus, OrganizationStatus, Theme, Currency } from "../enums";
|
|
2
|
-
export interface UserDto {
|
|
3
|
-
id: string;
|
|
4
|
-
email: string;
|
|
5
|
-
status: AccountStatus;
|
|
6
|
-
createdAt: Date;
|
|
7
|
-
updatedAt: Date;
|
|
8
|
-
}
|
|
9
|
-
export interface UserProfileDto {
|
|
10
|
-
id: string;
|
|
11
|
-
userId: string;
|
|
12
|
-
displayName: string | null;
|
|
13
|
-
avatarUrl: string | null;
|
|
14
|
-
jobTitle: string | null;
|
|
15
|
-
bio: string | null;
|
|
16
|
-
timezone: string | null;
|
|
17
|
-
locale: string;
|
|
18
|
-
country: string | null;
|
|
19
|
-
pronouns: string | null;
|
|
20
|
-
createdAt: Date;
|
|
21
|
-
updatedAt: Date;
|
|
22
|
-
}
|
|
23
|
-
export interface UserPreferencesDto {
|
|
24
|
-
id: string;
|
|
25
|
-
userId: string;
|
|
26
|
-
theme: Theme;
|
|
27
|
-
language: string;
|
|
28
|
-
currency: Currency;
|
|
29
|
-
emailAlerts: boolean;
|
|
30
|
-
createdAt: Date;
|
|
31
|
-
updatedAt: Date;
|
|
32
|
-
}
|
|
33
|
-
export interface OrganizationDto {
|
|
34
|
-
id: string;
|
|
35
|
-
name: string;
|
|
36
|
-
slug: string;
|
|
37
|
-
status: OrganizationStatus;
|
|
38
|
-
createdAt: Date;
|
|
39
|
-
updatedAt: Date;
|
|
40
|
-
deletedAt: Date | null;
|
|
41
|
-
}
|
|
42
|
-
export interface OrganizationMemberDto {
|
|
43
|
-
id: string;
|
|
44
|
-
userId: string;
|
|
45
|
-
organizationId: string;
|
|
46
|
-
role: OrganizationRole;
|
|
47
|
-
status: MembershipStatus;
|
|
48
|
-
createdAt: Date;
|
|
49
|
-
updatedAt: Date;
|
|
50
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export declare enum AccountStatus {
|
|
2
|
-
ACTIVE = "ACTIVE",
|
|
3
|
-
SUSPENDED = "SUSPENDED",
|
|
4
|
-
DELETED = "DELETED"
|
|
5
|
-
}
|
|
6
|
-
export declare enum AccountRole {
|
|
7
|
-
SUPERADMIN = "SUPERADMIN",
|
|
8
|
-
ADMIN = "ADMIN",
|
|
9
|
-
SUPPORT = "SUPPORT",
|
|
10
|
-
MODERATOR = "MODERATOR",
|
|
11
|
-
USER = "USER"
|
|
12
|
-
}
|