@actuate-media/cms-admin 0.26.0 → 0.28.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.
Files changed (105) hide show
  1. package/dist/__tests__/views/appearance-settings.render.test.d.ts +2 -0
  2. package/dist/__tests__/views/appearance-settings.render.test.d.ts.map +1 -0
  3. package/dist/__tests__/views/appearance-settings.render.test.js +95 -0
  4. package/dist/__tests__/views/appearance-settings.render.test.js.map +1 -0
  5. package/dist/__tests__/views/security-settings.render.test.d.ts +2 -0
  6. package/dist/__tests__/views/security-settings.render.test.d.ts.map +1 -0
  7. package/dist/__tests__/views/security-settings.render.test.js +175 -0
  8. package/dist/__tests__/views/security-settings.render.test.js.map +1 -0
  9. package/dist/actuate-admin.css +1 -1
  10. package/dist/components/ThemeProvider.d.ts +47 -4
  11. package/dist/components/ThemeProvider.d.ts.map +1 -1
  12. package/dist/components/ThemeProvider.js +173 -27
  13. package/dist/components/ThemeProvider.js.map +1 -1
  14. package/dist/components/ui/Badge.d.ts +1 -1
  15. package/dist/components/ui/Button.d.ts +1 -1
  16. package/dist/components/ui/Card.d.ts +2 -2
  17. package/dist/components/ui/Input.d.ts +1 -1
  18. package/dist/components/ui/Select.d.ts +1 -1
  19. package/dist/layout/Sidebar.d.ts.map +1 -1
  20. package/dist/layout/Sidebar.js +47 -8
  21. package/dist/layout/Sidebar.js.map +1 -1
  22. package/dist/views/Settings.d.ts.map +1 -1
  23. package/dist/views/Settings.js +5 -17
  24. package/dist/views/Settings.js.map +1 -1
  25. package/dist/views/settings/AccessProtectionCard.d.ts +10 -0
  26. package/dist/views/settings/AccessProtectionCard.d.ts.map +1 -0
  27. package/dist/views/settings/AccessProtectionCard.js +20 -0
  28. package/dist/views/settings/AccessProtectionCard.js.map +1 -0
  29. package/dist/views/settings/AdminThemeCard.d.ts +6 -0
  30. package/dist/views/settings/AdminThemeCard.d.ts.map +1 -0
  31. package/dist/views/settings/AdminThemeCard.js +31 -0
  32. package/dist/views/settings/AdminThemeCard.js.map +1 -0
  33. package/dist/views/settings/AppearanceTab.d.ts +7 -0
  34. package/dist/views/settings/AppearanceTab.d.ts.map +1 -0
  35. package/dist/views/settings/AppearanceTab.js +24 -0
  36. package/dist/views/settings/AppearanceTab.js.map +1 -0
  37. package/dist/views/settings/AuthMfaCard.d.ts +10 -0
  38. package/dist/views/settings/AuthMfaCard.d.ts.map +1 -0
  39. package/dist/views/settings/AuthMfaCard.js +33 -0
  40. package/dist/views/settings/AuthMfaCard.js.map +1 -0
  41. package/dist/views/settings/BrandPreviewCard.d.ts +5 -0
  42. package/dist/views/settings/BrandPreviewCard.d.ts.map +1 -0
  43. package/dist/views/settings/BrandPreviewCard.js +18 -0
  44. package/dist/views/settings/BrandPreviewCard.js.map +1 -0
  45. package/dist/views/settings/BrandingCard.d.ts +6 -0
  46. package/dist/views/settings/BrandingCard.d.ts.map +1 -0
  47. package/dist/views/settings/BrandingCard.js +38 -0
  48. package/dist/views/settings/BrandingCard.js.map +1 -0
  49. package/dist/views/settings/SecretsAuditCard.d.ts +11 -0
  50. package/dist/views/settings/SecretsAuditCard.d.ts.map +1 -0
  51. package/dist/views/settings/SecretsAuditCard.js +22 -0
  52. package/dist/views/settings/SecretsAuditCard.js.map +1 -0
  53. package/dist/views/settings/SecurityStatusCard.d.ts +6 -0
  54. package/dist/views/settings/SecurityStatusCard.d.ts.map +1 -0
  55. package/dist/views/settings/SecurityStatusCard.js +35 -0
  56. package/dist/views/settings/SecurityStatusCard.js.map +1 -0
  57. package/dist/views/settings/SecurityTab.d.ts +5 -0
  58. package/dist/views/settings/SecurityTab.d.ts.map +1 -0
  59. package/dist/views/settings/SecurityTab.js +41 -0
  60. package/dist/views/settings/SecurityTab.js.map +1 -0
  61. package/dist/views/settings/SessionPolicyCard.d.ts +11 -0
  62. package/dist/views/settings/SessionPolicyCard.d.ts.map +1 -0
  63. package/dist/views/settings/SessionPolicyCard.js +21 -0
  64. package/dist/views/settings/SessionPolicyCard.js.map +1 -0
  65. package/dist/views/settings/TypographyMotionCard.d.ts +6 -0
  66. package/dist/views/settings/TypographyMotionCard.d.ts.map +1 -0
  67. package/dist/views/settings/TypographyMotionCard.js +8 -0
  68. package/dist/views/settings/TypographyMotionCard.js.map +1 -0
  69. package/dist/views/settings/securityPrimitives.d.ts +21 -0
  70. package/dist/views/settings/securityPrimitives.d.ts.map +1 -0
  71. package/dist/views/settings/securityPrimitives.js +31 -0
  72. package/dist/views/settings/securityPrimitives.js.map +1 -0
  73. package/dist/views/settings/useAppearanceSettings.d.ts +69 -0
  74. package/dist/views/settings/useAppearanceSettings.d.ts.map +1 -0
  75. package/dist/views/settings/useAppearanceSettings.js +344 -0
  76. package/dist/views/settings/useAppearanceSettings.js.map +1 -0
  77. package/dist/views/settings/useGeneralSettings.d.ts +4 -1
  78. package/dist/views/settings/useGeneralSettings.d.ts.map +1 -1
  79. package/dist/views/settings/useGeneralSettings.js.map +1 -1
  80. package/dist/views/settings/useSecuritySettings.d.ts +37 -0
  81. package/dist/views/settings/useSecuritySettings.d.ts.map +1 -0
  82. package/dist/views/settings/useSecuritySettings.js +154 -0
  83. package/dist/views/settings/useSecuritySettings.js.map +1 -0
  84. package/package.json +2 -2
  85. package/src/__tests__/views/appearance-settings.render.test.tsx +132 -0
  86. package/src/__tests__/views/security-settings.render.test.tsx +212 -0
  87. package/src/components/ThemeProvider.tsx +249 -36
  88. package/src/layout/Sidebar.tsx +81 -13
  89. package/src/styles/theme.css +32 -2
  90. package/src/views/Settings.tsx +5 -68
  91. package/src/views/settings/AccessProtectionCard.tsx +143 -0
  92. package/src/views/settings/AdminThemeCard.tsx +145 -0
  93. package/src/views/settings/AppearanceTab.tsx +81 -0
  94. package/src/views/settings/AuthMfaCard.tsx +118 -0
  95. package/src/views/settings/BrandPreviewCard.tsx +125 -0
  96. package/src/views/settings/BrandingCard.tsx +332 -0
  97. package/src/views/settings/SecretsAuditCard.tsx +129 -0
  98. package/src/views/settings/SecurityStatusCard.tsx +110 -0
  99. package/src/views/settings/SecurityTab.tsx +162 -0
  100. package/src/views/settings/SessionPolicyCard.tsx +123 -0
  101. package/src/views/settings/TypographyMotionCard.tsx +65 -0
  102. package/src/views/settings/securityPrimitives.tsx +75 -0
  103. package/src/views/settings/useAppearanceSettings.ts +513 -0
  104. package/src/views/settings/useGeneralSettings.ts +4 -1
  105. package/src/views/settings/useSecuritySettings.ts +197 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=appearance-settings.render.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appearance-settings.render.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/views/appearance-settings.render.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,95 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // @vitest-environment happy-dom
