@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,5 +1,14 @@
|
|
|
1
1
|
import { AuthSession, SetzKastenConfig } from '@setzkasten-cms/core';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Returns the verified session iff the cookie's HMAC signature matches
|
|
5
|
+
* the server secret AND `expiresAt` is in the future. Pre-C1 this used
|
|
6
|
+
* to do `JSON.parse` on whatever the client sent — any unauthenticated
|
|
7
|
+
* attacker could forge an admin session in 30 seconds.
|
|
8
|
+
*
|
|
9
|
+
* Single source of truth: every guard (`requireAdmin`, `guardPageAccess`)
|
|
10
|
+
* runs through this. No code path reads the cookie directly anymore.
|
|
11
|
+
*/
|
|
3
12
|
declare function parseSession(raw: string | undefined): AuthSession | null;
|
|
4
13
|
/**
|
|
5
14
|
* Returns 401 if the request has no valid session, 403 if the user is not
|
|
@@ -38,9 +47,24 @@ declare function guardPageAccess(session: AuthSession | null, pageKey: string, f
|
|
|
38
47
|
* X-SK-Website header resolves to. Admins always pass; single-mode
|
|
39
48
|
* skips this check entirely (no website context).
|
|
40
49
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
50
|
+
* Default-deny semantics (post-C5): in multi-mode, an editor with no
|
|
51
|
+
* explicit `allowedEmails` entry on the target website is denied.
|
|
52
|
+
*
|
|
53
|
+
* Pre-fix: an editor on website A could send `X-SK-Website: B` and
|
|
54
|
+
* inherit access transitively from the global editors file, because
|
|
55
|
+
* "no allowedEmails on B" was treated as "no restriction". That made
|
|
56
|
+
* the global file the only effective gate — an editor for project A
|
|
57
|
+
* could mutate project B by spoofing one HTTP header.
|
|
58
|
+
*
|
|
59
|
+
* Now: empty / missing `allowedEmails` means "no editors allowed on
|
|
60
|
+
* this website" — only admins pass. Set `allowedEmails: [editor@…]`
|
|
61
|
+
* explicitly to grant access. Existing single-website-per-editor
|
|
62
|
+
* setups need to add the email to the website entry; the migration
|
|
63
|
+
* note in `docs/migration-single-to-multi.md` documents this.
|
|
64
|
+
*
|
|
65
|
+
* Returns 403 on deny, null on allow. Resolver failures (no website
|
|
66
|
+
* context) are still treated as "no check applies" so single-mode
|
|
67
|
+
* routes work unchanged.
|
|
44
68
|
*/
|
|
45
69
|
declare function guardWebsiteAccess(session: AuthSession, request: Request): Promise<Response | null>;
|
|
46
70
|
|
|
@@ -3,11 +3,14 @@ import {
|
|
|
3
3
|
guardWebsiteAccess,
|
|
4
4
|
parseSession,
|
|
5
5
|
requireAdmin
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-Q5HV47DW.js";
|
|
7
|
+
import "../chunk-QVCW6EF3.js";
|
|
8
|
+
import "../chunk-KENFINT4.js";
|
|
7
9
|
import "../chunk-6UIKVKED.js";
|
|
10
|
+
import "../chunk-ONP6BRZO.js";
|
|
8
11
|
import "../chunk-5PIMDP4N.js";
|
|
9
12
|
import "../chunk-45ARVNT3.js";
|
|
10
|
-
import "../chunk-
|
|
13
|
+
import "../chunk-DP6RTINQ.js";
|
|
11
14
|
import "../chunk-TJNJKPUL.js";
|
|
12
15
|
import "../chunk-KH22FJO5.js";
|
|
13
16
|
export {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
declare function ensureDevSessionSecret(): string;
|
|
2
|
+
/**
|
|
3
|
+
* Test-only escape hatch: lets a test helper set the secret without
|
|
4
|
+
* touching the filesystem. Production code never calls this.
|
|
5
|
+
*/
|
|
6
|
+
declare function __setDevSessionSecretForTests(secret: string | null): void;
|
|
7
|
+
|
|
8
|
+
export { __setDevSessionSecretForTests, ensureDevSessionSecret };
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
makeRoleResolver
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-5KMGSFCZ.js";
|
|
4
|
+
import "../chunk-Q5HV47DW.js";
|
|
5
|
+
import "../chunk-QVCW6EF3.js";
|
|
6
|
+
import "../chunk-KENFINT4.js";
|
|
5
7
|
import "../chunk-6UIKVKED.js";
|
|
8
|
+
import "../chunk-ONP6BRZO.js";
|
|
6
9
|
import "../chunk-5PIMDP4N.js";
|
|
7
10
|
import "../chunk-45ARVNT3.js";
|
|
8
|
-
import "../chunk-
|
|
11
|
+
import "../chunk-DP6RTINQ.js";
|
|
9
12
|
import "../chunk-TJNJKPUL.js";
|
|
10
13
|
import "../chunk-KH22FJO5.js";
|
|
11
14
|
export {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the HMAC secret used to sign session cookies.
|
|
3
|
+
*
|
|
4
|
+
* Priority:
|
|
5
|
+
* 1. `SETZKASTEN_SESSION_SECRET` env var (production requirement)
|
|
6
|
+
* 2. `__SETZKASTEN_BUILD_CONFIG__.sessionSecret` (build-time injection)
|
|
7
|
+
* 3. **Dev-only:** persistent per-machine random secret in
|
|
8
|
+
* `.setzkasten/dev-secret` (gitignored). Generated on first run,
|
|
9
|
+
* reused thereafter. Never the same string across machines, never
|
|
10
|
+
* checked into the repo.
|
|
11
|
+
*
|
|
12
|
+
* Pre-C1 there was no signing at all. Pre-this-fix the dev fallback was
|
|
13
|
+
* a hardcoded string in source — anyone reading the code could mint
|
|
14
|
+
* admin cookies against any deployment that accidentally ran without
|
|
15
|
+
* NODE_ENV=production. The fallback is now random per-machine.
|
|
16
|
+
*/
|
|
17
|
+
declare function resolveSessionSecret(): string;
|
|
18
|
+
|
|
19
|
+
export { resolveSessionSecret };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { AuthSession } from '@setzkasten-cms/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Signed session cookies.
|
|
5
|
+
*
|
|
6
|
+
* Pre-C1 the session cookie was `JSON.stringify(session)` — readable and
|
|
7
|
+
* forgeable. An unauthenticated attacker could craft any role for any
|
|
8
|
+
* email and walk in. This module replaces that with HMAC-SHA256 over a
|
|
9
|
+
* canonical JSON payload, and refuses cookies whose signature doesn't
|
|
10
|
+
* match a known secret.
|
|
11
|
+
*
|
|
12
|
+
* Wire format:
|
|
13
|
+
*
|
|
14
|
+
* <base64url(JSON.stringify(payload))>.<base64url(HMAC-SHA256(payload))>
|
|
15
|
+
*
|
|
16
|
+
* Two segments separated by a literal `.`. Both segments are base64url
|
|
17
|
+
* (no padding). Anything that doesn't decode to that shape is rejected.
|
|
18
|
+
*
|
|
19
|
+
* The secret is supplied per-call so this module stays pure and keeps
|
|
20
|
+
* the secret-resolution policy (env var, cookie domain, fail-closed in
|
|
21
|
+
* prod) at the integration layer.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Produces the cookie value for a verified session. Throws on missing
|
|
26
|
+
* secret — the caller (always server-side) must have one resolved.
|
|
27
|
+
*/
|
|
28
|
+
declare function signSession(session: AuthSession, secret: string): string;
|
|
29
|
+
type VerifyResult = {
|
|
30
|
+
readonly ok: true;
|
|
31
|
+
readonly value: AuthSession;
|
|
32
|
+
} | {
|
|
33
|
+
readonly ok: false;
|
|
34
|
+
readonly reason: VerifyFailureReason;
|
|
35
|
+
};
|
|
36
|
+
type VerifyFailureReason = 'malformed' | 'bad-signature' | 'invalid-payload' | 'expired' | 'missing-expiry';
|
|
37
|
+
/**
|
|
38
|
+
* Returns the parsed session iff the signature matches AND the payload
|
|
39
|
+
* has a numeric `expiresAt` in the future. Rejects everything else.
|
|
40
|
+
*
|
|
41
|
+
* @param now Override timestamp for tests. Defaults to `Date.now()`.
|
|
42
|
+
*/
|
|
43
|
+
declare function verifySessionCookie(raw: string, secret: string, now?: number): VerifyResult;
|
|
44
|
+
|
|
45
|
+
export { type VerifyFailureReason, type VerifyResult, signSession, verifySessionCookie };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
signPayload
|
|
3
|
-
} from "../chunk-737TIZRU.js";
|
|
4
1
|
import {
|
|
5
2
|
recordWebhookFire
|
|
6
3
|
} from "../chunk-W3QHY5GW.js";
|
|
4
|
+
import {
|
|
5
|
+
signPayload
|
|
6
|
+
} from "../chunk-737TIZRU.js";
|
|
7
7
|
import {
|
|
8
8
|
resolveStorageConfigForRequest
|
|
9
9
|
} from "../chunk-6UIKVKED.js";
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "../chunk-45ARVNT3.js";
|
|
13
13
|
import {
|
|
14
14
|
resolveGitHubTokenForRequest
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-DP6RTINQ.js";
|
|
16
16
|
|
|
17
17
|
// src/api-routes/_webhook-dispatcher.ts
|
|
18
18
|
import {
|
|
@@ -1,14 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
makeRoleResolver
|
|
3
|
+
} from "../chunk-5KMGSFCZ.js";
|
|
1
4
|
import {
|
|
2
5
|
sessionCookieOptions
|
|
3
6
|
} from "../chunk-JHY6XTLL.js";
|
|
7
|
+
import "../chunk-Q5HV47DW.js";
|
|
4
8
|
import {
|
|
5
|
-
|
|
6
|
-
} from "../chunk-
|
|
7
|
-
import
|
|
9
|
+
resolveSessionSecret
|
|
10
|
+
} from "../chunk-QVCW6EF3.js";
|
|
11
|
+
import {
|
|
12
|
+
signSession
|
|
13
|
+
} from "../chunk-KENFINT4.js";
|
|
8
14
|
import "../chunk-6UIKVKED.js";
|
|
15
|
+
import "../chunk-ONP6BRZO.js";
|
|
9
16
|
import "../chunk-5PIMDP4N.js";
|
|
10
17
|
import "../chunk-45ARVNT3.js";
|
|
11
|
-
import "../chunk-
|
|
18
|
+
import "../chunk-DP6RTINQ.js";
|
|
12
19
|
import "../chunk-TJNJKPUL.js";
|
|
13
20
|
import "../chunk-KH22FJO5.js";
|
|
14
21
|
|
|
@@ -55,7 +62,7 @@ var GET = async ({ request, url, cookies, redirect }) => {
|
|
|
55
62
|
const session = sessionResult.value;
|
|
56
63
|
cookies.set(
|
|
57
64
|
"setzkasten_session",
|
|
58
|
-
|
|
65
|
+
signSession({ user: session.user, expiresAt: session.expiresAt }, resolveSessionSecret()),
|
|
59
66
|
sessionCookieOptions(import.meta.env.PROD)
|
|
60
67
|
);
|
|
61
68
|
return redirect(adminPath);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { APIRoute } from 'astro';
|
|
2
2
|
|
|
3
|
+
declare const POST: APIRoute;
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* browser actually deletes it on subdomains in standalone-admin setups.
|
|
5
|
+
* Backwards-compat: a few existing UI links still hit GET. We keep the
|
|
6
|
+
* handler but log a deprecation warning so the SPA can be migrated.
|
|
7
7
|
*/
|
|
8
8
|
declare const GET: APIRoute;
|
|
9
9
|
|
|
10
|
-
export { GET };
|
|
10
|
+
export { GET, POST };
|
|
@@ -3,11 +3,17 @@ import {
|
|
|
3
3
|
} from "../chunk-JHY6XTLL.js";
|
|
4
4
|
|
|
5
5
|
// src/api-routes/auth-logout.ts
|
|
6
|
-
|
|
6
|
+
async function handleLogout({ cookies, redirect }) {
|
|
7
7
|
const opts = sessionCookieOptions(false);
|
|
8
8
|
cookies.delete("setzkasten_session", { path: "/", domain: opts.domain });
|
|
9
9
|
return redirect("/");
|
|
10
|
+
}
|
|
11
|
+
var POST = handleLogout;
|
|
12
|
+
var GET = async (ctx) => {
|
|
13
|
+
console.warn("[setzkasten] GET /api/setzkasten/logout is deprecated \u2014 use POST");
|
|
14
|
+
return handleLogout(ctx);
|
|
10
15
|
};
|
|
11
16
|
export {
|
|
12
|
-
GET
|
|
17
|
+
GET,
|
|
18
|
+
POST
|
|
13
19
|
};
|
|
@@ -3,6 +3,12 @@ import { APIRoute } from 'astro';
|
|
|
3
3
|
/**
|
|
4
4
|
* Session check – returns current user info or 401.
|
|
5
5
|
* Used by the admin SPA to check if the user is logged in.
|
|
6
|
+
*
|
|
7
|
+
* Goes through `parseSession` so signature verification + expiry are
|
|
8
|
+
* enforced in exactly one place. Pre-C1 this route did its own
|
|
9
|
+
* `JSON.parse` — the only one that ever checked `expiresAt`, while
|
|
10
|
+
* `_auth-guard.parseSession` ignored it. Now both run through the
|
|
11
|
+
* same verifier.
|
|
6
12
|
*/
|
|
7
13
|
declare const GET: APIRoute;
|
|
8
14
|
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseSession
|
|
3
|
+
} from "../chunk-Q5HV47DW.js";
|
|
4
|
+
import "../chunk-QVCW6EF3.js";
|
|
5
|
+
import "../chunk-KENFINT4.js";
|
|
6
|
+
import "../chunk-6UIKVKED.js";
|
|
7
|
+
import "../chunk-ONP6BRZO.js";
|
|
8
|
+
import "../chunk-5PIMDP4N.js";
|
|
9
|
+
import "../chunk-45ARVNT3.js";
|
|
10
|
+
import "../chunk-DP6RTINQ.js";
|
|
11
|
+
import "../chunk-TJNJKPUL.js";
|
|
12
|
+
import "../chunk-KH22FJO5.js";
|
|
13
|
+
|
|
1
14
|
// src/api-routes/auth-session.ts
|
|
2
15
|
var GET = async ({ cookies }) => {
|
|
3
|
-
const
|
|
16
|
+
const raw = cookies.get("setzkasten_session")?.value;
|
|
17
|
+
const session = parseSession(raw);
|
|
4
18
|
if (!session) {
|
|
19
|
+
if (raw) cookies.delete("setzkasten_session", { path: "/" });
|
|
5
20
|
return new Response(JSON.stringify({ authenticated: false }), {
|
|
6
21
|
status: 401,
|
|
7
22
|
headers: { "Content-Type": "application/json" }
|
|
8
23
|
});
|
|
9
24
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
}
|
|
25
|
+
return new Response(JSON.stringify({ authenticated: true, user: session.user }), {
|
|
26
|
+
headers: { "Content-Type": "application/json" }
|
|
27
|
+
});
|
|
28
28
|
};
|
|
29
29
|
export {
|
|
30
30
|
GET
|
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readGlobalConfig
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-UJAFZEX2.js";
|
|
4
|
+
import {
|
|
5
|
+
makeRoleResolver
|
|
6
|
+
} from "../chunk-5KMGSFCZ.js";
|
|
4
7
|
import {
|
|
5
8
|
sessionCookieOptions
|
|
6
9
|
} from "../chunk-JHY6XTLL.js";
|
|
7
|
-
import {
|
|
8
|
-
makeRoleResolver
|
|
9
|
-
} from "../chunk-ZQDGGWJP.js";
|
|
10
10
|
import {
|
|
11
11
|
readEditorsFile
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-Q5HV47DW.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveSessionSecret
|
|
15
|
+
} from "../chunk-QVCW6EF3.js";
|
|
16
|
+
import {
|
|
17
|
+
signSession
|
|
18
|
+
} from "../chunk-KENFINT4.js";
|
|
13
19
|
import {
|
|
14
20
|
resolveStorageConfig
|
|
15
21
|
} from "../chunk-6UIKVKED.js";
|
|
22
|
+
import "../chunk-ONP6BRZO.js";
|
|
16
23
|
import {
|
|
17
24
|
gateFeature
|
|
18
25
|
} from "../chunk-5PIMDP4N.js";
|
|
19
26
|
import "../chunk-45ARVNT3.js";
|
|
20
27
|
import {
|
|
21
28
|
resolveConfigRepoToken
|
|
22
|
-
} from "../chunk-
|
|
29
|
+
} from "../chunk-DP6RTINQ.js";
|
|
23
30
|
import "../chunk-TJNJKPUL.js";
|
|
24
31
|
import "../chunk-KH22FJO5.js";
|
|
25
32
|
|
|
@@ -64,7 +71,7 @@ var POST = async ({ request, cookies }) => {
|
|
|
64
71
|
const session = result.value;
|
|
65
72
|
cookies.set(
|
|
66
73
|
"setzkasten_session",
|
|
67
|
-
|
|
74
|
+
signSession({ user: session.user, expiresAt: session.expiresAt }, resolveSessionSecret()),
|
|
68
75
|
sessionCookieOptions(import.meta.env.PROD)
|
|
69
76
|
);
|
|
70
77
|
return Response.json({ ok: true });
|
|
@@ -9,16 +9,19 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
guardPageAccess,
|
|
11
11
|
parseSession
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-Q5HV47DW.js";
|
|
13
|
+
import "../chunk-QVCW6EF3.js";
|
|
14
|
+
import "../chunk-KENFINT4.js";
|
|
13
15
|
import {
|
|
14
16
|
prefixPath,
|
|
15
17
|
resolveStorageConfigForRequest
|
|
16
18
|
} from "../chunk-6UIKVKED.js";
|
|
19
|
+
import "../chunk-ONP6BRZO.js";
|
|
17
20
|
import "../chunk-5PIMDP4N.js";
|
|
18
21
|
import "../chunk-45ARVNT3.js";
|
|
19
22
|
import {
|
|
20
23
|
resolveGitHubTokenForRequest
|
|
21
|
-
} from "../chunk-
|
|
24
|
+
} from "../chunk-DP6RTINQ.js";
|
|
22
25
|
import "../chunk-TJNJKPUL.js";
|
|
23
26
|
import {
|
|
24
27
|
withTrailers
|
|
@@ -40,7 +43,10 @@ var POST = async ({ request, cookies }) => {
|
|
|
40
43
|
try {
|
|
41
44
|
validated = validateCatalogAddBody(body);
|
|
42
45
|
} catch (e) {
|
|
43
|
-
return Response.json(
|
|
46
|
+
return Response.json(
|
|
47
|
+
{ error: e instanceof Error ? e.message : "Invalid request" },
|
|
48
|
+
{ status: 400 }
|
|
49
|
+
);
|
|
44
50
|
}
|
|
45
51
|
const { templateName, pageKey } = validated;
|
|
46
52
|
const storage = await resolveStorageConfigForRequest(request, body);
|
|
@@ -49,7 +55,12 @@ var POST = async ({ request, cookies }) => {
|
|
|
49
55
|
const serverConfig = globalThis.__SETZKASTEN_CONFIG__;
|
|
50
56
|
const fullConfig = globalThis.__SETZKASTEN_FULL_CONFIG__;
|
|
51
57
|
const contentPath = body.contentPath || serverConfig?.storage?.contentPath || "content";
|
|
52
|
-
const denied = await guardPageAccess(
|
|
58
|
+
const denied = await guardPageAccess(
|
|
59
|
+
parseSession(cookies.get("setzkasten_session")?.value),
|
|
60
|
+
pageKey,
|
|
61
|
+
fullConfig,
|
|
62
|
+
request
|
|
63
|
+
);
|
|
53
64
|
if (denied) return denied;
|
|
54
65
|
const headers = {
|
|
55
66
|
Authorization: `Bearer ${githubToken}`,
|
|
@@ -66,7 +77,10 @@ var POST = async ({ request, cookies }) => {
|
|
|
66
77
|
const existingKeys = (pageConfig.sections ?? []).map((s) => s.key);
|
|
67
78
|
const sectionKey = validated.sectionKey ?? generateAddKey(existingKeys, templateName);
|
|
68
79
|
if (existingKeys.includes(sectionKey)) {
|
|
69
|
-
return Response.json(
|
|
80
|
+
return Response.json(
|
|
81
|
+
{ error: `Key "${sectionKey}" already exists on this page` },
|
|
82
|
+
{ status: 409 }
|
|
83
|
+
);
|
|
70
84
|
}
|
|
71
85
|
const template = registry.get(templateName);
|
|
72
86
|
const { sectionJsonPath } = buildCatalogAddCommit({
|
|
@@ -106,7 +120,13 @@ async function fetchFileContent(owner, repo, branch, path, token) {
|
|
|
106
120
|
try {
|
|
107
121
|
const res = await fetch(
|
|
108
122
|
`https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
|
|
109
|
-
{
|
|
123
|
+
{
|
|
124
|
+
headers: {
|
|
125
|
+
Authorization: `Bearer ${token}`,
|
|
126
|
+
Accept: "application/vnd.github+json",
|
|
127
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
110
130
|
);
|
|
111
131
|
if (!res.ok) return null;
|
|
112
132
|
const data = await res.json();
|
|
@@ -117,16 +137,34 @@ async function fetchFileContent(owner, repo, branch, path, token) {
|
|
|
117
137
|
}
|
|
118
138
|
async function batchCommit(owner, repo, branch, files, message, headers) {
|
|
119
139
|
try {
|
|
120
|
-
const refRes = await fetch(
|
|
140
|
+
const refRes = await fetch(
|
|
141
|
+
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
|
142
|
+
{ headers }
|
|
143
|
+
);
|
|
121
144
|
if (!refRes.ok) return { ok: false, error: `Failed to get HEAD: ${refRes.status}` };
|
|
122
|
-
const {
|
|
123
|
-
|
|
145
|
+
const {
|
|
146
|
+
object: { sha: headSha }
|
|
147
|
+
} = await refRes.json();
|
|
148
|
+
const commitRes = await fetch(
|
|
149
|
+
`https://api.github.com/repos/${owner}/${repo}/git/commits/${headSha}`,
|
|
150
|
+
{ headers }
|
|
151
|
+
);
|
|
124
152
|
if (!commitRes.ok) return { ok: false, error: `Failed to get commit: ${commitRes.status}` };
|
|
125
|
-
const {
|
|
153
|
+
const {
|
|
154
|
+
tree: { sha: baseSha }
|
|
155
|
+
} = await commitRes.json();
|
|
126
156
|
const treeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
|
|
127
157
|
method: "POST",
|
|
128
158
|
headers,
|
|
129
|
-
body: JSON.stringify({
|
|
159
|
+
body: JSON.stringify({
|
|
160
|
+
base_tree: baseSha,
|
|
161
|
+
tree: files.map((f) => ({
|
|
162
|
+
path: f.path,
|
|
163
|
+
mode: "100644",
|
|
164
|
+
type: "blob",
|
|
165
|
+
content: f.content
|
|
166
|
+
}))
|
|
167
|
+
})
|
|
130
168
|
});
|
|
131
169
|
if (!treeRes.ok) return { ok: false, error: `Failed to create tree: ${treeRes.status}` };
|
|
132
170
|
const { sha: treeSha } = await treeRes.json();
|
|
@@ -135,13 +173,17 @@ async function batchCommit(owner, repo, branch, files, message, headers) {
|
|
|
135
173
|
headers,
|
|
136
174
|
body: JSON.stringify({ tree: treeSha, parents: [headSha], message })
|
|
137
175
|
});
|
|
138
|
-
if (!newCommitRes.ok)
|
|
176
|
+
if (!newCommitRes.ok)
|
|
177
|
+
return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` };
|
|
139
178
|
const { sha: newSha } = await newCommitRes.json();
|
|
140
|
-
const updateRes = await fetch(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
179
|
+
const updateRes = await fetch(
|
|
180
|
+
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
|
181
|
+
{
|
|
182
|
+
method: "PATCH",
|
|
183
|
+
headers,
|
|
184
|
+
body: JSON.stringify({ sha: newSha })
|
|
185
|
+
}
|
|
186
|
+
);
|
|
145
187
|
if (!updateRes.ok) return { ok: false, error: `Failed to update ref: ${updateRes.status}` };
|
|
146
188
|
return { ok: true, sha: newSha };
|
|
147
189
|
} catch (error) {
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "../chunk-6UIKVKED.js";
|
|
5
5
|
import {
|
|
6
6
|
resolveGitHubTokenForRequest
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-DP6RTINQ.js";
|
|
8
8
|
|
|
9
9
|
// src/api-routes/catalog-export.ts
|
|
10
10
|
import { exportTemplate } from "@setzkasten-cms/catalog";
|
|
@@ -30,11 +30,15 @@ var POST = async ({ request, cookies }) => {
|
|
|
30
30
|
const { sectionKey } = body;
|
|
31
31
|
const sectionJsonPath = prefixPath(`${contentPath}/_sections/${sectionKey}.json`, projectPrefix);
|
|
32
32
|
const contentRaw = await fetchFileContent(owner, repo, branch, sectionJsonPath, githubToken);
|
|
33
|
-
if (!contentRaw)
|
|
33
|
+
if (!contentRaw)
|
|
34
|
+
return Response.json({ error: `Section content not found: ${sectionKey}` }, { status: 404 });
|
|
34
35
|
const content = JSON.parse(contentRaw);
|
|
35
36
|
const sectionDef = findSectionDef(fullConfig, sectionKey);
|
|
36
37
|
if (!sectionDef) {
|
|
37
|
-
return Response.json(
|
|
38
|
+
return Response.json(
|
|
39
|
+
{ error: `Section definition not found for key: ${sectionKey}` },
|
|
40
|
+
{ status: 404 }
|
|
41
|
+
);
|
|
38
42
|
}
|
|
39
43
|
const templateJson = exportTemplate(sectionKey, sectionDef, content);
|
|
40
44
|
return Response.json({ success: true, template: templateJson });
|
|
@@ -57,7 +61,13 @@ async function fetchFileContent(owner, repo, branch, path, token) {
|
|
|
57
61
|
try {
|
|
58
62
|
const res = await fetch(
|
|
59
63
|
`https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
|
|
60
|
-
{
|
|
64
|
+
{
|
|
65
|
+
headers: {
|
|
66
|
+
Authorization: `Bearer ${token}`,
|
|
67
|
+
Accept: "application/vnd.github+json",
|
|
68
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
61
71
|
);
|
|
62
72
|
if (!res.ok) return null;
|
|
63
73
|
const data = await res.json();
|
|
@@ -3,9 +3,16 @@ import { APIRoute } from 'astro';
|
|
|
3
3
|
/**
|
|
4
4
|
* GET /api/setzkasten/config
|
|
5
5
|
*
|
|
6
|
-
* Returns the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Returns the SetzKastenConfig as JSON. The login screen (pre-session)
|
|
7
|
+
* needs to know which auth providers are enabled and Firebase project
|
|
8
|
+
* id, so a *minimal public subset* is exposed unauthenticated. The full
|
|
9
|
+
* config (storage params, products, page schema, etc.) is only served
|
|
10
|
+
* to authenticated users — pre-fix anyone could harvest the deployment
|
|
11
|
+
* blueprint.
|
|
12
|
+
*
|
|
13
|
+
* Fields from GlobalConfig (`_global_config.json`) are merged over the
|
|
14
|
+
* static config so admins can change theme and Firebase settings
|
|
15
|
+
* without a code deployment.
|
|
9
16
|
*/
|
|
10
17
|
declare const GET: APIRoute;
|
|
11
18
|
|
|
@@ -1,21 +1,43 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readGlobalConfig
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import
|
|
3
|
+
} from "../chunk-UJAFZEX2.js";
|
|
4
|
+
import {
|
|
5
|
+
parseSession
|
|
6
|
+
} from "../chunk-Q5HV47DW.js";
|
|
7
|
+
import "../chunk-QVCW6EF3.js";
|
|
8
|
+
import "../chunk-KENFINT4.js";
|
|
5
9
|
import "../chunk-6UIKVKED.js";
|
|
10
|
+
import "../chunk-ONP6BRZO.js";
|
|
6
11
|
import "../chunk-5PIMDP4N.js";
|
|
7
12
|
import "../chunk-45ARVNT3.js";
|
|
8
|
-
import "../chunk-
|
|
13
|
+
import "../chunk-DP6RTINQ.js";
|
|
9
14
|
import "../chunk-TJNJKPUL.js";
|
|
10
15
|
import "../chunk-KH22FJO5.js";
|
|
11
16
|
|
|
12
17
|
// src/api-routes/config.ts
|
|
13
|
-
var GET = async () => {
|
|
18
|
+
var GET = async ({ cookies }) => {
|
|
14
19
|
const config = globalThis.__SETZKASTEN_FULL_CONFIG__ ?? {};
|
|
15
20
|
const ssrConfig = globalThis.__SETZKASTEN_CONFIG__;
|
|
16
21
|
const globalCfg = await readGlobalConfig().catch(() => null);
|
|
17
22
|
const staticTheme = config.theme ?? {};
|
|
18
23
|
const globalTheme = globalCfg?.theme ?? {};
|
|
24
|
+
const session = parseSession(cookies?.get("setzkasten_session")?.value);
|
|
25
|
+
if (!session) {
|
|
26
|
+
return new Response(
|
|
27
|
+
JSON.stringify({
|
|
28
|
+
auth: config.auth ?? { providers: ["github"] },
|
|
29
|
+
theme: { ...staticTheme, ...globalTheme },
|
|
30
|
+
adminPath: ssrConfig?.adminPath ?? "/admin",
|
|
31
|
+
_firebaseConfig: globalCfg?.firebaseConfig ?? null,
|
|
32
|
+
_hasGitHub: ssrConfig?.hasGitHub ?? false,
|
|
33
|
+
_hasGoogle: ssrConfig?.hasGoogle ?? false
|
|
34
|
+
}),
|
|
35
|
+
{
|
|
36
|
+
status: 200,
|
|
37
|
+
headers: { "Content-Type": "application/json" }
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
}
|
|
19
41
|
const result = {
|
|
20
42
|
// Default fallback when no config is injected at build time. Real
|
|
21
43
|
// values are spread from `config` below.
|