@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.
Files changed (166) hide show
  1. package/dist/api-routes/_auth-guard.d.ts +27 -3
  2. package/dist/api-routes/_auth-guard.js +5 -2
  3. package/dist/api-routes/_dev-session-secret.d.ts +8 -0
  4. package/dist/api-routes/_dev-session-secret.js +8 -0
  5. package/dist/api-routes/_github-token.js +1 -1
  6. package/dist/api-routes/_role-resolver.js +6 -3
  7. package/dist/api-routes/_session-secret.d.ts +19 -0
  8. package/dist/api-routes/_session-secret.js +7 -0
  9. package/dist/api-routes/_session-signing.d.ts +45 -0
  10. package/dist/api-routes/_session-signing.js +8 -0
  11. package/dist/api-routes/_webhook-dispatcher.js +4 -4
  12. package/dist/api-routes/asset-proxy.js +1 -1
  13. package/dist/api-routes/auth-callback.js +12 -5
  14. package/dist/api-routes/auth-logout.d.ts +4 -4
  15. package/dist/api-routes/auth-logout.js +8 -2
  16. package/dist/api-routes/auth-session.d.ts +6 -0
  17. package/dist/api-routes/auth-session.js +19 -19
  18. package/dist/api-routes/auth-setzkasten-login.js +14 -7
  19. package/dist/api-routes/catalog-add.js +59 -17
  20. package/dist/api-routes/catalog-export.js +14 -4
  21. package/dist/api-routes/config.d.ts +10 -3
  22. package/dist/api-routes/config.js +26 -4
  23. package/dist/api-routes/deploy-hook.js +8 -8
  24. package/dist/api-routes/editors.d.ts +1 -1
  25. package/dist/api-routes/editors.js +5 -2
  26. package/dist/api-routes/github-proxy.js +30 -8
  27. package/dist/api-routes/global-config.js +6 -3
  28. package/dist/api-routes/history-rollback.js +31 -14
  29. package/dist/api-routes/history-version.js +8 -6
  30. package/dist/api-routes/history.js +5 -2
  31. package/dist/api-routes/icons-local.js +1 -1
  32. package/dist/api-routes/init-add-section.js +113 -47
  33. package/dist/api-routes/init-apply.js +56 -42
  34. package/dist/api-routes/init-migrate.js +43 -36
  35. package/dist/api-routes/init-scan-page.d.ts +1 -1
  36. package/dist/api-routes/init-scan-page.js +59 -13
  37. package/dist/api-routes/init-scan.js +22 -7
  38. package/dist/api-routes/migrate-to-multi.js +5 -2
  39. package/dist/api-routes/pages.js +15 -4
  40. package/dist/api-routes/section-add.js +68 -16
  41. package/dist/api-routes/section-commit-pending.js +70 -22
  42. package/dist/api-routes/section-delete.js +49 -14
  43. package/dist/api-routes/section-duplicate.js +65 -16
  44. package/dist/api-routes/section-prepare-copy.js +15 -2
  45. package/dist/api-routes/section-prepare.js +25 -4
  46. package/dist/api-routes/setup-github-app-bounce.js +15 -1
  47. package/dist/api-routes/setup-github-app-branches.js +9 -6
  48. package/dist/api-routes/setup-github-app-callback.js +24 -1
  49. package/dist/api-routes/setup-github-app-credentials.d.ts +27 -0
  50. package/dist/api-routes/setup-github-app-credentials.js +43 -0
  51. package/dist/api-routes/setup-github-app-installed.js +22 -1
  52. package/dist/api-routes/setup-github-app-repos.js +5 -2
  53. package/dist/api-routes/setup-github-app.d.ts +4 -0
  54. package/dist/api-routes/setup-github-app.js +19 -2
  55. package/dist/api-routes/updater-register.js +7 -1
  56. package/dist/api-routes/webhooks-status.js +5 -2
  57. package/dist/api-routes/webhooks-test.js +9 -8
  58. package/dist/api-routes/webhooks.js +12 -14
  59. package/dist/api-routes/websites-add.js +5 -2
  60. package/dist/api-routes/websites-remove.js +5 -2
  61. package/dist/{chunk-ZQDGGWJP.js → chunk-5KMGSFCZ.js} +2 -2
  62. package/dist/{chunk-Q3N336KR.js → chunk-CDXCYYQR.js} +29 -24
  63. package/dist/{chunk-NKDATSPA.js → chunk-DP6RTINQ.js} +1 -1
  64. package/dist/chunk-KENFINT4.js +76 -0
  65. package/dist/chunk-ONP6BRZO.js +47 -0
  66. package/dist/{chunk-INIWFKQ3.js → chunk-Q5HV47DW.js} +33 -19
  67. package/dist/chunk-QVCW6EF3.js +26 -0
  68. package/dist/{chunk-TD76R3A6.js → chunk-UHI6323G.js} +293 -174
  69. package/dist/{chunk-AM4DZXXM.js → chunk-UJAFZEX2.js} +76 -9
  70. package/package.json +12 -6
  71. package/src/api-routes/__tests__/_session-signing.test.ts +114 -0
  72. package/src/api-routes/__tests__/_session-test-helper.ts +27 -0
  73. package/src/api-routes/__tests__/add-section-helpers.test.ts +59 -25
  74. package/src/api-routes/__tests__/auth-guard.test.ts +46 -7
  75. package/src/api-routes/__tests__/catalog-api.test.ts +14 -6
  76. package/src/api-routes/__tests__/commit-trailers.test.ts +5 -5
  77. package/src/api-routes/__tests__/deferred-operations.test.ts +9 -12
  78. package/src/api-routes/__tests__/deploy-hook.test.ts +3 -8
  79. package/src/api-routes/__tests__/feature-gate.test.ts +1 -1
  80. package/src/api-routes/__tests__/github-cache.test.ts +1 -1
  81. package/src/api-routes/__tests__/github-token.test.ts +1 -1
  82. package/src/api-routes/__tests__/global-config-theme.test.ts +4 -4
  83. package/src/api-routes/__tests__/history-rollback.test.ts +6 -3
  84. package/src/api-routes/__tests__/history.test.ts +9 -6
  85. package/src/api-routes/__tests__/init-scan-page-resolve-config.test.ts +11 -3
  86. package/src/api-routes/__tests__/migrate-to-multi.test.ts +5 -1
  87. package/src/api-routes/__tests__/pages-meta-store.test.ts +10 -5
  88. package/src/api-routes/__tests__/pages.test.ts +7 -2
  89. package/src/api-routes/__tests__/patch-page-file.test.ts +71 -19
  90. package/src/api-routes/__tests__/route-registry.test.ts +11 -18
  91. package/src/api-routes/__tests__/scan-page-helpers.test.ts +13 -10
  92. package/src/api-routes/__tests__/section-management.test.ts +28 -28
  93. package/src/api-routes/__tests__/setup-github-app-callback.test.ts +58 -16
  94. package/src/api-routes/__tests__/setup-github-app-repos.test.ts +4 -5
  95. package/src/api-routes/__tests__/setup-github-app.test.ts +27 -7
  96. package/src/api-routes/__tests__/storage-config-for-request.test.ts +83 -0
  97. package/src/api-routes/__tests__/updater-register.test.ts +230 -0
  98. package/src/api-routes/__tests__/webhook-signing.test.ts +1 -1
  99. package/src/api-routes/__tests__/webhooks.test.ts +19 -7
  100. package/src/api-routes/__tests__/websites-add.test.ts +2 -1
  101. package/src/api-routes/__tests__/websites-remove.test.ts +2 -1
  102. package/src/api-routes/_auth-guard.ts +47 -15
  103. package/src/api-routes/_commit-trailers.ts +3 -2
  104. package/src/api-routes/_dev-session-secret.ts +79 -0
  105. package/src/api-routes/_github-token.ts +1 -1
  106. package/src/api-routes/_pages-meta-store.ts +2 -2
  107. package/src/api-routes/_role-resolver.ts +7 -5
  108. package/src/api-routes/_session-secret.ts +46 -0
  109. package/src/api-routes/_session-signing.ts +135 -0
  110. package/src/api-routes/_vercel-origin.ts +2 -6
  111. package/src/api-routes/_webhook-dispatcher.ts +12 -16
  112. package/src/api-routes/_website-resolver.ts +3 -10
  113. package/src/api-routes/auth-callback.ts +9 -5
  114. package/src/api-routes/auth-login.ts +5 -3
  115. package/src/api-routes/auth-logout.ts +18 -1
  116. package/src/api-routes/auth-session.ts +13 -21
  117. package/src/api-routes/auth-setzkasten-login.ts +12 -9
  118. package/src/api-routes/catalog-add.ts +89 -31
  119. package/src/api-routes/catalog-export.ts +30 -10
  120. package/src/api-routes/config.ts +39 -6
  121. package/src/api-routes/deploy-hook.ts +13 -11
  122. package/src/api-routes/editors.ts +33 -22
  123. package/src/api-routes/github-proxy.ts +25 -11
  124. package/src/api-routes/global-config.ts +103 -18
  125. package/src/api-routes/history-rollback.ts +41 -14
  126. package/src/api-routes/history-version.ts +5 -6
  127. package/src/api-routes/history.ts +3 -3
  128. package/src/api-routes/icons-local.ts +2 -2
  129. package/src/api-routes/init-add-section.ts +174 -79
  130. package/src/api-routes/init-apply.ts +71 -56
  131. package/src/api-routes/init-migrate.ts +54 -48
  132. package/src/api-routes/init-scan-page.ts +77 -30
  133. package/src/api-routes/init-scan.ts +19 -11
  134. package/src/api-routes/pages.ts +16 -11
  135. package/src/api-routes/section-add.ts +98 -27
  136. package/src/api-routes/section-commit-pending.ts +87 -34
  137. package/src/api-routes/section-delete.ts +76 -27
  138. package/src/api-routes/section-duplicate.ts +95 -28
  139. package/src/api-routes/section-management.ts +3 -7
  140. package/src/api-routes/section-prepare-copy.ts +29 -8
  141. package/src/api-routes/section-prepare.ts +38 -10
  142. package/src/api-routes/setup-github-app-bounce.ts +7 -1
  143. package/src/api-routes/setup-github-app-branches.ts +6 -7
  144. package/src/api-routes/setup-github-app-callback.ts +18 -1
  145. package/src/api-routes/setup-github-app-credentials.ts +55 -0
  146. package/src/api-routes/setup-github-app-installed.ts +12 -1
  147. package/src/api-routes/setup-github-app-repos.ts +2 -3
  148. package/src/api-routes/setup-github-app.ts +14 -5
  149. package/src/api-routes/updater-check.ts +6 -4
  150. package/src/api-routes/updater-register.ts +34 -20
  151. package/src/api-routes/updater-transfer.ts +8 -6
  152. package/src/api-routes/updater-unbind.ts +14 -10
  153. package/src/api-routes/webhooks-test.ts +9 -11
  154. package/src/api-routes/webhooks.ts +15 -19
  155. package/src/init/__tests__/page-level.test.ts +279 -105
  156. package/src/init/__tests__/page-list-coverage.test.ts +70 -70
  157. package/src/init/__tests__/patcher-child-component.test.ts +12 -3
  158. package/src/init/__tests__/patcher-edge-cases.test.ts +47 -23
  159. package/src/init/__tests__/patcher-mixed-content-wrapper.test.ts +16 -6
  160. package/src/init/__tests__/patcher-page-mode.test.ts +30 -20
  161. package/src/init/__tests__/section-pipeline.test.ts +53 -19
  162. package/src/init/astro-config-patcher.ts +4 -18
  163. package/src/init/astro-detector.ts +2 -7
  164. package/src/init/astro-section-analyzer-v2.ts +475 -193
  165. package/src/init/field-label-enricher.ts +6 -6
  166. package/src/init/template-patcher-v2.ts +218 -97
