@actuate-media/cms-core 0.35.1 → 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/appearance-routes.test.d.ts +2 -0
- package/dist/__tests__/api/appearance-routes.test.d.ts.map +1 -0
- package/dist/__tests__/api/appearance-routes.test.js +134 -0
- package/dist/__tests__/api/appearance-routes.test.js.map +1 -0
- 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__/appearance/appearance.test.d.ts +2 -0
- package/dist/__tests__/appearance/appearance.test.d.ts.map +1 -0
- package/dist/__tests__/appearance/appearance.test.js +273 -0
- package/dist/__tests__/appearance/appearance.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 +261 -3
- package/dist/api/handlers.js.map +1 -1
- package/dist/appearance/color.d.ts +24 -0
- package/dist/appearance/color.d.ts.map +1 -0
- package/dist/appearance/color.js +90 -0
- package/dist/appearance/color.js.map +1 -0
- package/dist/appearance/index.d.ts +13 -0
- package/dist/appearance/index.d.ts.map +1 -0
- package/dist/appearance/index.js +5 -0
- package/dist/appearance/index.js.map +1 -0
- package/dist/appearance/logo-shape.d.ts +26 -0
- package/dist/appearance/logo-shape.d.ts.map +1 -0
- package/dist/appearance/logo-shape.js +62 -0
- package/dist/appearance/logo-shape.js.map +1 -0
- package/dist/appearance/resolve.d.ts +46 -0
- package/dist/appearance/resolve.d.ts.map +1 -0
- package/dist/appearance/resolve.js +208 -0
- package/dist/appearance/resolve.js.map +1 -0
- package/dist/appearance/theme-tokens.d.ts +50 -0
- package/dist/appearance/theme-tokens.d.ts.map +1 -0
- package/dist/appearance/theme-tokens.js +198 -0
- package/dist/appearance/theme-tokens.js.map +1 -0
- package/dist/appearance/types.d.ts +110 -0
- package/dist/appearance/types.d.ts.map +1 -0
- package/dist/appearance/types.js +16 -0
- package/dist/appearance/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.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 +11 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gather.js","sourceRoot":"","sources":["../../../src/security/center/gather.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Center — public barrel.
|
|
3
|
+
*
|
|
4
|
+
* Exports the PURE pieces only (types, settings parse/validate, IP validation,
|
|
5
|
+
* status computation, cross-module summaries) so the admin client can import
|
|
6
|
+
* them without pulling in server-side DB/env code. The async gatherer
|
|
7
|
+
* (`gather.ts`) is imported directly by the API handlers, not re-exported here.
|
|
8
|
+
*/
|
|
9
|
+
export type { ApiKeysSecuritySummary, CheckStatus, IpAllowlistStatus, MfaSettings, SecretsStatus, SecurityCenterData, SecurityCheck, SecuritySettings, SecuritySourceMetadata, SecurityStatus, SecurityStatusInput, SecurityStatusLevel, SecurityWarning, SecurityWarningCategory, SessionSettings, UsersSecuritySummary, } from './types.js';
|
|
10
|
+
export { DEFAULT_SECURITY_SETTINGS, MAX_CONCURRENT_SESSION_OPTIONS, MFA_GRACE_PERIOD_OPTIONS, mfaEnableLockoutError, parseSecuritySettings, validateIpRange, validateSecuritySettings, type SecurityValidationIssue, } from './settings.js';
|
|
11
|
+
export { computeSecurityStatus } from './status.js';
|
|
12
|
+
export { summarizeApiKeys, summarizeUsers, type ApiKeyRow, type UserRow } from './summaries.js';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/security/center/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,YAAY,EACV,sBAAsB,EACtB,WAAW,EACX,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,sBAAsB,EACtB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,uBAAuB,EACvB,eAAe,EACf,oBAAoB,GACrB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,yBAAyB,EACzB,8BAA8B,EAC9B,wBAAwB,EACxB,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EACf,wBAAwB,EACxB,KAAK,uBAAuB,GAC7B,MAAM,eAAe,CAAA;AAEtB,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEnD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { DEFAULT_SECURITY_SETTINGS, MAX_CONCURRENT_SESSION_OPTIONS, MFA_GRACE_PERIOD_OPTIONS, mfaEnableLockoutError, parseSecuritySettings, validateIpRange, validateSecuritySettings, } from './settings.js';
|
|
2
|
+
export { computeSecurityStatus } from './status.js';
|
|
3
|
+
export { summarizeApiKeys, summarizeUsers } from './summaries.js';
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/security/center/index.ts"],"names":[],"mappings":"AA2BA,OAAO,EACL,yBAAyB,EACzB,8BAA8B,EAC9B,wBAAwB,EACxB,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EACf,wBAAwB,GAEzB,MAAM,eAAe,CAAA;AAEtB,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEnD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAgC,MAAM,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security settings parsing + validation (pure).
|
|
3
|
+
*
|
|
4
|
+
* The persisted, enforced policy lives under the `security` key of the
|
|
5
|
+
* `settings` global. Only fields the runtime actually enforces are stored
|
|
6
|
+
* here (MFA requirement + grace, concurrent-session cap) so the UI never
|
|
7
|
+
* presents a control that does nothing.
|
|
8
|
+
*/
|
|
9
|
+
import type { SecuritySettings } from './types.js';
|
|
10
|
+
export declare const DEFAULT_SECURITY_SETTINGS: SecuritySettings;
|
|
11
|
+
export declare const MFA_GRACE_PERIOD_OPTIONS: readonly [0, 1, 7, 14, 30];
|
|
12
|
+
export declare const MAX_CONCURRENT_SESSION_OPTIONS: readonly [0, 3, 5, 10];
|
|
13
|
+
export interface SecurityValidationIssue {
|
|
14
|
+
field: string;
|
|
15
|
+
message: string;
|
|
16
|
+
severity: 'error' | 'warning';
|
|
17
|
+
}
|
|
18
|
+
/** Read the `security` blob off a settings document, coercing to a safe shape. */
|
|
19
|
+
export declare function parseSecuritySettings(blob: unknown): SecuritySettings;
|
|
20
|
+
/** Validate an incoming settings payload. Pure — no DB / lockout checks here. */
|
|
21
|
+
export declare function validateSecuritySettings(settings: SecuritySettings): SecurityValidationIssue[];
|
|
22
|
+
/**
|
|
23
|
+
* Lockout-safe MFA enable check. Enabling org-wide MFA while the acting admin
|
|
24
|
+
* has no TOTP configured would lock them (and possibly everyone) out. Callers
|
|
25
|
+
* MUST run this server-side before persisting `mfa.required = true`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function mfaEnableLockoutError(input: {
|
|
28
|
+
enabling: boolean;
|
|
29
|
+
viewerHasMfa: boolean;
|
|
30
|
+
totalAdmins: number;
|
|
31
|
+
}): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Validate a single allowlist entry: an IPv4/IPv6 literal or a CIDR range.
|
|
34
|
+
* Standalone (does not import the matcher's private helpers) so it can run in
|
|
35
|
+
* the client bundle for inline form validation.
|
|
36
|
+
*/
|
|
37
|
+
export declare function validateIpRange(entry: string): boolean;
|
|
38
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../src/security/center/settings.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,eAAO,MAAM,yBAAyB,EAAE,gBAGvC,CAAA;AAED,eAAO,MAAM,wBAAwB,4BAA6B,CAAA;AAClE,eAAO,MAAM,8BAA8B,wBAAyB,CAAA;AAEpE,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAA;CAC9B;AAOD,kFAAkF;AAClF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,gBAAgB,CAiCrE;AAcD,iFAAiF;AACjF,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,uBAAuB,EAAE,CAmB9F;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,EAAE,OAAO,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;CACpB,GAAG,MAAM,GAAG,IAAI,CAMhB;AAKD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAatD"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export const DEFAULT_SECURITY_SETTINGS = {
|
|
2
|
+
mfa: { required: false, gracePeriodDays: 7 },
|
|
3
|
+
session: { maxConcurrentSessions: 5 },
|
|
4
|
+
};
|
|
5
|
+
export const MFA_GRACE_PERIOD_OPTIONS = [0, 1, 7, 14, 30];
|
|
6
|
+
export const MAX_CONCURRENT_SESSION_OPTIONS = [0, 3, 5, 10];
|
|
7
|
+
function toInt(value, fallback) {
|
|
8
|
+
const n = typeof value === 'number' ? value : Number(value);
|
|
9
|
+
return Number.isFinite(n) ? Math.trunc(n) : fallback;
|
|
10
|
+
}
|
|
11
|
+
/** Read the `security` blob off a settings document, coercing to a safe shape. */
|
|
12
|
+
export function parseSecuritySettings(blob) {
|
|
13
|
+
const root = (blob && typeof blob === 'object' ? blob : {}) ?? {};
|
|
14
|
+
const sec = (root.security && typeof root.security === 'object'
|
|
15
|
+
? root.security
|
|
16
|
+
: {});
|
|
17
|
+
const mfa = (sec.mfa && typeof sec.mfa === 'object' ? sec.mfa : {});
|
|
18
|
+
const session = (sec.session && typeof sec.session === 'object' ? sec.session : {});
|
|
19
|
+
return {
|
|
20
|
+
mfa: {
|
|
21
|
+
required: Boolean(mfa.required),
|
|
22
|
+
gracePeriodDays: clampGrace(toInt(mfa.gracePeriodDays, DEFAULT_SECURITY_SETTINGS.mfa.gracePeriodDays)),
|
|
23
|
+
requiredAt: typeof mfa.requiredAt === 'string' ? mfa.requiredAt : undefined,
|
|
24
|
+
},
|
|
25
|
+
session: {
|
|
26
|
+
maxConcurrentSessions: clampSessions(toInt(session.maxConcurrentSessions, DEFAULT_SECURITY_SETTINGS.session.maxConcurrentSessions)),
|
|
27
|
+
},
|
|
28
|
+
updatedAt: typeof sec.updatedAt === 'string' ? sec.updatedAt : undefined,
|
|
29
|
+
updatedBy: typeof sec.updatedBy === 'string' ? sec.updatedBy : undefined,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function clampGrace(n) {
|
|
33
|
+
if (n < 0)
|
|
34
|
+
return 0;
|
|
35
|
+
if (n > 365)
|
|
36
|
+
return 365;
|
|
37
|
+
return n;
|
|
38
|
+
}
|
|
39
|
+
function clampSessions(n) {
|
|
40
|
+
if (n < 0)
|
|
41
|
+
return 0;
|
|
42
|
+
if (n > 100)
|
|
43
|
+
return 100;
|
|
44
|
+
return n;
|
|
45
|
+
}
|
|
46
|
+
/** Validate an incoming settings payload. Pure — no DB / lockout checks here. */
|
|
47
|
+
export function validateSecuritySettings(settings) {
|
|
48
|
+
const issues = [];
|
|
49
|
+
const grace = settings.mfa.gracePeriodDays;
|
|
50
|
+
if (!Number.isInteger(grace) || grace < 0 || grace > 365) {
|
|
51
|
+
issues.push({
|
|
52
|
+
field: 'mfaGracePeriodDays',
|
|
53
|
+
message: 'MFA grace period must be a whole number of days between 0 and 365.',
|
|
54
|
+
severity: 'error',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const max = settings.session.maxConcurrentSessions;
|
|
58
|
+
if (!Number.isInteger(max) || max < 0 || max > 100) {
|
|
59
|
+
issues.push({
|
|
60
|
+
field: 'maxConcurrentSessions',
|
|
61
|
+
message: 'Maximum concurrent sessions must be a whole number between 0 (unlimited) and 100.',
|
|
62
|
+
severity: 'error',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return issues;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Lockout-safe MFA enable check. Enabling org-wide MFA while the acting admin
|
|
69
|
+
* has no TOTP configured would lock them (and possibly everyone) out. Callers
|
|
70
|
+
* MUST run this server-side before persisting `mfa.required = true`.
|
|
71
|
+
*/
|
|
72
|
+
export function mfaEnableLockoutError(input) {
|
|
73
|
+
if (!input.enabling)
|
|
74
|
+
return null;
|
|
75
|
+
if (!input.viewerHasMfa) {
|
|
76
|
+
return 'Set up MFA for your own account before requiring it for everyone — otherwise you would lock yourself out.';
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const IPV4_OCTET = '(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)';
|
|
81
|
+
const IPV4_RE = new RegExp(`^${IPV4_OCTET}(\\.${IPV4_OCTET}){3}$`);
|
|
82
|
+
/**
|
|
83
|
+
* Validate a single allowlist entry: an IPv4/IPv6 literal or a CIDR range.
|
|
84
|
+
* Standalone (does not import the matcher's private helpers) so it can run in
|
|
85
|
+
* the client bundle for inline form validation.
|
|
86
|
+
*/
|
|
87
|
+
export function validateIpRange(entry) {
|
|
88
|
+
const value = entry.trim();
|
|
89
|
+
if (!value)
|
|
90
|
+
return false;
|
|
91
|
+
const [addr, maskStr, ...rest] = value.split('/');
|
|
92
|
+
if (rest.length > 0 || addr === undefined)
|
|
93
|
+
return false;
|
|
94
|
+
const isV6 = addr.includes(':');
|
|
95
|
+
if (maskStr !== undefined) {
|
|
96
|
+
if (!/^\d{1,3}$/.test(maskStr))
|
|
97
|
+
return false;
|
|
98
|
+
const mask = Number(maskStr);
|
|
99
|
+
if (isV6 ? mask > 128 : mask > 32)
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
return isV6 ? isValidIpv6(addr) : IPV4_RE.test(addr);
|
|
103
|
+
}
|
|
104
|
+
function isValidIpv6(addr) {
|
|
105
|
+
// Reject IPv4-mapped + multiple `::`; otherwise validate 1–8 hextet groups.
|
|
106
|
+
if (addr.includes('.'))
|
|
107
|
+
return false;
|
|
108
|
+
const halves = addr.split('::');
|
|
109
|
+
if (halves.length > 2)
|
|
110
|
+
return false;
|
|
111
|
+
const groups = addr
|
|
112
|
+
.replace('::', ':')
|
|
113
|
+
.split(':')
|
|
114
|
+
.filter((g) => g.length > 0);
|
|
115
|
+
if (groups.length === 0 && halves.length !== 2)
|
|
116
|
+
return false;
|
|
117
|
+
if (groups.length > 8)
|
|
118
|
+
return false;
|
|
119
|
+
return groups.every((g) => /^[0-9a-fA-F]{1,4}$/.test(g));
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../../src/security/center/settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure security-posture computation.
|
|
3
|
+
*
|
|
4
|
+
* Turns gathered signals ({@link SecurityStatusInput}) into checks, warnings,
|
|
5
|
+
* an overall level, and an advisory score. No I/O — fully unit-tested so the
|
|
6
|
+
* "is this safe for production?" verdict is deterministic and reviewable.
|
|
7
|
+
*/
|
|
8
|
+
import type { SecurityStatus, SecurityStatusInput } from './types.js';
|
|
9
|
+
export declare function computeSecurityStatus(input: SecurityStatusInput): SecurityStatus;
|
|
10
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/security/center/status.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAEV,cAAc,EACd,mBAAmB,EAGpB,MAAM,YAAY,CAAA;AA2BnB,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,mBAAmB,GAAG,cAAc,CAuNhF"}
|
|
@@ -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"}
|