@discover-cloud/shared 1.0.0 → 1.0.1
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 -0
- package/dist/authorization/index.js +18 -0
- package/dist/authorization/permission-cache.service.d.ts +12 -0
- package/dist/authorization/permission-cache.service.js +132 -0
- package/dist/authorization/permissions.d.ts +74 -0
- package/dist/authorization/permissions.js +171 -0
- package/dist/dto/auth-service.dtos.d.ts +5 -12
- package/dist/dto/response.dtos.d.ts +34 -27
- package/dist/dto/response.dtos.js +4 -0
- package/dist/dto/user-service.dtos.d.ts +9 -13
- package/dist/enums/domain.enums.d.ts +42 -0
- package/dist/enums/domain.enums.js +79 -0
- package/dist/enums/index.d.ts +2 -3
- package/dist/enums/index.js +2 -3
- package/dist/enums/permissions.enums.d.ts +124 -0
- package/dist/enums/permissions.enums.js +141 -0
- package/dist/errors/app-error.d.ts +19 -2
- package/dist/errors/app-error.js +17 -2
- package/dist/errors/http-errors.d.ts +18 -0
- package/dist/errors/http-errors.js +25 -1
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +17 -0
- package/dist/http/service-client.d.ts +23 -7
- package/dist/http/service-client.js +54 -22
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/jwt/index.d.ts +1 -2
- package/dist/jwt/index.js +1 -2
- package/dist/jwt/internal-jwt-verifier.d.ts +35 -0
- package/dist/jwt/internal-jwt-verifier.js +162 -0
- package/dist/middleware/authorize.middleware.d.ts +22 -0
- package/dist/middleware/authorize.middleware.js +77 -0
- package/dist/middleware/error-handler.middleware.d.ts +23 -0
- package/dist/middleware/error-handler.middleware.js +52 -0
- package/dist/middleware/index.d.ts +4 -5
- package/dist/middleware/index.js +4 -5
- package/dist/middleware/request-id.middleware.d.ts +20 -0
- package/dist/middleware/request-id.middleware.js +34 -0
- package/dist/middleware/validate.middleware.d.ts +26 -0
- package/dist/middleware/validate.middleware.js +41 -0
- package/dist/types/express.types.d.ts +148 -0
- package/dist/types/express.types.js +42 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -1
- package/dist/utils/response.d.ts +2 -1
- package/dist/utils/response.js +6 -3
- package/package.json +3 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./permissions"), exports);
|
|
18
|
+
__exportStar(require("./permission-cache.service"), exports);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { GlobalPermission, AccountRole } from "../enums";
|
|
2
|
+
export declare class PermissionCacheService {
|
|
3
|
+
private readonly client;
|
|
4
|
+
private connected;
|
|
5
|
+
constructor();
|
|
6
|
+
resolve(accountId: string, accountRole: AccountRole): Promise<GlobalPermission[]>;
|
|
7
|
+
invalidate(accountId: string): Promise<void>;
|
|
8
|
+
invalidateMany(accountIds: string[]): Promise<void>;
|
|
9
|
+
private key;
|
|
10
|
+
private safeGet;
|
|
11
|
+
private safeSet;
|
|
12
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PermissionCacheService = void 0;
|
|
4
|
+
const redis_1 = require("redis");
|
|
5
|
+
const permissions_1 = require("./permissions");
|
|
6
|
+
/**
|
|
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.
|
|
21
|
+
*
|
|
22
|
+
* Invalidation triggers:
|
|
23
|
+
* - Role change → invalidate(accountId)
|
|
24
|
+
* - Suspension → invalidate(accountId)
|
|
25
|
+
* - Account deletion → invalidate(accountId)
|
|
26
|
+
*
|
|
27
|
+
* NOTE: Uses console.* for logging — shared cannot depend on a service-specific
|
|
28
|
+
* logger (pino, winston, etc.). Each service's own logger will capture these
|
|
29
|
+
* via stdout in production.
|
|
30
|
+
*/
|
|
31
|
+
const CACHE_TTL_SECONDS = 24 * 60 * 60; // 24 hours
|
|
32
|
+
const KEY_PREFIX = "perms";
|
|
33
|
+
class PermissionCacheService {
|
|
34
|
+
constructor() {
|
|
35
|
+
this.connected = false;
|
|
36
|
+
this.client = (0, redis_1.createClient)({
|
|
37
|
+
url: process.env.REDIS_URL ?? "redis://localhost:6379",
|
|
38
|
+
});
|
|
39
|
+
this.client.on("error", (err) => {
|
|
40
|
+
console.error("[PermissionCache] Redis client error", err);
|
|
41
|
+
});
|
|
42
|
+
this.client.on("ready", () => {
|
|
43
|
+
this.connected = true;
|
|
44
|
+
console.info("[PermissionCache] Redis connected");
|
|
45
|
+
});
|
|
46
|
+
this.client.connect().catch((err) => {
|
|
47
|
+
console.error("[PermissionCache] Redis connection failed", err);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/* ----------------------------------------------------------------
|
|
51
|
+
resolve
|
|
52
|
+
Returns permissions for an account, loading from cache or deriving
|
|
53
|
+
from the role map on a miss.
|
|
54
|
+
|
|
55
|
+
Never throws — on any Redis failure it falls back to deriving
|
|
56
|
+
from the role map directly (degraded but functional).
|
|
57
|
+
---------------------------------------------------------------- */
|
|
58
|
+
async resolve(accountId, accountRole) {
|
|
59
|
+
const cached = await this.safeGet(accountId);
|
|
60
|
+
if (cached !== null)
|
|
61
|
+
return cached;
|
|
62
|
+
// Cache miss — derive from role map (no DB call)
|
|
63
|
+
const perms = [
|
|
64
|
+
...(permissions_1.globalRolePermissions[accountRole] ?? []),
|
|
65
|
+
];
|
|
66
|
+
// Write back (fire-and-forget — don't block the request)
|
|
67
|
+
this.safeSet(accountId, perms).catch(() => { });
|
|
68
|
+
return perms;
|
|
69
|
+
}
|
|
70
|
+
/* ----------------------------------------------------------------
|
|
71
|
+
invalidate
|
|
72
|
+
Removes a specific account's permission cache entry.
|
|
73
|
+
Call on: role change, suspension, account deletion.
|
|
74
|
+
---------------------------------------------------------------- */
|
|
75
|
+
async invalidate(accountId) {
|
|
76
|
+
try {
|
|
77
|
+
await this.client.del(this.key(accountId));
|
|
78
|
+
console.info(`[PermissionCache] Invalidated: ${accountId}`);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
// Log but don't throw — stale entry expires via TTL at worst
|
|
82
|
+
console.error(`[PermissionCache] Failed to invalidate: ${accountId}`, err);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/* ----------------------------------------------------------------
|
|
86
|
+
invalidateMany
|
|
87
|
+
Batch invalidation — use when a role definition changes globally.
|
|
88
|
+
---------------------------------------------------------------- */
|
|
89
|
+
async invalidateMany(accountIds) {
|
|
90
|
+
if (accountIds.length === 0)
|
|
91
|
+
return;
|
|
92
|
+
try {
|
|
93
|
+
const keys = accountIds.map((id) => this.key(id));
|
|
94
|
+
await this.client.del(keys);
|
|
95
|
+
console.info(`[PermissionCache] Batch invalidated ${accountIds.length} entries`);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
console.error("[PermissionCache] Batch invalidation failed", err);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/* ----------------------------------------------------------------
|
|
102
|
+
Private helpers
|
|
103
|
+
---------------------------------------------------------------- */
|
|
104
|
+
key(accountId) {
|
|
105
|
+
return `${KEY_PREFIX}:${accountId}`;
|
|
106
|
+
}
|
|
107
|
+
async safeGet(accountId) {
|
|
108
|
+
if (!this.connected)
|
|
109
|
+
return null;
|
|
110
|
+
try {
|
|
111
|
+
const raw = await this.client.get(this.key(accountId));
|
|
112
|
+
if (!raw)
|
|
113
|
+
return null;
|
|
114
|
+
return JSON.parse(raw);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
console.warn(`[PermissionCache] Cache get failed for ${accountId}, bypassing`, err);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async safeSet(accountId, perms) {
|
|
122
|
+
if (!this.connected)
|
|
123
|
+
return;
|
|
124
|
+
try {
|
|
125
|
+
await this.client.set(this.key(accountId), JSON.stringify(perms), { EX: CACHE_TTL_SECONDS });
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
console.warn(`[PermissionCache] Cache set failed for ${accountId}`, err);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
exports.PermissionCacheService = PermissionCacheService;
|
|
@@ -0,0 +1,74 @@
|
|
|
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, not hardcoded list)
|
|
12
|
+
* - Each role has only the minimum permissions it needs (least privilege)
|
|
13
|
+
* - Permissions are additive — no role inherits from another at runtime
|
|
14
|
+
* - Adding a new permission: add to the enum above, then grant it here
|
|
15
|
+
*
|
|
16
|
+
* ┌─────────────┬──────────────────────────────────────────────────────┐
|
|
17
|
+
* │ Role │ Key permissions │
|
|
18
|
+
* ├─────────────┼──────────────────────────────────────────────────────┤
|
|
19
|
+
* │ SUPERADMIN │ Everything — full platform control │
|
|
20
|
+
* │ ADMIN │ User mgmt, cost data access, logs, health, support │
|
|
21
|
+
* │ SUPPORT │ Read-only user + cost data for support work │
|
|
22
|
+
* │ MODERATOR │ Content moderation only (future) │
|
|
23
|
+
* │ USER │ No global permissions — service layer gates own data │
|
|
24
|
+
* └─────────────┴──────────────────────────────────────────────────────┘
|
|
25
|
+
*/
|
|
26
|
+
export declare const globalRolePermissions: Record<AccountRole, readonly GlobalPermission[]>;
|
|
27
|
+
/**
|
|
28
|
+
* ORG ROLE PERMISSION MAP (future work)
|
|
29
|
+
* ────────────────────────────────────────
|
|
30
|
+
* Org permissions are NOT in the JWT — they're fetched from the DB
|
|
31
|
+
* by tenant middleware within each service because:
|
|
32
|
+
* 1. Org membership changes frequently (invites, role changes, removal)
|
|
33
|
+
* 2. A user can be in multiple orgs with different roles
|
|
34
|
+
* 3. Embedding them in the JWT would create stale permission windows
|
|
35
|
+
*
|
|
36
|
+
* This map is used by tenant middleware for the DB-free fast path:
|
|
37
|
+
* if the org role is already known (e.g. from a prior DB fetch cached in
|
|
38
|
+
* Redis), derive permissions from here rather than re-querying the DB.
|
|
39
|
+
*/
|
|
40
|
+
export declare const orgRolePermissions: Record<OrganizationRole, readonly OrgPermission[]>;
|
|
41
|
+
/**
|
|
42
|
+
* hasGlobalPermission
|
|
43
|
+
* Checks if a GlobalPermission is in the AccessContext's perms array.
|
|
44
|
+
* Perms come from the Redis permission cache, not from the JWT directly.
|
|
45
|
+
*
|
|
46
|
+
* This is the primary check for platform-level authorization.
|
|
47
|
+
*/
|
|
48
|
+
export declare const hasGlobalPermission: (ctx: AccessContext, permission: GlobalPermission) => boolean;
|
|
49
|
+
/**
|
|
50
|
+
* isAllowed
|
|
51
|
+
* Unified permission check for both GlobalPermission and OrgPermission.
|
|
52
|
+
*
|
|
53
|
+
* GlobalPermission → checked against ctx.perms (Redis cache, derived from role)
|
|
54
|
+
* OrgPermission → returns false here; handled by tenant middleware in each
|
|
55
|
+
* service via DB lookup. This function is intentionally not
|
|
56
|
+
* the place for org permission checks.
|
|
57
|
+
*
|
|
58
|
+
* This makes the boundary explicit: callers that need org permission checks
|
|
59
|
+
* should use their service's tenant middleware, not isAllowed().
|
|
60
|
+
*/
|
|
61
|
+
export declare const isAllowed: (ctx: AccessContext, permission: GlobalPermission | OrgPermission) => boolean;
|
|
62
|
+
/**
|
|
63
|
+
* isSuperAdmin
|
|
64
|
+
* Convenience guard for the rare cases where superadmin-only logic
|
|
65
|
+
* is needed beyond a single permission check.
|
|
66
|
+
*/
|
|
67
|
+
export declare const isSuperAdmin: (ctx: AccessContext) => boolean;
|
|
68
|
+
/**
|
|
69
|
+
* deriveGlobalPermissions
|
|
70
|
+
* Derives permissions for a role from the static map.
|
|
71
|
+
* Called by PermissionCacheService on a cold cache miss.
|
|
72
|
+
* Returns a mutable copy — callers can extend it if needed.
|
|
73
|
+
*/
|
|
74
|
+
export declare const deriveGlobalPermissions: (role: AccountRole) => GlobalPermission[];
|
|
@@ -0,0 +1,171 @@
|
|
|
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, not hardcoded list)
|
|
14
|
+
* - Each role has only the minimum permissions it needs (least privilege)
|
|
15
|
+
* - Permissions are additive — no role inherits from another at runtime
|
|
16
|
+
* - Adding a new permission: add to the enum above, then grant it here
|
|
17
|
+
*
|
|
18
|
+
* ┌─────────────┬──────────────────────────────────────────────────────┐
|
|
19
|
+
* │ Role │ Key permissions │
|
|
20
|
+
* ├─────────────┼──────────────────────────────────────────────────────┤
|
|
21
|
+
* │ SUPERADMIN │ Everything — full platform control │
|
|
22
|
+
* │ ADMIN │ User mgmt, cost data access, logs, health, support │
|
|
23
|
+
* │ SUPPORT │ Read-only user + cost data for support work │
|
|
24
|
+
* │ MODERATOR │ Content moderation only (future) │
|
|
25
|
+
* │ USER │ No global permissions — service layer gates own data │
|
|
26
|
+
* └─────────────┴──────────────────────────────────────────────────────┘
|
|
27
|
+
*/
|
|
28
|
+
exports.globalRolePermissions = {
|
|
29
|
+
[enums_1.AccountRole.SUPERADMIN]: Object.values(enums_1.GlobalPermission),
|
|
30
|
+
[enums_1.AccountRole.ADMIN]: [
|
|
31
|
+
// Account management (no impersonate, no hard delete)
|
|
32
|
+
enums_1.GlobalPermission.MANAGE_ACCOUNTS,
|
|
33
|
+
enums_1.GlobalPermission.VIEW_ANY_ACCOUNT,
|
|
34
|
+
enums_1.GlobalPermission.SUSPEND_ACCOUNT,
|
|
35
|
+
// Cloud cost data
|
|
36
|
+
enums_1.GlobalPermission.VIEW_ANY_COST_DATA,
|
|
37
|
+
enums_1.GlobalPermission.EXPORT_ANY_COST_DATA,
|
|
38
|
+
enums_1.GlobalPermission.MANAGE_COST_ALERTS,
|
|
39
|
+
// Platform operations
|
|
40
|
+
enums_1.GlobalPermission.VIEW_SYSTEM_LOGS,
|
|
41
|
+
enums_1.GlobalPermission.VIEW_SYSTEM_HEALTH,
|
|
42
|
+
// Support tooling
|
|
43
|
+
enums_1.GlobalPermission.SUPPORT_VIEW,
|
|
44
|
+
enums_1.GlobalPermission.SUPPORT_WRITE,
|
|
45
|
+
],
|
|
46
|
+
[enums_1.AccountRole.SUPPORT]: [
|
|
47
|
+
// Read-only access to user data and cost data for support purposes
|
|
48
|
+
// Cannot write, cannot manage accounts, cannot access system internals
|
|
49
|
+
enums_1.GlobalPermission.VIEW_ANY_ACCOUNT,
|
|
50
|
+
enums_1.GlobalPermission.VIEW_ANY_COST_DATA,
|
|
51
|
+
enums_1.GlobalPermission.SUPPORT_VIEW,
|
|
52
|
+
],
|
|
53
|
+
[enums_1.AccountRole.MODERATOR]: [
|
|
54
|
+
// Only content moderation — no access to user accounts or cost data
|
|
55
|
+
enums_1.GlobalPermission.MODERATE_CONTENT,
|
|
56
|
+
],
|
|
57
|
+
// Standard users have zero global permissions.
|
|
58
|
+
// All their data access is handled at the service level
|
|
59
|
+
// (they can only see their own cost data, their own account, etc.)
|
|
60
|
+
[enums_1.AccountRole.USER]: [],
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* ORG ROLE PERMISSION MAP (future work)
|
|
64
|
+
* ────────────────────────────────────────
|
|
65
|
+
* Org permissions are NOT in the JWT — they're fetched from the DB
|
|
66
|
+
* by tenant middleware within each service because:
|
|
67
|
+
* 1. Org membership changes frequently (invites, role changes, removal)
|
|
68
|
+
* 2. A user can be in multiple orgs with different roles
|
|
69
|
+
* 3. Embedding them in the JWT would create stale permission windows
|
|
70
|
+
*
|
|
71
|
+
* This map is used by tenant middleware for the DB-free fast path:
|
|
72
|
+
* if the org role is already known (e.g. from a prior DB fetch cached in
|
|
73
|
+
* Redis), derive permissions from here rather than re-querying the DB.
|
|
74
|
+
*/
|
|
75
|
+
exports.orgRolePermissions = {
|
|
76
|
+
[enums_1.OrganizationRole.OWNER]: Object.values(enums_1.OrgPermission), // Full org control
|
|
77
|
+
[enums_1.OrganizationRole.ADMIN]: [
|
|
78
|
+
enums_1.GlobalPermission.MANAGE_ACCOUNTS, // intentional re-use shape — org admin manages org members
|
|
79
|
+
enums_1.OrgPermission.MANAGE_ORG_SETTINGS,
|
|
80
|
+
enums_1.OrgPermission.INVITE_MEMBERS,
|
|
81
|
+
enums_1.OrgPermission.REMOVE_MEMBERS,
|
|
82
|
+
enums_1.OrgPermission.CHANGE_MEMBER_ROLES,
|
|
83
|
+
enums_1.OrgPermission.VIEW_MEMBERS,
|
|
84
|
+
enums_1.OrgPermission.VIEW_COST_DATA,
|
|
85
|
+
enums_1.OrgPermission.EXPORT_COST_DATA,
|
|
86
|
+
enums_1.OrgPermission.MANAGE_COST_REPORTS,
|
|
87
|
+
enums_1.OrgPermission.MANAGE_ALERTS,
|
|
88
|
+
enums_1.OrgPermission.MANAGE_CLOUD_ACCOUNTS,
|
|
89
|
+
enums_1.OrgPermission.VIEW_CLOUD_ACCOUNTS,
|
|
90
|
+
enums_1.OrgPermission.VIEW_BILLING,
|
|
91
|
+
],
|
|
92
|
+
[enums_1.OrganizationRole.EDITOR]: [
|
|
93
|
+
enums_1.OrgPermission.VIEW_MEMBERS,
|
|
94
|
+
enums_1.OrgPermission.VIEW_COST_DATA,
|
|
95
|
+
enums_1.OrgPermission.EXPORT_COST_DATA,
|
|
96
|
+
enums_1.OrgPermission.MANAGE_COST_REPORTS,
|
|
97
|
+
enums_1.OrgPermission.MANAGE_ALERTS,
|
|
98
|
+
enums_1.OrgPermission.VIEW_CLOUD_ACCOUNTS,
|
|
99
|
+
],
|
|
100
|
+
[enums_1.OrganizationRole.VIEWER]: [
|
|
101
|
+
enums_1.OrgPermission.VIEW_MEMBERS,
|
|
102
|
+
enums_1.OrgPermission.VIEW_COST_DATA,
|
|
103
|
+
enums_1.OrgPermission.VIEW_CLOUD_ACCOUNTS,
|
|
104
|
+
],
|
|
105
|
+
[enums_1.OrganizationRole.BILLING]: [
|
|
106
|
+
enums_1.OrgPermission.VIEW_BILLING,
|
|
107
|
+
enums_1.OrgPermission.MANAGE_BILLING,
|
|
108
|
+
enums_1.OrgPermission.VIEW_COST_DATA,
|
|
109
|
+
enums_1.OrgPermission.EXPORT_COST_DATA,
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
/* ====================================================================
|
|
113
|
+
PERMISSION CHECK HELPERS
|
|
114
|
+
==================================================================== */
|
|
115
|
+
/**
|
|
116
|
+
* hasGlobalPermission
|
|
117
|
+
* Checks if a GlobalPermission is in the AccessContext's perms array.
|
|
118
|
+
* Perms come from the Redis permission cache, not from the JWT directly.
|
|
119
|
+
*
|
|
120
|
+
* This is the primary check for platform-level authorization.
|
|
121
|
+
*/
|
|
122
|
+
const hasGlobalPermission = (ctx, permission) => {
|
|
123
|
+
if (ctx.kind !== "human")
|
|
124
|
+
return false;
|
|
125
|
+
return ctx.perms.includes(permission);
|
|
126
|
+
};
|
|
127
|
+
exports.hasGlobalPermission = hasGlobalPermission;
|
|
128
|
+
/**
|
|
129
|
+
* isAllowed
|
|
130
|
+
* Unified permission check for both GlobalPermission and OrgPermission.
|
|
131
|
+
*
|
|
132
|
+
* GlobalPermission → checked against ctx.perms (Redis cache, derived from role)
|
|
133
|
+
* OrgPermission → returns false here; handled by tenant middleware in each
|
|
134
|
+
* service via DB lookup. This function is intentionally not
|
|
135
|
+
* the place for org permission checks.
|
|
136
|
+
*
|
|
137
|
+
* This makes the boundary explicit: callers that need org permission checks
|
|
138
|
+
* should use their service's tenant middleware, not isAllowed().
|
|
139
|
+
*/
|
|
140
|
+
const isAllowed = (ctx, permission) => {
|
|
141
|
+
if (ctx.kind !== "human")
|
|
142
|
+
return false;
|
|
143
|
+
if (Object.values(enums_1.GlobalPermission).includes(permission)) {
|
|
144
|
+
return (0, exports.hasGlobalPermission)(ctx, permission);
|
|
145
|
+
}
|
|
146
|
+
// OrgPermission — not handled here by design.
|
|
147
|
+
// Use tenant middleware (DB-backed) within the relevant service.
|
|
148
|
+
return false;
|
|
149
|
+
};
|
|
150
|
+
exports.isAllowed = isAllowed;
|
|
151
|
+
/**
|
|
152
|
+
* isSuperAdmin
|
|
153
|
+
* Convenience guard for the rare cases where superadmin-only logic
|
|
154
|
+
* is needed beyond a single permission check.
|
|
155
|
+
*/
|
|
156
|
+
const isSuperAdmin = (ctx) => {
|
|
157
|
+
if (ctx.kind !== "human")
|
|
158
|
+
return false;
|
|
159
|
+
return ctx.accountRole === enums_1.AccountRole.SUPERADMIN;
|
|
160
|
+
};
|
|
161
|
+
exports.isSuperAdmin = isSuperAdmin;
|
|
162
|
+
/**
|
|
163
|
+
* deriveGlobalPermissions
|
|
164
|
+
* Derives permissions for a role from the static map.
|
|
165
|
+
* Called by PermissionCacheService on a cold cache miss.
|
|
166
|
+
* Returns a mutable copy — callers can extend it if needed.
|
|
167
|
+
*/
|
|
168
|
+
const deriveGlobalPermissions = (role) => {
|
|
169
|
+
return [...(exports.globalRolePermissions[role] ?? [])];
|
|
170
|
+
};
|
|
171
|
+
exports.deriveGlobalPermissions = deriveGlobalPermissions;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AccountStatus } from "../enums";
|
|
2
|
-
export interface
|
|
2
|
+
export interface AccountDto {
|
|
3
3
|
id: string;
|
|
4
4
|
email: string;
|
|
5
5
|
isVerified: boolean;
|
|
@@ -7,7 +7,7 @@ export interface AccountDTO {
|
|
|
7
7
|
createdAt: Date;
|
|
8
8
|
updatedAt: Date;
|
|
9
9
|
}
|
|
10
|
-
export interface
|
|
10
|
+
export interface EmailVerificationDto {
|
|
11
11
|
id: string;
|
|
12
12
|
accountId: string;
|
|
13
13
|
token: string;
|
|
@@ -15,17 +15,15 @@ export interface EmailVerificationDTO {
|
|
|
15
15
|
isUsed: boolean;
|
|
16
16
|
createdAt: Date;
|
|
17
17
|
}
|
|
18
|
-
export interface
|
|
18
|
+
export interface OAuthIdentityDto {
|
|
19
19
|
id: string;
|
|
20
20
|
accountId: string;
|
|
21
21
|
provider: string;
|
|
22
22
|
providerId: string;
|
|
23
|
-
accessToken: string | null;
|
|
24
|
-
refreshToken: string | null;
|
|
25
23
|
createdAt: Date;
|
|
26
24
|
updatedAt: Date;
|
|
27
25
|
}
|
|
28
|
-
export interface
|
|
26
|
+
export interface PasswordResetDto {
|
|
29
27
|
id: string;
|
|
30
28
|
accountId: string;
|
|
31
29
|
token: string;
|
|
@@ -33,10 +31,9 @@ export interface PasswordResetDTO {
|
|
|
33
31
|
isUsed: boolean;
|
|
34
32
|
createdAt: Date;
|
|
35
33
|
}
|
|
36
|
-
export interface
|
|
34
|
+
export interface SessionDto {
|
|
37
35
|
id: string;
|
|
38
36
|
accountId: string;
|
|
39
|
-
token: string;
|
|
40
37
|
expiresAt: Date;
|
|
41
38
|
revokedAt: Date | null;
|
|
42
39
|
ipAddress: string | null;
|
|
@@ -45,7 +42,3 @@ export interface SessionDTO {
|
|
|
45
42
|
createdAt: Date;
|
|
46
43
|
updatedAt: Date;
|
|
47
44
|
}
|
|
48
|
-
export interface TokensResponseDto {
|
|
49
|
-
accessToken: string;
|
|
50
|
-
refreshToken: string;
|
|
51
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface ApiMeta {
|
|
2
|
-
requestId: string
|
|
2
|
+
requestId: string;
|
|
3
3
|
timestamp: string;
|
|
4
4
|
}
|
|
5
5
|
export interface ApiSuccessResponse<T> {
|
|
@@ -10,39 +10,46 @@ export interface ApiSuccessResponse<T> {
|
|
|
10
10
|
export interface ApiErrorResponse {
|
|
11
11
|
success: false;
|
|
12
12
|
error: {
|
|
13
|
-
code
|
|
13
|
+
code: string;
|
|
14
14
|
message: string;
|
|
15
15
|
details?: unknown;
|
|
16
16
|
};
|
|
17
17
|
meta: ApiMeta;
|
|
18
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 */
|
|
19
30
|
export interface MessageResponseDto {
|
|
20
31
|
message: string;
|
|
21
32
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
activated: boolean;
|
|
33
|
-
}
|
|
34
|
-
export interface DeletedResponseDto {
|
|
35
|
-
deleted: boolean;
|
|
36
|
-
}
|
|
37
|
-
export interface RevokedResponseDto {
|
|
38
|
-
revoked: boolean;
|
|
39
|
-
}
|
|
40
|
-
export interface AddedResponseDto {
|
|
41
|
-
added: boolean;
|
|
42
|
-
}
|
|
43
|
-
export interface UpdatedResponseDto {
|
|
44
|
-
updated: boolean;
|
|
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;
|
|
45
43
|
}
|
|
46
|
-
export interface
|
|
47
|
-
|
|
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;
|
|
48
55
|
}
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/* ====================================================================
|
|
3
|
+
RESPONSE DTOs
|
|
4
|
+
Shared HTTP response shapes used across all services.
|
|
5
|
+
==================================================================== */
|
|
2
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { OrganizationRole, MembershipStatus, OrganizationStatus, Theme, Currency
|
|
2
|
-
export interface
|
|
1
|
+
import { AccountStatus, OrganizationRole, MembershipStatus, OrganizationStatus, Theme, Currency } from "../enums";
|
|
2
|
+
export interface UserDto {
|
|
3
3
|
id: string;
|
|
4
4
|
email: string;
|
|
5
|
-
status:
|
|
5
|
+
status: AccountStatus;
|
|
6
6
|
createdAt: Date;
|
|
7
7
|
updatedAt: Date;
|
|
8
|
-
deletedAt: Date | null;
|
|
9
8
|
}
|
|
10
|
-
export interface
|
|
11
|
-
id: string
|
|
9
|
+
export interface UserProfileDto {
|
|
10
|
+
id: string;
|
|
12
11
|
userId: string;
|
|
13
12
|
displayName: string | null;
|
|
14
13
|
avatarUrl: string | null;
|
|
@@ -20,10 +19,9 @@ export interface UserProfileDTO {
|
|
|
20
19
|
pronouns: string | null;
|
|
21
20
|
createdAt: Date;
|
|
22
21
|
updatedAt: Date;
|
|
23
|
-
deletedAt: Date | null;
|
|
24
22
|
}
|
|
25
|
-
export interface
|
|
26
|
-
id: string
|
|
23
|
+
export interface UserPreferencesDto {
|
|
24
|
+
id: string;
|
|
27
25
|
userId: string;
|
|
28
26
|
theme: Theme;
|
|
29
27
|
language: string;
|
|
@@ -31,9 +29,8 @@ export interface UserPreferencesDTO {
|
|
|
31
29
|
emailAlerts: boolean;
|
|
32
30
|
createdAt: Date;
|
|
33
31
|
updatedAt: Date;
|
|
34
|
-
deletedAt: Date | null;
|
|
35
32
|
}
|
|
36
|
-
export interface
|
|
33
|
+
export interface OrganizationDto {
|
|
37
34
|
id: string;
|
|
38
35
|
name: string;
|
|
39
36
|
slug: string;
|
|
@@ -42,7 +39,7 @@ export interface OrganizationDTO {
|
|
|
42
39
|
updatedAt: Date;
|
|
43
40
|
deletedAt: Date | null;
|
|
44
41
|
}
|
|
45
|
-
export interface
|
|
42
|
+
export interface OrganizationMemberDto {
|
|
46
43
|
id: string;
|
|
47
44
|
userId: string;
|
|
48
45
|
organizationId: string;
|
|
@@ -50,5 +47,4 @@ export interface OrganizationMemberDTO {
|
|
|
50
47
|
status: MembershipStatus;
|
|
51
48
|
createdAt: Date;
|
|
52
49
|
updatedAt: Date;
|
|
53
|
-
deletedAt: Date | null;
|
|
54
50
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOMAIN STATUS & CONFIGURATION ENUMS
|
|
3
|
+
* ──────────────────────────────────────
|
|
4
|
+
* General-purpose enums for entity lifecycle, UI preferences, and
|
|
5
|
+
* configuration. Permission-related enums live in permissions.types.ts.
|
|
6
|
+
*/
|
|
7
|
+
export declare enum AccountStatus {
|
|
8
|
+
ACTIVE = "ACTIVE",
|
|
9
|
+
SUSPENDED = "SUSPENDED",// Access revoked, data retained, reversible
|
|
10
|
+
DELETED = "DELETED"
|
|
11
|
+
}
|
|
12
|
+
export declare enum OrganizationStatus {
|
|
13
|
+
ACTIVE = "ACTIVE",
|
|
14
|
+
SUSPENDED = "SUSPENDED",// Platform-level suspension (non-payment, policy)
|
|
15
|
+
CLOSED = "CLOSED"
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* OrganizationRole lives in permissions.types.ts alongside OrgPermission
|
|
19
|
+
* so the role → permission map stays co-located with the role definition.
|
|
20
|
+
* Re-exported from enums/index.ts for convenience.
|
|
21
|
+
*/
|
|
22
|
+
export declare enum MembershipStatus {
|
|
23
|
+
PENDING = "PENDING",
|
|
24
|
+
ACTIVE = "ACTIVE",
|
|
25
|
+
SUSPENDED = "SUSPENDED",
|
|
26
|
+
REMOVED = "REMOVED"
|
|
27
|
+
}
|
|
28
|
+
export declare enum Theme {
|
|
29
|
+
LIGHT = "LIGHT",
|
|
30
|
+
DARK = "DARK",
|
|
31
|
+
SYSTEM = "SYSTEM"
|
|
32
|
+
}
|
|
33
|
+
export declare enum Currency {
|
|
34
|
+
USD = "USD",// US Dollar — default, all cloud providers
|
|
35
|
+
EUR = "EUR",// Euro — AWS/GCP/Azure EU regions
|
|
36
|
+
GBP = "GBP",// British Pound — AWS/GCP/Azure UK regions
|
|
37
|
+
INR = "INR",// Indian Rupee — AWS/GCP/Azure AP south regions
|
|
38
|
+
AUD = "AUD",// Australian Dollar
|
|
39
|
+
CAD = "CAD",// Canadian Dollar
|
|
40
|
+
JPY = "JPY",// Japanese Yen
|
|
41
|
+
SGD = "SGD"
|
|
42
|
+
}
|