@actuate-media/cms-core 0.36.0 → 0.37.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/__tests__/api/security-routes.test.d.ts +2 -0
- package/dist/__tests__/api/security-routes.test.d.ts.map +1 -0
- package/dist/__tests__/api/security-routes.test.js +212 -0
- package/dist/__tests__/api/security-routes.test.js.map +1 -0
- package/dist/__tests__/security/center.test.d.ts +2 -0
- package/dist/__tests__/security/center.test.d.ts.map +1 -0
- package/dist/__tests__/security/center.test.js +226 -0
- package/dist/__tests__/security/center.test.js.map +1 -0
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +201 -4
- package/dist/api/handlers.js.map +1 -1
- package/dist/security/center/gather.d.ts +12 -0
- package/dist/security/center/gather.d.ts.map +1 -0
- package/dist/security/center/gather.js +185 -0
- package/dist/security/center/gather.js.map +1 -0
- package/dist/security/center/index.d.ts +13 -0
- package/dist/security/center/index.d.ts.map +1 -0
- package/dist/security/center/index.js +4 -0
- package/dist/security/center/index.js.map +1 -0
- package/dist/security/center/settings.d.ts +38 -0
- package/dist/security/center/settings.d.ts.map +1 -0
- package/dist/security/center/settings.js +121 -0
- package/dist/security/center/settings.js.map +1 -0
- package/dist/security/center/status.d.ts +10 -0
- package/dist/security/center/status.d.ts.map +1 -0
- package/dist/security/center/status.js +249 -0
- package/dist/security/center/status.js.map +1 -0
- package/dist/security/center/summaries.d.ts +21 -0
- package/dist/security/center/summaries.d.ts.map +1 -0
- package/dist/security/center/summaries.js +65 -0
- package/dist/security/center/summaries.js.map +1 -0
- package/dist/security/center/types.d.ts +127 -0
- package/dist/security/center/types.d.ts.map +1 -0
- package/dist/security/center/types.js +12 -0
- package/dist/security/center/types.js.map +1 -0
- package/package.json +6 -1
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
const ADMIN_HREF = '/settings?tab=users';
|
|
2
|
+
const API_KEYS_HREF = '/settings?tab=api-keys';
|
|
3
|
+
const FAILED_LOGIN_WARN_THRESHOLD = 20;
|
|
4
|
+
const LEVEL_COPY = {
|
|
5
|
+
secure: {
|
|
6
|
+
label: 'Secure',
|
|
7
|
+
description: 'Core protections are configured. Keep MFA coverage and API keys under review.',
|
|
8
|
+
},
|
|
9
|
+
needs_attention: {
|
|
10
|
+
label: 'Needs attention',
|
|
11
|
+
description: 'Some recommended controls are missing. Review the warnings below.',
|
|
12
|
+
},
|
|
13
|
+
at_risk: {
|
|
14
|
+
label: 'At risk',
|
|
15
|
+
description: 'Critical protections are missing or misconfigured. Resolve these before running in production.',
|
|
16
|
+
},
|
|
17
|
+
unknown: {
|
|
18
|
+
label: 'Unknown',
|
|
19
|
+
description: 'Security state could not be fully determined. Some services may be unavailable.',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
export function computeSecurityStatus(input) {
|
|
23
|
+
const checks = [];
|
|
24
|
+
const warnings = [];
|
|
25
|
+
// ── MFA coverage ──────────────────────────────────────────────────────────
|
|
26
|
+
if (input.users.totalAdmins === 0) {
|
|
27
|
+
checks.push({ key: 'mfa', label: 'MFA coverage', status: 'unknown', value: 'No admins' });
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const allCovered = input.users.adminsWithoutMfa === 0;
|
|
31
|
+
const mfaStatus = allCovered ? 'pass' : input.settings.mfa.required ? 'fail' : 'warning';
|
|
32
|
+
checks.push({
|
|
33
|
+
key: 'mfa',
|
|
34
|
+
label: 'MFA coverage',
|
|
35
|
+
status: mfaStatus,
|
|
36
|
+
value: `${input.users.withMfa} of ${input.users.totalAdmins} admins`,
|
|
37
|
+
actionHref: ADMIN_HREF,
|
|
38
|
+
});
|
|
39
|
+
if (input.settings.mfa.required && input.users.adminsWithoutMfa > 0) {
|
|
40
|
+
warnings.push({
|
|
41
|
+
id: 'mfa-required-gap',
|
|
42
|
+
severity: 'critical',
|
|
43
|
+
category: 'auth',
|
|
44
|
+
title: 'Admins without MFA while MFA is required',
|
|
45
|
+
description: `${input.users.adminsWithoutMfa} admin account(s) have not set up MFA. They must enroll within the grace period.`,
|
|
46
|
+
actionLabel: 'Review users',
|
|
47
|
+
actionHref: ADMIN_HREF,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
else if (!input.settings.mfa.required && input.isProduction) {
|
|
51
|
+
warnings.push({
|
|
52
|
+
id: 'mfa-not-required',
|
|
53
|
+
severity: 'warning',
|
|
54
|
+
category: 'auth',
|
|
55
|
+
title: 'MFA is not required in production',
|
|
56
|
+
description: 'Requiring MFA for admins significantly reduces account-takeover risk.',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// ── Active sessions (informational) ─────────────────────────────────────────
|
|
61
|
+
checks.push({
|
|
62
|
+
key: 'sessions',
|
|
63
|
+
label: 'Active admin sessions',
|
|
64
|
+
status: 'pass',
|
|
65
|
+
value: String(input.activeSessions),
|
|
66
|
+
actionHref: '/settings?tab=security§ion=sessions',
|
|
67
|
+
});
|
|
68
|
+
// ── API keys ────────────────────────────────────────────────────────────────
|
|
69
|
+
const keyRisk = input.apiKeys.broadScope > 0
|
|
70
|
+
? 'fail'
|
|
71
|
+
: input.apiKeys.withoutExpiration > 0 || input.apiKeys.staleUnused90d > 0
|
|
72
|
+
? 'warning'
|
|
73
|
+
: 'pass';
|
|
74
|
+
checks.push({
|
|
75
|
+
key: 'api_keys',
|
|
76
|
+
label: 'High-risk API keys',
|
|
77
|
+
status: keyRisk === 'pass' ? 'pass' : keyRisk,
|
|
78
|
+
value: keyRisk === 'pass' ? `${input.apiKeys.active} active` : describeKeyRisk(input.apiKeys),
|
|
79
|
+
actionHref: API_KEYS_HREF,
|
|
80
|
+
});
|
|
81
|
+
if (input.apiKeys.broadScope > 0) {
|
|
82
|
+
warnings.push({
|
|
83
|
+
id: 'api-keys-broad',
|
|
84
|
+
severity: 'warning',
|
|
85
|
+
category: 'api_keys',
|
|
86
|
+
title: 'API keys with broad scopes',
|
|
87
|
+
description: `${input.apiKeys.broadScope} key(s) hold admin or full read/write scope. Scope keys to the minimum required.`,
|
|
88
|
+
actionLabel: 'Review API keys',
|
|
89
|
+
actionHref: API_KEYS_HREF,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (input.apiKeys.withoutExpiration > 0) {
|
|
93
|
+
warnings.push({
|
|
94
|
+
id: 'api-keys-no-expiry',
|
|
95
|
+
severity: 'info',
|
|
96
|
+
category: 'api_keys',
|
|
97
|
+
title: 'API keys without expiration',
|
|
98
|
+
description: `${input.apiKeys.withoutExpiration} key(s) never expire. Consider setting an expiry.`,
|
|
99
|
+
actionLabel: 'Review API keys',
|
|
100
|
+
actionHref: API_KEYS_HREF,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// ── Failed logins (24h) ──────────────────────────────────────────────────────
|
|
104
|
+
const loginStatus = input.failedLogins24h >= FAILED_LOGIN_WARN_THRESHOLD ? 'warning' : 'pass';
|
|
105
|
+
checks.push({
|
|
106
|
+
key: 'failed_logins',
|
|
107
|
+
label: 'Failed logins (24h)',
|
|
108
|
+
status: loginStatus,
|
|
109
|
+
value: String(input.failedLogins24h),
|
|
110
|
+
});
|
|
111
|
+
if (loginStatus === 'warning') {
|
|
112
|
+
warnings.push({
|
|
113
|
+
id: 'failed-logins-spike',
|
|
114
|
+
severity: 'warning',
|
|
115
|
+
category: 'auth',
|
|
116
|
+
title: 'Elevated failed login attempts',
|
|
117
|
+
description: `${input.failedLogins24h} failed logins in the last 24h. This may indicate a brute-force attempt.`,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
// ── CSRF ──────────────────────────────────────────────────────────────────────
|
|
121
|
+
checks.push({
|
|
122
|
+
key: 'csrf',
|
|
123
|
+
label: 'CSRF protection',
|
|
124
|
+
status: input.csrfEnabled ? 'pass' : 'fail',
|
|
125
|
+
value: input.csrfEnabled ? 'Enabled' : 'Disabled',
|
|
126
|
+
});
|
|
127
|
+
if (!input.csrfEnabled) {
|
|
128
|
+
warnings.push({
|
|
129
|
+
id: 'csrf-disabled',
|
|
130
|
+
severity: 'critical',
|
|
131
|
+
category: 'access',
|
|
132
|
+
title: 'CSRF protection disabled',
|
|
133
|
+
description: 'State-changing requests are not CSRF-protected. This exposes the admin to cross-site request forgery.',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// ── Rate limiting ──────────────────────────────────────────────────────────────
|
|
137
|
+
const distributed = input.secrets.rateLimitBackend === 'distributed';
|
|
138
|
+
checks.push({
|
|
139
|
+
key: 'rate_limit',
|
|
140
|
+
label: 'Rate limiting',
|
|
141
|
+
status: distributed ? 'pass' : 'warning',
|
|
142
|
+
value: distributed ? 'Distributed (Upstash)' : 'In-memory',
|
|
143
|
+
});
|
|
144
|
+
if (!distributed && input.isProduction) {
|
|
145
|
+
warnings.push({
|
|
146
|
+
id: 'rate-limit-in-memory',
|
|
147
|
+
severity: 'warning',
|
|
148
|
+
category: 'rate_limit',
|
|
149
|
+
title: 'Rate limiting is in-memory',
|
|
150
|
+
description: 'Without Upstash/Redis, rate limits are per-process and ineffective across serverless invocations. Configure UPSTASH_REDIS_REST_URL.',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// ── Upload validation ───────────────────────────────────────────────────────
|
|
154
|
+
checks.push({
|
|
155
|
+
key: 'uploads',
|
|
156
|
+
label: 'Upload validation',
|
|
157
|
+
status: input.uploadValidation ? 'pass' : 'warning',
|
|
158
|
+
value: input.uploadValidation ? 'MIME + magic bytes + SVG sanitization' : 'Unknown',
|
|
159
|
+
});
|
|
160
|
+
// ── Secrets / encryption ─────────────────────────────────────────────────────
|
|
161
|
+
checks.push({
|
|
162
|
+
key: 'cms_secret',
|
|
163
|
+
label: 'CMS secret',
|
|
164
|
+
status: input.secrets.cmsSecret,
|
|
165
|
+
value: secretValueLabel(input.secrets.cmsSecret),
|
|
166
|
+
});
|
|
167
|
+
if (input.secrets.cmsSecret === 'fail') {
|
|
168
|
+
warnings.push({
|
|
169
|
+
id: 'cms-secret-missing',
|
|
170
|
+
severity: 'critical',
|
|
171
|
+
category: 'secrets',
|
|
172
|
+
title: 'CMS_SECRET is missing or weak',
|
|
173
|
+
description: 'Session JWTs cannot be signed securely. Set a 32+ character CMS_SECRET.',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
checks.push({
|
|
177
|
+
key: 'encryption_key',
|
|
178
|
+
label: 'Encryption key',
|
|
179
|
+
status: input.secrets.encryptionKey,
|
|
180
|
+
value: secretValueLabel(input.secrets.encryptionKey),
|
|
181
|
+
});
|
|
182
|
+
if (input.secrets.encryptionKey === 'fail') {
|
|
183
|
+
warnings.push({
|
|
184
|
+
id: 'encryption-key',
|
|
185
|
+
severity: input.isProduction ? 'critical' : 'warning',
|
|
186
|
+
category: 'secrets',
|
|
187
|
+
title: 'Encryption key not configured',
|
|
188
|
+
description: 'CMS_ENCRYPTION_KEY (64 hex chars) is required to encrypt secrets at rest (webhook secrets, OAuth tokens, TOTP secrets).',
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
// ── Audit logging ──────────────────────────────────────────────────────────────
|
|
192
|
+
checks.push({
|
|
193
|
+
key: 'audit',
|
|
194
|
+
label: 'Audit logging',
|
|
195
|
+
status: input.auditEnabled ? 'pass' : 'warning',
|
|
196
|
+
value: input.auditEnabled ? 'Enabled' : 'Disabled',
|
|
197
|
+
actionHref: '/settings?tab=security§ion=audit',
|
|
198
|
+
});
|
|
199
|
+
// ── Roll up level + score ────────────────────────────────────────────────────
|
|
200
|
+
const hasCritical = warnings.some((w) => w.severity === 'critical');
|
|
201
|
+
const hasWarning = warnings.some((w) => w.severity === 'warning');
|
|
202
|
+
const unknown = input.secrets.cmsSecret === 'unknown';
|
|
203
|
+
let level;
|
|
204
|
+
if (unknown)
|
|
205
|
+
level = 'unknown';
|
|
206
|
+
else if (hasCritical)
|
|
207
|
+
level = 'at_risk';
|
|
208
|
+
else if (hasWarning)
|
|
209
|
+
level = 'needs_attention';
|
|
210
|
+
else
|
|
211
|
+
level = 'secure';
|
|
212
|
+
let score = 100;
|
|
213
|
+
for (const w of warnings) {
|
|
214
|
+
score -= w.severity === 'critical' ? 30 : w.severity === 'warning' ? 12 : 3;
|
|
215
|
+
}
|
|
216
|
+
score = Math.max(0, Math.min(100, score));
|
|
217
|
+
return {
|
|
218
|
+
status: level,
|
|
219
|
+
score,
|
|
220
|
+
label: LEVEL_COPY[level].label,
|
|
221
|
+
description: LEVEL_COPY[level].description,
|
|
222
|
+
checks,
|
|
223
|
+
warnings,
|
|
224
|
+
lastCheckedAt: new Date().toISOString(),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function describeKeyRisk(k) {
|
|
228
|
+
const parts = [];
|
|
229
|
+
if (k.broadScope > 0)
|
|
230
|
+
parts.push(`${k.broadScope} broad-scope`);
|
|
231
|
+
if (k.withoutExpiration > 0)
|
|
232
|
+
parts.push(`${k.withoutExpiration} no expiry`);
|
|
233
|
+
if (k.staleUnused90d > 0)
|
|
234
|
+
parts.push(`${k.staleUnused90d} stale`);
|
|
235
|
+
return parts.join(', ') || 'OK';
|
|
236
|
+
}
|
|
237
|
+
function secretValueLabel(status) {
|
|
238
|
+
switch (status) {
|
|
239
|
+
case 'pass':
|
|
240
|
+
return 'Configured';
|
|
241
|
+
case 'warning':
|
|
242
|
+
return 'Not configured';
|
|
243
|
+
case 'fail':
|
|
244
|
+
return 'Missing / invalid';
|
|
245
|
+
default:
|
|
246
|
+
return 'Unknown';
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/security/center/status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure cross-module risk summaries (users + API keys).
|
|
3
|
+
*
|
|
4
|
+
* Take already-fetched rows so they can be unit-tested without a DB. The
|
|
5
|
+
* gatherer queries the minimal columns and hands them here.
|
|
6
|
+
*/
|
|
7
|
+
import type { ApiKeysSecuritySummary, UsersSecuritySummary } from './types.js';
|
|
8
|
+
export interface UserRow {
|
|
9
|
+
role: string;
|
|
10
|
+
isActive: boolean;
|
|
11
|
+
totpEnabled: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function summarizeUsers(rows: UserRow[]): UsersSecuritySummary;
|
|
14
|
+
export interface ApiKeyRow {
|
|
15
|
+
scopes: unknown;
|
|
16
|
+
expiresAt: Date | string | null;
|
|
17
|
+
lastUsedAt: Date | string | null;
|
|
18
|
+
revokedAt: Date | string | null;
|
|
19
|
+
}
|
|
20
|
+
export declare function summarizeApiKeys(rows: ApiKeyRow[], now?: number): ApiKeysSecuritySummary;
|
|
21
|
+
//# sourceMappingURL=summaries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summaries.d.ts","sourceRoot":"","sources":["../../../src/security/center/summaries.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAE9E,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,oBAAoB,CAwBpE;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IAC/B,UAAU,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IAChC,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;CAChC;AAgBD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,SAAS,EAAE,EACjB,GAAG,GAAE,MAAmB,GACvB,sBAAsB,CAgBxB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export function summarizeUsers(rows) {
|
|
2
|
+
let totalAdmins = 0;
|
|
3
|
+
let withMfa = 0;
|
|
4
|
+
let adminsWithoutMfa = 0;
|
|
5
|
+
let withoutMfa = 0;
|
|
6
|
+
let inactive = 0;
|
|
7
|
+
for (const u of rows) {
|
|
8
|
+
if (!u.isActive)
|
|
9
|
+
inactive++;
|
|
10
|
+
if (!u.totpEnabled)
|
|
11
|
+
withoutMfa++;
|
|
12
|
+
const isAdmin = u.role === 'ADMIN';
|
|
13
|
+
if (isAdmin) {
|
|
14
|
+
totalAdmins++;
|
|
15
|
+
if (u.totpEnabled)
|
|
16
|
+
withMfa++;
|
|
17
|
+
else if (u.isActive)
|
|
18
|
+
adminsWithoutMfa++;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
totalUsers: rows.length,
|
|
23
|
+
totalAdmins,
|
|
24
|
+
withMfa,
|
|
25
|
+
withoutMfa,
|
|
26
|
+
adminsWithoutMfa,
|
|
27
|
+
inactive,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const STALE_MS = 90 * 24 * 60 * 60 * 1000;
|
|
31
|
+
/** A key is "broad" if it carries the admin scope (full control). */
|
|
32
|
+
function isBroadScope(scopes) {
|
|
33
|
+
if (!scopes || typeof scopes !== 'object')
|
|
34
|
+
return false;
|
|
35
|
+
return scopes.admin === true;
|
|
36
|
+
}
|
|
37
|
+
function toTime(v) {
|
|
38
|
+
if (!v)
|
|
39
|
+
return null;
|
|
40
|
+
const t = v instanceof Date ? v.getTime() : new Date(v).getTime();
|
|
41
|
+
return Number.isFinite(t) ? t : null;
|
|
42
|
+
}
|
|
43
|
+
export function summarizeApiKeys(rows, now = Date.now()) {
|
|
44
|
+
let active = 0;
|
|
45
|
+
let withoutExpiration = 0;
|
|
46
|
+
let broadScope = 0;
|
|
47
|
+
let staleUnused90d = 0;
|
|
48
|
+
for (const k of rows) {
|
|
49
|
+
if (k.revokedAt)
|
|
50
|
+
continue;
|
|
51
|
+
const exp = toTime(k.expiresAt);
|
|
52
|
+
if (exp !== null && exp <= now)
|
|
53
|
+
continue; // expired ⇒ not active
|
|
54
|
+
active++;
|
|
55
|
+
if (exp === null)
|
|
56
|
+
withoutExpiration++;
|
|
57
|
+
if (isBroadScope(k.scopes))
|
|
58
|
+
broadScope++;
|
|
59
|
+
const used = toTime(k.lastUsedAt);
|
|
60
|
+
if (used !== null && now - used > STALE_MS)
|
|
61
|
+
staleUnused90d++;
|
|
62
|
+
}
|
|
63
|
+
return { active, withoutExpiration, broadScope, staleUnused90d };
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=summaries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summaries.js","sourceRoot":"","sources":["../../../src/security/center/summaries.ts"],"names":[],"mappings":"AAcA,MAAM,UAAU,cAAc,CAAC,IAAe;IAC5C,IAAI,WAAW,GAAG,CAAC,CAAA;IACnB,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,IAAI,gBAAgB,GAAG,CAAC,CAAA;IACxB,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,IAAI,QAAQ,GAAG,CAAC,CAAA;IAChB,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,CAAC,QAAQ;YAAE,QAAQ,EAAE,CAAA;QAC3B,IAAI,CAAC,CAAC,CAAC,WAAW;YAAE,UAAU,EAAE,CAAA;QAChC,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,OAAO,CAAA;QAClC,IAAI,OAAO,EAAE,CAAC;YACZ,WAAW,EAAE,CAAA;YACb,IAAI,CAAC,CAAC,WAAW;gBAAE,OAAO,EAAE,CAAA;iBACvB,IAAI,CAAC,CAAC,QAAQ;gBAAE,gBAAgB,EAAE,CAAA;QACzC,CAAC;IACH,CAAC;IACD,OAAO;QACL,UAAU,EAAE,IAAI,CAAC,MAAM;QACvB,WAAW;QACX,OAAO;QACP,UAAU;QACV,gBAAgB;QAChB,QAAQ;KACT,CAAA;AACH,CAAC;AASD,MAAM,QAAQ,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAEzC,qEAAqE;AACrE,SAAS,YAAY,CAAC,MAAe;IACnC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IACvD,OAAQ,MAAkC,CAAC,KAAK,KAAK,IAAI,CAAA;AAC3D,CAAC;AAED,SAAS,MAAM,CAAC,CAAuB;IACrC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IACnB,MAAM,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAA;IACjE,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,IAAiB,EACjB,MAAc,IAAI,CAAC,GAAG,EAAE;IAExB,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,IAAI,iBAAiB,GAAG,CAAC,CAAA;IACzB,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,IAAI,cAAc,GAAG,CAAC,CAAA;IACtB,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,SAAS;YAAE,SAAQ;QACzB,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;QAC/B,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG;YAAE,SAAQ,CAAC,uBAAuB;QAChE,MAAM,EAAE,CAAA;QACR,IAAI,GAAG,KAAK,IAAI;YAAE,iBAAiB,EAAE,CAAA;QACrC,IAAI,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC;YAAE,UAAU,EAAE,CAAA;QACxC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAA;QACjC,IAAI,IAAI,KAAK,IAAI,IAAI,GAAG,GAAG,IAAI,GAAG,QAAQ;YAAE,cAAc,EAAE,CAAA;IAC9D,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,UAAU,EAAE,cAAc,EAAE,CAAA;AAClE,CAAC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Center types.
|
|
3
|
+
*
|
|
4
|
+
* The Security Center answers one question for an operator: "is this CMS safe
|
|
5
|
+
* to run in production?" It is mostly a *computed posture* surface — it reads
|
|
6
|
+
* real auth/session/secret/audit state — plus a small set of genuinely
|
|
7
|
+
* enforced, persisted policy settings (MFA requirement, concurrent-session
|
|
8
|
+
* cap). Anything that is not actually enforced is reported as honest status
|
|
9
|
+
* with a source badge, never as a decorative toggle.
|
|
10
|
+
*/
|
|
11
|
+
export type SecurityStatusLevel = 'secure' | 'needs_attention' | 'at_risk' | 'unknown';
|
|
12
|
+
export type CheckStatus = 'pass' | 'warning' | 'fail' | 'unknown';
|
|
13
|
+
export interface SecurityCheck {
|
|
14
|
+
key: string;
|
|
15
|
+
label: string;
|
|
16
|
+
status: CheckStatus;
|
|
17
|
+
/** Short human value, e.g. "3 of 4 admins" or "Enabled". */
|
|
18
|
+
value?: string;
|
|
19
|
+
/** In-admin deep link the operator can follow to act on this check. */
|
|
20
|
+
actionHref?: string;
|
|
21
|
+
}
|
|
22
|
+
export type SecurityWarningCategory = 'auth' | 'sessions' | 'api_keys' | 'uploads' | 'rate_limit' | 'audit' | 'secrets' | 'access';
|
|
23
|
+
export interface SecurityWarning {
|
|
24
|
+
id: string;
|
|
25
|
+
severity: 'critical' | 'warning' | 'info';
|
|
26
|
+
category: SecurityWarningCategory;
|
|
27
|
+
title: string;
|
|
28
|
+
description: string;
|
|
29
|
+
actionLabel?: string;
|
|
30
|
+
actionHref?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface SecurityStatus {
|
|
33
|
+
status: SecurityStatusLevel;
|
|
34
|
+
/** 0–100 posture score (advisory; the level is the source of truth). */
|
|
35
|
+
score: number;
|
|
36
|
+
label: string;
|
|
37
|
+
description: string;
|
|
38
|
+
checks: SecurityCheck[];
|
|
39
|
+
warnings: SecurityWarning[];
|
|
40
|
+
lastCheckedAt: string;
|
|
41
|
+
}
|
|
42
|
+
/** Persisted, enforced MFA policy. */
|
|
43
|
+
export interface MfaSettings {
|
|
44
|
+
required: boolean;
|
|
45
|
+
gracePeriodDays: number;
|
|
46
|
+
/** ISO timestamp when `required` was last switched on (drives the grace window). */
|
|
47
|
+
requiredAt?: string;
|
|
48
|
+
}
|
|
49
|
+
/** Persisted, enforced session policy. `maxConcurrentSessions = 0` ⇒ unlimited. */
|
|
50
|
+
export interface SessionSettings {
|
|
51
|
+
maxConcurrentSessions: number;
|
|
52
|
+
}
|
|
53
|
+
export interface SecuritySettings {
|
|
54
|
+
mfa: MfaSettings;
|
|
55
|
+
session: SessionSettings;
|
|
56
|
+
updatedAt?: string;
|
|
57
|
+
updatedBy?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface UsersSecuritySummary {
|
|
60
|
+
totalUsers: number;
|
|
61
|
+
totalAdmins: number;
|
|
62
|
+
withMfa: number;
|
|
63
|
+
withoutMfa: number;
|
|
64
|
+
adminsWithoutMfa: number;
|
|
65
|
+
inactive: number;
|
|
66
|
+
}
|
|
67
|
+
export interface ApiKeysSecuritySummary {
|
|
68
|
+
active: number;
|
|
69
|
+
withoutExpiration: number;
|
|
70
|
+
broadScope: number;
|
|
71
|
+
staleUnused90d: number;
|
|
72
|
+
}
|
|
73
|
+
/** Status of secrets/encryption — never the values themselves. */
|
|
74
|
+
export interface SecretsStatus {
|
|
75
|
+
cmsSecret: CheckStatus;
|
|
76
|
+
encryptionKey: CheckStatus;
|
|
77
|
+
rateLimitBackend: 'distributed' | 'in_memory';
|
|
78
|
+
apiKeysHashed: boolean;
|
|
79
|
+
webhookSecretsEncrypted: boolean;
|
|
80
|
+
}
|
|
81
|
+
export interface IpAllowlistStatus {
|
|
82
|
+
enabled: boolean;
|
|
83
|
+
source: 'config' | 'none';
|
|
84
|
+
ranges: string[];
|
|
85
|
+
currentIp: string | null;
|
|
86
|
+
currentIpAllowed: boolean | null;
|
|
87
|
+
}
|
|
88
|
+
export interface SecuritySourceMetadata {
|
|
89
|
+
source: 'config' | 'environment' | 'database' | 'plugin' | 'computed';
|
|
90
|
+
editable: boolean;
|
|
91
|
+
reason?: string;
|
|
92
|
+
}
|
|
93
|
+
/** Everything the admin Security tab needs in one round-trip. */
|
|
94
|
+
export interface SecurityCenterData {
|
|
95
|
+
settings: SecuritySettings;
|
|
96
|
+
status: SecurityStatus;
|
|
97
|
+
users: UsersSecuritySummary;
|
|
98
|
+
apiKeys: ApiKeysSecuritySummary;
|
|
99
|
+
secrets: SecretsStatus;
|
|
100
|
+
ipAllowlist: IpAllowlistStatus;
|
|
101
|
+
failedLogins24h: number;
|
|
102
|
+
activeSessions: number;
|
|
103
|
+
csrfEnabled: boolean;
|
|
104
|
+
uploadValidation: boolean;
|
|
105
|
+
auditEnabled: boolean;
|
|
106
|
+
/** Effective max session age in hours (config/default; read-only today). */
|
|
107
|
+
sessionMaxAgeHours: number;
|
|
108
|
+
/** Whether the current admin (the viewer) has TOTP enabled — drives lockout-safe MFA enable. */
|
|
109
|
+
viewerHasMfa: boolean;
|
|
110
|
+
environment: string;
|
|
111
|
+
sources: Record<string, SecuritySourceMetadata>;
|
|
112
|
+
}
|
|
113
|
+
/** Signals consumed by the pure {@link computeSecurityStatus}. */
|
|
114
|
+
export interface SecurityStatusInput {
|
|
115
|
+
isProduction: boolean;
|
|
116
|
+
settings: SecuritySettings;
|
|
117
|
+
users: UsersSecuritySummary;
|
|
118
|
+
apiKeys: ApiKeysSecuritySummary;
|
|
119
|
+
secrets: SecretsStatus;
|
|
120
|
+
ipAllowlist: IpAllowlistStatus;
|
|
121
|
+
failedLogins24h: number;
|
|
122
|
+
activeSessions: number;
|
|
123
|
+
csrfEnabled: boolean;
|
|
124
|
+
uploadValidation: boolean;
|
|
125
|
+
auditEnabled: boolean;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/security/center/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Center types.
|
|
3
|
+
*
|
|
4
|
+
* The Security Center answers one question for an operator: "is this CMS safe
|
|
5
|
+
* to run in production?" It is mostly a *computed posture* surface — it reads
|
|
6
|
+
* real auth/session/secret/audit state — plus a small set of genuinely
|
|
7
|
+
* enforced, persisted policy settings (MFA requirement, concurrent-session
|
|
8
|
+
* cap). Anything that is not actually enforced is reported as honest status
|
|
9
|
+
* with a source badge, never as a decorative toggle.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/security/center/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actuate-media/cms-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/actuate-media/actuatecms.git",
|
|
@@ -59,6 +59,11 @@
|
|
|
59
59
|
"import": "./dist/appearance/index.js",
|
|
60
60
|
"default": "./dist/appearance/index.js"
|
|
61
61
|
},
|
|
62
|
+
"./security-center": {
|
|
63
|
+
"types": "./dist/security/center/index.d.ts",
|
|
64
|
+
"import": "./dist/security/center/index.js",
|
|
65
|
+
"default": "./dist/security/center/index.js"
|
|
66
|
+
},
|
|
62
67
|
"./page-builder": {
|
|
63
68
|
"types": "./dist/page-builder/index.d.ts",
|
|
64
69
|
"import": "./dist/page-builder/index.js",
|