@caretcms/core 0.1.0 → 0.1.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/README.md +117 -6
- package/dist/browser-runtime.d.ts.map +1 -1
- package/dist/browser-runtime.js +34 -20
- package/dist/browser-runtime.js.map +1 -1
- package/dist/contracts.d.ts +14 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +14 -0
- package/dist/contracts.js.map +1 -0
- package/dist/editable.d.ts +28 -0
- package/dist/editable.d.ts.map +1 -0
- package/dist/editable.js +122 -0
- package/dist/editable.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +134 -2
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +45 -3
- package/dist/loader.js.map +1 -1
- package/dist/providers/storage/markdown.d.ts +7 -0
- package/dist/providers/storage/markdown.d.ts.map +1 -0
- package/dist/providers/storage/markdown.js +6 -0
- package/dist/providers/storage/markdown.js.map +1 -0
- package/dist/runtime/auth/cookie-utils.d.ts +1 -0
- package/dist/runtime/auth/cookie-utils.d.ts.map +1 -1
- package/dist/runtime/auth/cookie-utils.js +1 -1
- package/dist/runtime/auth/cookie-utils.js.map +1 -1
- package/dist/runtime/auth/rate-limiter.d.ts.map +1 -1
- package/dist/runtime/auth/rate-limiter.js +19 -13
- package/dist/runtime/auth/rate-limiter.js.map +1 -1
- package/dist/runtime/auth/session.d.ts +8 -0
- package/dist/runtime/auth/session.d.ts.map +1 -1
- package/dist/runtime/auth/session.js +72 -11
- package/dist/runtime/auth/session.js.map +1 -1
- package/dist/runtime/config.d.ts +1 -0
- package/dist/runtime/config.d.ts.map +1 -1
- package/dist/runtime/config.js +2 -1
- package/dist/runtime/config.js.map +1 -1
- package/dist/runtime/git-journal.d.ts +19 -0
- package/dist/runtime/git-journal.d.ts.map +1 -0
- package/dist/runtime/git-journal.js +64 -0
- package/dist/runtime/git-journal.js.map +1 -0
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/middleware.d.ts.map +1 -1
- package/dist/runtime/middleware.js +125 -8
- package/dist/runtime/middleware.js.map +1 -1
- package/dist/runtime/mutations/contracts.d.ts.map +1 -1
- package/dist/runtime/mutations/contracts.js +7 -4
- package/dist/runtime/mutations/contracts.js.map +1 -1
- package/dist/runtime/mutations/engine.d.ts +7 -0
- package/dist/runtime/mutations/engine.d.ts.map +1 -1
- package/dist/runtime/mutations/engine.js +30 -17
- package/dist/runtime/mutations/engine.js.map +1 -1
- package/dist/runtime/providers.d.ts +7 -0
- package/dist/runtime/providers.d.ts.map +1 -1
- package/dist/runtime/providers.js +10 -0
- package/dist/runtime/providers.js.map +1 -1
- package/dist/runtime/publish.d.ts +38 -0
- package/dist/runtime/publish.d.ts.map +1 -0
- package/dist/runtime/publish.js +71 -0
- package/dist/runtime/publish.js.map +1 -0
- package/dist/runtime/request-context.d.ts +19 -0
- package/dist/runtime/request-context.d.ts.map +1 -1
- package/dist/runtime/request-context.js.map +1 -1
- package/dist/runtime/rewrite.d.ts +11 -1
- package/dist/runtime/rewrite.d.ts.map +1 -1
- package/dist/runtime/rewrite.js +21 -15
- package/dist/runtime/rewrite.js.map +1 -1
- package/dist/runtime/rich-allowlist.d.ts +28 -0
- package/dist/runtime/rich-allowlist.d.ts.map +1 -0
- package/dist/runtime/rich-allowlist.js +43 -0
- package/dist/runtime/rich-allowlist.js.map +1 -0
- package/dist/runtime/routes/_helpers.d.ts +26 -0
- package/dist/runtime/routes/_helpers.d.ts.map +1 -1
- package/dist/runtime/routes/_helpers.js +83 -0
- package/dist/runtime/routes/_helpers.js.map +1 -1
- package/dist/runtime/routes/admin.d.ts.map +1 -1
- package/dist/runtime/routes/admin.js +55 -6
- package/dist/runtime/routes/admin.js.map +1 -1
- package/dist/runtime/routes/auth-login.d.ts.map +1 -1
- package/dist/runtime/routes/auth-login.js +25 -8
- package/dist/runtime/routes/auth-login.js.map +1 -1
- package/dist/runtime/routes/auth-logout.d.ts.map +1 -1
- package/dist/runtime/routes/auth-logout.js +4 -0
- package/dist/runtime/routes/auth-logout.js.map +1 -1
- package/dist/runtime/routes/collections-metadata.d.ts.map +1 -1
- package/dist/runtime/routes/collections-metadata.js +4 -2
- package/dist/runtime/routes/collections-metadata.js.map +1 -1
- package/dist/runtime/routes/draft.d.ts +8 -0
- package/dist/runtime/routes/draft.d.ts.map +1 -0
- package/dist/runtime/routes/draft.js +38 -0
- package/dist/runtime/routes/draft.js.map +1 -0
- package/dist/runtime/routes/history.d.ts.map +1 -1
- package/dist/runtime/routes/history.js +21 -8
- package/dist/runtime/routes/history.js.map +1 -1
- package/dist/runtime/routes/mutate.d.ts.map +1 -1
- package/dist/runtime/routes/mutate.js +5 -9
- package/dist/runtime/routes/mutate.js.map +1 -1
- package/dist/runtime/routes/publish.d.ts +9 -0
- package/dist/runtime/routes/publish.d.ts.map +1 -0
- package/dist/runtime/routes/publish.js +66 -0
- package/dist/runtime/routes/publish.js.map +1 -0
- package/dist/runtime/routes/schema.d.ts.map +1 -1
- package/dist/runtime/routes/schema.js +4 -2
- package/dist/runtime/routes/schema.js.map +1 -1
- package/dist/runtime/routes/upload.d.ts.map +1 -1
- package/dist/runtime/routes/upload.js +16 -3
- package/dist/runtime/routes/upload.js.map +1 -1
- package/dist/runtime/sanitize-html.d.ts +8 -2
- package/dist/runtime/sanitize-html.d.ts.map +1 -1
- package/dist/runtime/sanitize-html.js +57 -38
- package/dist/runtime/sanitize-html.js.map +1 -1
- package/dist/runtime/stega.d.ts +38 -0
- package/dist/runtime/stega.d.ts.map +1 -0
- package/dist/runtime/stega.js +98 -0
- package/dist/runtime/stega.js.map +1 -0
- package/dist/runtime/storage/atomic-write.d.ts +22 -0
- package/dist/runtime/storage/atomic-write.d.ts.map +1 -0
- package/dist/runtime/storage/atomic-write.js +44 -0
- package/dist/runtime/storage/atomic-write.js.map +1 -0
- package/dist/runtime/storage/filesystem-adapter.d.ts +14 -11
- package/dist/runtime/storage/filesystem-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/filesystem-adapter.js +71 -201
- package/dist/runtime/storage/filesystem-adapter.js.map +1 -1
- package/dist/runtime/storage/frontmatter-codec.d.ts +47 -0
- package/dist/runtime/storage/frontmatter-codec.d.ts.map +1 -0
- package/dist/runtime/storage/frontmatter-codec.js +515 -0
- package/dist/runtime/storage/frontmatter-codec.js.map +1 -0
- package/dist/runtime/storage/fs-runtime.d.ts +8 -0
- package/dist/runtime/storage/fs-runtime.d.ts.map +1 -0
- package/dist/runtime/storage/fs-runtime.js +40 -0
- package/dist/runtime/storage/fs-runtime.js.map +1 -0
- package/dist/runtime/storage/id-contracts.d.ts +22 -0
- package/dist/runtime/storage/id-contracts.d.ts.map +1 -0
- package/dist/runtime/storage/id-contracts.js +27 -0
- package/dist/runtime/storage/id-contracts.js.map +1 -0
- package/dist/runtime/storage/image-validation.d.ts +10 -0
- package/dist/runtime/storage/image-validation.d.ts.map +1 -1
- package/dist/runtime/storage/image-validation.js +17 -4
- package/dist/runtime/storage/image-validation.js.map +1 -1
- package/dist/runtime/storage/in-memory-adapter.d.ts +4 -0
- package/dist/runtime/storage/in-memory-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/in-memory-adapter.js +11 -0
- package/dist/runtime/storage/in-memory-adapter.js.map +1 -1
- package/dist/runtime/storage/markdown-adapter.d.ts +63 -0
- package/dist/runtime/storage/markdown-adapter.d.ts.map +1 -0
- package/dist/runtime/storage/markdown-adapter.js +227 -0
- package/dist/runtime/storage/markdown-adapter.js.map +1 -0
- package/dist/runtime/storage/quota-upload-handler.d.ts +2 -0
- package/dist/runtime/storage/quota-upload-handler.d.ts.map +1 -1
- package/dist/runtime/storage/quota-upload-handler.js +34 -8
- package/dist/runtime/storage/quota-upload-handler.js.map +1 -1
- package/dist/runtime/storage/session-overlay-adapter.d.ts +3 -0
- package/dist/runtime/storage/session-overlay-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/session-overlay-adapter.js +3 -1
- package/dist/runtime/storage/session-overlay-adapter.js.map +1 -1
- package/dist/runtime/storage/sidecar-meta-store.d.ts +51 -0
- package/dist/runtime/storage/sidecar-meta-store.d.ts.map +1 -0
- package/dist/runtime/storage/sidecar-meta-store.js +209 -0
- package/dist/runtime/storage/sidecar-meta-store.js.map +1 -0
- package/dist/runtime/themes/preset-tokens.d.ts +0 -1
- package/dist/runtime/themes/preset-tokens.d.ts.map +1 -1
- package/dist/runtime/themes/preset-tokens.js +1 -1
- package/dist/runtime/themes/preset-tokens.js.map +1 -1
- package/dist/runtime/utils.d.ts +9 -1
- package/dist/runtime/utils.d.ts.map +1 -1
- package/dist/runtime/utils.js +21 -1
- package/dist/runtime/utils.js.map +1 -1
- package/dist/schema-utils.d.ts.map +1 -1
- package/dist/schema-utils.js +1 -0
- package/dist/schema-utils.js.map +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -1
- package/static/cms/dev-toolbar/app.js +263 -0
- package/static/cms/editor/highlight.js +46 -0
- package/static/cms/editor/sanitize.js +48 -5
- package/static/cms/editor/stega-hydrate.js +80 -0
- package/static/cms/editor/text-edit.js +22 -0
- package/static/cms/editor/tokens-and-text.css +1 -1
- package/static/cms/editor/toolbar.css +42 -0
- package/static/cms/editor/toolbar.js +97 -15
- package/static/cms/editor.js +15 -0
- package/static/cms/signin-hint.css +74 -0
- package/dist/runtime/schema/template.d.ts +0 -15
- package/dist/runtime/schema/template.d.ts.map +0 -1
- package/dist/runtime/schema/template.js +0 -38
- package/dist/runtime/schema/template.js.map +0 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared rich-text allowlist + class matcher.
|
|
3
|
+
*
|
|
4
|
+
* The single source of truth for what the rich-text sanitizers keep. Imported
|
|
5
|
+
* by the two TypeScript sanitizers — the server render path (sanitize-html.ts,
|
|
6
|
+
* regex-based, no DOM) and the cloud live-sync path (browser-runtime.ts, DOM).
|
|
7
|
+
*
|
|
8
|
+
* The editor-save sanitizer in static/cms/editor/sanitize.js is shipped as raw,
|
|
9
|
+
* unbundled JS, so it can't import this module — it MUST mirror these values
|
|
10
|
+
* and the classAllowed() logic by hand. Keep all three in lockstep.
|
|
11
|
+
*/
|
|
12
|
+
/** Inline formatting tags the rich-text sanitizer preserves. */
|
|
13
|
+
export declare const RICH_ALLOWED_TAGS: Set<string>;
|
|
14
|
+
/** Attributes preserved per tag (class is gated separately, by allowedClasses). */
|
|
15
|
+
export declare const RICH_ALLOWED_ATTRS: Record<string, Set<string>>;
|
|
16
|
+
export declare const SAFE_HREF_RE: RegExp;
|
|
17
|
+
/**
|
|
18
|
+
* Per-tag class allowlist. Keys are tag names, values are allowed class names.
|
|
19
|
+
* A pattern ending in `*` is a prefix match (`text-*` allows `text-primary`);
|
|
20
|
+
* a lone `*` allows any class on that tag. Anything not matched is dropped.
|
|
21
|
+
* Mirrors the `allowedClasses` option of the `sanitize-html` package.
|
|
22
|
+
*/
|
|
23
|
+
export type AllowedClasses = Record<string, readonly string[]>;
|
|
24
|
+
/** Does `cls` match any pattern in `patterns`? (exact, `prefix-*`, or lone `*`) */
|
|
25
|
+
export declare function classAllowed(cls: string, patterns: readonly string[]): boolean;
|
|
26
|
+
/** Filter a raw class attribute value to the tag's allowed patterns. */
|
|
27
|
+
export declare function filterClasses(value: string, patterns: readonly string[]): string;
|
|
28
|
+
//# sourceMappingURL=rich-allowlist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rich-allowlist.d.ts","sourceRoot":"","sources":["../../src/runtime/rich-allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,gEAAgE;AAChE,eAAO,MAAM,iBAAiB,aAE5B,CAAC;AAEH,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAE1D,CAAC;AAEF,eAAO,MAAM,YAAY,QAAkC,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;AAE/D,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAU9E;AAED,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAKhF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared rich-text allowlist + class matcher.
|
|
3
|
+
*
|
|
4
|
+
* The single source of truth for what the rich-text sanitizers keep. Imported
|
|
5
|
+
* by the two TypeScript sanitizers — the server render path (sanitize-html.ts,
|
|
6
|
+
* regex-based, no DOM) and the cloud live-sync path (browser-runtime.ts, DOM).
|
|
7
|
+
*
|
|
8
|
+
* The editor-save sanitizer in static/cms/editor/sanitize.js is shipped as raw,
|
|
9
|
+
* unbundled JS, so it can't import this module — it MUST mirror these values
|
|
10
|
+
* and the classAllowed() logic by hand. Keep all three in lockstep.
|
|
11
|
+
*/
|
|
12
|
+
/** Inline formatting tags the rich-text sanitizer preserves. */
|
|
13
|
+
export const RICH_ALLOWED_TAGS = new Set([
|
|
14
|
+
"b", "strong", "i", "em", "u", "s", "a", "br", "sub", "sup",
|
|
15
|
+
]);
|
|
16
|
+
/** Attributes preserved per tag (class is gated separately, by allowedClasses). */
|
|
17
|
+
export const RICH_ALLOWED_ATTRS = {
|
|
18
|
+
a: new Set(["href", "target", "rel"]),
|
|
19
|
+
};
|
|
20
|
+
export const SAFE_HREF_RE = /^(?:https?:|mailto:|tel:|\/)/i;
|
|
21
|
+
/** Does `cls` match any pattern in `patterns`? (exact, `prefix-*`, or lone `*`) */
|
|
22
|
+
export function classAllowed(cls, patterns) {
|
|
23
|
+
for (const p of patterns) {
|
|
24
|
+
if (p === "*")
|
|
25
|
+
return true;
|
|
26
|
+
if (p.endsWith("*")) {
|
|
27
|
+
if (cls.startsWith(p.slice(0, -1)))
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
else if (cls === p) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
/** Filter a raw class attribute value to the tag's allowed patterns. */
|
|
37
|
+
export function filterClasses(value, patterns) {
|
|
38
|
+
return value
|
|
39
|
+
.split(/\s+/)
|
|
40
|
+
.filter((c) => c !== "" && classAllowed(c, patterns))
|
|
41
|
+
.join(" ");
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=rich-allowlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rich-allowlist.js","sourceRoot":"","sources":["../../src/runtime/rich-allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,gEAAgE;AAChE,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IACvC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK;CAC5D,CAAC,CAAC;AAEH,mFAAmF;AACnF,MAAM,CAAC,MAAM,kBAAkB,GAAgC;IAC7D,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,+BAA+B,CAAC;AAU5D,mFAAmF;AACnF,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,QAA2B;IACnE,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAC3B,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAC;QAClD,CAAC;aAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,QAA2B;IACtE,OAAO,KAAK;SACT,KAAK,CAAC,KAAK,CAAC;SACZ,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,YAAY,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;SACpD,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import type { StorageAdapter, UploadHandler } from "../../types.js";
|
|
2
2
|
export declare function json(body: Record<string, unknown>, status?: number): Response;
|
|
3
|
+
/** Default cap for JSON command payloads (mutate/history/login). */
|
|
4
|
+
export declare const MAX_JSON_BODY_BYTES: number;
|
|
5
|
+
/** Default cap for multipart upload bodies before they're buffered. */
|
|
6
|
+
export declare const MAX_UPLOAD_BODY_BYTES: number;
|
|
7
|
+
/**
|
|
8
|
+
* Reject a request whose declared Content-Length exceeds `maxBytes`. Cheap
|
|
9
|
+
* first line of defense for bodies we must hand to a buffering parser (e.g.
|
|
10
|
+
* formData()): an honest client and most attacks announce their size. Returns
|
|
11
|
+
* a 413 response when over the cap, otherwise null. Absent/garbage
|
|
12
|
+
* Content-Length passes here — pair with a streaming cap where possible.
|
|
13
|
+
*/
|
|
14
|
+
export declare function enforceContentLength(request: Request, maxBytes: number): Response | null;
|
|
15
|
+
type JsonBodyResult = {
|
|
16
|
+
ok: true;
|
|
17
|
+
value: unknown;
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
response: Response;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Read and JSON-parse a request body with a hard byte cap, streaming the body
|
|
24
|
+
* and aborting once the running total exceeds `maxBytes` so an attacker can't
|
|
25
|
+
* exhaust memory by omitting Content-Length and sending a chunked flood.
|
|
26
|
+
*/
|
|
27
|
+
export declare function readJsonBody(request: Request, maxBytes?: number): Promise<JsonBodyResult>;
|
|
3
28
|
/**
|
|
4
29
|
* Lightweight CSRF defense for mutating endpoints.
|
|
5
30
|
*
|
|
@@ -14,4 +39,5 @@ export declare function enforceCsrfHeader(request: Request): Response | null;
|
|
|
14
39
|
export declare function getAdapter(): StorageAdapter;
|
|
15
40
|
export declare function getUploadHandler(): UploadHandler;
|
|
16
41
|
export declare function getSessionId(): string | null;
|
|
42
|
+
export {};
|
|
17
43
|
//# sourceMappingURL=_helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_helpers.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAMpE,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,SAAM,GAAG,QAAQ,CAK1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAWnE;AAED,wBAAgB,UAAU,IAAI,cAAc,CAE3C;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD;AAED,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C"}
|
|
1
|
+
{"version":3,"file":"_helpers.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAMpE,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,SAAM,GAAG,QAAQ,CAK1E;AAED,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,QAAc,CAAC;AAC/C,uEAAuE;AACvE,eAAO,MAAM,qBAAqB,QAAmB,CAAC;AAEtD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,GACf,QAAQ,GAAG,IAAI,CAMjB;AAED,KAAK,cAAc,GACf;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC5B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEtC;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,OAAO,EAChB,QAAQ,GAAE,MAA4B,GACrC,OAAO,CAAC,cAAc,CAAC,CAmBzB;AA2CD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAWnE;AAED,wBAAgB,UAAU,IAAI,cAAc,CAE3C;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD;AAED,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C"}
|
|
@@ -7,6 +7,89 @@ export function json(body, status = 200) {
|
|
|
7
7
|
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
8
8
|
});
|
|
9
9
|
}
|
|
10
|
+
/** Default cap for JSON command payloads (mutate/history/login). */
|
|
11
|
+
export const MAX_JSON_BODY_BYTES = 1024 * 1024; // 1 MB
|
|
12
|
+
/** Default cap for multipart upload bodies before they're buffered. */
|
|
13
|
+
export const MAX_UPLOAD_BODY_BYTES = 25 * 1024 * 1024; // 25 MB
|
|
14
|
+
/**
|
|
15
|
+
* Reject a request whose declared Content-Length exceeds `maxBytes`. Cheap
|
|
16
|
+
* first line of defense for bodies we must hand to a buffering parser (e.g.
|
|
17
|
+
* formData()): an honest client and most attacks announce their size. Returns
|
|
18
|
+
* a 413 response when over the cap, otherwise null. Absent/garbage
|
|
19
|
+
* Content-Length passes here — pair with a streaming cap where possible.
|
|
20
|
+
*/
|
|
21
|
+
export function enforceContentLength(request, maxBytes) {
|
|
22
|
+
const declared = Number(request.headers.get("content-length") ?? "");
|
|
23
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
24
|
+
return json({ error: "Payload too large" }, 413);
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Read and JSON-parse a request body with a hard byte cap, streaming the body
|
|
30
|
+
* and aborting once the running total exceeds `maxBytes` so an attacker can't
|
|
31
|
+
* exhaust memory by omitting Content-Length and sending a chunked flood.
|
|
32
|
+
*/
|
|
33
|
+
export async function readJsonBody(request, maxBytes = MAX_JSON_BODY_BYTES) {
|
|
34
|
+
const tooLarge = enforceContentLength(request, maxBytes);
|
|
35
|
+
if (tooLarge)
|
|
36
|
+
return { ok: false, response: tooLarge };
|
|
37
|
+
let text;
|
|
38
|
+
try {
|
|
39
|
+
text = await readBodyTextCapped(request, maxBytes);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error instanceof BodyTooLargeError) {
|
|
43
|
+
return { ok: false, response: json({ error: "Payload too large" }, 413) };
|
|
44
|
+
}
|
|
45
|
+
return { ok: false, response: json({ error: "Invalid body" }, 400) };
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
return { ok: true, value: JSON.parse(text) };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { ok: false, response: json({ error: "Invalid JSON" }, 400) };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
class BodyTooLargeError extends Error {
|
|
55
|
+
}
|
|
56
|
+
async function readBodyTextCapped(request, maxBytes) {
|
|
57
|
+
const body = request.body;
|
|
58
|
+
if (!body) {
|
|
59
|
+
const text = await request.text();
|
|
60
|
+
if (text.length > maxBytes)
|
|
61
|
+
throw new BodyTooLargeError();
|
|
62
|
+
return text;
|
|
63
|
+
}
|
|
64
|
+
const reader = body.getReader();
|
|
65
|
+
const chunks = [];
|
|
66
|
+
let total = 0;
|
|
67
|
+
try {
|
|
68
|
+
for (;;) {
|
|
69
|
+
const { done, value } = await reader.read();
|
|
70
|
+
if (done)
|
|
71
|
+
break;
|
|
72
|
+
if (!value)
|
|
73
|
+
continue;
|
|
74
|
+
total += value.byteLength;
|
|
75
|
+
if (total > maxBytes) {
|
|
76
|
+
await reader.cancel().catch(() => { });
|
|
77
|
+
throw new BodyTooLargeError();
|
|
78
|
+
}
|
|
79
|
+
chunks.push(value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
reader.releaseLock();
|
|
84
|
+
}
|
|
85
|
+
const merged = new Uint8Array(total);
|
|
86
|
+
let offset = 0;
|
|
87
|
+
for (const chunk of chunks) {
|
|
88
|
+
merged.set(chunk, offset);
|
|
89
|
+
offset += chunk.byteLength;
|
|
90
|
+
}
|
|
91
|
+
return new TextDecoder().decode(merged);
|
|
92
|
+
}
|
|
10
93
|
/**
|
|
11
94
|
* Lightweight CSRF defense for mutating endpoints.
|
|
12
95
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_helpers.js","sourceRoot":"","sources":["../../../src/runtime/routes/_helpers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,MAAM,WAAW,GAAG,iBAAiB,CAAC;AACtC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B,MAAM,UAAU,IAAI,CAAC,IAA6B,EAAE,MAAM,GAAG,GAAG;IAC9D,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QACxC,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,iCAAiC,EAAE;KAC/D,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAgB;IAChD,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,iBAAiB,EAAE,CAAC;QAC3D,OAAO,IAAI,CACT;YACE,KAAK,EAAE,iCAAiC;YACxC,MAAM,EAAE,SAAS,WAAW,KAAK,iBAAiB,6BAA6B;SAChF,EACD,GAAG,CACJ,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,qBAAqB,EAAE,CAAC,OAAO,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,qBAAqB,EAAE,CAAC,aAAa,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,qBAAqB,EAAE,CAAC,SAAS,CAAC;AAC3C,CAAC"}
|
|
1
|
+
{"version":3,"file":"_helpers.js","sourceRoot":"","sources":["../../../src/runtime/routes/_helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAuPxC,wBAAsB,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CA+BhE"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { hasConfiguredEditorPassword, isEditorAuthenticated, } from "../auth/session.js";
|
|
1
|
+
import { hasConfiguredEditorPassword, isDevEditorPasswordActive, isEditorAuthenticated, } from "../auth/session.js";
|
|
2
2
|
import { sanitizeRedirect } from "../auth/cookie-utils.js";
|
|
3
3
|
import { getRuntimeConfig } from "../config.js";
|
|
4
4
|
function escapeHtml(value) {
|
|
@@ -20,12 +20,43 @@ function renderBrandMark(logoUrl) {
|
|
|
20
20
|
</div>`;
|
|
21
21
|
}
|
|
22
22
|
function renderLogin(opts) {
|
|
23
|
-
const { hasPassword, apiBasePath, redirectTo, brandName, brandLogo, faviconUrl, themeCssHref, studioCssHref, loginCssHref, } = opts;
|
|
23
|
+
const { hasPassword, devActive, apiBasePath, redirectTo, brandName, brandLogo, faviconUrl, themeCssHref, studioCssHref, loginCssHref, } = opts;
|
|
24
24
|
const safeBrand = escapeHtml(brandName);
|
|
25
25
|
const safeRedirect = escapeHtml(redirectTo);
|
|
26
|
-
const statusNote = hasPassword
|
|
27
|
-
? "
|
|
28
|
-
:
|
|
26
|
+
const statusNote = !hasPassword
|
|
27
|
+
? "Caret needs an editor password before you can sign in."
|
|
28
|
+
: devActive
|
|
29
|
+
? "Temporary dev password active — check your terminal to sign in."
|
|
30
|
+
: "Sign in to manage content.";
|
|
31
|
+
// When no password is configured the form is disabled, so give the developer
|
|
32
|
+
// an actionable setup guide instead of a dead end. When a dev fallback is
|
|
33
|
+
// active the form works — just nudge them toward a permanent password.
|
|
34
|
+
const docsUrl = "https://caretcms.com/docs";
|
|
35
|
+
let helperBlock = "";
|
|
36
|
+
if (!hasPassword) {
|
|
37
|
+
helperBlock = `
|
|
38
|
+
<div class="studio-login-setup" style="margin-top:1.25rem;padding:1rem 1.1rem;border:1px solid rgba(127,127,127,0.28);border-radius:10px;text-align:left;font-size:0.85rem;line-height:1.55;">
|
|
39
|
+
<p style="margin:0 0 0.6rem;font-weight:600;">Finish setting up Caret</p>
|
|
40
|
+
<ol style="margin:0;padding-left:1.2rem;display:grid;gap:0.45rem;">
|
|
41
|
+
<li>Create a <code>.env</code> file in your project root</li>
|
|
42
|
+
<li>
|
|
43
|
+
Add this line:
|
|
44
|
+
<div style="display:flex;gap:0.4rem;align-items:center;margin-top:0.35rem;">
|
|
45
|
+
<code id="caret-env-snippet" style="flex:1;padding:0.4rem 0.55rem;border-radius:6px;background:rgba(0,0,0,0.28);overflow:auto;white-space:nowrap;">CARET_EDIT_PASSWORD=your-password</code>
|
|
46
|
+
<button type="button" id="caret-copy-env" style="cursor:pointer;border:1px solid currentColor;background:transparent;color:inherit;border-radius:6px;padding:0.4rem 0.65rem;font:inherit;">Copy</button>
|
|
47
|
+
</div>
|
|
48
|
+
</li>
|
|
49
|
+
<li>Restart the dev server, then refresh this page</li>
|
|
50
|
+
</ol>
|
|
51
|
+
<a href="${docsUrl}" style="display:inline-block;margin-top:0.7rem;">Read the setup guide →</a>
|
|
52
|
+
</div>`;
|
|
53
|
+
}
|
|
54
|
+
else if (devActive) {
|
|
55
|
+
helperBlock = `
|
|
56
|
+
<div class="studio-login-setup" style="margin-top:1rem;padding:0.8rem 1rem;border:1px dashed rgba(127,127,127,0.4);border-radius:10px;text-align:left;font-size:0.82rem;line-height:1.55;">
|
|
57
|
+
Signed in with a <strong>temporary dev password</strong> printed in your terminal. To set a permanent one, add <code>CARET_EDIT_PASSWORD</code> to a <code>.env</code> file and restart.
|
|
58
|
+
</div>`;
|
|
59
|
+
}
|
|
29
60
|
const favicon = faviconUrl
|
|
30
61
|
? `<link rel="icon" href="${escapeHtml(faviconUrl)}" />`
|
|
31
62
|
: "";
|
|
@@ -102,6 +133,7 @@ function renderLogin(opts) {
|
|
|
102
133
|
</span>
|
|
103
134
|
</button>
|
|
104
135
|
</form>
|
|
136
|
+
${helperBlock}
|
|
105
137
|
|
|
106
138
|
<div class="studio-login-footer">
|
|
107
139
|
<a href="/">← Back to site</a>
|
|
@@ -111,6 +143,18 @@ function renderLogin(opts) {
|
|
|
111
143
|
|
|
112
144
|
<script>
|
|
113
145
|
(function () {
|
|
146
|
+
var copyBtn = document.getElementById('caret-copy-env');
|
|
147
|
+
var snippet = document.getElementById('caret-env-snippet');
|
|
148
|
+
if (copyBtn && snippet && navigator.clipboard && navigator.clipboard.writeText) {
|
|
149
|
+
copyBtn.addEventListener('click', function () {
|
|
150
|
+
navigator.clipboard.writeText(snippet.textContent || '').then(function () {
|
|
151
|
+
var prev = copyBtn.textContent;
|
|
152
|
+
copyBtn.textContent = 'Copied';
|
|
153
|
+
setTimeout(function () { copyBtn.textContent = prev; }, 1500);
|
|
154
|
+
}).catch(function () {});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
114
158
|
var form = document.getElementById('login-form');
|
|
115
159
|
var error = document.getElementById('login-error');
|
|
116
160
|
var errorText = document.getElementById('login-error-text');
|
|
@@ -138,6 +182,7 @@ function renderLogin(opts) {
|
|
|
138
182
|
})
|
|
139
183
|
.then(function (result) {
|
|
140
184
|
if (result.ok && result.data && result.data.redirect) {
|
|
185
|
+
try { sessionStorage.setItem('caret:welcome', '1'); } catch (e) {}
|
|
141
186
|
window.location.href = result.data.redirect;
|
|
142
187
|
return;
|
|
143
188
|
}
|
|
@@ -167,7 +212,10 @@ function html(content, status = 200, setCookie) {
|
|
|
167
212
|
}
|
|
168
213
|
export async function GET(context) {
|
|
169
214
|
const runtime = getRuntimeConfig();
|
|
170
|
-
|
|
215
|
+
// Default post-login landing is the live site so the inline editor activates
|
|
216
|
+
// immediately (Caret is inline-first). The Studio stays reachable at
|
|
217
|
+
// `${mountPath}/cms`, and a `?redirect=` param still overrides this.
|
|
218
|
+
const redirectTarget = sanitizeRedirect(context.url.searchParams.get("redirect"), runtime.editorHome);
|
|
171
219
|
if (isEditorAuthenticated(context)) {
|
|
172
220
|
return new Response(null, {
|
|
173
221
|
status: 302,
|
|
@@ -176,6 +224,7 @@ export async function GET(context) {
|
|
|
176
224
|
}
|
|
177
225
|
return html(renderLogin({
|
|
178
226
|
hasPassword: hasConfiguredEditorPassword(),
|
|
227
|
+
devActive: isDevEditorPasswordActive(),
|
|
179
228
|
apiBasePath: runtime.apiBasePath,
|
|
180
229
|
redirectTo: redirectTarget,
|
|
181
230
|
brandName: runtime.brand.name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../../../src/runtime/routes/admin.ts"],"names":[],"mappings":"AACA,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,eAAe,CAAC,OAAsB;IAC7C,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,aAAa,UAAU,CAAC,OAAO,CAAC,oEAAoE,CAAC;IAC9G,CAAC;IACD,OAAO;;;;SAIA,CAAC;AACV,CAAC;AAED,SAAS,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../../../src/runtime/routes/admin.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-login.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/auth-login.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-login.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/auth-login.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAkCxC,wBAAsB,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyEjE;AAED,wBAAsB,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,CAQ7C"}
|
|
@@ -2,6 +2,7 @@ import { hasConfiguredEditorPassword, isEditorPasswordValid, issueEditorSessionC
|
|
|
2
2
|
import { sanitizeRedirect } from "../auth/cookie-utils.js";
|
|
3
3
|
import { checkLoginRateLimit, clearLoginAttempts, extractRateLimitKey, recordLoginFailure, } from "../auth/rate-limiter.js";
|
|
4
4
|
import { getRuntimeConfig } from "../config.js";
|
|
5
|
+
import { enforceContentLength, MAX_JSON_BODY_BYTES } from "./_helpers.js";
|
|
5
6
|
function redirect(pathname, setCookie) {
|
|
6
7
|
const headers = { Location: pathname };
|
|
7
8
|
if (setCookie)
|
|
@@ -20,6 +21,9 @@ export async function POST(context) {
|
|
|
20
21
|
if (!hasConfiguredEditorPassword()) {
|
|
21
22
|
return json({ error: "Editor password is not configured." }, 500);
|
|
22
23
|
}
|
|
24
|
+
const tooLarge = enforceContentLength(context.request, MAX_JSON_BODY_BYTES);
|
|
25
|
+
if (tooLarge)
|
|
26
|
+
return tooLarge;
|
|
23
27
|
const rateLimitKey = extractRateLimitKey(context.request);
|
|
24
28
|
const rateLimit = checkLoginRateLimit(rateLimitKey);
|
|
25
29
|
if (!rateLimit.allowed) {
|
|
@@ -48,15 +52,28 @@ export async function POST(context) {
|
|
|
48
52
|
? formData?.get("redirect")
|
|
49
53
|
: redirectTo;
|
|
50
54
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// Default to the live site (inline editor) when no explicit redirect is given;
|
|
56
|
+
// `editorHome` (default "/") makes that landing page configurable.
|
|
57
|
+
const safeRedirect = sanitizeRedirect(redirectTo, runtime.editorHome);
|
|
58
|
+
// Password check + session minting both derive HMACs from the session
|
|
59
|
+
// secret, which throws in production when CARET_SESSION_SECRET is unset.
|
|
60
|
+
// Surface that as a configuration error instead of an unhandled 500.
|
|
61
|
+
let passwordValid;
|
|
62
|
+
let cookie;
|
|
63
|
+
try {
|
|
64
|
+
passwordValid = isEditorPasswordValid(password);
|
|
65
|
+
if (!passwordValid) {
|
|
66
|
+
recordLoginFailure(rateLimitKey);
|
|
67
|
+
if (wantsJsonResponse)
|
|
68
|
+
return json({ error: "Invalid password" }, 401);
|
|
69
|
+
return redirect(`${runtime.mountPath}?error=invalid`);
|
|
70
|
+
}
|
|
71
|
+
clearLoginAttempts(rateLimitKey);
|
|
72
|
+
cookie = issueEditorSessionCookie("/", context.request);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
return json({ error: err.message }, 500);
|
|
57
76
|
}
|
|
58
|
-
clearLoginAttempts(rateLimitKey);
|
|
59
|
-
const cookie = issueEditorSessionCookie("/", context.request);
|
|
60
77
|
if (wantsJsonResponse) {
|
|
61
78
|
return json({ ok: true, redirect: safeRedirect }, 200, { "Set-Cookie": cookie });
|
|
62
79
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-login.js","sourceRoot":"","sources":["../../../src/runtime/routes/auth-login.ts"],"names":[],"mappings":"AACA,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-login.js","sourceRoot":"","sources":["../../../src/runtime/routes/auth-login.ts"],"names":[],"mappings":"AACA,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAE1E,SAAS,QAAQ,CAAC,QAAgB,EAAE,SAAkB;IACpD,MAAM,OAAO,GAA2B,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IAC/D,IAAI,SAAS;QAAE,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;IACjD,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,IAAI,CACX,IAA6B,EAC7B,MAAM,GAAG,GAAG,EACZ,YAAqC;IAErC,MAAM,OAAO,GAA2B;QACtC,cAAc,EAAE,iCAAiC;QACjD,GAAG,YAAY;KAChB,CAAC;IACF,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAmB;IAC5C,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;IAEnC,IAAI,CAAC,2BAA2B,EAAE,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oCAAoC,EAAE,EAAE,GAAG,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,QAAQ,GAAG,oBAAoB,CAAC,OAAO,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IAC5E,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,MAAM,YAAY,GAAG,mBAAmB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,MAAM,SAAS,GAAG,mBAAmB,CAAC,YAAY,CAAC,CAAC;IACpD,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QACvB,OAAO,IAAI,CACT,EAAE,KAAK,EAAE,qDAAqD,EAAE,EAChE,GAAG,EACH,EAAE,aAAa,EAAE,MAAM,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,CACvD,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IACtE,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC3D,MAAM,UAAU,GAAG,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC5D,MAAM,iBAAiB,GAAG,UAAU,IAAI,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAE5E,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,IAAI,UAAU,GAAkB,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAEzE,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,IAAI,GAAG,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAEpD,CAAC;QACT,QAAQ,GAAG,OAAO,IAAI,EAAE,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,UAAU;YACR,OAAO,IAAI,EAAE,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC;IACpE,CAAC;SAAM,CAAC;QACN,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACpE,QAAQ;YACN,OAAO,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAC,KAAK,QAAQ;gBAC3C,CAAC,CAAE,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAY;gBACvC,CAAC,CAAC,EAAE,CAAC;QACT,UAAU;YACR,OAAO,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAC,KAAK,QAAQ;gBAC3C,CAAC,CAAE,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAY;gBACvC,CAAC,CAAC,UAAU,CAAC;IACnB,CAAC;IAED,+EAA+E;IAC/E,mEAAmE;IACnE,MAAM,YAAY,GAAG,gBAAgB,CAAC,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAEtE,sEAAsE;IACtE,yEAAyE;IACzE,qEAAqE;IACrE,IAAI,aAAsB,CAAC;IAC3B,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,aAAa,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,kBAAkB,CAAC,YAAY,CAAC,CAAC;YACjC,IAAI,iBAAiB;gBAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,GAAG,CAAC,CAAC;YACvE,OAAO,QAAQ,CAAC,GAAG,OAAO,CAAC,SAAS,gBAAgB,CAAC,CAAC;QACxD,CAAC;QACD,kBAAkB,CAAC,YAAY,CAAC,CAAC;QACjC,MAAM,GAAG,wBAAwB,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,IAAI,CAAC,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,iBAAiB,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,GAAG;IACvB,OAAO,IAAI,CACT;QACE,KAAK,EAAE,oBAAoB;QAC3B,cAAc,EAAE,CAAC,MAAM,CAAC;KACzB,EACD,GAAG,CACJ,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-logout.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/auth-logout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-logout.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/auth-logout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAwBxC,wBAAsB,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CA4BjE;AAED,wBAAsB,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,CAQ7C"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { clearEditorSessionCookie } from "../auth/session.js";
|
|
2
2
|
import { sanitizeRedirect } from "../auth/cookie-utils.js";
|
|
3
3
|
import { getRuntimeConfig } from "../config.js";
|
|
4
|
+
import { enforceContentLength, MAX_JSON_BODY_BYTES } from "./_helpers.js";
|
|
4
5
|
function redirect(pathname, setCookie) {
|
|
5
6
|
const headers = { Location: pathname };
|
|
6
7
|
if (setCookie)
|
|
@@ -16,6 +17,9 @@ function json(body, status = 200, setCookie) {
|
|
|
16
17
|
return new Response(JSON.stringify(body), { status, headers });
|
|
17
18
|
}
|
|
18
19
|
export async function POST(context) {
|
|
20
|
+
const tooLarge = enforceContentLength(context.request, MAX_JSON_BODY_BYTES);
|
|
21
|
+
if (tooLarge)
|
|
22
|
+
return tooLarge;
|
|
19
23
|
const runtime = getRuntimeConfig();
|
|
20
24
|
const contentType = context.request.headers.get("content-type") ?? "";
|
|
21
25
|
const isJson = contentType.includes("application/json");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-logout.js","sourceRoot":"","sources":["../../../src/runtime/routes/auth-logout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-logout.js","sourceRoot":"","sources":["../../../src/runtime/routes/auth-logout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAE1E,SAAS,QAAQ,CAAC,QAAgB,EAAE,SAAkB;IACpD,MAAM,OAAO,GAA2B,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IAC/D,IAAI,SAAS;QAAE,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;IACjD,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,IAAI,CACX,IAA6B,EAC7B,MAAM,GAAG,GAAG,EACZ,SAAkB;IAElB,MAAM,OAAO,GAA2B;QACtC,cAAc,EAAE,iCAAiC;KAClD,CAAC;IACF,IAAI,SAAS;QAAE,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;IACjD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAmB;IAC5C,MAAM,QAAQ,GAAG,oBAAoB,CAAC,OAAO,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IAC5E,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;IACnC,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IACtE,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IACxD,MAAM,MAAM,GAAG,wBAAwB,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,IAAI,UAAU,GAAkB,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAEzE,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,IAAI,GAAG,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAEpD,CAAC;QACT,UAAU;YACR,OAAO,IAAI,EAAE,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC;IACpE,CAAC;SAAM,CAAC;QACN,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACpE,UAAU;YACR,OAAO,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAC,KAAK,QAAQ;gBAC3C,CAAC,CAAE,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAY;gBACvC,CAAC,CAAC,UAAU,CAAC;IACnB,CAAC;IAED,MAAM,YAAY,GAAG,gBAAgB,CAAC,UAAU,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAErE,IAAI,MAAM;QAAE,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IAC3E,OAAO,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,GAAG;IACvB,OAAO,IAAI,CACT;QACE,KAAK,EAAE,oBAAoB;QAC3B,cAAc,EAAE,CAAC,MAAM,CAAC;KACzB,EACD,GAAG,CACJ,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collections-metadata.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/collections-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAIxC,wBAAsB,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"collections-metadata.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/collections-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAIxC,wBAAsB,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAgBhE"}
|
|
@@ -10,8 +10,10 @@ export async function GET(context) {
|
|
|
10
10
|
return json({ collections });
|
|
11
11
|
}
|
|
12
12
|
catch (error) {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// Don't echo the raw error to the client — it can carry filesystem paths
|
|
14
|
+
// or other internal detail. Log it server-side, return an opaque message.
|
|
15
|
+
console.error("[caretcms] Failed to load collection metadata:", error);
|
|
16
|
+
return json({ error: "Failed to load collections" }, 500);
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
19
|
//# sourceMappingURL=collections-metadata.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collections-metadata.js","sourceRoot":"","sources":["../../../src/runtime/routes/collections-metadata.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEjD,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,OAAmB;IAC3C,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAE7B,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,sBAAsB,EAAE,CAAC;QAC3D,OAAO,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,
|
|
1
|
+
{"version":3,"file":"collections-metadata.js","sourceRoot":"","sources":["../../../src/runtime/routes/collections-metadata.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEjD,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,OAAmB;IAC3C,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAE7B,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,sBAAsB,EAAE,CAAC;QAC3D,OAAO,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,yEAAyE;QACzE,0EAA0E;QAC1E,OAAO,CAAC,KAAK,CAAC,gDAAgD,EAAE,KAAK,CAAC,CAAC;QACvE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,4BAA4B,EAAE,EAAE,GAAG,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { APIContext } from "astro";
|
|
2
|
+
/**
|
|
3
|
+
* DELETE /api/cms/draft — discard the current editor's draft(s) without
|
|
4
|
+
* publishing. Optional `?collection=&id=` query scopes it to one entry or
|
|
5
|
+
* collection; absent, the editor's whole draft is dropped.
|
|
6
|
+
*/
|
|
7
|
+
export declare function DELETE(context: APIContext): Promise<Response>;
|
|
8
|
+
//# sourceMappingURL=draft.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/draft.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAMxC;;;;GAIG;AACH,wBAAsB,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyBnE"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { isEditorAuthenticated, getEditorId } from "../auth/session.js";
|
|
2
|
+
import { getRuntimeServices } from "../providers.js";
|
|
3
|
+
import { discardOverlay } from "../publish.js";
|
|
4
|
+
import { json, enforceCsrfHeader } from "./_helpers.js";
|
|
5
|
+
/**
|
|
6
|
+
* DELETE /api/cms/draft — discard the current editor's draft(s) without
|
|
7
|
+
* publishing. Optional `?collection=&id=` query scopes it to one entry or
|
|
8
|
+
* collection; absent, the editor's whole draft is dropped.
|
|
9
|
+
*/
|
|
10
|
+
export async function DELETE(context) {
|
|
11
|
+
const csrf = enforceCsrfHeader(context.request);
|
|
12
|
+
if (csrf)
|
|
13
|
+
return csrf;
|
|
14
|
+
if (!isEditorAuthenticated(context))
|
|
15
|
+
return json({ error: "Unauthorized" }, 401);
|
|
16
|
+
const editorId = getEditorId(context);
|
|
17
|
+
if (!editorId)
|
|
18
|
+
return json({ error: "No editor draft session" }, 400);
|
|
19
|
+
const url = new URL(context.request.url);
|
|
20
|
+
const scope = {};
|
|
21
|
+
const collection = url.searchParams.get("collection");
|
|
22
|
+
const id = url.searchParams.get("id");
|
|
23
|
+
if (collection)
|
|
24
|
+
scope.collection = collection;
|
|
25
|
+
if (id)
|
|
26
|
+
scope.id = id;
|
|
27
|
+
if (scope.id && !scope.collection) {
|
|
28
|
+
return json({ error: "id requires a collection" }, 400);
|
|
29
|
+
}
|
|
30
|
+
const { adapter: base } = await getRuntimeServices();
|
|
31
|
+
if (!base.makeEditorOverlay) {
|
|
32
|
+
return json({ error: "Drafts are not supported by the configured storage adapter" }, 400);
|
|
33
|
+
}
|
|
34
|
+
const overlay = await base.makeEditorOverlay(editorId);
|
|
35
|
+
const cleared = await discardOverlay(overlay, scope);
|
|
36
|
+
return json({ ok: true, cleared });
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=draft.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../../../src/runtime/routes/draft.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAqB,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAExD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,OAAmB;IAC9C,MAAM,IAAI,GAAG,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC;IAEjF,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,EAAE,GAAG,CAAC,CAAC;IAEtE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,MAAM,UAAU,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IACtD,MAAM,EAAE,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,UAAU;QAAE,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC;IAC9C,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC;IACtB,IAAI,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,EAAE,GAAG,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,kBAAkB,EAAE,CAAC;IACrD,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,4DAA4D,EAAE,EAAE,GAAG,CAAC,CAAC;IAC5F,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACrD,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAMxC,wBAAsB,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiBhE;AAED,wBAAsB,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkDjE"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isEditorAuthenticated } from "../auth/session.js";
|
|
2
2
|
import { parseEntryId } from "../mutations/contracts.js";
|
|
3
|
-
import {
|
|
3
|
+
import { withEntryLock } from "../mutations/engine.js";
|
|
4
|
+
import { json, getAdapter, enforceCsrfHeader, readJsonBody } from "./_helpers.js";
|
|
4
5
|
export async function GET(context) {
|
|
5
6
|
if (!isEditorAuthenticated(context)) {
|
|
6
7
|
return json({ error: "Unauthorized" }, 401);
|
|
@@ -23,7 +24,12 @@ export async function POST(context) {
|
|
|
23
24
|
if (!isEditorAuthenticated(context)) {
|
|
24
25
|
return json({ error: "Unauthorized" }, 401);
|
|
25
26
|
}
|
|
26
|
-
const
|
|
27
|
+
const parsed = await readJsonBody(context.request);
|
|
28
|
+
if (!parsed.ok)
|
|
29
|
+
return parsed.response;
|
|
30
|
+
const body = parsed.value && typeof parsed.value === "object" && !Array.isArray(parsed.value)
|
|
31
|
+
? parsed.value
|
|
32
|
+
: null;
|
|
27
33
|
if (!body)
|
|
28
34
|
return json({ error: "Invalid payload" }, 400);
|
|
29
35
|
const adapter = getAdapter();
|
|
@@ -42,12 +48,19 @@ export async function POST(context) {
|
|
|
42
48
|
if (!snapshot.data || typeof snapshot.data !== "object" || Array.isArray(snapshot.data)) {
|
|
43
49
|
return json({ error: "Snapshot data invalid" }, 500);
|
|
44
50
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
const snapshotData = snapshot.data;
|
|
52
|
+
// Hold the engine's per-entry lock so the restore can't interleave with a
|
|
53
|
+
// concurrent save_field/put_entry and tear the entry/revision pair. Write
|
|
54
|
+
// before appending history so a failed write doesn't record a phantom event.
|
|
55
|
+
const revision = await withEntryLock(collectionRaw, id, async () => {
|
|
56
|
+
await adapter.writeEntry(collectionRaw, id, snapshotData);
|
|
57
|
+
const next = await adapter.bumpRevision(collectionRaw, id);
|
|
58
|
+
await adapter.appendHistory(collectionRaw, id, {
|
|
59
|
+
ts: Date.now(),
|
|
60
|
+
action: "restore",
|
|
61
|
+
data: snapshotData,
|
|
62
|
+
});
|
|
63
|
+
return next;
|
|
51
64
|
});
|
|
52
65
|
return json({ ok: true, revision });
|
|
53
66
|
}
|