@caretcms/core 0.1.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/LICENSE +21 -0
- package/README.md +191 -0
- package/dist/browser-runtime.d.ts +12 -0
- package/dist/browser-runtime.d.ts.map +1 -0
- package/dist/browser-runtime.js +298 -0
- package/dist/browser-runtime.js.map +1 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +343 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +61 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +80 -0
- package/dist/loader.js.map +1 -0
- package/dist/providers/storage/filesystem.d.ts +7 -0
- package/dist/providers/storage/filesystem.d.ts.map +1 -0
- package/dist/providers/storage/filesystem.js +6 -0
- package/dist/providers/storage/filesystem.js.map +1 -0
- package/dist/providers/uploads/local.d.ts +6 -0
- package/dist/providers/uploads/local.d.ts.map +1 -0
- package/dist/providers/uploads/local.js +6 -0
- package/dist/providers/uploads/local.js.map +1 -0
- package/dist/runtime/auth/cookie-utils.d.ts +3 -0
- package/dist/runtime/auth/cookie-utils.d.ts.map +1 -0
- package/dist/runtime/auth/cookie-utils.js +33 -0
- package/dist/runtime/auth/cookie-utils.js.map +1 -0
- package/dist/runtime/auth/demo-session.d.ts +24 -0
- package/dist/runtime/auth/demo-session.d.ts.map +1 -0
- package/dist/runtime/auth/demo-session.js +42 -0
- package/dist/runtime/auth/demo-session.js.map +1 -0
- package/dist/runtime/auth/rate-limiter.d.ts +26 -0
- package/dist/runtime/auth/rate-limiter.d.ts.map +1 -0
- package/dist/runtime/auth/rate-limiter.js +65 -0
- package/dist/runtime/auth/rate-limiter.js.map +1 -0
- package/dist/runtime/auth/session.d.ts +14 -0
- package/dist/runtime/auth/session.d.ts.map +1 -0
- package/dist/runtime/auth/session.js +128 -0
- package/dist/runtime/auth/session.js.map +1 -0
- package/dist/runtime/bind.d.ts +14 -0
- package/dist/runtime/bind.d.ts.map +1 -0
- package/dist/runtime/bind.js +19 -0
- package/dist/runtime/bind.js.map +1 -0
- package/dist/runtime/config.d.ts +20 -0
- package/dist/runtime/config.d.ts.map +1 -0
- package/dist/runtime/config.js +40 -0
- package/dist/runtime/config.js.map +1 -0
- package/dist/runtime/content.d.ts +28 -0
- package/dist/runtime/content.d.ts.map +1 -0
- package/dist/runtime/content.js +37 -0
- package/dist/runtime/content.js.map +1 -0
- package/dist/runtime/index.d.ts +14 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +16 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/middleware.d.ts +14 -0
- package/dist/runtime/middleware.d.ts.map +1 -0
- package/dist/runtime/middleware.js +80 -0
- package/dist/runtime/middleware.js.map +1 -0
- package/dist/runtime/mutations/contracts.d.ts +91 -0
- package/dist/runtime/mutations/contracts.d.ts.map +1 -0
- package/dist/runtime/mutations/contracts.js +320 -0
- package/dist/runtime/mutations/contracts.js.map +1 -0
- package/dist/runtime/mutations/engine.d.ts +23 -0
- package/dist/runtime/mutations/engine.d.ts.map +1 -0
- package/dist/runtime/mutations/engine.js +255 -0
- package/dist/runtime/mutations/engine.js.map +1 -0
- package/dist/runtime/providers.d.ts +9 -0
- package/dist/runtime/providers.d.ts.map +1 -0
- package/dist/runtime/providers.js +44 -0
- package/dist/runtime/providers.js.map +1 -0
- package/dist/runtime/request-context.d.ts +11 -0
- package/dist/runtime/request-context.d.ts.map +1 -0
- package/dist/runtime/request-context.js +16 -0
- package/dist/runtime/request-context.js.map +1 -0
- package/dist/runtime/rewrite.d.ts +13 -0
- package/dist/runtime/rewrite.d.ts.map +1 -0
- package/dist/runtime/rewrite.js +253 -0
- package/dist/runtime/rewrite.js.map +1 -0
- package/dist/runtime/routes/_helpers.d.ts +17 -0
- package/dist/runtime/routes/_helpers.d.ts.map +1 -0
- package/dist/runtime/routes/_helpers.js +38 -0
- package/dist/runtime/routes/_helpers.js.map +1 -0
- package/dist/runtime/routes/admin-entry.d.ts +3 -0
- package/dist/runtime/routes/admin-entry.d.ts.map +1 -0
- package/dist/runtime/routes/admin-entry.js +170 -0
- package/dist/runtime/routes/admin-entry.js.map +1 -0
- package/dist/runtime/routes/admin.d.ts +3 -0
- package/dist/runtime/routes/admin.d.ts.map +1 -0
- package/dist/runtime/routes/admin.js +189 -0
- package/dist/runtime/routes/admin.js.map +1 -0
- package/dist/runtime/routes/auth-login.d.ts +4 -0
- package/dist/runtime/routes/auth-login.d.ts.map +1 -0
- package/dist/runtime/routes/auth-login.js +71 -0
- package/dist/runtime/routes/auth-login.js.map +1 -0
- package/dist/runtime/routes/auth-logout.d.ts +4 -0
- package/dist/runtime/routes/auth-logout.d.ts.map +1 -0
- package/dist/runtime/routes/auth-logout.js +47 -0
- package/dist/runtime/routes/auth-logout.js.map +1 -0
- package/dist/runtime/routes/auth-session.d.ts +3 -0
- package/dist/runtime/routes/auth-session.d.ts.map +1 -0
- package/dist/runtime/routes/auth-session.js +8 -0
- package/dist/runtime/routes/auth-session.js.map +1 -0
- package/dist/runtime/routes/collection-list.d.ts +3 -0
- package/dist/runtime/routes/collection-list.d.ts.map +1 -0
- package/dist/runtime/routes/collection-list.js +420 -0
- package/dist/runtime/routes/collection-list.js.map +1 -0
- package/dist/runtime/routes/collections-metadata.d.ts +3 -0
- package/dist/runtime/routes/collections-metadata.d.ts.map +1 -0
- package/dist/runtime/routes/collections-metadata.js +17 -0
- package/dist/runtime/routes/collections-metadata.js.map +1 -0
- package/dist/runtime/routes/editor-assets.d.ts +3 -0
- package/dist/runtime/routes/editor-assets.d.ts.map +1 -0
- package/dist/runtime/routes/editor-assets.js +73 -0
- package/dist/runtime/routes/editor-assets.js.map +1 -0
- package/dist/runtime/routes/entries.d.ts +3 -0
- package/dist/runtime/routes/entries.d.ts.map +1 -0
- package/dist/runtime/routes/entries.js +96 -0
- package/dist/runtime/routes/entries.js.map +1 -0
- package/dist/runtime/routes/history.d.ts +4 -0
- package/dist/runtime/routes/history.d.ts.map +1 -0
- package/dist/runtime/routes/history.js +54 -0
- package/dist/runtime/routes/history.js.map +1 -0
- package/dist/runtime/routes/mutate.d.ts +4 -0
- package/dist/runtime/routes/mutate.d.ts.map +1 -0
- package/dist/runtime/routes/mutate.js +38 -0
- package/dist/runtime/routes/mutate.js.map +1 -0
- package/dist/runtime/routes/schema.d.ts +3 -0
- package/dist/runtime/routes/schema.d.ts.map +1 -0
- package/dist/runtime/routes/schema.js +58 -0
- package/dist/runtime/routes/schema.js.map +1 -0
- package/dist/runtime/routes/studio-home.d.ts +3 -0
- package/dist/runtime/routes/studio-home.d.ts.map +1 -0
- package/dist/runtime/routes/studio-home.js +99 -0
- package/dist/runtime/routes/studio-home.js.map +1 -0
- package/dist/runtime/routes/theme-stylesheet.d.ts +3 -0
- package/dist/runtime/routes/theme-stylesheet.d.ts.map +1 -0
- package/dist/runtime/routes/theme-stylesheet.js +32 -0
- package/dist/runtime/routes/theme-stylesheet.js.map +1 -0
- package/dist/runtime/routes/upload.d.ts +3 -0
- package/dist/runtime/routes/upload.d.ts.map +1 -0
- package/dist/runtime/routes/upload.js +33 -0
- package/dist/runtime/routes/upload.js.map +1 -0
- package/dist/runtime/sanitize-html.d.ts +8 -0
- package/dist/runtime/sanitize-html.d.ts.map +1 -0
- package/dist/runtime/sanitize-html.js +86 -0
- package/dist/runtime/sanitize-html.js.map +1 -0
- package/dist/runtime/schema/template.d.ts +15 -0
- package/dist/runtime/schema/template.d.ts.map +1 -0
- package/dist/runtime/schema/template.js +38 -0
- package/dist/runtime/schema/template.js.map +1 -0
- package/dist/runtime/schema-registry.d.ts +17 -0
- package/dist/runtime/schema-registry.d.ts.map +1 -0
- package/dist/runtime/schema-registry.js +16 -0
- package/dist/runtime/schema-registry.js.map +1 -0
- package/dist/runtime/storage/filesystem-adapter.d.ts +36 -0
- package/dist/runtime/storage/filesystem-adapter.d.ts.map +1 -0
- package/dist/runtime/storage/filesystem-adapter.js +291 -0
- package/dist/runtime/storage/filesystem-adapter.js.map +1 -0
- package/dist/runtime/storage/filesystem-upload-handler.d.ts +11 -0
- package/dist/runtime/storage/filesystem-upload-handler.d.ts.map +1 -0
- package/dist/runtime/storage/filesystem-upload-handler.js +20 -0
- package/dist/runtime/storage/filesystem-upload-handler.js.map +1 -0
- package/dist/runtime/storage/image-validation.d.ts +28 -0
- package/dist/runtime/storage/image-validation.d.ts.map +1 -0
- package/dist/runtime/storage/image-validation.js +94 -0
- package/dist/runtime/storage/image-validation.js.map +1 -0
- package/dist/runtime/storage/in-memory-adapter.d.ts +29 -0
- package/dist/runtime/storage/in-memory-adapter.d.ts.map +1 -0
- package/dist/runtime/storage/in-memory-adapter.js +110 -0
- package/dist/runtime/storage/in-memory-adapter.js.map +1 -0
- package/dist/runtime/storage/quota-upload-handler.d.ts +27 -0
- package/dist/runtime/storage/quota-upload-handler.d.ts.map +1 -0
- package/dist/runtime/storage/quota-upload-handler.js +34 -0
- package/dist/runtime/storage/quota-upload-handler.js.map +1 -0
- package/dist/runtime/storage/session-overlay-adapter.d.ts +33 -0
- package/dist/runtime/storage/session-overlay-adapter.d.ts.map +1 -0
- package/dist/runtime/storage/session-overlay-adapter.js +121 -0
- package/dist/runtime/storage/session-overlay-adapter.js.map +1 -0
- package/dist/runtime/themes/preset-tokens.d.ts +17 -0
- package/dist/runtime/themes/preset-tokens.d.ts.map +1 -0
- package/dist/runtime/themes/preset-tokens.js +47 -0
- package/dist/runtime/themes/preset-tokens.js.map +1 -0
- package/dist/runtime/utils.d.ts +6 -0
- package/dist/runtime/utils.d.ts.map +1 -0
- package/dist/runtime/utils.js +29 -0
- package/dist/runtime/utils.js.map +1 -0
- package/dist/runtime/views/studio-layout.d.ts +25 -0
- package/dist/runtime/views/studio-layout.d.ts.map +1 -0
- package/dist/runtime/views/studio-layout.js +108 -0
- package/dist/runtime/views/studio-layout.js.map +1 -0
- package/dist/schema-utils.d.ts +16 -0
- package/dist/schema-utils.d.ts.map +1 -0
- package/dist/schema-utils.js +62 -0
- package/dist/schema-utils.js.map +1 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/editor-runtime.d.ts +6 -0
- package/editor-runtime.js +2 -0
- package/package.json +88 -0
- package/static/cms/admin-entry.css +207 -0
- package/static/cms/admin-entry.js +1083 -0
- package/static/cms/editor/config.js +118 -0
- package/static/cms/editor/content-map.css +141 -0
- package/static/cms/editor/content-map.js +172 -0
- package/static/cms/editor/guards.js +72 -0
- package/static/cms/editor/helpers.js +51 -0
- package/static/cms/editor/image-edit.js +151 -0
- package/static/cms/editor/image-utils.js +84 -0
- package/static/cms/editor/image.css +133 -0
- package/static/cms/editor/link-follow.js +144 -0
- package/static/cms/editor/link-ui.css +199 -0
- package/static/cms/editor/linkify.js +55 -0
- package/static/cms/editor/panel.css +91 -0
- package/static/cms/editor/panel.js +70 -0
- package/static/cms/editor/rich-toolbar.css +67 -0
- package/static/cms/editor/rich-toolbar.js +200 -0
- package/static/cms/editor/sanitize.js +89 -0
- package/static/cms/editor/save-queue.js +166 -0
- package/static/cms/editor/section-controls/activation.js +10 -0
- package/static/cms/editor/section-controls/api.js +87 -0
- package/static/cms/editor/section-controls/constants.js +24 -0
- package/static/cms/editor/section-controls/index.js +622 -0
- package/static/cms/editor/section-controls/model.js +76 -0
- package/static/cms/editor/section-controls/mutations.js +112 -0
- package/static/cms/editor/section-controls/page-context.js +34 -0
- package/static/cms/editor/section-controls/pickers.js +196 -0
- package/static/cms/editor/section-controls/reorder.js +92 -0
- package/static/cms/editor/section-controls/selection.js +54 -0
- package/static/cms/editor/section-controls/spacing-drag.js +83 -0
- package/static/cms/editor/section-controls/ui-elements.js +54 -0
- package/static/cms/editor/section-controls/utils.js +35 -0
- package/static/cms/editor/section-controls.css +349 -0
- package/static/cms/editor/section-controls.js +1 -0
- package/static/cms/editor/security.js +48 -0
- package/static/cms/editor/sync.js +72 -0
- package/static/cms/editor/text-edit.js +151 -0
- package/static/cms/editor/text-link-interactions.js +254 -0
- package/static/cms/editor/toast.css +50 -0
- package/static/cms/editor/toast.js +29 -0
- package/static/cms/editor/tokens-and-text.css +93 -0
- package/static/cms/editor/toolbar.css +249 -0
- package/static/cms/editor/toolbar.js +117 -0
- package/static/cms/editor.css +12 -0
- package/static/cms/editor.js +116 -0
- package/static/cms/studio-login.css +300 -0
- package/static/cms/studio-tokens.css +85 -0
- package/static/cms/studio.css +437 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { shouldUseSecureCookies } from "./cookie-utils.js";
|
|
2
|
+
const COOKIE_NAME = "caret_demo_session";
|
|
3
|
+
const TTL_SECONDS = 60 * 60 * 2; // 2h sliding window
|
|
4
|
+
const ID_PATTERN = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
|
|
5
|
+
function serialize(value, maxAge, request) {
|
|
6
|
+
const parts = [
|
|
7
|
+
`${COOKIE_NAME}=${value}`,
|
|
8
|
+
"Path=/",
|
|
9
|
+
"HttpOnly",
|
|
10
|
+
"SameSite=Lax",
|
|
11
|
+
`Max-Age=${maxAge}`,
|
|
12
|
+
];
|
|
13
|
+
if (shouldUseSecureCookies(request))
|
|
14
|
+
parts.push("Secure");
|
|
15
|
+
return parts.join("; ");
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Detect demo mode from any available env source. Workers populate env via
|
|
19
|
+
* `context.locals.runtime.env` (Astro Cloudflare adapter); Node hosts use
|
|
20
|
+
* `process.env`. Pass `runtimeEnv` when available — falls back to process.env.
|
|
21
|
+
*/
|
|
22
|
+
export function isDemoModeEnabled(runtimeEnv) {
|
|
23
|
+
if (runtimeEnv && runtimeEnv.CARET_DEMO_MODE === "true")
|
|
24
|
+
return true;
|
|
25
|
+
if (typeof process !== "undefined" && process.env?.CARET_DEMO_MODE === "true") {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Read the demo session cookie or mint a new one. Always refreshes the TTL,
|
|
32
|
+
* giving us a sliding 2h window per active visitor.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveDemoSession(context, request) {
|
|
35
|
+
const existing = context.cookies?.get(COOKIE_NAME)?.value;
|
|
36
|
+
const sessionId = existing && ID_PATTERN.test(existing) ? existing : crypto.randomUUID();
|
|
37
|
+
return {
|
|
38
|
+
sessionId,
|
|
39
|
+
setCookieHeader: serialize(sessionId, TTL_SECONDS, request),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=demo-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo-session.js","sourceRoot":"","sources":["../../../src/runtime/auth/demo-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,MAAM,WAAW,GAAG,oBAAoB,CAAC;AACzC,MAAM,WAAW,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,oBAAoB;AACrD,MAAM,UAAU,GAAG,iEAAiE,CAAC;AAMrF,SAAS,SAAS,CAAC,KAAa,EAAE,MAAc,EAAE,OAAiB;IACjE,MAAM,KAAK,GAAG;QACZ,GAAG,WAAW,IAAI,KAAK,EAAE;QACzB,QAAQ;QACR,UAAU;QACV,cAAc;QACd,WAAW,MAAM,EAAE;KACpB,CAAC;IACF,IAAI,sBAAsB,CAAC,OAAO,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,UAA2C;IAE3C,IAAI,UAAU,IAAI,UAAU,CAAC,eAAe,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACrE,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,EAAE,CAAC;QAC9E,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAOD;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAkB,EAClB,OAAgB;IAEhB,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC;IAC1D,MAAM,SAAS,GAAG,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;IACzF,OAAO;QACL,SAAS;QACT,eAAe,EAAE,SAAS,CAAC,SAAS,EAAE,WAAW,EAAE,OAAO,CAAC;KAC5D,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sliding-window failure counter for login attempts. Holds state in-process,
|
|
3
|
+
* so single-Node deployments get a meaningful brute-force defense out of the
|
|
4
|
+
* box. Multi-instance or Workers deployments should layer Durable Objects or
|
|
5
|
+
* a WAF rate-limit rule on top — there's no shared state here.
|
|
6
|
+
*/
|
|
7
|
+
export type RateLimitResult = {
|
|
8
|
+
allowed: true;
|
|
9
|
+
} | {
|
|
10
|
+
allowed: false;
|
|
11
|
+
retryAfterSeconds: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function checkLoginRateLimit(key: string, options?: {
|
|
14
|
+
maxFailures?: number;
|
|
15
|
+
windowMs?: number;
|
|
16
|
+
now?: number;
|
|
17
|
+
}): RateLimitResult;
|
|
18
|
+
export declare function recordLoginFailure(key: string, options?: {
|
|
19
|
+
windowMs?: number;
|
|
20
|
+
now?: number;
|
|
21
|
+
}): void;
|
|
22
|
+
export declare function clearLoginAttempts(key: string): void;
|
|
23
|
+
/** Test-only. Resets all counters. */
|
|
24
|
+
export declare function __resetLoginRateLimiter(): void;
|
|
25
|
+
export declare function extractRateLimitKey(request: Request): string;
|
|
26
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../../src/runtime/auth/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkBH,MAAM,MAAM,eAAe,GACvB;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GACjB;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GACtE,eAAe,CAcjB;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAChD,IAAI,CAWN;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED,sCAAsC;AACtC,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAe5D"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sliding-window failure counter for login attempts. Holds state in-process,
|
|
3
|
+
* so single-Node deployments get a meaningful brute-force defense out of the
|
|
4
|
+
* box. Multi-instance or Workers deployments should layer Durable Objects or
|
|
5
|
+
* a WAF rate-limit rule on top — there's no shared state here.
|
|
6
|
+
*/
|
|
7
|
+
const DEFAULT_MAX_FAILURES = 5;
|
|
8
|
+
const DEFAULT_WINDOW_MS = 15 * 60 * 1000;
|
|
9
|
+
const windows = new Map();
|
|
10
|
+
function prune(window, now, windowMs) {
|
|
11
|
+
const cutoff = now - windowMs;
|
|
12
|
+
while (window.attempts.length > 0 && window.attempts[0] <= cutoff) {
|
|
13
|
+
window.attempts.shift();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function checkLoginRateLimit(key, options = {}) {
|
|
17
|
+
const maxFailures = options.maxFailures ?? DEFAULT_MAX_FAILURES;
|
|
18
|
+
const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
|
|
19
|
+
const now = options.now ?? Date.now();
|
|
20
|
+
const window = windows.get(key);
|
|
21
|
+
if (!window)
|
|
22
|
+
return { allowed: true };
|
|
23
|
+
prune(window, now, windowMs);
|
|
24
|
+
if (window.attempts.length < maxFailures)
|
|
25
|
+
return { allowed: true };
|
|
26
|
+
const earliest = window.attempts[0];
|
|
27
|
+
const retryAfterMs = Math.max(0, earliest + windowMs - now);
|
|
28
|
+
return { allowed: false, retryAfterSeconds: Math.ceil(retryAfterMs / 1000) };
|
|
29
|
+
}
|
|
30
|
+
export function recordLoginFailure(key, options = {}) {
|
|
31
|
+
const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
|
|
32
|
+
const now = options.now ?? Date.now();
|
|
33
|
+
let window = windows.get(key);
|
|
34
|
+
if (!window) {
|
|
35
|
+
window = { attempts: [] };
|
|
36
|
+
windows.set(key, window);
|
|
37
|
+
}
|
|
38
|
+
prune(window, now, windowMs);
|
|
39
|
+
window.attempts.push(now);
|
|
40
|
+
}
|
|
41
|
+
export function clearLoginAttempts(key) {
|
|
42
|
+
windows.delete(key);
|
|
43
|
+
}
|
|
44
|
+
/** Test-only. Resets all counters. */
|
|
45
|
+
export function __resetLoginRateLimiter() {
|
|
46
|
+
windows.clear();
|
|
47
|
+
}
|
|
48
|
+
export function extractRateLimitKey(request) {
|
|
49
|
+
// Best-effort client identifier. Astro doesn't expose remote IP directly
|
|
50
|
+
// in all adapters, so we fall back to a forwarded-for header. The key
|
|
51
|
+
// doesn't need to be cryptographically distinct — it just has to make
|
|
52
|
+
// brute-force costly for a typical attacker.
|
|
53
|
+
const headers = request.headers;
|
|
54
|
+
const candidates = [
|
|
55
|
+
headers.get("cf-connecting-ip"),
|
|
56
|
+
headers.get("x-real-ip"),
|
|
57
|
+
headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? null,
|
|
58
|
+
];
|
|
59
|
+
for (const candidate of candidates) {
|
|
60
|
+
if (candidate && candidate.length > 0)
|
|
61
|
+
return candidate;
|
|
62
|
+
}
|
|
63
|
+
return "unknown";
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../../src/runtime/auth/rate-limiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function isEditorPasswordValid(candidate: string): boolean;
|
|
2
|
+
export declare function issueEditorSessionCookie(path?: string, request?: Request): string;
|
|
3
|
+
export declare function clearEditorSessionCookie(path?: string, request?: Request): string;
|
|
4
|
+
export declare function hasConfiguredEditorPassword(): boolean;
|
|
5
|
+
type CookieBagLike = {
|
|
6
|
+
cookies?: {
|
|
7
|
+
get: (name: string) => {
|
|
8
|
+
value: string;
|
|
9
|
+
} | undefined;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export declare function isEditorAuthenticated(context: CookieBagLike): boolean;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/runtime/auth/session.ts"],"names":[],"mappings":"AAiHA,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAWhE;AAED,wBAAgB,wBAAwB,CAAC,IAAI,SAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAS9E;AAED,wBAAgB,wBAAwB,CAAC,IAAI,SAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAE9E;AAED,wBAAgB,2BAA2B,IAAI,OAAO,CAErD;AAED,KAAK,aAAa,GAAG;IACnB,OAAO,CAAC,EAAE;QACR,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,GAAG,SAAS,CAAC;KACtD,CAAC;CACH,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAUrE"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
2
|
+
import { shouldUseSecureCookies } from "./cookie-utils.js";
|
|
3
|
+
import { getRequestContext } from "../request-context.js";
|
|
4
|
+
const SESSION_COOKIE_NAME = "caret_session";
|
|
5
|
+
const SESSION_TTL_SECONDS = 60 * 60 * 12;
|
|
6
|
+
const DEV_SECRET_FALLBACK = "caretcms-dev-secret";
|
|
7
|
+
let devSecretWarned = false;
|
|
8
|
+
function getEditorPassword() {
|
|
9
|
+
const value = process.env.CARET_EDIT_PASSWORD ?? process.env.EDIT_PASSWORD ?? "";
|
|
10
|
+
return value.trim().length > 0 ? value : null;
|
|
11
|
+
}
|
|
12
|
+
function getSessionSecret() {
|
|
13
|
+
const configured = process.env.CARET_SESSION_SECRET;
|
|
14
|
+
if (configured && configured.trim().length > 0)
|
|
15
|
+
return configured;
|
|
16
|
+
// No secret configured. Safe to fall back only when the CMS is effectively
|
|
17
|
+
// disabled (no editor password). Once a password is set, the dev fallback
|
|
18
|
+
// is a known string that lets anyone forge a session — refuse it in
|
|
19
|
+
// production and warn loudly in dev.
|
|
20
|
+
if (getEditorPassword() === null)
|
|
21
|
+
return DEV_SECRET_FALLBACK;
|
|
22
|
+
if (process.env.NODE_ENV === "production") {
|
|
23
|
+
throw new Error("[caretcms] CARET_SESSION_SECRET is required in production when CARET_EDIT_PASSWORD is set. " +
|
|
24
|
+
"Generate one with `openssl rand -base64 32` and set it in your environment.");
|
|
25
|
+
}
|
|
26
|
+
if (!devSecretWarned) {
|
|
27
|
+
devSecretWarned = true;
|
|
28
|
+
console.warn("[caretcms] CARET_SESSION_SECRET is unset; using a public dev fallback. " +
|
|
29
|
+
"Set CARET_SESSION_SECRET before exposing this site to the network.");
|
|
30
|
+
}
|
|
31
|
+
return DEV_SECRET_FALLBACK;
|
|
32
|
+
}
|
|
33
|
+
function signPayload(payloadB64) {
|
|
34
|
+
return createHmac("sha256", getSessionSecret()).update(payloadB64).digest("base64url");
|
|
35
|
+
}
|
|
36
|
+
function serializeEditorSessionCookie(value, path, maxAge, request) {
|
|
37
|
+
const parts = [
|
|
38
|
+
`${SESSION_COOKIE_NAME}=${value}`,
|
|
39
|
+
`Path=${path}`,
|
|
40
|
+
"HttpOnly",
|
|
41
|
+
"SameSite=Lax",
|
|
42
|
+
`Max-Age=${maxAge}`,
|
|
43
|
+
];
|
|
44
|
+
if (shouldUseSecureCookies(request)) {
|
|
45
|
+
parts.push("Secure");
|
|
46
|
+
}
|
|
47
|
+
return parts.join("; ");
|
|
48
|
+
}
|
|
49
|
+
function buildToken(payload) {
|
|
50
|
+
const payloadB64 = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
51
|
+
const sig = signPayload(payloadB64);
|
|
52
|
+
return `${payloadB64}.${sig}`;
|
|
53
|
+
}
|
|
54
|
+
function parseToken(token) {
|
|
55
|
+
const parts = token.split(".");
|
|
56
|
+
if (parts.length !== 2)
|
|
57
|
+
return null;
|
|
58
|
+
const [payloadB64, signature] = parts;
|
|
59
|
+
if (!payloadB64 || !signature)
|
|
60
|
+
return null;
|
|
61
|
+
const expected = signPayload(payloadB64);
|
|
62
|
+
let sigBuf;
|
|
63
|
+
let expectedBuf;
|
|
64
|
+
try {
|
|
65
|
+
sigBuf = Buffer.from(signature, "base64url");
|
|
66
|
+
expectedBuf = Buffer.from(expected, "base64url");
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (sigBuf.length === 0 || sigBuf.length !== expectedBuf.length)
|
|
72
|
+
return null;
|
|
73
|
+
if (!timingSafeEqual(sigBuf, expectedBuf))
|
|
74
|
+
return null;
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf8"));
|
|
77
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
78
|
+
return null;
|
|
79
|
+
const payload = parsed;
|
|
80
|
+
if (payload.editor !== true)
|
|
81
|
+
return null;
|
|
82
|
+
if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp))
|
|
83
|
+
return null;
|
|
84
|
+
if (payload.exp <= Date.now())
|
|
85
|
+
return null;
|
|
86
|
+
return payload;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export function isEditorPasswordValid(candidate) {
|
|
93
|
+
const expected = getEditorPassword();
|
|
94
|
+
if (!expected)
|
|
95
|
+
return false;
|
|
96
|
+
if (!candidate)
|
|
97
|
+
return false;
|
|
98
|
+
// Hash both sides to fixed-length digests so the comparison cannot leak
|
|
99
|
+
// the configured password's length through a length-mismatch shortcut.
|
|
100
|
+
const secret = getSessionSecret();
|
|
101
|
+
const candidateDigest = createHmac("sha256", secret).update(candidate).digest();
|
|
102
|
+
const expectedDigest = createHmac("sha256", secret).update(expected).digest();
|
|
103
|
+
return timingSafeEqual(candidateDigest, expectedDigest);
|
|
104
|
+
}
|
|
105
|
+
export function issueEditorSessionCookie(path = "/", request) {
|
|
106
|
+
const exp = Date.now() + SESSION_TTL_SECONDS * 1000;
|
|
107
|
+
const token = buildToken({ editor: true, exp });
|
|
108
|
+
return serializeEditorSessionCookie(encodeURIComponent(token), path, SESSION_TTL_SECONDS, request);
|
|
109
|
+
}
|
|
110
|
+
export function clearEditorSessionCookie(path = "/", request) {
|
|
111
|
+
return serializeEditorSessionCookie("", path, 0, request);
|
|
112
|
+
}
|
|
113
|
+
export function hasConfiguredEditorPassword() {
|
|
114
|
+
return getEditorPassword() !== null;
|
|
115
|
+
}
|
|
116
|
+
export function isEditorAuthenticated(context) {
|
|
117
|
+
// Demo mode: any visitor with a resolved sandbox session is an editor of
|
|
118
|
+
// their own private overlay. Middleware sets `demoMode` + `sessionId` in
|
|
119
|
+
// the request-scoped ALS context before this check runs.
|
|
120
|
+
const ctx = getRequestContext();
|
|
121
|
+
if (ctx?.demoMode && ctx.sessionId)
|
|
122
|
+
return true;
|
|
123
|
+
const token = context.cookies?.get(SESSION_COOKIE_NAME)?.value;
|
|
124
|
+
if (!token)
|
|
125
|
+
return false;
|
|
126
|
+
return parseToken(token) !== null;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/runtime/auth/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit inline binding per ADR-004.
|
|
3
|
+
*
|
|
4
|
+
* Usage in .astro:
|
|
5
|
+
* const e = bindEntry({ collection: 'pages', id: 'home' })
|
|
6
|
+
* <h1 {...e('hero.title')}>{hero.title}</h1>
|
|
7
|
+
*/
|
|
8
|
+
export declare function bindEntry(entry: {
|
|
9
|
+
collection: string;
|
|
10
|
+
id: string;
|
|
11
|
+
}): (field: string, options?: {
|
|
12
|
+
rich?: boolean;
|
|
13
|
+
}) => Record<string, string>;
|
|
14
|
+
//# sourceMappingURL=bind.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind.d.ts","sourceRoot":"","sources":["../../src/runtime/bind.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,IACzD,OAAO,MAAM,EAAE,UAAU;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,4BASpD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit inline binding per ADR-004.
|
|
3
|
+
*
|
|
4
|
+
* Usage in .astro:
|
|
5
|
+
* const e = bindEntry({ collection: 'pages', id: 'home' })
|
|
6
|
+
* <h1 {...e('hero.title')}>{hero.title}</h1>
|
|
7
|
+
*/
|
|
8
|
+
export function bindEntry(entry) {
|
|
9
|
+
return (field, options) => {
|
|
10
|
+
const attrs = {
|
|
11
|
+
"data-caret": `${entry.collection}::${entry.id}::${field}`,
|
|
12
|
+
};
|
|
13
|
+
if (options?.rich) {
|
|
14
|
+
attrs["data-caret-rich"] = "";
|
|
15
|
+
}
|
|
16
|
+
return attrs;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=bind.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind.js","sourceRoot":"","sources":["../../src/runtime/bind.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,KAAyC;IACjE,OAAO,CAAC,KAAa,EAAE,OAA4B,EAAE,EAAE;QACrD,MAAM,KAAK,GAA2B;YACpC,YAAY,EAAE,GAAG,KAAK,CAAC,UAAU,KAAK,KAAK,CAAC,EAAE,KAAK,KAAK,EAAE;SAC3D,CAAC;QACF,IAAI,OAAO,EAAE,IAAI,EAAE,CAAC;YAClB,KAAK,CAAC,iBAAiB,CAAC,GAAG,EAAE,CAAC;QAChC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CaretMode } from "../types.js";
|
|
2
|
+
export type CaretBrandConfig = {
|
|
3
|
+
name: string;
|
|
4
|
+
logo: string | null;
|
|
5
|
+
faviconUrl: string | null;
|
|
6
|
+
};
|
|
7
|
+
export type CaretThemeConfig = {
|
|
8
|
+
presetId: string | null;
|
|
9
|
+
tokenOverrides: Record<string, string>;
|
|
10
|
+
};
|
|
11
|
+
export type CaretRuntimeConfig = {
|
|
12
|
+
mountPath: string;
|
|
13
|
+
apiBasePath: string;
|
|
14
|
+
mode: CaretMode;
|
|
15
|
+
theme: CaretThemeConfig;
|
|
16
|
+
brand: CaretBrandConfig;
|
|
17
|
+
};
|
|
18
|
+
export declare function normalizePath(input: string | undefined, fallback: string): string;
|
|
19
|
+
export declare function getRuntimeConfig(): CaretRuntimeConfig;
|
|
20
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/runtime/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAQ7C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,gBAAgB,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAEF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CASjF;AAsBD,wBAAgB,gBAAgB,IAAI,kBAAkB,CAcrD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function normalizePath(input, fallback) {
|
|
2
|
+
if (!input)
|
|
3
|
+
return fallback;
|
|
4
|
+
const trimmed = input.trim();
|
|
5
|
+
if (!trimmed)
|
|
6
|
+
return fallback;
|
|
7
|
+
const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
8
|
+
if (withLeadingSlash.length > 1 && withLeadingSlash.endsWith("/")) {
|
|
9
|
+
return withLeadingSlash.slice(0, -1);
|
|
10
|
+
}
|
|
11
|
+
return withLeadingSlash;
|
|
12
|
+
}
|
|
13
|
+
function safeParse(raw, fallback) {
|
|
14
|
+
if (!raw)
|
|
15
|
+
return fallback;
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const DEFAULT_BRAND = {
|
|
24
|
+
name: "Content Studio",
|
|
25
|
+
logo: null,
|
|
26
|
+
faviconUrl: null,
|
|
27
|
+
};
|
|
28
|
+
const DEFAULT_THEME = {
|
|
29
|
+
presetId: null,
|
|
30
|
+
tokenOverrides: {},
|
|
31
|
+
};
|
|
32
|
+
export function getRuntimeConfig() {
|
|
33
|
+
const mountPath = normalizePath(__ASTRO_CARET_MOUNT_PATH__, "/admin");
|
|
34
|
+
const apiBasePath = normalizePath(__ASTRO_CARET_API_BASE_PATH__, "/api/cms");
|
|
35
|
+
const mode = (__ASTRO_CARET_MODE__ ?? "embedded");
|
|
36
|
+
const theme = safeParse(__ASTRO_CARET_THEME_CONFIG__, DEFAULT_THEME);
|
|
37
|
+
const brand = safeParse(__ASTRO_CARET_BRAND_CONFIG__, DEFAULT_BRAND);
|
|
38
|
+
return { mountPath, apiBasePath, mode, theme, brand };
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/runtime/config.ts"],"names":[],"mappings":"AA2BA,MAAM,UAAU,aAAa,CAAC,KAAyB,EAAE,QAAgB;IACvE,IAAI,CAAC,KAAK;QAAE,OAAO,QAAQ,CAAC;IAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;IAC3E,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,IAAI,gBAAgB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAClE,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,SAAS,SAAS,CAAI,GAAuB,EAAE,QAAW;IACxD,IAAI,CAAC,GAAG;QAAE,OAAO,QAAQ,CAAC;IAC1B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED,MAAM,aAAa,GAAqB;IACtC,IAAI,EAAE,gBAAgB;IACtB,IAAI,EAAE,IAAI;IACV,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF,MAAM,aAAa,GAAqB;IACtC,QAAQ,EAAE,IAAI;IACd,cAAc,EAAE,EAAE;CACnB,CAAC;AAEF,MAAM,UAAU,gBAAgB;IAC9B,MAAM,SAAS,GAAG,aAAa,CAAC,0BAA0B,EAAE,QAAQ,CAAC,CAAC;IACtE,MAAM,WAAW,GAAG,aAAa,CAAC,6BAA6B,EAAE,UAAU,CAAC,CAAC;IAC7E,MAAM,IAAI,GAAG,CAAC,oBAAoB,IAAI,UAAU,CAAc,CAAC;IAC/D,MAAM,KAAK,GAAG,SAAS,CACrB,4BAA4B,EAC5B,aAAa,CACd,CAAC;IACF,MAAM,KAAK,GAAG,SAAS,CACrB,4BAA4B,EAC5B,aAAa,CACd,CAAC;IAEF,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public content loading API for Astro pages.
|
|
3
|
+
*
|
|
4
|
+
* Use these in your .astro frontmatter to load CMS content:
|
|
5
|
+
*
|
|
6
|
+
* import { loadEntry, loadCollection } from '@caretcms/core/runtime'
|
|
7
|
+
*
|
|
8
|
+
* const home = await loadEntry('pages', 'home')
|
|
9
|
+
* const posts = await loadCollection('posts')
|
|
10
|
+
*/
|
|
11
|
+
import type { EntryData } from "../types.js";
|
|
12
|
+
export type { EntryData };
|
|
13
|
+
/**
|
|
14
|
+
* Load a single entry by collection and id.
|
|
15
|
+
* Returns the entry data, or null if not found.
|
|
16
|
+
*/
|
|
17
|
+
export declare function loadEntry(collection: string, id: string): Promise<Record<string, unknown> | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Load all entries in a collection.
|
|
20
|
+
* Returns an array of { id, data } objects.
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadCollection(collection: string): Promise<EntryData[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Discover all collection names.
|
|
25
|
+
* Returns sorted collection names.
|
|
26
|
+
*/
|
|
27
|
+
export declare function listCollections(): Promise<string[]>;
|
|
28
|
+
//# sourceMappingURL=content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/runtime/content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,YAAY,EAAE,SAAS,EAAE,CAAC;AAE1B;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAIzC;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAG7E;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAGzD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public content loading API for Astro pages.
|
|
3
|
+
*
|
|
4
|
+
* Use these in your .astro frontmatter to load CMS content:
|
|
5
|
+
*
|
|
6
|
+
* import { loadEntry, loadCollection } from '@caretcms/core/runtime'
|
|
7
|
+
*
|
|
8
|
+
* const home = await loadEntry('pages', 'home')
|
|
9
|
+
* const posts = await loadCollection('posts')
|
|
10
|
+
*/
|
|
11
|
+
import { requireRequestContext } from "./request-context.js";
|
|
12
|
+
/**
|
|
13
|
+
* Load a single entry by collection and id.
|
|
14
|
+
* Returns the entry data, or null if not found.
|
|
15
|
+
*/
|
|
16
|
+
export async function loadEntry(collection, id) {
|
|
17
|
+
const { adapter } = requireRequestContext();
|
|
18
|
+
const entry = await adapter.getEntry(collection, id);
|
|
19
|
+
return entry?.data ?? null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Load all entries in a collection.
|
|
23
|
+
* Returns an array of { id, data } objects.
|
|
24
|
+
*/
|
|
25
|
+
export async function loadCollection(collection) {
|
|
26
|
+
const { adapter } = requireRequestContext();
|
|
27
|
+
return adapter.listEntries(collection);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Discover all collection names.
|
|
31
|
+
* Returns sorted collection names.
|
|
32
|
+
*/
|
|
33
|
+
export async function listCollections() {
|
|
34
|
+
const { adapter } = requireRequestContext();
|
|
35
|
+
return adapter.discoverCollections();
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/runtime/content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAI7D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,UAAkB,EAClB,EAAU;IAEV,MAAM,EAAE,OAAO,EAAE,GAAG,qBAAqB,EAAE,CAAC;IAC5C,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACrD,OAAO,KAAK,EAAE,IAAI,IAAI,IAAI,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,UAAkB;IACrD,MAAM,EAAE,OAAO,EAAE,GAAG,qBAAqB,EAAE,CAAC;IAC5C,OAAO,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,EAAE,OAAO,EAAE,GAAG,qBAAqB,EAAE,CAAC;IAC5C,OAAO,OAAO,CAAC,mBAAmB,EAAE,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime exports for use in Astro pages.
|
|
3
|
+
*
|
|
4
|
+
* import { bindEntry, loadEntry, loadCollection } from '@caretcms/core/runtime'
|
|
5
|
+
*/
|
|
6
|
+
export { bindEntry } from "./bind.js";
|
|
7
|
+
export { loadEntry, loadCollection, listCollections } from "./content.js";
|
|
8
|
+
export type { EntryData } from "./content.js";
|
|
9
|
+
export { SessionOverlayAdapter } from "./storage/session-overlay-adapter.js";
|
|
10
|
+
export { QuotaUploadHandler } from "./storage/quota-upload-handler.js";
|
|
11
|
+
export type { QuotaCounter, QuotaUploadHandlerOptions, } from "./storage/quota-upload-handler.js";
|
|
12
|
+
export { IMAGE_KIND_TO_EXT, IMAGE_MAX_SIZE, IMAGE_MIME_TO_KIND, detectImageKind, readAndValidateImage, sanitizeImageBaseName, } from "./storage/image-validation.js";
|
|
13
|
+
export type { AllowedImageKind } from "./storage/image-validation.js";
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC1E,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,YAAY,EACV,YAAY,EACZ,yBAAyB,GAC1B,MAAM,mCAAmC,CAAC;AAI3C,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime exports for use in Astro pages.
|
|
3
|
+
*
|
|
4
|
+
* import { bindEntry, loadEntry, loadCollection } from '@caretcms/core/runtime'
|
|
5
|
+
*/
|
|
6
|
+
// Binding — ADR-004 explicit inline binding (power-user API)
|
|
7
|
+
export { bindEntry } from "./bind.js";
|
|
8
|
+
// Content loading — read stored data in .astro frontmatter (power-user API)
|
|
9
|
+
export { loadEntry, loadCollection, listCollections } from "./content.js";
|
|
10
|
+
// Sandbox / multi-tenant primitives — used by Cloudflare adapter for demo mode
|
|
11
|
+
export { SessionOverlayAdapter } from "./storage/session-overlay-adapter.js";
|
|
12
|
+
export { QuotaUploadHandler } from "./storage/quota-upload-handler.js";
|
|
13
|
+
// Shared image-upload validation — adapter authors should call
|
|
14
|
+
// readAndValidateImage() before writing user-supplied bytes to storage.
|
|
15
|
+
export { IMAGE_KIND_TO_EXT, IMAGE_MAX_SIZE, IMAGE_MIME_TO_KIND, detectImageKind, readAndValidateImage, sanitizeImageBaseName, } from "./storage/image-validation.js";
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,6DAA6D;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,4EAA4E;AAC5E,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG1E,+EAA+E;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAMvE,+DAA+D;AAC/D,wEAAwE;AACxE,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import "virtual:caretcms/schemas";
|
|
2
|
+
type MiddlewareContext = {
|
|
3
|
+
locals: Record<string, unknown>;
|
|
4
|
+
cookies?: {
|
|
5
|
+
get: (name: string) => {
|
|
6
|
+
value: string;
|
|
7
|
+
} | undefined;
|
|
8
|
+
};
|
|
9
|
+
request?: Request;
|
|
10
|
+
};
|
|
11
|
+
type MiddlewareNext = () => Promise<Response>;
|
|
12
|
+
export declare function onRequest(context: MiddlewareContext, next: MiddlewareNext): Promise<Response>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/runtime/middleware.ts"],"names":[],"mappings":"AASA,OAAO,0BAA0B,CAAC;AAElC,KAAK,iBAAiB,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE;QACR,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,GAAG,SAAS,CAAC;KACtD,CAAC;IACF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAkBF,KAAK,cAAc,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE9C,wBAAsB,SAAS,CAC7B,OAAO,EAAE,iBAAiB,EAC1B,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,QAAQ,CAAC,CAkEnB"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { isEditorAuthenticated } from "./auth/session.js";
|
|
2
|
+
import { isDemoModeEnabled, resolveDemoSession } from "./auth/demo-session.js";
|
|
3
|
+
import { getRuntimeServices } from "./providers.js";
|
|
4
|
+
import { runWithRequestContext } from "./request-context.js";
|
|
5
|
+
import { rewriteCaretAttributes } from "./rewrite.js";
|
|
6
|
+
import { SessionOverlayAdapter } from "./storage/session-overlay-adapter.js";
|
|
7
|
+
// Side-effect import: registers any user-provided schemas into the schema registry
|
|
8
|
+
import "virtual:caretcms/schemas";
|
|
9
|
+
let _cachedRuntimeEnv;
|
|
10
|
+
async function resolveRuntimeEnv() {
|
|
11
|
+
if (_cachedRuntimeEnv !== undefined)
|
|
12
|
+
return _cachedRuntimeEnv;
|
|
13
|
+
try {
|
|
14
|
+
const moduleId = "cloudflare:workers";
|
|
15
|
+
const mod = (await import(/* @vite-ignore */ moduleId));
|
|
16
|
+
_cachedRuntimeEnv = mod.env ?? null;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
_cachedRuntimeEnv = null;
|
|
20
|
+
}
|
|
21
|
+
return _cachedRuntimeEnv;
|
|
22
|
+
}
|
|
23
|
+
export async function onRequest(context, next) {
|
|
24
|
+
const services = await getRuntimeServices();
|
|
25
|
+
const runtimeEnv = await resolveRuntimeEnv();
|
|
26
|
+
let adapter = services.adapter;
|
|
27
|
+
let uploadHandler = services.uploadHandler;
|
|
28
|
+
let sessionId = null;
|
|
29
|
+
let setCookieHeader = null;
|
|
30
|
+
const demoMode = isDemoModeEnabled(runtimeEnv);
|
|
31
|
+
if (demoMode) {
|
|
32
|
+
const request = context.request ?? new Request("http://localhost/");
|
|
33
|
+
const resolution = resolveDemoSession(context, request);
|
|
34
|
+
sessionId = resolution.sessionId;
|
|
35
|
+
setCookieHeader = resolution.setCookieHeader;
|
|
36
|
+
if (services.adapter.makeSessionOverlay) {
|
|
37
|
+
const overlay = await services.adapter.makeSessionOverlay(sessionId);
|
|
38
|
+
adapter = new SessionOverlayAdapter(services.adapter, overlay);
|
|
39
|
+
}
|
|
40
|
+
if (services.uploadHandler.makeSessionWrapper) {
|
|
41
|
+
uploadHandler = await services.uploadHandler.makeSessionWrapper(sessionId);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return runWithRequestContext({ adapter, uploadHandler, sessionId, demoMode }, async () => {
|
|
45
|
+
context.locals.isEditor = isEditorAuthenticated(context);
|
|
46
|
+
const inner = await next();
|
|
47
|
+
const contentType = inner.headers.get("content-type") ?? "";
|
|
48
|
+
const contentEncoding = inner.headers.get("content-encoding") ?? "";
|
|
49
|
+
// Skip compressed bodies — calling .text() on a gzip/brotli stream
|
|
50
|
+
// returns binary garbage, which then either fails the data-caret probe
|
|
51
|
+
// (silent corruption) or injects rewrites into a non-text payload.
|
|
52
|
+
// Identity / empty are the only encodings safe to decode as text.
|
|
53
|
+
const isIdentity = contentEncoding === "" || contentEncoding.toLowerCase() === "identity";
|
|
54
|
+
const isHtml = contentType.includes("text/html") && isIdentity;
|
|
55
|
+
let body = inner.body;
|
|
56
|
+
let bodyConsumed = false;
|
|
57
|
+
let rewritten = false;
|
|
58
|
+
if (isHtml) {
|
|
59
|
+
const html = await inner.text();
|
|
60
|
+
bodyConsumed = true;
|
|
61
|
+
if (html.includes("data-caret")) {
|
|
62
|
+
body = await rewriteCaretAttributes(html, adapter);
|
|
63
|
+
rewritten = true;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
body = html;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!bodyConsumed && !setCookieHeader) {
|
|
70
|
+
return inner;
|
|
71
|
+
}
|
|
72
|
+
const headers = new Headers(inner.headers);
|
|
73
|
+
if (rewritten)
|
|
74
|
+
headers.delete("content-length");
|
|
75
|
+
if (setCookieHeader)
|
|
76
|
+
headers.append("Set-Cookie", setCookieHeader);
|
|
77
|
+
return new Response(body, { status: inner.status, headers });
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=middleware.js.map
|