3
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
4
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
5
+ // The Appearance tab reads `/appearance` (settings + resolved brand), then on
6
+ // save read-modify-writes `/globals/settings`. Uploads POST `/media/upload`.
7
+ let appearanceData;
8
+ const cmsApi = vi.fn(async (endpoint, options) => {
9
+ const method = options?.method ?? 'GET';
10
+ if (endpoint === '/appearance' && method === 'GET')
11
+ return { data: appearanceData, status: 200 };
12
+ if (endpoint === '/globals/settings' && method === 'GET')
13
+ return { data: { siteTitle: 'Actuate Media' }, status: 200 };
14
+ if (endpoint === '/globals/settings' && method === 'PUT')
15
+ return { data: {}, status: 200 };
16
+ if (endpoint === '/media/upload' && method === 'POST')
17
+ return {
18
+ data: { id: 'new-logo', url: 'https://cdn.example.com/x.svg', mimeType: 'image/svg+xml' },
19
+ status: 200,
20
+ };
21
+ return { data: {}, status: 200 };
22
+ });
23
+ vi.mock('../../lib/api.js', () => ({ cmsApi: (e, o) => cmsApi(e, o) }));
24
+ const { AppearanceTab } = await import('../../views/settings/AppearanceTab.js');
25
+ function baseData(overrides = {}) {
26
+ return {
27
+ appearance: {
28
+ adminColorModeDefault: 'system',
29
+ adminAccentColor: 'purple',
30
+ sidebarDensity: 'default',
31
+ contentFont: 'DM Sans',
32
+ reduceMotion: false,
33
+ },
34
+ brand: { mode: 'inheritPublicBrand' },
35
+ resolved: { admin: { initials: 'AM' } },
36
+ assets: [],
37
+ siteTitle: 'Actuate Media',
38
+ ...overrides,
39
+ };
40
+ }
41
+ beforeEach(() => {
42
+ cmsApi.mockClear();
43
+ appearanceData = baseData();
44
+ });
45
+ describe('AppearanceTab', () => {
46
+ it('renders the branding/theme/typography cards with a clean (disabled) save bar', async () => {
47
+ render(_jsx(AppearanceTab, { role: "ADMIN" }));
48
+ await screen.findByText('Branding');
49
+ expect(screen.getByText('Admin Theme')).toBeTruthy();
50
+ expect(screen.getByText('Typography & Motion')).toBeTruthy();
51
+ expect(screen.getByText('Brand Preview')).toBeTruthy();
52
+ expect(screen.getByRole('button', { name: 'Save Changes' }).disabled).toBe(true);
53
+ });
54
+ it('enables Save when the accent changes and persists appearance to the settings global', async () => {
55
+ render(_jsx(AppearanceTab, { role: "ADMIN" }));
56
+ await screen.findByText('Branding');
57
+ fireEvent.click(screen.getByRole('radio', { name: 'Blue' }));
58
+ const save = screen.getByRole('button', { name: 'Save Changes' });
59
+ expect(save.disabled).toBe(false);
60
+ fireEvent.click(save);
61
+ await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/globals/settings', expect.objectContaining({
62
+ method: 'PUT',
63
+ body: expect.stringContaining('"adminAccentColor":"blue"'),
64
+ })));
65
+ });
66
+ it('warns when dark mode is the default but no dark-mode logo is set', async () => {
67
+ appearanceData = baseData({
68
+ appearance: {
69
+ adminColorModeDefault: 'dark',
70
+ adminAccentColor: 'purple',
71
+ sidebarDensity: 'default',
72
+ contentFont: 'DM Sans',
73
+ reduceMotion: false,
74
+ },
75
+ });
76
+ render(_jsx(AppearanceTab, { role: "ADMIN" }));
77
+ expect(await screen.findByText(/no dark-mode logo is set/i)).toBeTruthy();
78
+ });
79
+ it('uploads a logo through the Media Center and enables Save', async () => {
80
+ render(_jsx(AppearanceTab, { role: "ADMIN" }));
81
+ await screen.findByText('Branding');
82
+ const input = screen.getByLabelText('Primary Logo');
83
+ const file = new File(['<svg viewBox="0 0 400 100" xmlns="http://www.w3.org/2000/svg"></svg>'], 'logo.svg', { type: 'image/svg+xml' });
84
+ fireEvent.change(input, { target: { files: [file] } });
85
+ await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/media/upload', expect.objectContaining({ method: 'POST' })));
86
+ await waitFor(() => expect(screen.getByRole('button', { name: 'Save Changes' }).disabled).toBe(false));
87
+ });
88
+ it('is read-only for non-admins', async () => {
89
+ render(_jsx(AppearanceTab, { role: "EDITOR" }));
90
+ expect(await screen.findByText(/read-only access to appearance/i)).toBeTruthy();
91
+ // Upload control disabled.
92
+ expect(screen.getByRole('button', { name: /upload primary logo/i }).disabled).toBe(true);
93
+ });
94
+ });
95
+ //# sourceMappingURL=appearance-settings.render.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appearance-settings.render.test.js","sourceRoot":"","sources":["../../../src/__tests__/views/appearance-settings.render.test.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=security-settings.render.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-settings.render.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/views/security-settings.render.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,175 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // @vitest-environment happy-dom
3
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
4
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
5
+ // The Security tab reads `/security` (computed posture + summaries + editable
6
+ // policy), saves via PUT `/security`, and revokes sessions via POST.
7
+ let securityData;
8
+ const cmsApi = vi.fn(async (endpoint, options) => {
9
+ const method = options?.method ?? 'GET';
10
+ if (endpoint === '/security' && method === 'GET')
11
+ return { data: securityData, status: 200 };
12
+ if (endpoint === '/security' && method === 'PUT') {
13
+ const body = JSON.parse(String(options?.body ?? '{}'));
14
+ securityData = {
15
+ ...securityData,
16
+ settings: {
17
+ mfa: { required: body.mfa.required, gracePeriodDays: body.mfa.gracePeriodDays },
18
+ session: { maxConcurrentSessions: body.session.maxConcurrentSessions },
19
+ },
20
+ };
21
+ return { data: securityData, status: 200 };
22
+ }
23
+ if (endpoint === '/security/sessions/revoke-all' && method === 'POST')
24
+ return { data: { revoked: 2 }, status: 200 };
25
+ return { data: {}, status: 200 };
26
+ });
27
+ vi.mock('../../lib/api.js', () => ({ cmsApi: (e, o) => cmsApi(e, o) }));
28
+ const { SecurityTab } = await import('../../views/settings/SecurityTab.js');
29
+ function baseData(overrides = {}) {
30
+ return {
31
+ settings: {
32
+ mfa: { required: false, gracePeriodDays: 7 },
33
+ session: { maxConcurrentSessions: 5 },
34
+ },
35
+ status: {
36
+ status: 'needs_attention',
37
+ score: 88,
38
+ label: 'Needs attention',
39
+ description: 'Some recommended controls are missing.',
40
+ checks: [
41
+ { key: 'mfa', label: 'MFA coverage', status: 'warning', value: '1 of 2 admins' },
42
+ { key: 'csrf', label: 'CSRF protection', status: 'pass', value: 'Enabled' },
43
+ ],
44
+ warnings: [
45
+ {
46
+ id: 'mfa-not-required',
47
+ severity: 'warning',
48
+ category: 'auth',
49
+ title: 'MFA is not required in production',
50
+ description: 'Requiring MFA reduces account-takeover risk.',
51
+ },
52
+ ],
53
+ lastCheckedAt: '2026-06-01T00:00:00.000Z',
54
+ },
55
+ users: {
56
+ totalUsers: 3,
57
+ totalAdmins: 2,
58
+ withMfa: 1,
59
+ withoutMfa: 2,
60
+ adminsWithoutMfa: 1,
61
+ inactive: 0,
62
+ },
63
+ apiKeys: { active: 1, withoutExpiration: 0, broadScope: 0, staleUnused90d: 0 },
64
+ secrets: {
65
+ cmsSecret: 'pass',
66
+ encryptionKey: 'pass',
67
+ rateLimitBackend: 'distributed',
68
+ apiKeysHashed: true,
69
+ webhookSecretsEncrypted: true,
70
+ },
71
+ ipAllowlist: {
72
+ enabled: false,
73
+ source: 'none',
74
+ ranges: [],
75
+ currentIp: '1.2.3.4',
76
+ currentIpAllowed: null,
77
+ },
78
+ failedLogins24h: 0,
79
+ activeSessions: 2,
80
+ csrfEnabled: true,
81
+ uploadValidation: true,
82
+ auditEnabled: true,
83
+ sessionMaxAgeHours: 168,
84
+ viewerHasMfa: true,
85
+ environment: 'production',
86
+ sources: {
87
+ ipAllowlist: { source: 'config', editable: false },
88
+ csrf: { source: 'computed', editable: false },
89
+ rateLimiting: { source: 'environment', editable: false },
90
+ uploadValidation: { source: 'computed', editable: false },
91
+ cmsSecret: { source: 'environment', editable: false },
92
+ encryptionKey: { source: 'environment', editable: false },
93
+ audit: { source: 'computed', editable: false },
94
+ },
95
+ ...overrides,
96
+ };
97
+ }
98
+ beforeEach(() => {
99
+ cmsApi.mockClear();
100
+ securityData = baseData();
101
+ });
102
+ describe('SecurityTab', () => {
103
+ it('renders the status card and all security sections with a disabled save bar', async () => {
104
+ render(_jsx(SecurityTab, { role: "ADMIN" }));
105
+ await screen.findByText('Security Status');
106
+ expect(screen.getByText('Authentication & MFA')).toBeTruthy();
107
+ expect(screen.getByText('Session Policy')).toBeTruthy();
108
+ expect(screen.getByText('Access & Request Protection')).toBeTruthy();
109
+ expect(screen.getByText('Secrets, Encryption & Audit')).toBeTruthy();
110
+ // Status badge + warning surfaced.
111
+ expect(screen.getByText('Needs attention')).toBeTruthy();
112
+ expect(screen.getByText('MFA is not required in production')).toBeTruthy();
113
+ expect(screen.getByRole('button', { name: 'Save Changes' }).disabled).toBe(true);
114
+ });
115
+ it('enables Save when concurrent-session cap changes and persists via PUT /security', async () => {
116
+ render(_jsx(SecurityTab, { role: "ADMIN" }));
117
+ await screen.findByText('Security Status');
118
+ const select = screen.getByLabelText(/Maximum concurrent sessions/);
119
+ fireEvent.change(select, { target: { value: '3' } });
120
+ const save = screen.getByRole('button', { name: 'Save Changes' });
121
+ expect(save.disabled).toBe(false);
122
+ fireEvent.click(save);
123
+ await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/security', expect.objectContaining({
124
+ method: 'PUT',
125
+ body: expect.stringContaining('"maxConcurrentSessions":3'),
126
+ })));
127
+ });
128
+ it('blocks enabling Require MFA (lockout-safe) when the acting admin has no MFA', async () => {
129
+ securityData = baseData({ viewerHasMfa: false });
130
+ render(_jsx(SecurityTab, { role: "ADMIN" }));
131
+ await screen.findByText('Security Status');
132
+ const toggle = screen.getByRole('switch', {
133
+ name: 'Require MFA for admin users',
134
+ });
135
+ // Toggle is disabled until the admin sets up their own MFA.
136
+ expect(toggle.disabled).toBe(true);
137
+ expect(screen.getByText(/Set up MFA on your own account/i)).toBeTruthy();
138
+ });
139
+ it('confirms before requiring MFA and then persists it (admin has MFA)', async () => {
140
+ render(_jsx(SecurityTab, { role: "ADMIN" }));
141
+ await screen.findByText('Security Status');
142
+ fireEvent.click(screen.getByRole('switch', { name: 'Require MFA for admin users' }));
143
+ // Confirmation dialog appears; nothing saved yet.
144
+ const confirm = await screen.findByRole('button', { name: 'Require MFA' });
145
+ fireEvent.click(confirm);
146
+ const save = screen.getByRole('button', { name: 'Save Changes' });
147
+ expect(save.disabled).toBe(false);
148
+ fireEvent.click(save);
149
+ await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/security', expect.objectContaining({
150
+ method: 'PUT',
151
+ body: expect.stringContaining('"required":true'),
152
+ })));
153
+ });
154
+ it('revokes other sessions through a confirmation flow', async () => {
155
+ render(_jsx(SecurityTab, { role: "ADMIN" }));
156
+ await screen.findByText('Security Status');
157
+ fireEvent.click(screen.getByRole('button', { name: 'Revoke other sessions' }));
158
+ const confirm = await screen.findByRole('button', { name: 'Revoke sessions' });
159
+ fireEvent.click(confirm);
160
+ await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/security/sessions/revoke-all', expect.objectContaining({ method: 'POST' })));
161
+ await screen.findByText('Other sessions revoked.');
162
+ });
163
+ it('is read-only for non-admin roles', async () => {
164
+ render(_jsx(SecurityTab, { role: "EDITOR" }));
165
+ await screen.findByText('Security Status');
166
+ expect(screen.getByText(/read-only access/i)).toBeTruthy();
167
+ const toggle = screen.getByRole('switch', {
168
+ name: 'Require MFA for admin users',
169
+ });
170
+ expect(toggle.disabled).toBe(true);
171
+ const select = screen.getByLabelText(/Maximum concurrent sessions/);
172
+ expect(select.disabled).toBe(true);
173
+ });
174
+ });
175
+ //# sourceMappingURL=security-settings.render.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-settings.render.test.js","sourceRoot":"","sources":["../../../src/__tests__/views/security-settings.render.test.tsx"],"names":[],"mappings":"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"}