@@ -1,23 +1,26 @@
1
1
  import {
2
2
  convertToSetHtml
3
- } from "../chunk-Q3N336KR.js";
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-INIWFKQ3.js";
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-NKDATSPA.js";
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({ error: "pageKey, pageConfig, and sections are required" }, { status: 400 });
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(parseSession(cookies.get("setzkasten_session")?.value), pageKey, fullConfig, request);
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((f) => writeFile(join(repoRoot, f.path), f.content, "utf-8").catch(() => {
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(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, { headers });
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 { object: { sha: headSha } } = await refRes.json();
177
- const commitRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/commits/${headSha}`, { headers });
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 { tree: { sha: baseSha } } = await commitRes.json();
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({ base_tree: baseSha, tree: files.map((f) => ({ path: f.path, mode: "100644", type: "blob", content: f.content })) })
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) return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` };
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(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
195
- method: "PATCH",
196
- headers,
197
- body: JSON.stringify({ sha: newSha })
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-INIWFKQ3.js";
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-NKDATSPA.js";
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
- const denied = await guardPageAccess(parseSession(cookies.get("setzkasten_session")?.value), pageKey, fullConfig, request);
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
- { headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" } }
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(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, { headers });
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 { object: { sha: headSha } } = await refRes.json();
115
- const commitRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/commits/${headSha}`, { headers });
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 { tree: { sha: baseSha } } = await commitRes.json();
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) return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` };
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(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
137
- method: "PATCH",
138
- headers,
139
- body: JSON.stringify({ sha: newSha })
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-INIWFKQ3.js";
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-NKDATSPA.js";
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
- const denied = await guardPageAccess(parseSession(cookies.get("setzkasten_session")?.value), pageKey, fullConfig, request);
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(owner, repo, branch, originalJsonPath, githubToken);
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
- { headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" } }
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(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, { headers });
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 { object: { sha: headSha } } = await refRes.json();
113
- const commitRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/commits/${headSha}`, { headers });
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 { tree: { sha: baseSha } } = await commitRes.json();
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({ base_tree: baseSha, tree: files.map((f) => ({ path: f.path, mode: "100644", type: "blob", content: f.content })) })
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) return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` };
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(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
131
- method: "PATCH",
132
- headers,
133
- body: JSON.stringify({ sha: newSha })
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-NKDATSPA.js";
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
- { headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" } }
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-INIWFKQ3.js";
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-NKDATSPA.js";
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
- const denied = await guardPageAccess(parseSession(cookies.get("setzkasten_session")?.value), pageKey, fullConfig, request);
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
- { headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" } }
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-INIWFKQ3.js";
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-NKDATSPA.js";
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
- JSON.stringify({ error: '?repo must be in "owner/name" format.' }),
38
- { status: 400, headers: { "Content-Type": "application/json" } }
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
- { httpOnly: false, sameSite: "lax", maxAge: COOKIE_MAX_AGE, path: "/" }
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 };