@setzkasten-cms/astro-admin 1.4.6 → 1.5.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/api-routes/_auth-guard.d.ts +27 -3
- package/dist/api-routes/_auth-guard.js +5 -2
- package/dist/api-routes/_dev-session-secret.d.ts +8 -0
- package/dist/api-routes/_dev-session-secret.js +8 -0
- package/dist/api-routes/_github-token.js +1 -1
- package/dist/api-routes/_role-resolver.js +6 -3
- package/dist/api-routes/_session-secret.d.ts +19 -0
- package/dist/api-routes/_session-secret.js +7 -0
- package/dist/api-routes/_session-signing.d.ts +45 -0
- package/dist/api-routes/_session-signing.js +8 -0
- package/dist/api-routes/_webhook-dispatcher.js +4 -4
- package/dist/api-routes/asset-proxy.js +1 -1
- package/dist/api-routes/auth-callback.js +12 -5
- package/dist/api-routes/auth-logout.d.ts +4 -4
- package/dist/api-routes/auth-logout.js +8 -2
- package/dist/api-routes/auth-session.d.ts +6 -0
- package/dist/api-routes/auth-session.js +19 -19
- package/dist/api-routes/auth-setzkasten-login.js +14 -7
- package/dist/api-routes/catalog-add.js +59 -17
- package/dist/api-routes/catalog-export.js +14 -4
- package/dist/api-routes/config.d.ts +10 -3
- package/dist/api-routes/config.js +26 -4
- package/dist/api-routes/deploy-hook.js +8 -8
- package/dist/api-routes/editors.d.ts +1 -1
- package/dist/api-routes/editors.js +5 -2
- package/dist/api-routes/github-proxy.js +30 -8
- package/dist/api-routes/global-config.js +6 -3
- package/dist/api-routes/history-rollback.js +31 -14
- package/dist/api-routes/history-version.js +8 -6
- package/dist/api-routes/history.js +5 -2
- package/dist/api-routes/icons-local.js +1 -1
- package/dist/api-routes/init-add-section.js +113 -47
- package/dist/api-routes/init-apply.js +56 -42
- package/dist/api-routes/init-migrate.js +43 -36
- package/dist/api-routes/init-scan-page.d.ts +1 -1
- package/dist/api-routes/init-scan-page.js +59 -13
- package/dist/api-routes/init-scan.js +22 -7
- package/dist/api-routes/migrate-to-multi.js +5 -2
- package/dist/api-routes/pages.js +15 -4
- package/dist/api-routes/section-add.js +68 -16
- package/dist/api-routes/section-commit-pending.js +70 -22
- package/dist/api-routes/section-delete.js +49 -14
- package/dist/api-routes/section-duplicate.js +65 -16
- package/dist/api-routes/section-prepare-copy.js +15 -2
- package/dist/api-routes/section-prepare.js +25 -4
- package/dist/api-routes/setup-github-app-bounce.js +15 -1
- package/dist/api-routes/setup-github-app-branches.js +9 -6
- package/dist/api-routes/setup-github-app-callback.js +24 -1
- package/dist/api-routes/setup-github-app-credentials.d.ts +27 -0
- package/dist/api-routes/setup-github-app-credentials.js +43 -0
- package/dist/api-routes/setup-github-app-installed.js +22 -1
- package/dist/api-routes/setup-github-app-repos.js +5 -2
- package/dist/api-routes/setup-github-app.d.ts +4 -0
- package/dist/api-routes/setup-github-app.js +19 -2
- package/dist/api-routes/updater-register.js +7 -1
- package/dist/api-routes/webhooks-status.js +5 -2
- package/dist/api-routes/webhooks-test.js +9 -8
- package/dist/api-routes/webhooks.js +12 -14
- package/dist/api-routes/websites-add.js +5 -2
- package/dist/api-routes/websites-remove.js +5 -2
- package/dist/{chunk-ZQDGGWJP.js → chunk-5KMGSFCZ.js} +2 -2
- package/dist/{chunk-Q3N336KR.js → chunk-CDXCYYQR.js} +29 -24
- package/dist/{chunk-NKDATSPA.js → chunk-DP6RTINQ.js} +1 -1
- package/dist/chunk-KENFINT4.js +76 -0
- package/dist/chunk-ONP6BRZO.js +47 -0
- package/dist/{chunk-INIWFKQ3.js → chunk-Q5HV47DW.js} +33 -19
- package/dist/chunk-QVCW6EF3.js +26 -0
- package/dist/{chunk-TD76R3A6.js → chunk-UHI6323G.js} +293 -174
- package/dist/{chunk-AM4DZXXM.js → chunk-UJAFZEX2.js} +76 -9
- package/package.json +12 -6
- package/src/api-routes/__tests__/_session-signing.test.ts +114 -0
- package/src/api-routes/__tests__/_session-test-helper.ts +27 -0
- package/src/api-routes/__tests__/add-section-helpers.test.ts +59 -25
- package/src/api-routes/__tests__/auth-guard.test.ts +46 -7
- package/src/api-routes/__tests__/catalog-api.test.ts +14 -6
- package/src/api-routes/__tests__/commit-trailers.test.ts +5 -5
- package/src/api-routes/__tests__/deferred-operations.test.ts +9 -12
- package/src/api-routes/__tests__/deploy-hook.test.ts +3 -8
- package/src/api-routes/__tests__/feature-gate.test.ts +1 -1
- package/src/api-routes/__tests__/github-cache.test.ts +1 -1
- package/src/api-routes/__tests__/github-token.test.ts +1 -1
- package/src/api-routes/__tests__/global-config-theme.test.ts +4 -4
- package/src/api-routes/__tests__/history-rollback.test.ts +6 -3
- package/src/api-routes/__tests__/history.test.ts +9 -6
- package/src/api-routes/__tests__/init-scan-page-resolve-config.test.ts +11 -3
- package/src/api-routes/__tests__/migrate-to-multi.test.ts +5 -1
- package/src/api-routes/__tests__/pages-meta-store.test.ts +10 -5
- package/src/api-routes/__tests__/pages.test.ts +7 -2
- package/src/api-routes/__tests__/patch-page-file.test.ts +71 -19
- package/src/api-routes/__tests__/route-registry.test.ts +11 -18
- package/src/api-routes/__tests__/scan-page-helpers.test.ts +13 -10
- package/src/api-routes/__tests__/section-management.test.ts +28 -28
- package/src/api-routes/__tests__/setup-github-app-callback.test.ts +58 -16
- package/src/api-routes/__tests__/setup-github-app-repos.test.ts +4 -5
- package/src/api-routes/__tests__/setup-github-app.test.ts +27 -7
- package/src/api-routes/__tests__/storage-config-for-request.test.ts +83 -0
- package/src/api-routes/__tests__/updater-register.test.ts +230 -0
- package/src/api-routes/__tests__/webhook-signing.test.ts +1 -1
- package/src/api-routes/__tests__/webhooks.test.ts +19 -7
- package/src/api-routes/__tests__/websites-add.test.ts +2 -1
- package/src/api-routes/__tests__/websites-remove.test.ts +2 -1
- package/src/api-routes/_auth-guard.ts +47 -15
- package/src/api-routes/_commit-trailers.ts +3 -2
- package/src/api-routes/_dev-session-secret.ts +79 -0
- package/src/api-routes/_github-token.ts +1 -1
- package/src/api-routes/_pages-meta-store.ts +2 -2
- package/src/api-routes/_role-resolver.ts +7 -5
- package/src/api-routes/_session-secret.ts +46 -0
- package/src/api-routes/_session-signing.ts +135 -0
- package/src/api-routes/_vercel-origin.ts +2 -6
- package/src/api-routes/_webhook-dispatcher.ts +12 -16
- package/src/api-routes/_website-resolver.ts +3 -10
- package/src/api-routes/auth-callback.ts +9 -5
- package/src/api-routes/auth-login.ts +5 -3
- package/src/api-routes/auth-logout.ts +18 -1
- package/src/api-routes/auth-session.ts +13 -21
- package/src/api-routes/auth-setzkasten-login.ts +12 -9
- package/src/api-routes/catalog-add.ts +89 -31
- package/src/api-routes/catalog-export.ts +30 -10
- package/src/api-routes/config.ts +39 -6
- package/src/api-routes/deploy-hook.ts +13 -11
- package/src/api-routes/editors.ts +33 -22
- package/src/api-routes/github-proxy.ts +25 -11
- package/src/api-routes/global-config.ts +103 -18
- package/src/api-routes/history-rollback.ts +41 -14
- package/src/api-routes/history-version.ts +5 -6
- package/src/api-routes/history.ts +3 -3
- package/src/api-routes/icons-local.ts +2 -2
- package/src/api-routes/init-add-section.ts +174 -79
- package/src/api-routes/init-apply.ts +71 -56
- package/src/api-routes/init-migrate.ts +54 -48
- package/src/api-routes/init-scan-page.ts +77 -30
- package/src/api-routes/init-scan.ts +19 -11
- package/src/api-routes/pages.ts +16 -11
- package/src/api-routes/section-add.ts +98 -27
- package/src/api-routes/section-commit-pending.ts +87 -34
- package/src/api-routes/section-delete.ts +76 -27
- package/src/api-routes/section-duplicate.ts +95 -28
- package/src/api-routes/section-management.ts +3 -7
- package/src/api-routes/section-prepare-copy.ts +29 -8
- package/src/api-routes/section-prepare.ts +38 -10
- package/src/api-routes/setup-github-app-bounce.ts +7 -1
- package/src/api-routes/setup-github-app-branches.ts +6 -7
- package/src/api-routes/setup-github-app-callback.ts +18 -1
- package/src/api-routes/setup-github-app-credentials.ts +55 -0
- package/src/api-routes/setup-github-app-installed.ts +12 -1
- package/src/api-routes/setup-github-app-repos.ts +2 -3
- package/src/api-routes/setup-github-app.ts +14 -5
- package/src/api-routes/updater-check.ts +6 -4
- package/src/api-routes/updater-register.ts +34 -20
- package/src/api-routes/updater-transfer.ts +8 -6
- package/src/api-routes/updater-unbind.ts +14 -10
- package/src/api-routes/webhooks-test.ts +9 -11
- package/src/api-routes/webhooks.ts +15 -19
- package/src/init/__tests__/page-level.test.ts +279 -105
- package/src/init/__tests__/page-list-coverage.test.ts +70 -70
- package/src/init/__tests__/patcher-child-component.test.ts +12 -3
- package/src/init/__tests__/patcher-edge-cases.test.ts +47 -23
- package/src/init/__tests__/patcher-mixed-content-wrapper.test.ts +16 -6
- package/src/init/__tests__/patcher-page-mode.test.ts +30 -20
- package/src/init/__tests__/section-pipeline.test.ts +53 -19
- package/src/init/astro-config-patcher.ts +4 -18
- package/src/init/astro-detector.ts +2 -7
- package/src/init/astro-section-analyzer-v2.ts +475 -193
- package/src/init/field-label-enricher.ts +6 -6
- package/src/init/template-patcher-v2.ts +218 -97
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
2
|
convertToSetHtml
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import {
|
|
5
|
-
readPagesMeta
|
|
6
|
-
} from "../chunk-FXNOTESI.js";
|
|
3
|
+
} from "../chunk-CDXCYYQR.js";
|
|
7
4
|
import {
|
|
8
5
|
guardPageAccess,
|
|
9
6
|
parseSession
|
|
10
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-Q5HV47DW.js";
|
|
8
|
+
import "../chunk-QVCW6EF3.js";
|
|
9
|
+
import "../chunk-KENFINT4.js";
|
|
11
10
|
import {
|
|
12
11
|
prefixPath,
|
|
13
12
|
resolveStorageConfigForRequest
|
|
14
13
|
} from "../chunk-6UIKVKED.js";
|
|
14
|
+
import "../chunk-ONP6BRZO.js";
|
|
15
15
|
import "../chunk-5PIMDP4N.js";
|
|
16
16
|
import "../chunk-45ARVNT3.js";
|
|
17
17
|
import {
|
|
18
18
|
resolveGitHubTokenForRequest
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-DP6RTINQ.js";
|
|
20
20
|
import "../chunk-TJNJKPUL.js";
|
|
21
|
+
import {
|
|
22
|
+
readPagesMeta
|
|
23
|
+
} from "../chunk-FXNOTESI.js";
|
|
21
24
|
import {
|
|
22
25
|
withTrailers
|
|
23
26
|
} from "../chunk-KH22FJO5.js";
|
|
@@ -25,7 +28,7 @@ import {
|
|
|
25
28
|
// src/api-routes/section-commit-pending.ts
|
|
26
29
|
import { writeFile } from "fs/promises";
|
|
27
30
|
import { join } from "path";
|
|
28
|
-
import { setPageLastModified } from "@setzkasten-cms/core";
|
|
31
|
+
import { isSafeKey, setPageLastModified } from "@setzkasten-cms/core";
|
|
29
32
|
var POST = async ({ request, cookies }) => {
|
|
30
33
|
const session = cookies.get("setzkasten_session")?.value;
|
|
31
34
|
if (!session) return new Response("Unauthorized", { status: 401 });
|
|
@@ -44,9 +47,30 @@ var POST = async ({ request, cookies }) => {
|
|
|
44
47
|
const contentPath = body.contentPath || serverConfig?.storage?.contentPath || "content";
|
|
45
48
|
const { pageKey, pageConfig, sections, edits = [] } = body;
|
|
46
49
|
if (!pageKey || !pageConfig || !Array.isArray(sections)) {
|
|
47
|
-
return Response.json(
|
|
50
|
+
return Response.json(
|
|
51
|
+
{ error: "pageKey, pageConfig, and sections are required" },
|
|
52
|
+
{ status: 400 }
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (!isSafeKey(pageKey)) {
|
|
56
|
+
return Response.json({ error: "invalid pageKey" }, { status: 400 });
|
|
57
|
+
}
|
|
58
|
+
for (const s of sections) {
|
|
59
|
+
if (!isSafeKey(s?.key)) {
|
|
60
|
+
return Response.json({ error: `invalid section key: ${s?.key}` }, { status: 400 });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const e of edits) {
|
|
64
|
+
if (!isSafeKey(e?.key)) {
|
|
65
|
+
return Response.json({ error: `invalid edit key: ${e?.key}` }, { status: 400 });
|
|
66
|
+
}
|
|
48
67
|
}
|
|
49
|
-
const denied = await guardPageAccess(
|
|
68
|
+
const denied = await guardPageAccess(
|
|
69
|
+
parseSession(cookies.get("setzkasten_session")?.value),
|
|
70
|
+
pageKey,
|
|
71
|
+
fullConfig,
|
|
72
|
+
request
|
|
73
|
+
);
|
|
50
74
|
if (denied) return denied;
|
|
51
75
|
const headers = {
|
|
52
76
|
Authorization: `Bearer ${githubToken}`,
|
|
@@ -111,8 +135,10 @@ var POST = async ({ request, cookies }) => {
|
|
|
111
135
|
const repoRoot = serverConfig?.repoRoot;
|
|
112
136
|
if (repoRoot) {
|
|
113
137
|
await Promise.all(
|
|
114
|
-
files.map(
|
|
115
|
-
|
|
138
|
+
files.map(
|
|
139
|
+
(f) => writeFile(join(repoRoot, f.path), f.content, "utf-8").catch(() => {
|
|
140
|
+
})
|
|
141
|
+
)
|
|
116
142
|
);
|
|
117
143
|
}
|
|
118
144
|
const { fireWebhooks } = await import("./_webhook-dispatcher.js");
|
|
@@ -171,16 +197,34 @@ async function fetchFileContent(owner, repo, branch, path, token) {
|
|
|
171
197
|
}
|
|
172
198
|
async function batchCommit(owner, repo, branch, files, message, headers) {
|
|
173
199
|
try {
|
|
174
|
-
const refRes = await fetch(
|
|
200
|
+
const refRes = await fetch(
|
|
201
|
+
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
|
202
|
+
{ headers }
|
|
203
|
+
);
|
|
175
204
|
if (!refRes.ok) return { ok: false, error: `Failed to get HEAD: ${refRes.status}` };
|
|
176
|
-
const {
|
|
177
|
-
|
|
205
|
+
const {
|
|
206
|
+
object: { sha: headSha }
|
|
207
|
+
} = await refRes.json();
|
|
208
|
+
const commitRes = await fetch(
|
|
209
|
+
`https://api.github.com/repos/${owner}/${repo}/git/commits/${headSha}`,
|
|
210
|
+
{ headers }
|
|
211
|
+
);
|
|
178
212
|
if (!commitRes.ok) return { ok: false, error: `Failed to get commit: ${commitRes.status}` };
|
|
179
|
-
const {
|
|
213
|
+
const {
|
|
214
|
+
tree: { sha: baseSha }
|
|
215
|
+
} = await commitRes.json();
|
|
180
216
|
const treeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
|
|
181
217
|
method: "POST",
|
|
182
218
|
headers,
|
|
183
|
-
body: JSON.stringify({
|
|
219
|
+
body: JSON.stringify({
|
|
220
|
+
base_tree: baseSha,
|
|
221
|
+
tree: files.map((f) => ({
|
|
222
|
+
path: f.path,
|
|
223
|
+
mode: "100644",
|
|
224
|
+
type: "blob",
|
|
225
|
+
content: f.content
|
|
226
|
+
}))
|
|
227
|
+
})
|
|
184
228
|
});
|
|
185
229
|
if (!treeRes.ok) return { ok: false, error: `Failed to create tree: ${treeRes.status}` };
|
|
186
230
|
const { sha: treeSha } = await treeRes.json();
|
|
@@ -189,13 +233,17 @@ async function batchCommit(owner, repo, branch, files, message, headers) {
|
|
|
189
233
|
headers,
|
|
190
234
|
body: JSON.stringify({ tree: treeSha, parents: [headSha], message })
|
|
191
235
|
});
|
|
192
|
-
if (!newCommitRes.ok)
|
|
236
|
+
if (!newCommitRes.ok)
|
|
237
|
+
return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` };
|
|
193
238
|
const { sha: newSha } = await newCommitRes.json();
|
|
194
|
-
const updateRes = await fetch(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
239
|
+
const updateRes = await fetch(
|
|
240
|
+
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
|
241
|
+
{
|
|
242
|
+
method: "PATCH",
|
|
243
|
+
headers,
|
|
244
|
+
body: JSON.stringify({ sha: newSha })
|
|
245
|
+
}
|
|
246
|
+
);
|
|
199
247
|
if (!updateRes.ok) return { ok: false, error: `Failed to update ref: ${updateRes.status}` };
|
|
200
248
|
return { ok: true, sha: newSha };
|
|
201
249
|
} catch (error) {
|
|
@@ -4,21 +4,25 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
guardPageAccess,
|
|
6
6
|
parseSession
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-Q5HV47DW.js";
|
|
8
|
+
import "../chunk-QVCW6EF3.js";
|
|
9
|
+
import "../chunk-KENFINT4.js";
|
|
8
10
|
import {
|
|
9
11
|
resolveStorageConfigForRequest
|
|
10
12
|
} from "../chunk-6UIKVKED.js";
|
|
13
|
+
import "../chunk-ONP6BRZO.js";
|
|
11
14
|
import "../chunk-5PIMDP4N.js";
|
|
12
15
|
import "../chunk-45ARVNT3.js";
|
|
13
16
|
import {
|
|
14
17
|
resolveGitHubTokenForRequest
|
|
15
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-DP6RTINQ.js";
|
|
16
19
|
import "../chunk-TJNJKPUL.js";
|
|
17
20
|
import {
|
|
18
21
|
withTrailers
|
|
19
22
|
} from "../chunk-KH22FJO5.js";
|
|
20
23
|
|
|
21
24
|
// src/api-routes/section-delete.ts
|
|
25
|
+
import { isSafeKey } from "@setzkasten-cms/core";
|
|
22
26
|
var DELETE = async ({ request, cookies }) => {
|
|
23
27
|
const session = cookies.get("setzkasten_session")?.value;
|
|
24
28
|
if (!session) return new Response("Unauthorized", { status: 401 });
|
|
@@ -39,7 +43,18 @@ var DELETE = async ({ request, cookies }) => {
|
|
|
39
43
|
if (!pageKey || !sectionKey) {
|
|
40
44
|
return Response.json({ error: "pageKey and sectionKey are required" }, { status: 400 });
|
|
41
45
|
}
|
|
42
|
-
|
|
46
|
+
if (!isSafeKey(pageKey)) {
|
|
47
|
+
return Response.json({ error: "invalid pageKey" }, { status: 400 });
|
|
48
|
+
}
|
|
49
|
+
if (!isSafeKey(sectionKey)) {
|
|
50
|
+
return Response.json({ error: "invalid sectionKey" }, { status: 400 });
|
|
51
|
+
}
|
|
52
|
+
const denied = await guardPageAccess(
|
|
53
|
+
parseSession(cookies.get("setzkasten_session")?.value),
|
|
54
|
+
pageKey,
|
|
55
|
+
fullConfig,
|
|
56
|
+
request
|
|
57
|
+
);
|
|
43
58
|
if (denied) return denied;
|
|
44
59
|
const headers = {
|
|
45
60
|
Authorization: `Bearer ${githubToken}`,
|
|
@@ -98,7 +113,13 @@ async function fetchFileContent(owner, repo, branch, path, token) {
|
|
|
98
113
|
try {
|
|
99
114
|
const res = await fetch(
|
|
100
115
|
`https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
|
|
101
|
-
{
|
|
116
|
+
{
|
|
117
|
+
headers: {
|
|
118
|
+
Authorization: `Bearer ${token}`,
|
|
119
|
+
Accept: "application/vnd.github+json",
|
|
120
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
102
123
|
);
|
|
103
124
|
if (!res.ok) return null;
|
|
104
125
|
const data = await res.json();
|
|
@@ -109,12 +130,22 @@ async function fetchFileContent(owner, repo, branch, path, token) {
|
|
|
109
130
|
}
|
|
110
131
|
async function batchCommitWithDeletions(owner, repo, branch, upserts, deletions, message, headers) {
|
|
111
132
|
try {
|
|
112
|
-
const refRes = await fetch(
|
|
133
|
+
const refRes = await fetch(
|
|
134
|
+
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
|
135
|
+
{ headers }
|
|
136
|
+
);
|
|
113
137
|
if (!refRes.ok) return { ok: false, error: `Failed to get HEAD: ${refRes.status}` };
|
|
114
|
-
const {
|
|
115
|
-
|
|
138
|
+
const {
|
|
139
|
+
object: { sha: headSha }
|
|
140
|
+
} = await refRes.json();
|
|
141
|
+
const commitRes = await fetch(
|
|
142
|
+
`https://api.github.com/repos/${owner}/${repo}/git/commits/${headSha}`,
|
|
143
|
+
{ headers }
|
|
144
|
+
);
|
|
116
145
|
if (!commitRes.ok) return { ok: false, error: `Failed to get commit: ${commitRes.status}` };
|
|
117
|
-
const {
|
|
146
|
+
const {
|
|
147
|
+
tree: { sha: baseSha }
|
|
148
|
+
} = await commitRes.json();
|
|
118
149
|
const tree = [
|
|
119
150
|
...upserts.map((f) => ({ path: f.path, mode: "100644", type: "blob", content: f.content })),
|
|
120
151
|
...deletions.map((path) => ({ path, mode: "100644", type: "blob", sha: null }))
|
|
@@ -131,13 +162,17 @@ async function batchCommitWithDeletions(owner, repo, branch, upserts, deletions,
|
|
|
131
162
|
headers,
|
|
132
163
|
body: JSON.stringify({ tree: treeSha, parents: [headSha], message })
|
|
133
164
|
});
|
|
134
|
-
if (!newCommitRes.ok)
|
|
165
|
+
if (!newCommitRes.ok)
|
|
166
|
+
return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` };
|
|
135
167
|
const { sha: newSha } = await newCommitRes.json();
|
|
136
|
-
const updateRes = await fetch(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
168
|
+
const updateRes = await fetch(
|
|
169
|
+
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
|
170
|
+
{
|
|
171
|
+
method: "PATCH",
|
|
172
|
+
headers,
|
|
173
|
+
body: JSON.stringify({ sha: newSha })
|
|
174
|
+
}
|
|
175
|
+
);
|
|
141
176
|
if (!updateRes.ok) return { ok: false, error: `Failed to update ref: ${updateRes.status}` };
|
|
142
177
|
return { ok: true, sha: newSha };
|
|
143
178
|
} catch (error) {
|
|
@@ -5,21 +5,25 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
guardPageAccess,
|
|
7
7
|
parseSession
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-Q5HV47DW.js";
|
|
9
|
+
import "../chunk-QVCW6EF3.js";
|
|
10
|
+
import "../chunk-KENFINT4.js";
|
|
9
11
|
import {
|
|
10
12
|
resolveStorageConfigForRequest
|
|
11
13
|
} from "../chunk-6UIKVKED.js";
|
|
14
|
+
import "../chunk-ONP6BRZO.js";
|
|
12
15
|
import "../chunk-5PIMDP4N.js";
|
|
13
16
|
import "../chunk-45ARVNT3.js";
|
|
14
17
|
import {
|
|
15
18
|
resolveGitHubTokenForRequest
|
|
16
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-DP6RTINQ.js";
|
|
17
20
|
import "../chunk-TJNJKPUL.js";
|
|
18
21
|
import {
|
|
19
22
|
withTrailers
|
|
20
23
|
} from "../chunk-KH22FJO5.js";
|
|
21
24
|
|
|
22
25
|
// src/api-routes/section-duplicate.ts
|
|
26
|
+
import { isSafeKey } from "@setzkasten-cms/core";
|
|
23
27
|
var POST = async ({ request, cookies }) => {
|
|
24
28
|
const session = cookies.get("setzkasten_session")?.value;
|
|
25
29
|
if (!session) return new Response("Unauthorized", { status: 401 });
|
|
@@ -40,7 +44,18 @@ var POST = async ({ request, cookies }) => {
|
|
|
40
44
|
if (!pageKey || !sectionKey) {
|
|
41
45
|
return Response.json({ error: "pageKey and sectionKey are required" }, { status: 400 });
|
|
42
46
|
}
|
|
43
|
-
|
|
47
|
+
if (!isSafeKey(pageKey)) {
|
|
48
|
+
return Response.json({ error: "invalid pageKey" }, { status: 400 });
|
|
49
|
+
}
|
|
50
|
+
if (!isSafeKey(sectionKey)) {
|
|
51
|
+
return Response.json({ error: "invalid sectionKey" }, { status: 400 });
|
|
52
|
+
}
|
|
53
|
+
const denied = await guardPageAccess(
|
|
54
|
+
parseSession(cookies.get("setzkasten_session")?.value),
|
|
55
|
+
pageKey,
|
|
56
|
+
fullConfig,
|
|
57
|
+
request
|
|
58
|
+
);
|
|
44
59
|
if (denied) return denied;
|
|
45
60
|
const headers = {
|
|
46
61
|
Authorization: `Bearer ${githubToken}`,
|
|
@@ -56,7 +71,13 @@ var POST = async ({ request, cookies }) => {
|
|
|
56
71
|
const existingKeys = (pageConfig.sections ?? []).map((s) => s.key);
|
|
57
72
|
const newKey = generateDuplicateKey(existingKeys, sectionKey);
|
|
58
73
|
const originalJsonPath = `${contentPath}/_sections/${sectionKey}.json`;
|
|
59
|
-
const originalContent = await fetchFileContent(
|
|
74
|
+
const originalContent = await fetchFileContent(
|
|
75
|
+
owner,
|
|
76
|
+
repo,
|
|
77
|
+
branch,
|
|
78
|
+
originalJsonPath,
|
|
79
|
+
githubToken
|
|
80
|
+
);
|
|
60
81
|
const updatedConfig = duplicateInPageConfig(pageConfig, sectionKey, newKey);
|
|
61
82
|
const filesToCommit = [
|
|
62
83
|
{ path: pageConfigPath, content: JSON.stringify(updatedConfig, null, 2) }
|
|
@@ -96,7 +117,13 @@ async function fetchFileContent(owner, repo, branch, path, token) {
|
|
|
96
117
|
try {
|
|
97
118
|
const res = await fetch(
|
|
98
119
|
`https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
|
|
99
|
-
{
|
|
120
|
+
{
|
|
121
|
+
headers: {
|
|
122
|
+
Authorization: `Bearer ${token}`,
|
|
123
|
+
Accept: "application/vnd.github+json",
|
|
124
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
100
127
|
);
|
|
101
128
|
if (!res.ok) return null;
|
|
102
129
|
const data = await res.json();
|
|
@@ -107,16 +134,34 @@ async function fetchFileContent(owner, repo, branch, path, token) {
|
|
|
107
134
|
}
|
|
108
135
|
async function batchCommit(owner, repo, branch, files, message, headers) {
|
|
109
136
|
try {
|
|
110
|
-
const refRes = await fetch(
|
|
137
|
+
const refRes = await fetch(
|
|
138
|
+
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
|
139
|
+
{ headers }
|
|
140
|
+
);
|
|
111
141
|
if (!refRes.ok) return { ok: false, error: `Failed to get HEAD: ${refRes.status}` };
|
|
112
|
-
const {
|
|
113
|
-
|
|
142
|
+
const {
|
|
143
|
+
object: { sha: headSha }
|
|
144
|
+
} = await refRes.json();
|
|
145
|
+
const commitRes = await fetch(
|
|
146
|
+
`https://api.github.com/repos/${owner}/${repo}/git/commits/${headSha}`,
|
|
147
|
+
{ headers }
|
|
148
|
+
);
|
|
114
149
|
if (!commitRes.ok) return { ok: false, error: `Failed to get commit: ${commitRes.status}` };
|
|
115
|
-
const {
|
|
150
|
+
const {
|
|
151
|
+
tree: { sha: baseSha }
|
|
152
|
+
} = await commitRes.json();
|
|
116
153
|
const treeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
|
|
117
154
|
method: "POST",
|
|
118
155
|
headers,
|
|
119
|
-
body: JSON.stringify({
|
|
156
|
+
body: JSON.stringify({
|
|
157
|
+
base_tree: baseSha,
|
|
158
|
+
tree: files.map((f) => ({
|
|
159
|
+
path: f.path,
|
|
160
|
+
mode: "100644",
|
|
161
|
+
type: "blob",
|
|
162
|
+
content: f.content
|
|
163
|
+
}))
|
|
164
|
+
})
|
|
120
165
|
});
|
|
121
166
|
if (!treeRes.ok) return { ok: false, error: `Failed to create tree: ${treeRes.status}` };
|
|
122
167
|
const { sha: treeSha } = await treeRes.json();
|
|
@@ -125,13 +170,17 @@ async function batchCommit(owner, repo, branch, files, message, headers) {
|
|
|
125
170
|
headers,
|
|
126
171
|
body: JSON.stringify({ tree: treeSha, parents: [headSha], message })
|
|
127
172
|
});
|
|
128
|
-
if (!newCommitRes.ok)
|
|
173
|
+
if (!newCommitRes.ok)
|
|
174
|
+
return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` };
|
|
129
175
|
const { sha: newSha } = await newCommitRes.json();
|
|
130
|
-
const updateRes = await fetch(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
176
|
+
const updateRes = await fetch(
|
|
177
|
+
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
|
178
|
+
{
|
|
179
|
+
method: "PATCH",
|
|
180
|
+
headers,
|
|
181
|
+
body: JSON.stringify({ sha: newSha })
|
|
182
|
+
}
|
|
183
|
+
);
|
|
135
184
|
if (!updateRes.ok) return { ok: false, error: `Failed to update ref: ${updateRes.status}` };
|
|
136
185
|
return { ok: true, sha: newSha };
|
|
137
186
|
} catch (error) {
|
|
@@ -7,9 +7,10 @@ import {
|
|
|
7
7
|
} from "../chunk-6UIKVKED.js";
|
|
8
8
|
import {
|
|
9
9
|
resolveGitHubTokenForRequest
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-DP6RTINQ.js";
|
|
11
11
|
|
|
12
12
|
// src/api-routes/section-prepare-copy.ts
|
|
13
|
+
import { isSafeKey } from "@setzkasten-cms/core";
|
|
13
14
|
var POST = async ({ request, cookies }) => {
|
|
14
15
|
const session = cookies.get("setzkasten_session")?.value;
|
|
15
16
|
if (!session) return new Response("Unauthorized", { status: 401 });
|
|
@@ -29,6 +30,12 @@ var POST = async ({ request, cookies }) => {
|
|
|
29
30
|
if (!pageKey || !sectionKey) {
|
|
30
31
|
return Response.json({ error: "pageKey and sectionKey are required" }, { status: 400 });
|
|
31
32
|
}
|
|
33
|
+
if (!isSafeKey(pageKey)) {
|
|
34
|
+
return Response.json({ error: "invalid pageKey" }, { status: 400 });
|
|
35
|
+
}
|
|
36
|
+
if (!isSafeKey(sectionKey)) {
|
|
37
|
+
return Response.json({ error: "invalid sectionKey" }, { status: 400 });
|
|
38
|
+
}
|
|
32
39
|
const configKey = "_" + pageKey.replace(/\//g, "_");
|
|
33
40
|
const pageConfigPath = `${contentPath}/pages/${configKey}.json`;
|
|
34
41
|
const pageConfigRaw = await fetchFileContent(owner, repo, branch, pageConfigPath, githubToken);
|
|
@@ -55,7 +62,13 @@ async function fetchFileContent(owner, repo, branch, path, token) {
|
|
|
55
62
|
try {
|
|
56
63
|
const res = await fetch(
|
|
57
64
|
`https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
|
|
58
|
-
{
|
|
65
|
+
{
|
|
66
|
+
headers: {
|
|
67
|
+
Authorization: `Bearer ${token}`,
|
|
68
|
+
Accept: "application/vnd.github+json",
|
|
69
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
59
72
|
);
|
|
60
73
|
if (!res.ok) return null;
|
|
61
74
|
const data = await res.json();
|
|
@@ -4,19 +4,23 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
guardPageAccess,
|
|
6
6
|
parseSession
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-Q5HV47DW.js";
|
|
8
|
+
import "../chunk-QVCW6EF3.js";
|
|
9
|
+
import "../chunk-KENFINT4.js";
|
|
8
10
|
import {
|
|
9
11
|
resolveStorageConfigForRequest
|
|
10
12
|
} from "../chunk-6UIKVKED.js";
|
|
13
|
+
import "../chunk-ONP6BRZO.js";
|
|
11
14
|
import "../chunk-5PIMDP4N.js";
|
|
12
15
|
import "../chunk-45ARVNT3.js";
|
|
13
16
|
import {
|
|
14
17
|
resolveGitHubTokenForRequest
|
|
15
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-DP6RTINQ.js";
|
|
16
19
|
import "../chunk-TJNJKPUL.js";
|
|
17
20
|
import "../chunk-KH22FJO5.js";
|
|
18
21
|
|
|
19
22
|
// src/api-routes/section-prepare.ts
|
|
23
|
+
import { isSafeKey } from "@setzkasten-cms/core";
|
|
20
24
|
var POST = async ({ request, cookies }) => {
|
|
21
25
|
const session = cookies.get("setzkasten_session")?.value;
|
|
22
26
|
if (!session) return new Response("Unauthorized", { status: 401 });
|
|
@@ -37,7 +41,18 @@ var POST = async ({ request, cookies }) => {
|
|
|
37
41
|
if (!pageKey || !sectionType) {
|
|
38
42
|
return Response.json({ error: "pageKey and sectionType are required" }, { status: 400 });
|
|
39
43
|
}
|
|
40
|
-
|
|
44
|
+
if (!isSafeKey(pageKey)) {
|
|
45
|
+
return Response.json({ error: "invalid pageKey" }, { status: 400 });
|
|
46
|
+
}
|
|
47
|
+
if (!isSafeKey(sectionType)) {
|
|
48
|
+
return Response.json({ error: "invalid sectionType" }, { status: 400 });
|
|
49
|
+
}
|
|
50
|
+
const denied = await guardPageAccess(
|
|
51
|
+
parseSession(cookies.get("setzkasten_session")?.value),
|
|
52
|
+
pageKey,
|
|
53
|
+
fullConfig,
|
|
54
|
+
request
|
|
55
|
+
);
|
|
41
56
|
if (denied) return denied;
|
|
42
57
|
const configKey = "_" + pageKey.replace(/\//g, "_");
|
|
43
58
|
const pageConfigPath = `${contentPath}/pages/${configKey}.json`;
|
|
@@ -90,7 +105,13 @@ async function fetchFileContent(owner, repo, branch, path, token) {
|
|
|
90
105
|
try {
|
|
91
106
|
const res = await fetch(
|
|
92
107
|
`https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
|
|
93
|
-
{
|
|
108
|
+
{
|
|
109
|
+
headers: {
|
|
110
|
+
Authorization: `Bearer ${token}`,
|
|
111
|
+
Accept: "application/vnd.github+json",
|
|
112
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
94
115
|
);
|
|
95
116
|
if (!res.ok) return null;
|
|
96
117
|
const data = await res.json();
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getPublicOrigin
|
|
3
3
|
} from "../chunk-5ZFTG4BW.js";
|
|
4
|
+
import {
|
|
5
|
+
requireAdmin
|
|
6
|
+
} from "../chunk-Q5HV47DW.js";
|
|
7
|
+
import "../chunk-QVCW6EF3.js";
|
|
8
|
+
import "../chunk-KENFINT4.js";
|
|
9
|
+
import "../chunk-6UIKVKED.js";
|
|
10
|
+
import "../chunk-ONP6BRZO.js";
|
|
11
|
+
import "../chunk-5PIMDP4N.js";
|
|
12
|
+
import "../chunk-45ARVNT3.js";
|
|
13
|
+
import "../chunk-DP6RTINQ.js";
|
|
14
|
+
import "../chunk-TJNJKPUL.js";
|
|
15
|
+
import "../chunk-KH22FJO5.js";
|
|
4
16
|
|
|
5
17
|
// src/api-routes/setup-github-app-bounce.ts
|
|
6
|
-
var GET = async ({ url, request }) => {
|
|
18
|
+
var GET = async ({ url, request, cookies }) => {
|
|
19
|
+
const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
|
|
20
|
+
if (denied) return denied;
|
|
7
21
|
const name = url.searchParams.get("name")?.trim() || "Setzkasten CMS";
|
|
8
22
|
const origin = getPublicOrigin(request);
|
|
9
23
|
const manifest = JSON.stringify({
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
requireAdmin
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-Q5HV47DW.js";
|
|
4
|
+
import "../chunk-QVCW6EF3.js";
|
|
5
|
+
import "../chunk-KENFINT4.js";
|
|
4
6
|
import "../chunk-6UIKVKED.js";
|
|
7
|
+
import "../chunk-ONP6BRZO.js";
|
|
5
8
|
import "../chunk-5PIMDP4N.js";
|
|
6
9
|
import "../chunk-45ARVNT3.js";
|
|
7
|
-
import "../chunk-
|
|
10
|
+
import "../chunk-DP6RTINQ.js";
|
|
8
11
|
import "../chunk-TJNJKPUL.js";
|
|
9
12
|
import "../chunk-KH22FJO5.js";
|
|
10
13
|
|
|
@@ -33,10 +36,10 @@ var GET = async ({ cookies, url }) => {
|
|
|
33
36
|
}
|
|
34
37
|
const slash = repoFull.indexOf("/");
|
|
35
38
|
if (slash <= 0 || slash === repoFull.length - 1) {
|
|
36
|
-
return new Response(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
);
|
|
39
|
+
return new Response(JSON.stringify({ error: '?repo must be in "owner/name" format.' }), {
|
|
40
|
+
status: 400,
|
|
41
|
+
headers: { "Content-Type": "application/json" }
|
|
42
|
+
});
|
|
40
43
|
}
|
|
41
44
|
const owner = repoFull.slice(0, slash);
|
|
42
45
|
const repo = repoFull.slice(slash + 1);
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getPublicOrigin
|
|
3
3
|
} from "../chunk-5ZFTG4BW.js";
|
|
4
|
+
import {
|
|
5
|
+
requireAdmin
|
|
6
|
+
} from "../chunk-Q5HV47DW.js";
|
|
7
|
+
import "../chunk-QVCW6EF3.js";
|
|
8
|
+
import "../chunk-KENFINT4.js";
|
|
9
|
+
import "../chunk-6UIKVKED.js";
|
|
10
|
+
import "../chunk-ONP6BRZO.js";
|
|
11
|
+
import "../chunk-5PIMDP4N.js";
|
|
12
|
+
import "../chunk-45ARVNT3.js";
|
|
13
|
+
import "../chunk-DP6RTINQ.js";
|
|
14
|
+
import "../chunk-TJNJKPUL.js";
|
|
15
|
+
import "../chunk-KH22FJO5.js";
|
|
4
16
|
|
|
5
17
|
// src/api-routes/setup-github-app-callback.ts
|
|
6
18
|
var COOKIE_NAME = "sk_app_setup";
|
|
7
19
|
var COOKIE_MAX_AGE = 600;
|
|
8
20
|
var GET = async ({ url, request, cookies }) => {
|
|
21
|
+
const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
|
|
22
|
+
if (denied) return denied;
|
|
9
23
|
const config = globalThis.__SETZKASTEN_CONFIG__;
|
|
10
24
|
const adminPath = config?.adminPath ?? "/admin";
|
|
11
25
|
const adminUrl = new URL(adminPath, getPublicOrigin(request));
|
|
@@ -36,7 +50,16 @@ var GET = async ({ url, request, cookies }) => {
|
|
|
36
50
|
clientId: data.client_id,
|
|
37
51
|
clientSecret: data.client_secret
|
|
38
52
|
}),
|
|
39
|
-
|
|
53
|
+
// httpOnly:true — pre-fix this was readable to any JS on origin (XSS,
|
|
54
|
+
// extensions). The SPA now reads via /api/setzkasten/setup/github-app/
|
|
55
|
+
// credentials (server reads the cookie, returns values).
|
|
56
|
+
{
|
|
57
|
+
httpOnly: true,
|
|
58
|
+
secure: import.meta.env.PROD,
|
|
59
|
+
sameSite: "lax",
|
|
60
|
+
maxAge: COOKIE_MAX_AGE,
|
|
61
|
+
path: "/"
|
|
62
|
+
}
|
|
40
63
|
);
|
|
41
64
|
return new Response(null, { status: 302, headers: { Location: adminUrl.toString() } });
|
|
42
65
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GET /api/setzkasten/setup/github-app/credentials
|
|
5
|
+
*
|
|
6
|
+
* Returns the freshly-minted GitHub App credentials so the admin SPA can
|
|
7
|
+
* display them (env-var copy step of the wizard). Pre-C6 the SPA read
|
|
8
|
+
* these directly from `document.cookie`, which required the cookie to
|
|
9
|
+
* be `httpOnly: false` — exposing the App private key to any JS on the
|
|
10
|
+
* origin (XSS, extensions, embedded widgets). Now the cookie is
|
|
11
|
+
* httpOnly and only this admin-gated endpoint can read it.
|
|
12
|
+
*
|
|
13
|
+
* The cookie itself stays for the wizard's full lifetime (10 min); this
|
|
14
|
+
* endpoint just makes the contents available without exposing them via
|
|
15
|
+
* the document.
|
|
16
|
+
*/
|
|
17
|
+
declare const GET: APIRoute;
|
|
18
|
+
/**
|
|
19
|
+
* DELETE /api/setzkasten/setup/github-app/credentials
|
|
20
|
+
*
|
|
21
|
+
* Clears the setup cookie once the admin has copied the env vars and
|
|
22
|
+
* confirmed the deploy. Removes the credentials from the wire as soon
|
|
23
|
+
* as they're no longer needed.
|
|
24
|
+
*/
|
|
25
|
+
declare const DELETE: APIRoute;
|
|
26
|
+
|
|
27
|
+
export { DELETE, GET };
|