emdash 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapters-C2BzVy0p.d.mts → adapters-Di31kZ28.d.mts} +16 -1
- package/dist/adapters-Di31kZ28.d.mts.map +1 -0
- package/dist/{apply-Cma_PiF6.mjs → apply-5uslYdUu.mjs} +197 -25
- package/dist/apply-5uslYdUu.mjs.map +1 -0
- package/dist/astro/index.d.mts +6 -6
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +203 -33
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +5 -5
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +30 -4
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/redirect.mjs +2 -2
- package/dist/astro/middleware/request-context.d.mts.map +1 -1
- package/dist/astro/middleware/request-context.mjs +11 -4
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +467 -186
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/types.d.mts +17 -9
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{byline-WuOq9MFJ.mjs → byline-C4OVd8b3.mjs} +3 -19
- package/dist/byline-C4OVd8b3.mjs.map +1 -0
- package/dist/{bylines-C_Wsnz4L.mjs → bylines-hPTW79hw.mjs} +20 -33
- package/dist/bylines-hPTW79hw.mjs.map +1 -0
- package/dist/{cache-E3Dts-yT.mjs → cache-BkKBuIvS.mjs} +1 -1
- package/dist/{cache-E3Dts-yT.mjs.map → cache-BkKBuIvS.mjs.map} +1 -1
- package/dist/chunks-HGz06Soa.mjs +19 -0
- package/dist/chunks-HGz06Soa.mjs.map +1 -0
- package/dist/cli/index.mjs +12 -11
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/{config-DkxPrM9l.mjs → config-BXwuX8Bx.mjs} +1 -1
- package/dist/{config-DkxPrM9l.mjs.map → config-BXwuX8Bx.mjs.map} +1 -1
- package/dist/{connection-B4zVnQIa.mjs → connection-2igzM-AT.mjs} +19 -2
- package/dist/connection-2igzM-AT.mjs.map +1 -0
- package/dist/{content-BsBoyj8G.mjs → content-D7J5y73J.mjs} +27 -1
- package/dist/{content-BsBoyj8G.mjs.map → content-D7J5y73J.mjs.map} +1 -1
- package/dist/database/instrumentation.d.mts +45 -0
- package/dist/database/instrumentation.d.mts.map +1 -0
- package/dist/database/instrumentation.mjs +61 -0
- package/dist/database/instrumentation.mjs.map +1 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +1 -1
- package/dist/db/index.mjs.map +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db-errors-D0UT85nC.mjs +41 -0
- package/dist/db-errors-D0UT85nC.mjs.map +1 -0
- package/dist/{default-PUx9RK6u.mjs → default-CME5YdZ3.mjs} +1 -1
- package/dist/{default-PUx9RK6u.mjs.map → default-CME5YdZ3.mjs.map} +1 -1
- package/dist/{error-HBeQbVhV.mjs → error-CiYn9yDu.mjs} +1 -1
- package/dist/{error-HBeQbVhV.mjs.map → error-CiYn9yDu.mjs.map} +1 -1
- package/dist/{index-CCWzlriB.d.mts → index-De6_Xv3v.d.mts} +209 -19
- package/dist/index-De6_Xv3v.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +23 -21
- package/dist/{load-BhSSm-TS.mjs → load-CBcmDIot.mjs} +1 -1
- package/dist/{load-BhSSm-TS.mjs.map → load-CBcmDIot.mjs.map} +1 -1
- package/dist/{loader-BYzwzORf.mjs → loader-DeiBJEMe.mjs} +18 -12
- package/dist/loader-DeiBJEMe.mjs.map +1 -0
- package/dist/{manifest-schema-BsXINkQD.mjs → manifest-schema-V30qsMft.mjs} +1 -1
- package/dist/{manifest-schema-BsXINkQD.mjs.map → manifest-schema-V30qsMft.mjs.map} +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/{mode-CyPLdO3C.mjs → mode-CpNnGkPz.mjs} +1 -1
- package/dist/{mode-CyPLdO3C.mjs.map → mode-CpNnGkPz.mjs.map} +1 -1
- package/dist/page/index.d.mts +11 -2
- package/dist/page/index.d.mts.map +1 -1
- package/dist/page/index.mjs +23 -1
- package/dist/page/index.mjs.map +1 -1
- package/dist/{placeholder-DntBEQo7.mjs → placeholder-C-fk5hYI.mjs} +1 -1
- package/dist/{placeholder-DntBEQo7.mjs.map → placeholder-C-fk5hYI.mjs.map} +1 -1
- package/dist/{placeholder-BBCtpTES.d.mts → placeholder-tzpqGWII.d.mts} +1 -1
- package/dist/{placeholder-BBCtpTES.d.mts.map → placeholder-tzpqGWII.d.mts.map} +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
- package/dist/{query-B6Vu0d2i.mjs → query-g4Ug-9j9.mjs} +79 -12
- package/dist/query-g4Ug-9j9.mjs.map +1 -0
- package/dist/{redirect-7lGhLBNZ.mjs → redirect-CN0Rt9Ob.mjs} +66 -10
- package/dist/redirect-CN0Rt9Ob.mjs.map +1 -0
- package/dist/{registry-BgnP3ysR.mjs → registry-Ci3WxVAr.mjs} +133 -97
- package/dist/registry-Ci3WxVAr.mjs.map +1 -0
- package/dist/request-cache-DiR961CV.mjs +79 -0
- package/dist/request-cache-DiR961CV.mjs.map +1 -0
- package/dist/request-context.d.mts +19 -16
- package/dist/request-context.d.mts.map +1 -1
- package/dist/request-context.mjs.map +1 -1
- package/dist/{runner-DYv3rX8P.d.mts → runner-BR2xKwhn.d.mts} +2 -2
- package/dist/{runner-DYv3rX8P.d.mts.map → runner-BR2xKwhn.d.mts.map} +1 -1
- package/dist/{runner-Cd-_WyDo.mjs → runner-tQ7BJ4T7.mjs} +211 -134
- package/dist/runner-tQ7BJ4T7.mjs.map +1 -0
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +1 -1
- package/dist/{search-Cn1SYvYF.mjs → search-B0effn3j.mjs} +210 -226
- package/dist/search-B0effn3j.mjs.map +1 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +10 -9
- package/dist/seo/index.d.mts +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/taxonomies-K2z0Uhnj.mjs +308 -0
- package/dist/taxonomies-K2z0Uhnj.mjs.map +1 -0
- package/dist/{tokens-DKHiCYCB.mjs → tokens-BFPFx3CA.mjs} +1 -1
- package/dist/{tokens-DKHiCYCB.mjs.map → tokens-BFPFx3CA.mjs.map} +1 -1
- package/dist/{transport-BtcQ-Z7T.mjs → transport-BykRfpyy.mjs} +1 -1
- package/dist/{transport-BtcQ-Z7T.mjs.map → transport-BykRfpyy.mjs.map} +1 -1
- package/dist/{transport-CKQA_G44.d.mts → transport-H4Iwx7tC.d.mts} +1 -1
- package/dist/{transport-CKQA_G44.d.mts.map → transport-H4Iwx7tC.d.mts.map} +1 -1
- package/dist/{types-BmkQR1En.d.mts → types-6CUZRrZP.d.mts} +1 -1
- package/dist/{types-BmkQR1En.d.mts.map → types-6CUZRrZP.d.mts.map} +1 -1
- package/dist/{types-Dz9_WMS6.mjs → types-BH2L167P.mjs} +1 -1
- package/dist/{types-Dz9_WMS6.mjs.map → types-BH2L167P.mjs.map} +1 -1
- package/dist/{types-B6BzlZxx.d.mts → types-C2v0c34j.d.mts} +10 -1
- package/dist/{types-B6BzlZxx.d.mts.map → types-C2v0c34j.d.mts.map} +1 -1
- package/dist/{types-DNZpaCBk.d.mts → types-CFWjXmus.d.mts} +1 -1
- package/dist/{types-DNZpaCBk.d.mts.map → types-CFWjXmus.d.mts.map} +1 -1
- package/dist/{types-DeG21anB.d.mts → types-CnZYHyLW.d.mts} +55 -5
- package/dist/types-CnZYHyLW.d.mts.map +1 -0
- package/dist/{types-xxCWI3j0.mjs → types-DDS4MxsT.mjs} +11 -3
- package/dist/types-DDS4MxsT.mjs.map +1 -0
- package/dist/{types-C3ronwXb.d.mts → types-DgrIP0tF.d.mts} +102 -4
- package/dist/types-DgrIP0tF.d.mts.map +1 -0
- package/dist/{validate-DuZDIxfy.mjs → validate-CqsNItbt.mjs} +2 -2
- package/dist/{validate-DuZDIxfy.mjs.map → validate-CqsNItbt.mjs.map} +1 -1
- package/dist/{validate-Db1yNL3i.d.mts → validate-kM8Pjuf7.d.mts} +5 -52
- package/dist/validate-kM8Pjuf7.d.mts.map +1 -0
- package/dist/version-BnTKdfam.mjs +7 -0
- package/dist/{version-CMMjTuqu.mjs.map → version-BnTKdfam.mjs.map} +1 -1
- package/package.json +10 -5
- package/src/after.ts +62 -0
- package/src/api/handlers/content.ts +2 -0
- package/src/api/handlers/oauth-authorization.ts +2 -32
- package/src/api/handlers/oauth-clients.ts +40 -4
- package/src/api/handlers/taxonomies.ts +13 -0
- package/src/api/oauth/redirect-uri.ts +34 -0
- package/src/api/openapi/document.ts +126 -118
- package/src/api/schemas/content.ts +8 -0
- package/src/api/schemas/media.ts +26 -15
- package/src/api/schemas/schema.ts +1 -0
- package/src/astro/integration/font-provider.ts +178 -0
- package/src/astro/integration/index.ts +44 -0
- package/src/astro/integration/routes.ts +6 -0
- package/src/astro/integration/runtime.ts +117 -0
- package/src/astro/integration/virtual-modules.ts +41 -39
- package/src/astro/integration/vite-config.ts +16 -5
- package/src/astro/middleware/auth.ts +33 -1
- package/src/astro/middleware/request-context.ts +15 -3
- package/src/astro/middleware.ts +340 -263
- package/src/astro/routes/admin.astro +21 -10
- package/src/astro/routes/api/auth/magic-link/send.ts +2 -1
- package/src/astro/routes/api/auth/passkey/options.ts +2 -1
- package/src/astro/routes/api/auth/passkey/verify.ts +5 -1
- package/src/astro/routes/api/auth/signup/request.ts +26 -8
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +10 -6
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +5 -0
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +26 -0
- package/src/astro/routes/api/content/[collection]/[id].ts +30 -2
- package/src/astro/routes/api/content/[collection]/index.ts +19 -1
- package/src/astro/routes/api/content/[collection]/trash.ts +1 -1
- package/src/astro/routes/api/import/wordpress/execute.ts +1 -1
- package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +4 -3
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +5 -4
- package/src/astro/routes/api/manifest.ts +7 -0
- package/src/astro/routes/api/media/upload-url.ts +10 -2
- package/src/astro/routes/api/media.ts +10 -7
- package/src/astro/routes/api/oauth/device/code.ts +2 -1
- package/src/astro/routes/api/oauth/device/token.ts +2 -1
- package/src/astro/routes/api/oauth/register.ts +178 -0
- package/src/astro/routes/api/oauth/token.ts +15 -0
- package/src/astro/routes/api/openapi.json.ts +15 -5
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +2 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +1 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +1 -0
- package/src/astro/routes/api/search/index.ts +5 -0
- package/src/astro/routes/api/search/suggest.ts +3 -0
- package/src/astro/routes/api/setup/admin-verify.ts +30 -5
- package/src/astro/routes/api/setup/admin.ts +32 -8
- package/src/astro/routes/api/setup/index.ts +5 -2
- package/src/astro/routes/api/taxonomies/index.ts +1 -0
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +1 -1
- package/src/astro/types.ts +9 -0
- package/src/auth/rate-limit.ts +50 -22
- package/src/auth/setup-nonce.ts +22 -0
- package/src/auth/trusted-proxy.ts +92 -0
- package/src/bylines/index.ts +22 -45
- package/src/components/EmDashHead.astro +23 -7
- package/src/database/connection.ts +23 -1
- package/src/database/instrumentation.ts +98 -0
- package/src/database/migrations/035_bounded_404_log.ts +112 -0
- package/src/database/migrations/runner.ts +2 -0
- package/src/database/repositories/content.ts +39 -0
- package/src/database/repositories/options.ts +25 -0
- package/src/database/repositories/redirect.ts +111 -8
- package/src/database/types.ts +9 -0
- package/src/db/adapters.ts +15 -0
- package/src/emdash-runtime.ts +312 -92
- package/src/import/registry.ts +4 -3
- package/src/import/ssrf.ts +253 -12
- package/src/index.ts +6 -0
- package/src/loader.ts +19 -24
- package/src/mcp/server.ts +76 -3
- package/src/menus/index.ts +6 -3
- package/src/page/index.ts +1 -1
- package/src/page/seo-contributions.ts +36 -0
- package/src/plugins/context.ts +15 -3
- package/src/plugins/manager.ts +6 -0
- package/src/plugins/request-meta.ts +66 -15
- package/src/plugins/routes.ts +3 -1
- package/src/query.ts +104 -7
- package/src/request-cache.ts +106 -0
- package/src/request-context.ts +19 -0
- package/src/schema/query.ts +5 -2
- package/src/schema/registry.ts +243 -166
- package/src/schema/types.ts +13 -2
- package/src/schema/zod-generator.ts +4 -0
- package/src/search/fts-manager.ts +19 -5
- package/src/search/query.ts +4 -3
- package/src/seed/apply.ts +41 -1
- package/src/settings/index.ts +24 -5
- package/src/taxonomies/index.ts +324 -124
- package/src/utils/db-errors.ts +46 -0
- package/src/virtual-modules.d.ts +31 -10
- package/src/visual-editing/toolbar.ts +6 -1
- package/src/widgets/index.ts +54 -25
- package/dist/adapters-C2BzVy0p.d.mts.map +0 -1
- package/dist/apply-Cma_PiF6.mjs.map +0 -1
- package/dist/byline-WuOq9MFJ.mjs.map +0 -1
- package/dist/bylines-C_Wsnz4L.mjs.map +0 -1
- package/dist/connection-B4zVnQIa.mjs.map +0 -1
- package/dist/index-CCWzlriB.d.mts.map +0 -1
- package/dist/loader-BYzwzORf.mjs.map +0 -1
- package/dist/query-B6Vu0d2i.mjs.map +0 -1
- package/dist/redirect-7lGhLBNZ.mjs.map +0 -1
- package/dist/registry-BgnP3ysR.mjs.map +0 -1
- package/dist/runner-Cd-_WyDo.mjs.map +0 -1
- package/dist/search-Cn1SYvYF.mjs.map +0 -1
- package/dist/types-C3ronwXb.d.mts.map +0 -1
- package/dist/types-DeG21anB.d.mts.map +0 -1
- package/dist/types-xxCWI3j0.mjs.map +0 -1
- package/dist/validate-Db1yNL3i.d.mts.map +0 -1
- package/dist/version-CMMjTuqu.mjs +0 -7
|
@@ -256,6 +256,19 @@ export interface EmDashConfig {
|
|
|
256
256
|
*/
|
|
257
257
|
marketplace?: string;
|
|
258
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Maximum allowed media file upload size in bytes.
|
|
261
|
+
*
|
|
262
|
+
* Applies to both direct multipart uploads and signed-URL uploads.
|
|
263
|
+
* When unset, defaults to 52_428_800 (50 MB).
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* emdash({ maxUploadSize: 100 * 1024 * 1024 }) // 100 MB
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
maxUploadSize?: number;
|
|
271
|
+
|
|
259
272
|
/**
|
|
260
273
|
* Public browser-facing origin for the site.
|
|
261
274
|
*
|
|
@@ -271,6 +284,32 @@ export interface EmDashConfig {
|
|
|
271
284
|
*/
|
|
272
285
|
siteUrl?: string;
|
|
273
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Headers to trust for client IP resolution when running behind a reverse
|
|
289
|
+
* proxy. The first header in this list that is present on the request
|
|
290
|
+
* wins. Applies to rate limiting for auth endpoints and comment
|
|
291
|
+
* submission.
|
|
292
|
+
*
|
|
293
|
+
* Common values:
|
|
294
|
+
* - `x-real-ip` — nginx, Caddy, Traefik
|
|
295
|
+
* - `fly-client-ip` — Fly.io
|
|
296
|
+
* - `x-forwarded-for` — generic (first entry is used)
|
|
297
|
+
*
|
|
298
|
+
* Only set this when you **control the reverse proxy**. Untrusted
|
|
299
|
+
* clients can set any header they like; trusting headers from an open
|
|
300
|
+
* network is an IP-spoofing vulnerability that defeats rate limiting.
|
|
301
|
+
*
|
|
302
|
+
* On Cloudflare the `cf` object on the request is used automatically —
|
|
303
|
+
* you normally don't need to set this. Leave unset (or empty) to
|
|
304
|
+
* preserve the default: IP is resolved only when the request came
|
|
305
|
+
* through Cloudflare's edge.
|
|
306
|
+
*
|
|
307
|
+
* Falls back to `EMDASH_TRUSTED_PROXY_HEADERS` env var (comma-separated)
|
|
308
|
+
* when this option is not set, so operators can configure at deploy
|
|
309
|
+
* time without touching the Astro config.
|
|
310
|
+
*/
|
|
311
|
+
trustedProxyHeaders?: string[];
|
|
312
|
+
|
|
274
313
|
/**
|
|
275
314
|
* Enable playground mode for ephemeral "try EmDash" sites.
|
|
276
315
|
*
|
|
@@ -322,6 +361,84 @@ export interface EmDashConfig {
|
|
|
322
361
|
* ```
|
|
323
362
|
*/
|
|
324
363
|
mediaProviders?: MediaProviderDescriptor[];
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Admin UI font configuration.
|
|
367
|
+
*
|
|
368
|
+
* By default, EmDash loads Noto Sans via the Astro Font API, covering
|
|
369
|
+
* Latin, Latin Extended, Cyrillic, Cyrillic Extended, Greek, Greek
|
|
370
|
+
* Extended, Devanagari, and Vietnamese. Fonts are downloaded from
|
|
371
|
+
* Google at build time and self-hosted, so there are no runtime CDN
|
|
372
|
+
* requests.
|
|
373
|
+
*
|
|
374
|
+
* To add support for additional writing systems (Arabic, CJK, etc.),
|
|
375
|
+
* pass script names. EmDash resolves the matching Noto Sans variant
|
|
376
|
+
* from Google Fonts and merges all script faces under a single
|
|
377
|
+
* font-family, so the browser downloads only the glyphs it needs
|
|
378
|
+
* via unicode-range.
|
|
379
|
+
*
|
|
380
|
+
* Set to `false` to disable font injection entirely and use system fonts.
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* ```ts
|
|
384
|
+
* // Add Arabic and Japanese support
|
|
385
|
+
* emdash({
|
|
386
|
+
* fonts: {
|
|
387
|
+
* scripts: ["arabic", "japanese"],
|
|
388
|
+
* },
|
|
389
|
+
* })
|
|
390
|
+
* ```
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* ```ts
|
|
394
|
+
* // Disable web fonts entirely (use system fonts)
|
|
395
|
+
* emdash({
|
|
396
|
+
* fonts: false,
|
|
397
|
+
* })
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
400
|
+
fonts?:
|
|
401
|
+
| false
|
|
402
|
+
| {
|
|
403
|
+
/**
|
|
404
|
+
* Additional Noto Sans script families to include.
|
|
405
|
+
*
|
|
406
|
+
* Available scripts: arabic, armenian, bengali, chinese-simplified,
|
|
407
|
+
* chinese-traditional, chinese-hongkong, devanagari, ethiopic, farsi,
|
|
408
|
+
* georgian, gujarati, gurmukhi, hebrew, japanese, kannada, khmer,
|
|
409
|
+
* korean, lao, malayalam, myanmar, oriya, sinhala, tamil, telugu,
|
|
410
|
+
* thai, tibetan.
|
|
411
|
+
*/
|
|
412
|
+
scripts?: string[];
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Admin UI branding (white-labeling).
|
|
417
|
+
*
|
|
418
|
+
* Overrides the default EmDash logo and name in the admin panel.
|
|
419
|
+
* Use this to white-label the CMS for agency or enterprise deployments.
|
|
420
|
+
* These settings are separate from the public site settings (title, logo,
|
|
421
|
+
* favicon) which remain available for SEO and front-end use.
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* ```ts
|
|
425
|
+
* emdash({
|
|
426
|
+
* admin: {
|
|
427
|
+
* logo: "/images/agency-logo.webp",
|
|
428
|
+
* siteName: "AgencyX CMS",
|
|
429
|
+
* favicon: "/favicon.ico",
|
|
430
|
+
* },
|
|
431
|
+
* })
|
|
432
|
+
* ```
|
|
433
|
+
*/
|
|
434
|
+
admin?: {
|
|
435
|
+
/** URL or path to a custom logo image for the admin UI (login page, sidebar). */
|
|
436
|
+
logo?: string;
|
|
437
|
+
/** Custom name displayed in the admin sidebar and browser tab. */
|
|
438
|
+
siteName?: string;
|
|
439
|
+
/** URL or path to a custom favicon for the admin panel. */
|
|
440
|
+
favicon?: string;
|
|
441
|
+
};
|
|
325
442
|
}
|
|
326
443
|
|
|
327
444
|
/**
|
|
@@ -56,6 +56,9 @@ export const RESOLVED_VIRTUAL_BLOCK_COMPONENTS_ID = "\0" + VIRTUAL_BLOCK_COMPONE
|
|
|
56
56
|
export const VIRTUAL_SEED_ID = "virtual:emdash/seed";
|
|
57
57
|
export const RESOLVED_VIRTUAL_SEED_ID = "\0" + VIRTUAL_SEED_ID;
|
|
58
58
|
|
|
59
|
+
export const VIRTUAL_WAIT_UNTIL_ID = "virtual:emdash/wait-until";
|
|
60
|
+
export const RESOLVED_VIRTUAL_WAIT_UNTIL_ID = "\0" + VIRTUAL_WAIT_UNTIL_ID;
|
|
61
|
+
|
|
59
62
|
/**
|
|
60
63
|
* Generates the config virtual module.
|
|
61
64
|
*/
|
|
@@ -65,62 +68,42 @@ export function generateConfigModule(serializableConfig: Record<string, unknown>
|
|
|
65
68
|
|
|
66
69
|
/**
|
|
67
70
|
* Generates the dialect virtual module.
|
|
68
|
-
* Statically imports the configured database dialect and exports the dialect type.
|
|
69
|
-
*
|
|
70
|
-
* For D1 adapters, also re-exports session helpers (isSessionEnabled, getD1Binding,
|
|
71
|
-
* getDefaultConstraint, getBookmarkCookieName, createSessionDialect) used by
|
|
72
|
-
* middleware for per-request read replica sessions.
|
|
73
71
|
*
|
|
74
|
-
*
|
|
72
|
+
* Adapters that set `supportsRequestScope: true` on their descriptor are
|
|
73
|
+
* expected to export `createRequestScopedDb` from their runtime entrypoint;
|
|
74
|
+
* the generator re-exports it so middleware can ask for a per-request Kysely
|
|
75
|
+
* (used for D1 Sessions API, bookmark cookies, read-replica routing). Other
|
|
76
|
+
* adapters get a stub that returns null.
|
|
75
77
|
*/
|
|
76
|
-
export function generateDialectModule(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
): string {
|
|
81
|
-
|
|
78
|
+
export function generateDialectModule(opts: {
|
|
79
|
+
entrypoint?: string;
|
|
80
|
+
type?: string;
|
|
81
|
+
supportsRequestScope: boolean;
|
|
82
|
+
}): string {
|
|
83
|
+
const { entrypoint, supportsRequestScope } = opts;
|
|
84
|
+
if (!entrypoint) {
|
|
82
85
|
return [
|
|
83
86
|
`export const createDialect = undefined;`,
|
|
84
87
|
`export const dialectType = "sqlite";`,
|
|
85
|
-
`export const
|
|
86
|
-
`export const getD1Binding = () => null;`,
|
|
87
|
-
`export const getDefaultConstraint = () => "first-unconstrained";`,
|
|
88
|
-
`export const getBookmarkCookieName = () => "";`,
|
|
89
|
-
`export const createSessionDialect = undefined;`,
|
|
88
|
+
`export const createRequestScopedDb = (_opts) => null;`,
|
|
90
89
|
].join("\n");
|
|
91
90
|
}
|
|
92
|
-
const type =
|
|
91
|
+
const type = opts.type ?? "sqlite";
|
|
93
92
|
|
|
94
|
-
|
|
95
|
-
const isD1 = dbEntrypoint.includes("cloudflare") && dbEntrypoint.includes("d1");
|
|
96
|
-
|
|
97
|
-
// Check if sessions are enabled in the config
|
|
98
|
-
const sessionMode =
|
|
99
|
-
isD1 && dbConfig && typeof dbConfig === "object" && "session" in dbConfig
|
|
100
|
-
? // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- runtime-checked above
|
|
101
|
-
(dbConfig as { session?: string }).session
|
|
102
|
-
: undefined;
|
|
103
|
-
const sessionEnabled = !!sessionMode && sessionMode !== "disabled";
|
|
104
|
-
|
|
105
|
-
if (isD1 && sessionEnabled) {
|
|
93
|
+
if (supportsRequestScope) {
|
|
106
94
|
return `
|
|
107
|
-
import { createDialect as _createDialect } from "${
|
|
108
|
-
export {
|
|
95
|
+
import { createDialect as _createDialect } from "${entrypoint}";
|
|
96
|
+
export { createRequestScopedDb } from "${entrypoint}";
|
|
109
97
|
export const createDialect = _createDialect;
|
|
110
98
|
export const dialectType = ${JSON.stringify(type)};
|
|
111
99
|
`;
|
|
112
100
|
}
|
|
113
101
|
|
|
114
|
-
// Non-D1 or sessions disabled: export no-ops
|
|
115
102
|
return `
|
|
116
|
-
import { createDialect as _createDialect } from "${
|
|
103
|
+
import { createDialect as _createDialect } from "${entrypoint}";
|
|
117
104
|
export const createDialect = _createDialect;
|
|
118
105
|
export const dialectType = ${JSON.stringify(type)};
|
|
119
|
-
export const
|
|
120
|
-
export const getD1Binding = () => null;
|
|
121
|
-
export const getDefaultConstraint = () => "first-unconstrained";
|
|
122
|
-
export const getBookmarkCookieName = () => "";
|
|
123
|
-
export const createSessionDialect = undefined;
|
|
106
|
+
export const createRequestScopedDb = (_opts) => null;
|
|
124
107
|
`;
|
|
125
108
|
}
|
|
126
109
|
|
|
@@ -353,6 +336,25 @@ export function generateBlockComponentsModule(descriptors: PluginDescriptor[]):
|
|
|
353
336
|
return `${imports.join("\n")}\nexport const pluginBlockComponents = { ${spreads.join(", ")} };`;
|
|
354
337
|
}
|
|
355
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Generates the wait-until virtual module.
|
|
341
|
+
*
|
|
342
|
+
* Under @astrojs/cloudflare, re-exports `waitUntil` from `cloudflare:workers`
|
|
343
|
+
* so `after(fn)` in core can extend the worker's lifetime past the response
|
|
344
|
+
* for deferred bookkeeping. For any other adapter, exports `undefined` —
|
|
345
|
+
* Node's long-lived event loop keeps deferred promises running without a
|
|
346
|
+
* lifetime extender.
|
|
347
|
+
*
|
|
348
|
+
* Keeping the adapter check here — rather than in core — means core itself
|
|
349
|
+
* has no Cloudflare-specific imports or code paths.
|
|
350
|
+
*/
|
|
351
|
+
export function generateWaitUntilModule(adapterName: string | undefined): string {
|
|
352
|
+
if (adapterName === "@astrojs/cloudflare") {
|
|
353
|
+
return `export { waitUntil } from "cloudflare:workers";`;
|
|
354
|
+
}
|
|
355
|
+
return `export const waitUntil = undefined;`;
|
|
356
|
+
}
|
|
357
|
+
|
|
356
358
|
/**
|
|
357
359
|
* Generates the seed virtual module.
|
|
358
360
|
* Reads the user's seed file at build time (in Node context) and embeds it,
|
|
@@ -38,7 +38,10 @@ import {
|
|
|
38
38
|
RESOLVED_VIRTUAL_BLOCK_COMPONENTS_ID,
|
|
39
39
|
VIRTUAL_SEED_ID,
|
|
40
40
|
RESOLVED_VIRTUAL_SEED_ID,
|
|
41
|
+
VIRTUAL_WAIT_UNTIL_ID,
|
|
42
|
+
RESOLVED_VIRTUAL_WAIT_UNTIL_ID,
|
|
41
43
|
generateSeedModule,
|
|
44
|
+
generateWaitUntilModule,
|
|
42
45
|
generateConfigModule,
|
|
43
46
|
generateDialectModule,
|
|
44
47
|
generateStorageModule,
|
|
@@ -176,6 +179,9 @@ export function createVirtualModulesPlugin(options: VitePluginOptions): Plugin {
|
|
|
176
179
|
if (id === VIRTUAL_SEED_ID) {
|
|
177
180
|
return RESOLVED_VIRTUAL_SEED_ID;
|
|
178
181
|
}
|
|
182
|
+
if (id === VIRTUAL_WAIT_UNTIL_ID) {
|
|
183
|
+
return RESOLVED_VIRTUAL_WAIT_UNTIL_ID;
|
|
184
|
+
}
|
|
179
185
|
},
|
|
180
186
|
load(id: string) {
|
|
181
187
|
if (id === RESOLVED_VIRTUAL_CONFIG_ID) {
|
|
@@ -184,11 +190,11 @@ export function createVirtualModulesPlugin(options: VitePluginOptions): Plugin {
|
|
|
184
190
|
// Generate a module that statically imports the configured dialect
|
|
185
191
|
// This allows Vite to properly resolve and bundle it
|
|
186
192
|
if (id === RESOLVED_VIRTUAL_DIALECT_ID) {
|
|
187
|
-
return generateDialectModule(
|
|
188
|
-
resolvedConfig.database?.entrypoint,
|
|
189
|
-
resolvedConfig.database?.type,
|
|
190
|
-
resolvedConfig.database?.
|
|
191
|
-
);
|
|
193
|
+
return generateDialectModule({
|
|
194
|
+
entrypoint: resolvedConfig.database?.entrypoint,
|
|
195
|
+
type: resolvedConfig.database?.type,
|
|
196
|
+
supportsRequestScope: resolvedConfig.database?.supportsRequestScope ?? false,
|
|
197
|
+
});
|
|
192
198
|
}
|
|
193
199
|
// Generate a module that statically imports the configured storage
|
|
194
200
|
if (id === RESOLVED_VIRTUAL_STORAGE_ID) {
|
|
@@ -235,6 +241,11 @@ export function createVirtualModulesPlugin(options: VitePluginOptions): Plugin {
|
|
|
235
241
|
const projectRoot = fileURLToPath(astroConfig.root);
|
|
236
242
|
return generateSeedModule(projectRoot);
|
|
237
243
|
}
|
|
244
|
+
// Generate wait-until module — re-exports cloudflare:workers'
|
|
245
|
+
// waitUntil under the Cloudflare adapter, undefined otherwise.
|
|
246
|
+
if (id === RESOLVED_VIRTUAL_WAIT_UNTIL_ID) {
|
|
247
|
+
return generateWaitUntilModule(astroConfig.adapter?.name);
|
|
248
|
+
}
|
|
238
249
|
},
|
|
239
250
|
};
|
|
240
251
|
}
|
|
@@ -102,6 +102,7 @@ const PUBLIC_API_PREFIXES = [
|
|
|
102
102
|
"/_emdash/api/oauth/device/token",
|
|
103
103
|
"/_emdash/api/oauth/device/code",
|
|
104
104
|
"/_emdash/api/oauth/token",
|
|
105
|
+
"/_emdash/api/oauth/register",
|
|
105
106
|
"/_emdash/api/comments/",
|
|
106
107
|
"/_emdash/api/media/file/",
|
|
107
108
|
"/_emdash/.well-known/",
|
|
@@ -118,6 +119,30 @@ const PUBLIC_API_EXACT = new Set([
|
|
|
118
119
|
"/_emdash/api/search",
|
|
119
120
|
]);
|
|
120
121
|
|
|
122
|
+
/**
|
|
123
|
+
* OAuth protocol endpoints that are CSRF-exempt by design.
|
|
124
|
+
*
|
|
125
|
+
* These are RFC-defined endpoints (RFC 6749 §3.2, RFC 7591 §3, RFC 8628 §3.1/§3.4)
|
|
126
|
+
* specified to be called cross-origin by external clients (MCP clients, CLIs,
|
|
127
|
+
* native apps). They authenticate each request on its own merits:
|
|
128
|
+
*
|
|
129
|
+
* - /oauth/token: requires PKCE code_verifier, device_code, or refresh_token
|
|
130
|
+
* - /oauth/register: RFC 7591 dynamic client registration — anonymous by design
|
|
131
|
+
* - /oauth/device/code: RFC 8628 device flow initiation — anonymous by design
|
|
132
|
+
* - /oauth/device/token: requires device_code the client already holds
|
|
133
|
+
*
|
|
134
|
+
* None of these rely on ambient cookie credentials, so browser-based CSRF
|
|
135
|
+
* attacks have nothing to exploit. The endpoints themselves advertise
|
|
136
|
+
* `Access-Control-Allow-Origin: *`. Note: /oauth/device/authorize (the user
|
|
137
|
+
* consent step) is NOT in this list — it is session-authenticated.
|
|
138
|
+
*/
|
|
139
|
+
const CSRF_EXEMPT_PUBLIC_ROUTES = new Set([
|
|
140
|
+
"/_emdash/api/oauth/token",
|
|
141
|
+
"/_emdash/api/oauth/register",
|
|
142
|
+
"/_emdash/api/oauth/device/code",
|
|
143
|
+
"/_emdash/api/oauth/device/token",
|
|
144
|
+
]);
|
|
145
|
+
|
|
121
146
|
function isPublicEmDashRoute(pathname: string): boolean {
|
|
122
147
|
if (PUBLIC_API_EXACT.has(pathname)) return true;
|
|
123
148
|
if (PUBLIC_API_PREFIXES.some((p) => pathname.startsWith(p))) return true;
|
|
@@ -125,6 +150,10 @@ function isPublicEmDashRoute(pathname: string): boolean {
|
|
|
125
150
|
return false;
|
|
126
151
|
}
|
|
127
152
|
|
|
153
|
+
function isCsrfExemptPublicRoute(pathname: string): boolean {
|
|
154
|
+
return CSRF_EXEMPT_PUBLIC_ROUTES.has(pathname);
|
|
155
|
+
}
|
|
156
|
+
|
|
128
157
|
export const onRequest = defineMiddleware(async (context, next) => {
|
|
129
158
|
const { url } = context;
|
|
130
159
|
|
|
@@ -141,7 +170,10 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
141
170
|
// This prevents cross-origin form submissions and fetch requests from malicious sites.
|
|
142
171
|
if (isPublicApiRoute) {
|
|
143
172
|
const method = context.request.method.toUpperCase();
|
|
144
|
-
if (
|
|
173
|
+
if (
|
|
174
|
+
isUnsafeMethod(method) &&
|
|
175
|
+
!isCsrfExemptPublicRoute(url.pathname) // OAuth protocol endpoints — cross-origin by design
|
|
176
|
+
) {
|
|
145
177
|
const publicOrigin = getPublicOrigin(url, context.locals.emdash?.config);
|
|
146
178
|
const csrfError = checkPublicCsrf(context.request, url, publicOrigin);
|
|
147
179
|
if (csrfError) return csrfError;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { defineMiddleware } from "astro:middleware";
|
|
14
14
|
|
|
15
15
|
import { verifyPreviewToken, parseContentId } from "../../preview/tokens.js";
|
|
16
|
-
import { runWithContext } from "../../request-context.js";
|
|
16
|
+
import { getRequestContext, runWithContext } from "../../request-context.js";
|
|
17
17
|
import { renderToolbar } from "../../visual-editing/toolbar.js";
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -49,11 +49,18 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
49
49
|
// Playground mode: the playground middleware (from @emdash-cms/cloudflare) stashes
|
|
50
50
|
// the per-session DO database on locals.__playgroundDb. We set it via ALS here
|
|
51
51
|
// (same module instance as the loader) so getDb() picks it up correctly.
|
|
52
|
+
//
|
|
53
|
+
// `dbIsIsolated: true` tells schema-derived caches (manifest, taxonomy defs,
|
|
54
|
+
// byline/term existence probes) to bypass module-scope memoization — each
|
|
55
|
+
// playground session is its own database with its own schema, so a cached
|
|
56
|
+
// value from another session would be wrong.
|
|
52
57
|
const playgroundDb = context.locals.__playgroundDb;
|
|
53
58
|
if (playgroundDb) {
|
|
54
59
|
// Check if playground user has toggled edit mode on
|
|
55
60
|
const hasEditCookie = cookies.get("emdash-edit-mode")?.value === "true";
|
|
56
|
-
return runWithContext({ editMode: hasEditCookie, db: playgroundDb }, () =>
|
|
61
|
+
return runWithContext({ editMode: hasEditCookie, db: playgroundDb, dbIsIsolated: true }, () =>
|
|
62
|
+
next(),
|
|
63
|
+
);
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
// Fast path: check for CMS signals before doing any work
|
|
@@ -90,7 +97,12 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
90
97
|
const needsContext = hasEditCookie || hasPreviewToken;
|
|
91
98
|
|
|
92
99
|
if (needsContext) {
|
|
93
|
-
|
|
100
|
+
// Merge with any outer ALS context (e.g. the per-request D1 session db
|
|
101
|
+
// set by the runtime middleware). `storage.run()` replaces the store
|
|
102
|
+
// wholesale, so without the spread the outer `db` would be lost and
|
|
103
|
+
// loaders would fall back to the singleton non-session dialect.
|
|
104
|
+
const parent = getRequestContext();
|
|
105
|
+
return runWithContext({ ...parent, editMode, preview, locale }, async () => {
|
|
94
106
|
let response = await next();
|
|
95
107
|
|
|
96
108
|
// Preview responses must not be cached -- draft content could leak past token expiry.
|