@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.
- package/dist/__tests__/api/updates-routes.test.d.ts +2 -0
- package/dist/__tests__/api/updates-routes.test.d.ts.map +1 -0
- package/dist/__tests__/api/updates-routes.test.js +248 -0
- package/dist/__tests__/api/updates-routes.test.js.map +1 -0
- package/dist/__tests__/upgrade/version-check.test.d.ts +2 -0
- package/dist/__tests__/upgrade/version-check.test.d.ts.map +1 -0
- package/dist/__tests__/upgrade/version-check.test.js +98 -0
- package/dist/__tests__/upgrade/version-check.test.js.map +1 -0
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +25 -11
- package/dist/api/handlers.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/upgrade/changelog.d.ts +6 -1
- package/dist/upgrade/changelog.d.ts.map +1 -1
- package/dist/upgrade/changelog.js +10 -4
- package/dist/upgrade/changelog.js.map +1 -1
- package/dist/upgrade/index.d.ts +2 -2
- package/dist/upgrade/index.d.ts.map +1 -1
- package/dist/upgrade/index.js +1 -1
- package/dist/upgrade/index.js.map +1 -1
- package/dist/upgrade/version-check.d.ts +38 -6
- package/dist/upgrade/version-check.d.ts.map +1 -1
- package/dist/upgrade/version-check.js +68 -0
- package/dist/upgrade/version-check.js.map +1 -1
- package/package.json +1 -1
|
@@ -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 @@
|
|
|
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,
|
|
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"}
|
package/dist/api/handlers.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
3429
|
-
return errorResponse('Unauthorized
|
|
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
|
|
3447
|
-
return errorResponse('Unauthorized
|
|
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
|
|
3493
|
-
return errorResponse('Unauthorized
|
|
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);
|