@setzkasten-cms/astro-admin 1.4.0 → 1.4.1
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 +47 -0
- package/dist/api-routes/_auth-guard.js +18 -0
- package/dist/api-routes/_commit-trailers.d.ts +8 -0
- package/dist/api-routes/_commit-trailers.js +8 -0
- package/dist/api-routes/_feature-gate.d.ts +23 -0
- package/dist/api-routes/_feature-gate.js +7 -0
- package/dist/api-routes/_github-cache.d.ts +4 -0
- package/dist/api-routes/_github-cache.js +8 -0
- package/dist/api-routes/_github-token.d.ts +27 -0
- package/dist/api-routes/_github-token.js +8 -0
- package/dist/api-routes/_license-tier.d.ts +22 -0
- package/dist/api-routes/_license-tier.js +6 -0
- package/dist/api-routes/_pages-meta-store.d.ts +32 -0
- package/dist/api-routes/_pages-meta-store.js +9 -0
- package/dist/api-routes/_role-resolver.d.ts +15 -0
- package/dist/api-routes/_role-resolver.js +13 -0
- package/dist/api-routes/_session-cookie.d.ts +18 -0
- package/dist/api-routes/_session-cookie.js +6 -0
- package/dist/api-routes/_storage-config.d.ts +60 -0
- package/dist/api-routes/_storage-config.js +10 -0
- package/dist/api-routes/_vercel-origin.d.ts +16 -0
- package/dist/api-routes/_vercel-origin.js +6 -0
- package/dist/api-routes/_webhook-dispatcher.d.ts +13 -0
- package/dist/api-routes/_webhook-dispatcher.js +97 -0
- package/dist/api-routes/_webhook-signing.d.ts +11 -0
- package/dist/api-routes/_webhook-signing.js +6 -0
- package/dist/api-routes/_webhook-status-store.d.ts +19 -0
- package/dist/api-routes/_webhook-status-store.js +10 -0
- package/dist/api-routes/_website-resolver.d.ts +49 -0
- package/dist/api-routes/_website-resolver.js +14 -0
- package/dist/api-routes/_websites-store.d.ts +30 -0
- package/dist/api-routes/_websites-store.js +11 -0
- package/dist/api-routes/asset-proxy.d.ts +12 -0
- package/dist/api-routes/asset-proxy.js +67 -0
- package/dist/api-routes/auth-callback.d.ts +9 -0
- package/dist/api-routes/auth-callback.js +68 -0
- package/dist/api-routes/auth-login.d.ts +11 -0
- package/dist/api-routes/auth-login.js +27 -0
- package/dist/api-routes/auth-logout.d.ts +10 -0
- package/dist/api-routes/auth-logout.js +13 -0
- package/dist/api-routes/auth-session.d.ts +9 -0
- package/dist/api-routes/auth-session.js +31 -0
- package/dist/api-routes/auth-setzkasten-login.d.ts +18 -0
- package/dist/api-routes/auth-setzkasten-login.js +74 -0
- package/dist/api-routes/catalog-add.d.ts +14 -0
- package/dist/api-routes/catalog-add.js +153 -0
- package/dist/api-routes/catalog-export.d.ts +13 -0
- package/dist/api-routes/catalog-export.js +71 -0
- package/dist/api-routes/catalog-helpers.d.ts +41 -0
- package/dist/api-routes/catalog-helpers.js +11 -0
- package/dist/api-routes/catalog-list.d.ts +11 -0
- package/dist/api-routes/catalog-list.js +12 -0
- package/dist/api-routes/config.d.ts +12 -0
- package/dist/api-routes/config.js +43 -0
- package/dist/api-routes/deploy-hook.d.ts +14 -0
- package/dist/api-routes/deploy-hook.js +52 -0
- package/dist/api-routes/editors.d.ts +29 -0
- package/dist/api-routes/editors.js +18 -0
- package/dist/api-routes/github-proxy.d.ts +12 -0
- package/dist/api-routes/github-proxy.js +82 -0
- package/dist/api-routes/global-config.d.ts +20 -0
- package/dist/api-routes/global-config.js +19 -0
- package/dist/api-routes/history-rollback.d.ts +22 -0
- package/dist/api-routes/history-rollback.js +111 -0
- package/dist/api-routes/history-version.d.ts +11 -0
- package/dist/api-routes/history-version.js +57 -0
- package/dist/api-routes/history.d.ts +13 -0
- package/dist/api-routes/history.js +85 -0
- package/dist/api-routes/icons-local.d.ts +28 -0
- package/dist/api-routes/icons-local.js +115 -0
- package/dist/api-routes/init-add-section.d.ts +23 -0
- package/dist/api-routes/init-add-section.js +396 -0
- package/dist/api-routes/init-apply.d.ts +11 -0
- package/dist/api-routes/init-apply.js +266 -0
- package/dist/api-routes/init-migrate.d.ts +16 -0
- package/dist/api-routes/init-migrate.js +205 -0
- package/dist/api-routes/init-scan-page.d.ts +39 -0
- package/dist/api-routes/init-scan-page.js +260 -0
- package/dist/api-routes/init-scan.d.ts +11 -0
- package/dist/api-routes/init-scan.js +128 -0
- package/dist/api-routes/migrate-to-multi.d.ts +26 -0
- package/dist/api-routes/migrate-to-multi.js +188 -0
- package/dist/api-routes/pages.d.ts +39 -0
- package/dist/api-routes/pages.js +88 -0
- package/dist/api-routes/section-add.d.ts +18 -0
- package/dist/api-routes/section-add.js +173 -0
- package/dist/api-routes/section-commit-pending.d.ts +18 -0
- package/dist/api-routes/section-commit-pending.js +207 -0
- package/dist/api-routes/section-delete.d.ts +15 -0
- package/dist/api-routes/section-delete.js +149 -0
- package/dist/api-routes/section-duplicate.d.ts +15 -0
- package/dist/api-routes/section-duplicate.js +143 -0
- package/dist/api-routes/section-management.d.ts +41 -0
- package/dist/api-routes/section-management.js +14 -0
- package/dist/api-routes/section-prepare-copy.d.ts +25 -0
- package/dist/api-routes/section-prepare-copy.js +69 -0
- package/dist/api-routes/section-prepare.d.ts +18 -0
- package/dist/api-routes/section-prepare.js +104 -0
- package/dist/api-routes/setup-github-app-bounce.d.ts +13 -0
- package/dist/api-routes/setup-github-app-bounce.js +45 -0
- package/dist/api-routes/setup-github-app-branches.d.ts +14 -0
- package/dist/api-routes/setup-github-app-branches.js +58 -0
- package/dist/api-routes/setup-github-app-callback.d.ts +15 -0
- package/dist/api-routes/setup-github-app-callback.js +45 -0
- package/dist/api-routes/setup-github-app-installed.d.ts +15 -0
- package/dist/api-routes/setup-github-app-installed.js +33 -0
- package/dist/api-routes/setup-github-app-repos.d.ts +17 -0
- package/dist/api-routes/setup-github-app-repos.js +41 -0
- package/dist/api-routes/setup-github-app.d.ts +15 -0
- package/dist/api-routes/setup-github-app.js +41 -0
- package/dist/api-routes/updater-check.d.ts +10 -0
- package/dist/api-routes/updater-check.js +37 -0
- package/dist/api-routes/updater-register.d.ts +14 -0
- package/dist/api-routes/updater-register.js +71 -0
- package/dist/api-routes/updater-transfer.d.ts +11 -0
- package/dist/api-routes/updater-transfer.js +37 -0
- package/dist/api-routes/updater-unbind.d.ts +17 -0
- package/dist/api-routes/updater-unbind.js +35 -0
- package/dist/api-routes/webhooks-status.d.ts +12 -0
- package/dist/api-routes/webhooks-status.js +22 -0
- package/dist/api-routes/webhooks-test.d.ts +13 -0
- package/dist/api-routes/webhooks-test.js +124 -0
- package/dist/api-routes/webhooks.d.ts +6 -0
- package/dist/api-routes/webhooks.js +148 -0
- package/dist/api-routes/websites-add.d.ts +15 -0
- package/dist/api-routes/websites-add.js +92 -0
- package/dist/api-routes/websites-list.d.ts +12 -0
- package/dist/api-routes/websites-list.js +35 -0
- package/dist/api-routes/websites-remove.d.ts +15 -0
- package/dist/api-routes/websites-remove.js +69 -0
- package/dist/chunk-35S35OIV.js +80 -0
- package/dist/chunk-45ARVNT3.js +25 -0
- package/dist/chunk-5PIMDP4N.js +25 -0
- package/dist/chunk-5ZFTG4BW.js +10 -0
- package/dist/chunk-6UIKVKED.js +51 -0
- package/dist/chunk-737TIZRU.js +9 -0
- package/dist/chunk-AM4DZXXM.js +120 -0
- package/dist/chunk-FXNOTESI.js +87 -0
- package/dist/chunk-GHNK2GFE.js +48 -0
- package/dist/chunk-GRG3LNKH.js +37 -0
- package/dist/chunk-INIWFKQ3.js +236 -0
- package/dist/chunk-JHY6XTLL.js +24 -0
- package/dist/chunk-K22A4ZBS.js +1574 -0
- package/dist/chunk-KH22FJO5.js +17 -0
- package/dist/chunk-NKDATSPA.js +43 -0
- package/dist/chunk-RHJONMLK.js +1267 -0
- package/dist/chunk-TJNJKPUL.js +11 -0
- package/dist/chunk-V6IMPVF3.js +120 -0
- package/dist/chunk-W3QHY5GW.js +19 -0
- package/dist/chunk-ZQDGGWJP.js +43 -0
- package/package.json +249 -53
- package/src/api-routes/__tests__/route-registry.test.ts +7 -1
- package/tsconfig.json +0 -9
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readWebsitesRegistryFromGitHub,
|
|
3
|
+
resolveConfigRepoTargetFromGlobals,
|
|
4
|
+
writeWebsitesRegistryToGitHub
|
|
5
|
+
} from "../chunk-35S35OIV.js";
|
|
6
|
+
import "../chunk-KH22FJO5.js";
|
|
7
|
+
export {
|
|
8
|
+
readWebsitesRegistryFromGitHub,
|
|
9
|
+
resolveConfigRepoTargetFromGlobals,
|
|
10
|
+
writeWebsitesRegistryToGitHub
|
|
11
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Asset proxy – serves images from the private GitHub repo.
|
|
5
|
+
* Used by the admin UI to display image thumbnails without exposing the GitHub token.
|
|
6
|
+
*
|
|
7
|
+
* GET /api/setzkasten/asset/public/images/about/LP_Logo.png
|
|
8
|
+
* → fetches from GitHub API and returns the raw binary with correct Content-Type.
|
|
9
|
+
*/
|
|
10
|
+
declare const GET: APIRoute;
|
|
11
|
+
|
|
12
|
+
export { GET };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveGitHubTokenForRequest
|
|
3
|
+
} from "../chunk-NKDATSPA.js";
|
|
4
|
+
|
|
5
|
+
// src/api-routes/asset-proxy.ts
|
|
6
|
+
var GET = async ({ params, request, cookies }) => {
|
|
7
|
+
const session = cookies.get("setzkasten_session")?.value;
|
|
8
|
+
if (!session) {
|
|
9
|
+
return new Response("Unauthorized", { status: 401 });
|
|
10
|
+
}
|
|
11
|
+
const assetPath = params.path;
|
|
12
|
+
if (!assetPath) {
|
|
13
|
+
return new Response("Missing path", { status: 400 });
|
|
14
|
+
}
|
|
15
|
+
const tokenResult = await resolveGitHubTokenForRequest(request);
|
|
16
|
+
if (!tokenResult.ok) {
|
|
17
|
+
return new Response(tokenResult.error.message, { status: 500 });
|
|
18
|
+
}
|
|
19
|
+
const githubToken = tokenResult.value;
|
|
20
|
+
const config = globalThis.__SETZKASTEN_CONFIG__;
|
|
21
|
+
if (!config?.storage) {
|
|
22
|
+
return new Response("Storage not configured", { status: 500 });
|
|
23
|
+
}
|
|
24
|
+
const { owner, repo, branch } = config.storage;
|
|
25
|
+
const githubUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${assetPath}?ref=${branch}`;
|
|
26
|
+
try {
|
|
27
|
+
const response = await fetch(githubUrl, {
|
|
28
|
+
headers: {
|
|
29
|
+
Authorization: `Bearer ${githubToken}`,
|
|
30
|
+
Accept: "application/vnd.github.raw+json",
|
|
31
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
return new Response("Asset not found", { status: response.status });
|
|
36
|
+
}
|
|
37
|
+
const contentType = guessMimeType(assetPath);
|
|
38
|
+
const body = await response.arrayBuffer();
|
|
39
|
+
return new Response(body, {
|
|
40
|
+
status: 200,
|
|
41
|
+
headers: {
|
|
42
|
+
"Content-Type": contentType,
|
|
43
|
+
"Cache-Control": "private, max-age=300"
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error("[setzkasten] Asset proxy error:", error);
|
|
48
|
+
return new Response("Failed to fetch asset", { status: 502 });
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
function guessMimeType(path) {
|
|
52
|
+
const ext = path.split(".").pop()?.toLowerCase();
|
|
53
|
+
const types = {
|
|
54
|
+
jpg: "image/jpeg",
|
|
55
|
+
jpeg: "image/jpeg",
|
|
56
|
+
png: "image/png",
|
|
57
|
+
gif: "image/gif",
|
|
58
|
+
webp: "image/webp",
|
|
59
|
+
avif: "image/avif",
|
|
60
|
+
svg: "image/svg+xml",
|
|
61
|
+
pdf: "application/pdf"
|
|
62
|
+
};
|
|
63
|
+
return types[ext ?? ""] ?? "application/octet-stream";
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
GET
|
|
67
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sessionCookieOptions
|
|
3
|
+
} from "../chunk-JHY6XTLL.js";
|
|
4
|
+
import {
|
|
5
|
+
makeRoleResolver
|
|
6
|
+
} from "../chunk-ZQDGGWJP.js";
|
|
7
|
+
import "../chunk-INIWFKQ3.js";
|
|
8
|
+
import "../chunk-6UIKVKED.js";
|
|
9
|
+
import "../chunk-5PIMDP4N.js";
|
|
10
|
+
import "../chunk-45ARVNT3.js";
|
|
11
|
+
import "../chunk-NKDATSPA.js";
|
|
12
|
+
import "../chunk-TJNJKPUL.js";
|
|
13
|
+
import "../chunk-KH22FJO5.js";
|
|
14
|
+
|
|
15
|
+
// src/api-routes/auth-callback.ts
|
|
16
|
+
import { createGitHubAuth } from "@setzkasten-cms/auth";
|
|
17
|
+
var GET = async ({ request, url, cookies, redirect }) => {
|
|
18
|
+
const code = url.searchParams.get("code");
|
|
19
|
+
const state = url.searchParams.get("state");
|
|
20
|
+
if (!code) {
|
|
21
|
+
return new Response("Missing authorization code", { status: 400 });
|
|
22
|
+
}
|
|
23
|
+
const storedRaw = cookies.get("setzkasten_oauth_state")?.value;
|
|
24
|
+
let storedState;
|
|
25
|
+
if (storedRaw) {
|
|
26
|
+
try {
|
|
27
|
+
storedState = JSON.parse(storedRaw).state;
|
|
28
|
+
} catch {
|
|
29
|
+
storedState = storedRaw;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (!storedState || state !== storedState) {
|
|
33
|
+
return new Response("Invalid state parameter", { status: 400 });
|
|
34
|
+
}
|
|
35
|
+
cookies.delete("setzkasten_oauth_state", { path: "/" });
|
|
36
|
+
const config = globalThis.__SETZKASTEN_CONFIG__;
|
|
37
|
+
const adminPath = config?.adminPath ?? "/admin";
|
|
38
|
+
const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host") ?? url.host;
|
|
39
|
+
const protocol = request.headers.get("x-forwarded-proto") ?? (url.protocol === "https:" ? "https" : "http");
|
|
40
|
+
const redirectUri = `${protocol}://${host}/api/setzkasten/auth/callback`;
|
|
41
|
+
const allowedEmailsRaw = import.meta.env.SETZKASTEN_ALLOWED_EMAILS ?? process.env.SETZKASTEN_ALLOWED_EMAILS ?? "";
|
|
42
|
+
const allowedEmails = allowedEmailsRaw ? allowedEmailsRaw.split(",").map((e) => e.trim()) : void 0;
|
|
43
|
+
const auth = createGitHubAuth({
|
|
44
|
+
clientId: import.meta.env.GITHUB_CLIENT_ID ?? process.env.GITHUB_CLIENT_ID ?? "",
|
|
45
|
+
clientSecret: import.meta.env.GITHUB_CLIENT_SECRET ?? process.env.GITHUB_CLIENT_SECRET ?? "",
|
|
46
|
+
redirectUri,
|
|
47
|
+
allowedEmails,
|
|
48
|
+
resolveRole: makeRoleResolver("github", allowedEmails)
|
|
49
|
+
});
|
|
50
|
+
try {
|
|
51
|
+
const sessionResult = await auth.handleCallback(code);
|
|
52
|
+
if (!sessionResult.ok) {
|
|
53
|
+
return new Response(`Authentication failed: ${sessionResult.error.message}`, { status: 403 });
|
|
54
|
+
}
|
|
55
|
+
const session = sessionResult.value;
|
|
56
|
+
cookies.set(
|
|
57
|
+
"setzkasten_session",
|
|
58
|
+
JSON.stringify({ user: session.user, expiresAt: session.expiresAt }),
|
|
59
|
+
sessionCookieOptions(import.meta.env.PROD)
|
|
60
|
+
);
|
|
61
|
+
return redirect(adminPath);
|
|
62
|
+
} catch {
|
|
63
|
+
return new Response("Authentication failed", { status: 500 });
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export {
|
|
67
|
+
GET
|
|
68
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Login initiation – redirects the user to GitHub OAuth.
|
|
5
|
+
* Google uses GIS (POST /api/setzkasten/auth/google) instead of this redirect flow.
|
|
6
|
+
*
|
|
7
|
+
* GET /api/setzkasten/auth/login?provider=github
|
|
8
|
+
*/
|
|
9
|
+
declare const GET: APIRoute;
|
|
10
|
+
|
|
11
|
+
export { GET };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/api-routes/auth-login.ts
|
|
2
|
+
import { createGitHubAuth } from "@setzkasten-cms/auth";
|
|
3
|
+
var GET = async ({ request, url, cookies, redirect }) => {
|
|
4
|
+
const config = globalThis.__SETZKASTEN_CONFIG__;
|
|
5
|
+
const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host") ?? url.host;
|
|
6
|
+
const protocol = request.headers.get("x-forwarded-proto") ?? (url.protocol === "https:" ? "https" : "http");
|
|
7
|
+
const origin = `${protocol}://${host}`;
|
|
8
|
+
const redirectUri = `${origin}/api/setzkasten/auth/callback`;
|
|
9
|
+
const ghClientId = import.meta.env.GITHUB_CLIENT_ID ?? process.env.GITHUB_CLIENT_ID ?? "";
|
|
10
|
+
const ghClientSecret = import.meta.env.GITHUB_CLIENT_SECRET ?? process.env.GITHUB_CLIENT_SECRET ?? "";
|
|
11
|
+
if (!ghClientId || !ghClientSecret) {
|
|
12
|
+
return new Response("GitHub OAuth not configured", { status: 500 });
|
|
13
|
+
}
|
|
14
|
+
const auth = createGitHubAuth({ clientId: ghClientId, clientSecret: ghClientSecret, redirectUri });
|
|
15
|
+
const { url: loginUrl, state } = auth.getLoginUrl();
|
|
16
|
+
cookies.set("setzkasten_oauth_state", JSON.stringify({ state, provider: "github" }), {
|
|
17
|
+
httpOnly: true,
|
|
18
|
+
secure: true,
|
|
19
|
+
sameSite: "lax",
|
|
20
|
+
path: "/",
|
|
21
|
+
maxAge: 600
|
|
22
|
+
});
|
|
23
|
+
return redirect(loginUrl);
|
|
24
|
+
};
|
|
25
|
+
export {
|
|
26
|
+
GET
|
|
27
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Logout – clears the session cookie and redirects to home.
|
|
5
|
+
* Mirrors the same `domain` attribute used when the cookie was set so the
|
|
6
|
+
* browser actually deletes it on subdomains in standalone-admin setups.
|
|
7
|
+
*/
|
|
8
|
+
declare const GET: APIRoute;
|
|
9
|
+
|
|
10
|
+
export { GET };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sessionCookieOptions
|
|
3
|
+
} from "../chunk-JHY6XTLL.js";
|
|
4
|
+
|
|
5
|
+
// src/api-routes/auth-logout.ts
|
|
6
|
+
var GET = async ({ cookies, redirect }) => {
|
|
7
|
+
const opts = sessionCookieOptions(false);
|
|
8
|
+
cookies.delete("setzkasten_session", { path: "/", domain: opts.domain });
|
|
9
|
+
return redirect("/");
|
|
10
|
+
};
|
|
11
|
+
export {
|
|
12
|
+
GET
|
|
13
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/api-routes/auth-session.ts
|
|
2
|
+
var GET = async ({ cookies }) => {
|
|
3
|
+
const session = cookies.get("setzkasten_session")?.value;
|
|
4
|
+
if (!session) {
|
|
5
|
+
return new Response(JSON.stringify({ authenticated: false }), {
|
|
6
|
+
status: 401,
|
|
7
|
+
headers: { "Content-Type": "application/json" }
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const parsed = JSON.parse(session);
|
|
12
|
+
if (parsed.expiresAt < Date.now()) {
|
|
13
|
+
cookies.delete("setzkasten_session", { path: "/" });
|
|
14
|
+
return new Response(JSON.stringify({ authenticated: false, reason: "expired" }), {
|
|
15
|
+
status: 401,
|
|
16
|
+
headers: { "Content-Type": "application/json" }
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return new Response(JSON.stringify({ authenticated: true, user: parsed.user }), {
|
|
20
|
+
headers: { "Content-Type": "application/json" }
|
|
21
|
+
});
|
|
22
|
+
} catch {
|
|
23
|
+
return new Response(JSON.stringify({ authenticated: false }), {
|
|
24
|
+
status: 401,
|
|
25
|
+
headers: { "Content-Type": "application/json" }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
export {
|
|
30
|
+
GET
|
|
31
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* POST /api/setzkasten/auth/setzkasten-login
|
|
5
|
+
* Body: { idToken: string } — Firebase ID token from signInWithPopup
|
|
6
|
+
*
|
|
7
|
+
* Verifies the Firebase JWT against Firebase's public JWKS (no secret needed).
|
|
8
|
+
* Access is gated exclusively by _editors.json (fail-closed).
|
|
9
|
+
*
|
|
10
|
+
* Editors live in the build-time-configured repo regardless of which website
|
|
11
|
+
* the request is targeting — in single-mode that's the website's repo, in
|
|
12
|
+
* multi-mode it's the config-repo. The per-request resolver and X-SK-Website
|
|
13
|
+
* header are intentionally NOT consulted here, because login predates any
|
|
14
|
+
* website selection.
|
|
15
|
+
*/
|
|
16
|
+
declare const POST: APIRoute;
|
|
17
|
+
|
|
18
|
+
export { POST };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readGlobalConfig
|
|
3
|
+
} from "../chunk-AM4DZXXM.js";
|
|
4
|
+
import {
|
|
5
|
+
sessionCookieOptions
|
|
6
|
+
} from "../chunk-JHY6XTLL.js";
|
|
7
|
+
import {
|
|
8
|
+
makeRoleResolver
|
|
9
|
+
} from "../chunk-ZQDGGWJP.js";
|
|
10
|
+
import {
|
|
11
|
+
readEditorsFile
|
|
12
|
+
} from "../chunk-INIWFKQ3.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveStorageConfig
|
|
15
|
+
} from "../chunk-6UIKVKED.js";
|
|
16
|
+
import {
|
|
17
|
+
gateFeature
|
|
18
|
+
} from "../chunk-5PIMDP4N.js";
|
|
19
|
+
import "../chunk-45ARVNT3.js";
|
|
20
|
+
import {
|
|
21
|
+
resolveConfigRepoToken
|
|
22
|
+
} from "../chunk-NKDATSPA.js";
|
|
23
|
+
import "../chunk-TJNJKPUL.js";
|
|
24
|
+
import "../chunk-KH22FJO5.js";
|
|
25
|
+
|
|
26
|
+
// src/api-routes/auth-setzkasten-login.ts
|
|
27
|
+
import { verifyFirebaseJwt } from "@setzkasten-cms/auth";
|
|
28
|
+
var POST = async ({ request, cookies }) => {
|
|
29
|
+
const gate = gateFeature("google-auth");
|
|
30
|
+
if (gate) return gate;
|
|
31
|
+
const body = await request.json().catch(() => null);
|
|
32
|
+
const idToken = body?.idToken;
|
|
33
|
+
if (!idToken) {
|
|
34
|
+
return new Response("Missing idToken", { status: 400 });
|
|
35
|
+
}
|
|
36
|
+
const storage = resolveStorageConfig();
|
|
37
|
+
if (!storage) {
|
|
38
|
+
return new Response("Storage not configured", { status: 500 });
|
|
39
|
+
}
|
|
40
|
+
const { owner, repo, branch } = storage;
|
|
41
|
+
const serverConfig = globalThis.__SETZKASTEN_CONFIG__;
|
|
42
|
+
const contentPath = serverConfig?.storage?.contentPath ?? "content";
|
|
43
|
+
const tokenResult = await resolveConfigRepoToken();
|
|
44
|
+
if (!tokenResult.ok) {
|
|
45
|
+
return new Response(`GitHub token unavailable: ${tokenResult.error.message}`, { status: 503 });
|
|
46
|
+
}
|
|
47
|
+
const globalCfg = await readGlobalConfig().catch(() => null);
|
|
48
|
+
if (!globalCfg?.firebaseConfig) {
|
|
49
|
+
return new Response("SetzKastenLogin not configured", { status: 500 });
|
|
50
|
+
}
|
|
51
|
+
const editors = await readEditorsFile(owner, repo, branch, contentPath, tokenResult.value);
|
|
52
|
+
if (editors === null) {
|
|
53
|
+
return new Response("Editors list unavailable \u2014 no access granted", { status: 503 });
|
|
54
|
+
}
|
|
55
|
+
const allowedEmails = editors.map((e) => e.email);
|
|
56
|
+
const result = await verifyFirebaseJwt(
|
|
57
|
+
idToken,
|
|
58
|
+
allowedEmails,
|
|
59
|
+
makeRoleResolver("google", allowedEmails)
|
|
60
|
+
);
|
|
61
|
+
if (!result.ok) {
|
|
62
|
+
return new Response(result.error.message, { status: 403 });
|
|
63
|
+
}
|
|
64
|
+
const session = result.value;
|
|
65
|
+
cookies.set(
|
|
66
|
+
"setzkasten_session",
|
|
67
|
+
JSON.stringify({ user: session.user, expiresAt: session.expiresAt }),
|
|
68
|
+
sessionCookieOptions(import.meta.env.PROD)
|
|
69
|
+
);
|
|
70
|
+
return Response.json({ ok: true });
|
|
71
|
+
};
|
|
72
|
+
export {
|
|
73
|
+
POST
|
|
74
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* POST /api/setzkasten/catalog/add
|
|
5
|
+
*
|
|
6
|
+
* Adds a catalog template to a page:
|
|
7
|
+
* - Creates content JSON with template's defaultContent (_sections/{key}.json)
|
|
8
|
+
* - Appends new entry to the page config (pages/_{pageKey}.json)
|
|
9
|
+
*
|
|
10
|
+
* Body: { templateName, pageKey, sectionKey? (override), owner?, repo?, branch?, contentPath? }
|
|
11
|
+
*/
|
|
12
|
+
declare const POST: APIRoute;
|
|
13
|
+
|
|
14
|
+
export { POST };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addToPageConfig,
|
|
3
|
+
generateAddKey
|
|
4
|
+
} from "../chunk-GHNK2GFE.js";
|
|
5
|
+
import {
|
|
6
|
+
buildCatalogAddCommit,
|
|
7
|
+
validateCatalogAddBody
|
|
8
|
+
} from "../chunk-GRG3LNKH.js";
|
|
9
|
+
import {
|
|
10
|
+
guardPageAccess,
|
|
11
|
+
parseSession
|
|
12
|
+
} from "../chunk-INIWFKQ3.js";
|
|
13
|
+
import {
|
|
14
|
+
prefixPath,
|
|
15
|
+
resolveStorageConfigForRequest
|
|
16
|
+
} from "../chunk-6UIKVKED.js";
|
|
17
|
+
import "../chunk-5PIMDP4N.js";
|
|
18
|
+
import "../chunk-45ARVNT3.js";
|
|
19
|
+
import {
|
|
20
|
+
resolveGitHubTokenForRequest
|
|
21
|
+
} from "../chunk-NKDATSPA.js";
|
|
22
|
+
import "../chunk-TJNJKPUL.js";
|
|
23
|
+
import {
|
|
24
|
+
withTrailers
|
|
25
|
+
} from "../chunk-KH22FJO5.js";
|
|
26
|
+
|
|
27
|
+
// src/api-routes/catalog-add.ts
|
|
28
|
+
import { registry } from "@setzkasten-cms/catalog";
|
|
29
|
+
var POST = async ({ request, cookies }) => {
|
|
30
|
+
const session = cookies.get("setzkasten_session")?.value;
|
|
31
|
+
if (!session) return new Response("Unauthorized", { status: 401 });
|
|
32
|
+
const tokenResult = await resolveGitHubTokenForRequest(request);
|
|
33
|
+
if (!tokenResult.ok) {
|
|
34
|
+
return new Response(tokenResult.error.message, { status: 500 });
|
|
35
|
+
}
|
|
36
|
+
const githubToken = tokenResult.value;
|
|
37
|
+
try {
|
|
38
|
+
const body = await request.json();
|
|
39
|
+
let validated;
|
|
40
|
+
try {
|
|
41
|
+
validated = validateCatalogAddBody(body);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
return Response.json({ error: e instanceof Error ? e.message : "Invalid request" }, { status: 400 });
|
|
44
|
+
}
|
|
45
|
+
const { templateName, pageKey } = validated;
|
|
46
|
+
const storage = await resolveStorageConfigForRequest(request, body);
|
|
47
|
+
if (!storage) return Response.json({ error: "Could not resolve owner/repo" }, { status: 400 });
|
|
48
|
+
const { owner, repo, branch, projectPrefix } = storage;
|
|
49
|
+
const serverConfig = globalThis.__SETZKASTEN_CONFIG__;
|
|
50
|
+
const fullConfig = globalThis.__SETZKASTEN_FULL_CONFIG__;
|
|
51
|
+
const contentPath = body.contentPath || serverConfig?.storage?.contentPath || "content";
|
|
52
|
+
const denied = await guardPageAccess(parseSession(cookies.get("setzkasten_session")?.value), pageKey, fullConfig, request);
|
|
53
|
+
if (denied) return denied;
|
|
54
|
+
const headers = {
|
|
55
|
+
Authorization: `Bearer ${githubToken}`,
|
|
56
|
+
Accept: "application/vnd.github+json",
|
|
57
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
58
|
+
"Content-Type": "application/json"
|
|
59
|
+
};
|
|
60
|
+
const configKey = "_" + pageKey.replace(/\//g, "_");
|
|
61
|
+
const rawPageConfigPath = `${contentPath}/pages/${configKey}.json`;
|
|
62
|
+
const pageConfigPath = prefixPath(rawPageConfigPath, projectPrefix);
|
|
63
|
+
const pageConfigRaw = await fetchFileContent(owner, repo, branch, pageConfigPath, githubToken);
|
|
64
|
+
if (!pageConfigRaw) return Response.json({ error: "Page config not found" }, { status: 404 });
|
|
65
|
+
const pageConfig = JSON.parse(pageConfigRaw);
|
|
66
|
+
const existingKeys = (pageConfig.sections ?? []).map((s) => s.key);
|
|
67
|
+
const sectionKey = validated.sectionKey ?? generateAddKey(existingKeys, templateName);
|
|
68
|
+
if (existingKeys.includes(sectionKey)) {
|
|
69
|
+
return Response.json({ error: `Key "${sectionKey}" already exists on this page` }, { status: 409 });
|
|
70
|
+
}
|
|
71
|
+
const template = registry.get(templateName);
|
|
72
|
+
const { sectionJsonPath } = buildCatalogAddCommit({
|
|
73
|
+
contentPath,
|
|
74
|
+
projectPrefix,
|
|
75
|
+
pageKey,
|
|
76
|
+
sectionKey,
|
|
77
|
+
templateName,
|
|
78
|
+
pageConfigPath: rawPageConfigPath
|
|
79
|
+
});
|
|
80
|
+
const updatedConfig = addToPageConfig(pageConfig, sectionKey, templateName);
|
|
81
|
+
const commitResult = await batchCommit(
|
|
82
|
+
owner,
|
|
83
|
+
repo,
|
|
84
|
+
branch,
|
|
85
|
+
[
|
|
86
|
+
{ path: pageConfigPath, content: JSON.stringify(updatedConfig, null, 2) },
|
|
87
|
+
{ path: sectionJsonPath, content: JSON.stringify(template.defaultContent, null, 2) }
|
|
88
|
+
],
|
|
89
|
+
withTrailers(
|
|
90
|
+
`content: add catalog template "${templateName}" as "${sectionKey}" to ${pageKey}`,
|
|
91
|
+
parseSession(cookies.get("setzkasten_session")?.value)?.user?.email
|
|
92
|
+
),
|
|
93
|
+
headers
|
|
94
|
+
);
|
|
95
|
+
if (!commitResult.ok) return Response.json({ error: commitResult.error }, { status: 500 });
|
|
96
|
+
return Response.json({ success: true, sectionKey, commitSha: commitResult.sha });
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error("[setzkasten] catalog-add error:", error);
|
|
99
|
+
return Response.json(
|
|
100
|
+
{ error: error instanceof Error ? error.message : "Catalog add failed" },
|
|
101
|
+
{ status: 500 }
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
async function fetchFileContent(owner, repo, branch, path, token) {
|
|
106
|
+
try {
|
|
107
|
+
const res = await fetch(
|
|
108
|
+
`https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
|
|
109
|
+
{ headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" } }
|
|
110
|
+
);
|
|
111
|
+
if (!res.ok) return null;
|
|
112
|
+
const data = await res.json();
|
|
113
|
+
return data.encoding === "base64" ? Buffer.from(data.content, "base64").toString("utf-8") : data.content;
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function batchCommit(owner, repo, branch, files, message, headers) {
|
|
119
|
+
try {
|
|
120
|
+
const refRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, { headers });
|
|
121
|
+
if (!refRes.ok) return { ok: false, error: `Failed to get HEAD: ${refRes.status}` };
|
|
122
|
+
const { object: { sha: headSha } } = await refRes.json();
|
|
123
|
+
const commitRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/commits/${headSha}`, { headers });
|
|
124
|
+
if (!commitRes.ok) return { ok: false, error: `Failed to get commit: ${commitRes.status}` };
|
|
125
|
+
const { tree: { sha: baseSha } } = await commitRes.json();
|
|
126
|
+
const treeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers,
|
|
129
|
+
body: JSON.stringify({ base_tree: baseSha, tree: files.map((f) => ({ path: f.path, mode: "100644", type: "blob", content: f.content })) })
|
|
130
|
+
});
|
|
131
|
+
if (!treeRes.ok) return { ok: false, error: `Failed to create tree: ${treeRes.status}` };
|
|
132
|
+
const { sha: treeSha } = await treeRes.json();
|
|
133
|
+
const newCommitRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/commits`, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers,
|
|
136
|
+
body: JSON.stringify({ tree: treeSha, parents: [headSha], message })
|
|
137
|
+
});
|
|
138
|
+
if (!newCommitRes.ok) return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` };
|
|
139
|
+
const { sha: newSha } = await newCommitRes.json();
|
|
140
|
+
const updateRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
|
|
141
|
+
method: "PATCH",
|
|
142
|
+
headers,
|
|
143
|
+
body: JSON.stringify({ sha: newSha })
|
|
144
|
+
});
|
|
145
|
+
if (!updateRes.ok) return { ok: false, error: `Failed to update ref: ${updateRes.status}` };
|
|
146
|
+
return { ok: true, sha: newSha };
|
|
147
|
+
} catch (error) {
|
|
148
|
+
return { ok: false, error: error instanceof Error ? error.message : "Commit failed" };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export {
|
|
152
|
+
POST
|
|
153
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* POST /api/setzkasten/catalog/export
|
|
5
|
+
*
|
|
6
|
+
* Exports a managed section as a `.setzkasten-template` JSON string.
|
|
7
|
+
* The caller can save this to a file or upload it to a shared catalog.
|
|
8
|
+
*
|
|
9
|
+
* Body: { sectionKey, owner?, repo?, branch?, contentPath? }
|
|
10
|
+
*/
|
|
11
|
+
declare const POST: APIRoute;
|
|
12
|
+
|
|
13
|
+
export { POST };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
prefixPath,
|
|
3
|
+
resolveStorageConfigForRequest
|
|
4
|
+
} from "../chunk-6UIKVKED.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveGitHubTokenForRequest
|
|
7
|
+
} from "../chunk-NKDATSPA.js";
|
|
8
|
+
|
|
9
|
+
// src/api-routes/catalog-export.ts
|
|
10
|
+
import { exportTemplate } from "@setzkasten-cms/catalog";
|
|
11
|
+
var POST = async ({ request, cookies }) => {
|
|
12
|
+
const session = cookies.get("setzkasten_session")?.value;
|
|
13
|
+
if (!session) return new Response("Unauthorized", { status: 401 });
|
|
14
|
+
const tokenResult = await resolveGitHubTokenForRequest(request);
|
|
15
|
+
if (!tokenResult.ok) {
|
|
16
|
+
return new Response(tokenResult.error.message, { status: 500 });
|
|
17
|
+
}
|
|
18
|
+
const githubToken = tokenResult.value;
|
|
19
|
+
try {
|
|
20
|
+
const body = await request.json();
|
|
21
|
+
if (!body.sectionKey) {
|
|
22
|
+
return Response.json({ error: "sectionKey is required" }, { status: 400 });
|
|
23
|
+
}
|
|
24
|
+
const storage = await resolveStorageConfigForRequest(request, body);
|
|
25
|
+
if (!storage) return Response.json({ error: "Could not resolve owner/repo" }, { status: 400 });
|
|
26
|
+
const { owner, repo, branch, projectPrefix } = storage;
|
|
27
|
+
const serverConfig = globalThis.__SETZKASTEN_CONFIG__;
|
|
28
|
+
const fullConfig = globalThis.__SETZKASTEN_FULL_CONFIG__;
|
|
29
|
+
const contentPath = body.contentPath || serverConfig?.storage?.contentPath || "content";
|
|
30
|
+
const { sectionKey } = body;
|
|
31
|
+
const sectionJsonPath = prefixPath(`${contentPath}/_sections/${sectionKey}.json`, projectPrefix);
|
|
32
|
+
const contentRaw = await fetchFileContent(owner, repo, branch, sectionJsonPath, githubToken);
|
|
33
|
+
if (!contentRaw) return Response.json({ error: `Section content not found: ${sectionKey}` }, { status: 404 });
|
|
34
|
+
const content = JSON.parse(contentRaw);
|
|
35
|
+
const sectionDef = findSectionDef(fullConfig, sectionKey);
|
|
36
|
+
if (!sectionDef) {
|
|
37
|
+
return Response.json({ error: `Section definition not found for key: ${sectionKey}` }, { status: 404 });
|
|
38
|
+
}
|
|
39
|
+
const templateJson = exportTemplate(sectionKey, sectionDef, content);
|
|
40
|
+
return Response.json({ success: true, template: templateJson });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error("[setzkasten] catalog-export error:", error);
|
|
43
|
+
return Response.json(
|
|
44
|
+
{ error: error instanceof Error ? error.message : "Export failed" },
|
|
45
|
+
{ status: 500 }
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
function findSectionDef(fullConfig, sectionKey) {
|
|
50
|
+
if (!fullConfig?.products) return null;
|
|
51
|
+
for (const product of Object.values(fullConfig.products)) {
|
|
52
|
+
if (product?.sections?.[sectionKey]) return product.sections[sectionKey];
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
async function fetchFileContent(owner, repo, branch, path, token) {
|
|
57
|
+
try {
|
|
58
|
+
const res = await fetch(
|
|
59
|
+
`https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
|
|
60
|
+
{ headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" } }
|
|
61
|
+
);
|
|
62
|
+
if (!res.ok) return null;
|
|
63
|
+
const data = await res.json();
|
|
64
|
+
return data.encoding === "base64" ? Buffer.from(data.content, "base64").toString("utf-8") : data.content;
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export {
|
|
70
|
+
POST
|
|
71
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as _setzkasten_cms_catalog from '@setzkasten-cms/catalog';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure helper functions for catalog API routes.
|
|
5
|
+
* No GitHub API, no Astro runtime — fully unit-testable.
|
|
6
|
+
*/
|
|
7
|
+
/** Returns the full catalog list (all built-in templates) for the API response. */
|
|
8
|
+
declare function buildCatalogResponse(): _setzkasten_cms_catalog.TemplateFile[];
|
|
9
|
+
interface CatalogAddBody {
|
|
10
|
+
templateName?: unknown;
|
|
11
|
+
pageKey?: unknown;
|
|
12
|
+
sectionKey?: unknown;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Validates the request body for POST /api/setzkasten/catalog/add.
|
|
17
|
+
* Throws with a descriptive message on any validation error.
|
|
18
|
+
*/
|
|
19
|
+
declare function validateCatalogAddBody(body: CatalogAddBody): {
|
|
20
|
+
templateName: string;
|
|
21
|
+
pageKey: string;
|
|
22
|
+
sectionKey?: string;
|
|
23
|
+
};
|
|
24
|
+
interface CatalogAddCommitOpts {
|
|
25
|
+
contentPath: string;
|
|
26
|
+
projectPrefix: string;
|
|
27
|
+
pageKey: string;
|
|
28
|
+
sectionKey: string;
|
|
29
|
+
templateName: string;
|
|
30
|
+
pageConfigPath: string;
|
|
31
|
+
}
|
|
32
|
+
interface CatalogAddCommitPaths {
|
|
33
|
+
sectionJsonPath: string;
|
|
34
|
+
pageConfigPath: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Builds the file paths that need to be committed when adding a catalog template to a page.
|
|
38
|
+
*/
|
|
39
|
+
declare function buildCatalogAddCommit(opts: CatalogAddCommitOpts): CatalogAddCommitPaths;
|
|
40
|
+
|
|
41
|
+
export { type CatalogAddBody, type CatalogAddCommitOpts, type CatalogAddCommitPaths, buildCatalogAddCommit, buildCatalogResponse, validateCatalogAddBody };
|