@actuate-media/cms-core 0.46.0 → 0.47.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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=updates-routes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updates-routes.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/api/updates-routes.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,248 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { handleActuateAPI } from '../../api/index.js';
3
+ import { initDB } from '../../db.js';
4
+ import { generateApiKey } from '../../security/api-key-enhanced.js';
5
+ /**
6
+ * Contract tests for the Settings → Updates endpoints:
7
+ * - GET /updates/check → any authed session; admins additionally see the
8
+ * GitHub-integration config hints (`canManage`).
9
+ * - GET /updates/config → admin only.
10
+ * - PUT /updates/config → admin only; encrypts credentials at rest.
11
+ * - POST /updates/apply → admin only.
12
+ *
13
+ * Regression guard for the role-check bug where these routes compared
14
+ * `session.role !== 'admin'` (lowercase) while roles are stored uppercase
15
+ * (`'ADMIN'` / `'OWNER'`) — which 403'd every real admin. Admin auth here is
16
+ * exercised through an admin-scoped API key (the same path real admin sessions
17
+ * resolve through `requireAdminScope`).
18
+ */
19
+ const VALID_SECRET = 'a'.repeat(48);
20
+ const VALID_ENC_KEY = 'a'.repeat(64);
21
+ function createMockDB(state, apiKey) {
22
+ return {
23
+ apiKey: {
24
+ findMany: async () => (apiKey ? [apiKey] : []),
25
+ findUnique: async ({ where }) => apiKey && apiKey.keyHash === where.keyHash ? apiKey : null,
26
+ update: async () => apiKey,
27
+ },
28
+ document: {
29
+ findFirst: async () => state.configDoc,
30
+ upsert: async (args) => {
31
+ state.upsertArgs = args;
32
+ return { id: 'cfg-doc' };
33
+ },
34
+ },
35
+ user: { count: async () => 1 },
36
+ auditLog: { create: async () => ({ id: 'log1' }) },
37
+ version: { create: async () => ({ id: 'v1' }) },
38
+ };
39
+ }
40
+ async function makeKey(scopes) {
41
+ const { key, keyHash, keyPrefix } = await generateApiKey({ prefix: 'act_sk', scopes });
42
+ return {
43
+ key,
44
+ record: {
45
+ id: 'apikey-1',
46
+ keyHash,
47
+ keyPrefix,
48
+ userId: 'user-1',
49
+ scopes,
50
+ ipRestrictions: null,
51
+ expiresAt: null,
52
+ lastUsedAt: null,
53
+ revokedAt: null,
54
+ },
55
+ };
56
+ }
57
+ function freshState() {
58
+ return { configDoc: null };
59
+ }
60
+ beforeEach(() => {
61
+ delete globalThis.__actuateConfig;
62
+ process.env.CMS_SECRET = VALID_SECRET;
63
+ process.env.CMS_ENCRYPTION_KEY = VALID_ENC_KEY;
64
+ delete process.env.ACTUATE_GITHUB_TOKEN;
65
+ delete process.env.ACTUATE_GITHUB_REPO;
66
+ });
67
+ afterEach(() => {
68
+ vi.restoreAllMocks();
69
+ });
70
+ /**
71
+ * Stub the network so `checkForUpdates` (and the future update-server hybrid)
72
+ * resolve deterministically without hitting npm. The update-server URL is
73
+ * forced to fail so the npm-registry fallback path is what we assert.
74
+ */
75
+ function stubUpdateNetwork(latest = '0.46.0') {
76
+ vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
77
+ const url = String(input);
78
+ if (url.includes('registry.npmjs.org')) {
79
+ return new Response(JSON.stringify({
80
+ 'dist-tags': { latest },
81
+ time: { [latest]: '2026-01-01T00:00:00.000Z' },
82
+ versions: { [latest]: {} },
83
+ }), { status: 200, headers: { 'Content-Type': 'application/json' } });
84
+ }
85
+ // Any update-server call fails → exercises the npm fallback.
86
+ return new Response('unavailable', { status: 503 });
87
+ });
88
+ }
89
+ describe('GET /updates/check', () => {
90
+ it('requires authentication', async () => {
91
+ const db = createMockDB(freshState());
92
+ initDB(db);
93
+ const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
94
+ const res = await handler(new Request('https://example.com/api/cms/updates/check'));
95
+ expect(res.status).toBe(401);
96
+ });
97
+ it('returns version info to a non-admin session without GitHub config hints', async () => {
98
+ stubUpdateNetwork();
99
+ const { key, record } = await makeKey({ media: true });
100
+ const db = createMockDB(freshState(), record);
101
+ initDB(db);
102
+ const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
103
+ const res = await handler(new Request('https://example.com/api/cms/updates/check', {
104
+ headers: { Authorization: `Bearer ${key}` },
105
+ }));
106
+ expect(res.status).toBe(200);
107
+ const body = (await res.json());
108
+ expect(body.data.canManage).toBe(false);
109
+ expect(body.data.githubRepo).toBeUndefined();
110
+ expect(body.data).not.toHaveProperty('hasGithubToken');
111
+ expect(typeof body.data.current).toBe('string');
112
+ });
113
+ it('exposes GitHub config hints to admins', async () => {
114
+ stubUpdateNetwork();
115
+ const { key, record } = await makeKey({ admin: true });
116
+ const db = createMockDB(freshState(), record);
117
+ initDB(db);
118
+ const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
119
+ const res = await handler(new Request('https://example.com/api/cms/updates/check', {
120
+ headers: { Authorization: `Bearer ${key}` },
121
+ }));
122
+ expect(res.status).toBe(200);
123
+ const body = (await res.json());
124
+ expect(body.data.canManage).toBe(true);
125
+ expect(body.data.hasGithubToken).toBe(false);
126
+ expect(body.data).toHaveProperty('githubRepo');
127
+ });
128
+ });
129
+ describe('GET /updates/config', () => {
130
+ it('rejects unauthenticated requests', async () => {
131
+ const db = createMockDB(freshState());
132
+ initDB(db);
133
+ const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
134
+ const res = await handler(new Request('https://example.com/api/cms/updates/config'));
135
+ expect(res.status).toBe(401);
136
+ });
137
+ it('rejects non-admin API keys', async () => {
138
+ const { key, record } = await makeKey({ media: true });
139
+ const db = createMockDB(freshState(), record);
140
+ initDB(db);
141
+ const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
142
+ const res = await handler(new Request('https://example.com/api/cms/updates/config', {
143
+ headers: { Authorization: `Bearer ${key}` },
144
+ }));
145
+ expect(res.status).toBe(403);
146
+ });
147
+ it('allows admins (regression: uppercase ADMIN role must pass)', async () => {
148
+ const { key, record } = await makeKey({ admin: true });
149
+ const db = createMockDB(freshState(), record);
150
+ initDB(db);
151
+ const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
152
+ const res = await handler(new Request('https://example.com/api/cms/updates/config', {
153
+ headers: { Authorization: `Bearer ${key}` },
154
+ }));
155
+ expect(res.status).toBe(200);
156
+ const body = (await res.json());
157
+ expect(body.data.hasGithubToken).toBe(false);
158
+ expect(body.data.githubRepo).toBe('');
159
+ });
160
+ });
161
+ describe('PUT /updates/config', () => {
162
+ it('rejects non-admin API keys', async () => {
163
+ const { key, record } = await makeKey({ media: true });
164
+ const db = createMockDB(freshState(), record);
165
+ initDB(db);
166
+ const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
167
+ const res = await handler(new Request('https://example.com/api/cms/updates/config', {
168
+ method: 'PUT',
169
+ headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
170
+ body: JSON.stringify({ githubRepo: 'owner/repo' }),
171
+ }));
172
+ expect(res.status).toBe(403);
173
+ });
174
+ it('validates the repository format', async () => {
175
+ const { key, record } = await makeKey({ admin: true });
176
+ const db = createMockDB(freshState(), record);
177
+ initDB(db);
178
+ const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
179
+ const res = await handler(new Request('https://example.com/api/cms/updates/config', {
180
+ method: 'PUT',
181
+ headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
182
+ body: JSON.stringify({ githubRepo: 'not a repo' }),
183
+ }));
184
+ expect(res.status).toBe(400);
185
+ });
186
+ it('encrypts credentials at rest (never persists plaintext)', async () => {
187
+ const { key, record } = await makeKey({ admin: true });
188
+ const state = freshState();
189
+ const db = createMockDB(state, record);
190
+ initDB(db);
191
+ const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
192
+ const res = await handler(new Request('https://example.com/api/cms/updates/config', {
193
+ method: 'PUT',
194
+ headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
195
+ body: JSON.stringify({ githubToken: 'ghp_secret_token_value', githubRepo: 'owner/repo' }),
196
+ }));
197
+ expect(res.status).toBe(200);
198
+ const persisted = state.upsertArgs?.create?.data ?? {};
199
+ expect(persisted.githubToken).toBeTruthy();
200
+ expect(persisted.githubRepo).toBeTruthy();
201
+ // The raw token/repo must never be stored in plaintext.
202
+ expect(JSON.stringify(state.upsertArgs)).not.toContain('ghp_secret_token_value');
203
+ expect(persisted.githubRepo).not.toBe('owner/repo');
204
+ });
205
+ });
206
+ describe('POST /updates/apply', () => {
207
+ it('rejects non-admin API keys', async () => {
208
+ const { key, record } = await makeKey({ media: true });
209
+ const db = createMockDB(freshState(), record);
210
+ initDB(db);
211
+ const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
212
+ const res = await handler(new Request('https://example.com/api/cms/updates/apply', {
213
+ method: 'POST',
214
+ headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
215
+ body: JSON.stringify({ targetVersion: '1.0.0' }),
216
+ }));
217
+ expect(res.status).toBe(403);
218
+ });
219
+ it('lets admins through the role gate (then fails on missing GitHub config)', async () => {
220
+ const { key, record } = await makeKey({ admin: true });
221
+ const db = createMockDB(freshState(), record);
222
+ initDB(db);
223
+ const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
224
+ const res = await handler(new Request('https://example.com/api/cms/updates/apply', {
225
+ method: 'POST',
226
+ headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
227
+ body: JSON.stringify({ targetVersion: '1.0.0' }),
228
+ }));
229
+ // Not 403 — the admin passed the role check; it fails later (400) because
230
+ // no GitHub token/repo is configured in this test environment.
231
+ expect(res.status).toBe(400);
232
+ const body = (await res.json());
233
+ expect(String(body.error)).toMatch(/github/i);
234
+ });
235
+ it('rejects an invalid target version', async () => {
236
+ const { key, record } = await makeKey({ admin: true });
237
+ const db = createMockDB(freshState(), record);
238
+ initDB(db);
239
+ const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
240
+ const res = await handler(new Request('https://example.com/api/cms/updates/apply', {
241
+ method: 'POST',
242
+ headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
243
+ body: JSON.stringify({ targetVersion: 'not-a-version' }),
244
+ }));
245
+ expect(res.status).toBe(400);
246
+ });
247
+ });
248
+ //# sourceMappingURL=updates-routes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updates-routes.test.js","sourceRoot":"","sources":["../../../src/__tests__/api/updates-routes.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=version-check.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-check.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/upgrade/version-check.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,98 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { checkForUpdatesViaServer, getUpdateStatus } from '../../upgrade/version-check.js';
3
+ const SERVER = 'https://updates.test.example';
4
+ function serverPayload() {
5
+ return {
6
+ latestVersion: '0.46.0',
7
+ currentVersion: '0.45.0',
8
+ severity: 'security',
9
+ changelog: [
10
+ {
11
+ version: '0.46.0',
12
+ date: '2026-01-02',
13
+ summary: 'Patch a security issue',
14
+ type: 'security',
15
+ },
16
+ ],
17
+ hasMigrations: true,
18
+ breakingChanges: ['Removed legacy API'],
19
+ pluginCompatibility: [
20
+ { name: '@actuate-media/plugin-seo', compatible: true, minVersion: '0.46.0' },
21
+ ],
22
+ updateCommand: 'npm update ...',
23
+ };
24
+ }
25
+ function npmPayload(latest = '0.46.0') {
26
+ return {
27
+ 'dist-tags': { latest },
28
+ time: { [latest]: '2026-01-02T00:00:00.000Z' },
29
+ versions: { '0.45.0': {}, [latest]: {} },
30
+ };
31
+ }
32
+ beforeEach(() => {
33
+ process.env.ACTUATE_UPDATE_SERVER_URL = SERVER;
34
+ });
35
+ afterEach(() => {
36
+ vi.restoreAllMocks();
37
+ delete process.env.ACTUATE_UPDATE_SERVER_URL;
38
+ });
39
+ describe('checkForUpdatesViaServer', () => {
40
+ it('maps a rich server response into VersionInfo', async () => {
41
+ vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response(JSON.stringify(serverPayload()), { status: 200 }));
42
+ const info = await checkForUpdatesViaServer('0.45.0', SERVER);
43
+ expect(info).not.toBeNull();
44
+ expect(info.updateAvailable).toBe(true);
45
+ expect(info.severity).toBe('security');
46
+ expect(info.hasMigrations).toBe(true);
47
+ expect(info.breakingChanges).toEqual(['Removed legacy API']);
48
+ expect(info.changelog?.[0]?.type).toBe('security');
49
+ expect(info.source).toBe('update-server');
50
+ });
51
+ it('returns null on a non-2xx response (caller falls back)', async () => {
52
+ vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response('nope', { status: 503 }));
53
+ expect(await checkForUpdatesViaServer('0.45.0', SERVER)).toBeNull();
54
+ });
55
+ it('returns null on a network error', async () => {
56
+ vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('boom'));
57
+ expect(await checkForUpdatesViaServer('0.45.0', SERVER)).toBeNull();
58
+ });
59
+ it('returns null for an unparseable current version', async () => {
60
+ expect(await checkForUpdatesViaServer('garbage', SERVER)).toBeNull();
61
+ });
62
+ it('reports no update when the server says up-to-date', async () => {
63
+ vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response(JSON.stringify({ latestVersion: '0.45.0', currentVersion: '0.45.0', upToDate: true }), {
64
+ status: 200,
65
+ }));
66
+ const info = await checkForUpdatesViaServer('0.45.0', SERVER);
67
+ expect(info.updateAvailable).toBe(false);
68
+ });
69
+ });
70
+ describe('getUpdateStatus (hybrid)', () => {
71
+ it('prefers the update server when available', async () => {
72
+ vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
73
+ if (String(input).includes(SERVER)) {
74
+ return new Response(JSON.stringify(serverPayload()), { status: 200 });
75
+ }
76
+ throw new Error('npm should not be called');
77
+ });
78
+ const info = await getUpdateStatus('0.45.0');
79
+ expect(info.source).toBe('update-server');
80
+ expect(info.severity).toBe('security');
81
+ });
82
+ it('falls back to the npm registry when the server fails', async () => {
83
+ vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
84
+ const url = String(input);
85
+ if (url.includes(SERVER))
86
+ return new Response('down', { status: 502 });
87
+ if (url.includes('registry.npmjs.org')) {
88
+ return new Response(JSON.stringify(npmPayload('0.46.0')), { status: 200 });
89
+ }
90
+ throw new Error(`unexpected fetch: ${url}`);
91
+ });
92
+ const info = await getUpdateStatus('0.45.0');
93
+ expect(info.source).toBe('npm');
94
+ expect(info.latest).toBe('0.46.0');
95
+ expect(info.updateAvailable).toBe(true);
96
+ });
97
+ });
98
+ //# sourceMappingURL=version-check.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-check.test.js","sourceRoot":"","sources":["../../../src/__tests__/upgrade/version-check.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/api/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AA+lC5C,iFAAiF;AACjF,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AA8JD,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AAiVD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA8uYzD"}
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/api/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AA+lC5C,iFAAiF;AACjF,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AA8JD,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AAiVD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAsvYzD"}
@@ -35,7 +35,7 @@ import { validateSubmissionFiles, FORM_UPLOAD_ALLOWED_MIME_TYPES, DEFAULT_MAX_FI
35
35
  import { resolveSubmissionFields } from '../forms/submission.js';
36
36
  import { createFormWebhook, listFormWebhooks, updateFormWebhook, deleteFormWebhook, testFormWebhook, FormWebhookError, } from '../forms/webhooks.js';
37
37
  import { sendFormNotifications } from '../forms/notify.js';
38
- import { checkForUpdates } from '../upgrade/version-check.js';
38
+ import { getUpdateStatus } from '../upgrade/version-check.js';
39
39
  import { createUpgradePR } from '../upgrade/upgrade-pr.js';
40
40
  import { encryptField, decryptField } from '../security/encrypted-fields.js';
41
41
  import { encryptSecret, decryptSecret, encryptStringArray, decryptStringArray, } from '../security/secret-storage.js';
@@ -3408,11 +3408,19 @@ export function registerCMSRoutes(router) {
3408
3408
  if (!session)
3409
3409
  return errorResponse('Unauthorized', 401);
3410
3410
  const coreVersion = getActuateCoreVersion() ?? '0.1.0';
3411
- const info = await checkForUpdates(coreVersion);
3411
+ const info = await getUpdateStatus(coreVersion);
3412
+ // GitHub integration config is only relevant to (and only exposed to)
3413
+ // admins — the repo name shouldn't leak to non-admin sessions. The
3414
+ // version/changelog payload itself is safe for any authenticated user.
3415
+ const isAdmin = requireAdminScope(session) === null;
3416
+ if (!isAdmin) {
3417
+ return json({ data: { ...info, canManage: false } });
3418
+ }
3412
3419
  const saved = await getUpdateConfig();
3413
3420
  return json({
3414
3421
  data: {
3415
3422
  ...info,
3423
+ canManage: true,
3416
3424
  hasGithubToken: !!(saved.githubToken || process.env.ACTUATE_GITHUB_TOKEN),
3417
3425
  githubRepo: saved.githubRepo || process.env.ACTUATE_GITHUB_REPO || '',
3418
3426
  },
@@ -3425,9 +3433,11 @@ export function registerCMSRoutes(router) {
3425
3433
  router.get('/updates/config', async (request) => {
3426
3434
  try {
3427
3435
  const session = await extractSession(request);
3428
- if (!session || session.role !== 'admin') {
3429
- return errorResponse('Unauthorized — admin only', 403);
3430
- }
3436
+ if (!session)
3437
+ return errorResponse('Unauthorized', 401);
3438
+ const adminErr = requireAdminScope(session);
3439
+ if (adminErr)
3440
+ return adminErr;
3431
3441
  const saved = await getUpdateConfig();
3432
3442
  return json({
3433
3443
  data: {
@@ -3443,9 +3453,11 @@ export function registerCMSRoutes(router) {
3443
3453
  router.put('/updates/config', async (request) => {
3444
3454
  try {
3445
3455
  const session = await extractSession(request);
3446
- if (!session || session.role !== 'admin') {
3447
- return errorResponse('Unauthorized — admin only', 403);
3448
- }
3456
+ if (!session)
3457
+ return errorResponse('Unauthorized', 401);
3458
+ const adminErr = requireAdminScope(session);
3459
+ if (adminErr)
3460
+ return adminErr;
3449
3461
  const encKey = process.env.CMS_ENCRYPTION_KEY;
3450
3462
  if (!encKey) {
3451
3463
  return errorResponse('CMS_ENCRYPTION_KEY is required to store encrypted credentials.', 400);
@@ -3489,9 +3501,11 @@ export function registerCMSRoutes(router) {
3489
3501
  router.post('/updates/apply', async (request) => {
3490
3502
  try {
3491
3503
  const session = await extractSession(request);
3492
- if (!session || session.role !== 'admin') {
3493
- return errorResponse('Unauthorized — admin only', 403);
3494
- }
3504
+ if (!session)
3505
+ return errorResponse('Unauthorized', 401);
3506
+ const adminErr = requireAdminScope(session);
3507
+ if (adminErr)
3508
+ return adminErr;
3495
3509
  const body = (await request.json());
3496
3510
  if (!body.targetVersion) {
3497
3511
  return errorResponse('targetVersion is required', 400);