emdash 0.0.0-b → 0.0.2
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 +87 -43
- package/dist/adapters-BLMa4JGD.d.mts +106 -0
- package/dist/adapters-BLMa4JGD.d.mts.map +1 -0
- package/dist/apply-Bjfq_b4-.mjs +1293 -0
- package/dist/apply-Bjfq_b4-.mjs.map +1 -0
- package/dist/astro/index.d.mts +51 -0
- package/dist/astro/index.d.mts.map +1 -0
- package/dist/astro/index.mjs +1336 -0
- package/dist/astro/index.mjs.map +1 -0
- package/dist/astro/middleware/auth.d.mts +31 -0
- package/dist/astro/middleware/auth.d.mts.map +1 -0
- package/dist/astro/middleware/auth.mjs +654 -0
- package/dist/astro/middleware/auth.mjs.map +1 -0
- package/dist/astro/middleware/redirect.d.mts +22 -0
- package/dist/astro/middleware/redirect.d.mts.map +1 -0
- package/dist/astro/middleware/redirect.mjs +63 -0
- package/dist/astro/middleware/redirect.mjs.map +1 -0
- package/dist/astro/middleware/request-context.d.mts +18 -0
- package/dist/astro/middleware/request-context.d.mts.map +1 -0
- package/dist/astro/middleware/request-context.mjs +1310 -0
- package/dist/astro/middleware/request-context.mjs.map +1 -0
- package/dist/astro/middleware/setup.d.mts +20 -0
- package/dist/astro/middleware/setup.d.mts.map +1 -0
- package/dist/astro/middleware/setup.mjs +47 -0
- package/dist/astro/middleware/setup.mjs.map +1 -0
- package/dist/astro/middleware.d.mts +13 -0
- package/dist/astro/middleware.d.mts.map +1 -0
- package/dist/astro/middleware.mjs +1613 -0
- package/dist/astro/middleware.mjs.map +1 -0
- package/dist/astro/types.d.mts +250 -0
- package/dist/astro/types.d.mts.map +1 -0
- package/dist/astro/types.mjs +1 -0
- package/dist/base64-MBPo9ozB.mjs +59 -0
- package/dist/base64-MBPo9ozB.mjs.map +1 -0
- package/dist/byline-CL847F26.mjs +213 -0
- package/dist/byline-CL847F26.mjs.map +1 -0
- package/dist/bylines-C2a-2TGt.mjs +136 -0
- package/dist/bylines-C2a-2TGt.mjs.map +1 -0
- package/dist/chunk-ClPoSABd.mjs +21 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +3909 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/client/cf-access.d.mts +60 -0
- package/dist/client/cf-access.d.mts.map +1 -0
- package/dist/client/cf-access.mjs +179 -0
- package/dist/client/cf-access.mjs.map +1 -0
- package/dist/client/index.d.mts +398 -0
- package/dist/client/index.d.mts.map +1 -0
- package/dist/client/index.mjs +346 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/config-CKE8p9xM.mjs +55 -0
- package/dist/config-CKE8p9xM.mjs.map +1 -0
- package/dist/connection-B4zVnQIa.mjs +40 -0
- package/dist/connection-B4zVnQIa.mjs.map +1 -0
- package/dist/content-D6C2WsZC.mjs +824 -0
- package/dist/content-D6C2WsZC.mjs.map +1 -0
- package/dist/db/index.d.mts +4 -0
- package/dist/db/index.mjs +62 -0
- package/dist/db/index.mjs.map +1 -0
- package/dist/db/libsql.d.mts +11 -0
- package/dist/db/libsql.d.mts.map +1 -0
- package/dist/db/libsql.mjs +17 -0
- package/dist/db/libsql.mjs.map +1 -0
- package/dist/db/postgres.d.mts +11 -0
- package/dist/db/postgres.d.mts.map +1 -0
- package/dist/db/postgres.mjs +30 -0
- package/dist/db/postgres.mjs.map +1 -0
- package/dist/db/sqlite.d.mts +11 -0
- package/dist/db/sqlite.d.mts.map +1 -0
- package/dist/db/sqlite.mjs +16 -0
- package/dist/db/sqlite.mjs.map +1 -0
- package/dist/default-Cyi4aAxu.mjs +81 -0
- package/dist/default-Cyi4aAxu.mjs.map +1 -0
- package/dist/dialect-helpers-B9uSp2GJ.mjs +90 -0
- package/dist/dialect-helpers-B9uSp2GJ.mjs.map +1 -0
- package/dist/error-Cxz0tQeO.mjs +27 -0
- package/dist/error-Cxz0tQeO.mjs.map +1 -0
- package/dist/index-C1xF3OGh.d.mts +4527 -0
- package/dist/index-C1xF3OGh.d.mts.map +1 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +30 -0
- package/dist/load-yOOlckBj.mjs +28 -0
- package/dist/load-yOOlckBj.mjs.map +1 -0
- package/dist/loader-fz8Q_3EO.mjs +447 -0
- package/dist/loader-fz8Q_3EO.mjs.map +1 -0
- package/dist/manifest-schema-Dcl0R6nM.mjs +184 -0
- package/dist/manifest-schema-Dcl0R6nM.mjs.map +1 -0
- package/dist/media/index.d.mts +26 -0
- package/dist/media/index.d.mts.map +1 -0
- package/dist/media/index.mjs +55 -0
- package/dist/media/index.mjs.map +1 -0
- package/dist/media/local-runtime.d.mts +39 -0
- package/dist/media/local-runtime.d.mts.map +1 -0
- package/dist/media/local-runtime.mjs +133 -0
- package/dist/media/local-runtime.mjs.map +1 -0
- package/dist/media-DqHVh136.mjs +200 -0
- package/dist/media-DqHVh136.mjs.map +1 -0
- package/dist/mode-C2EzN1uE.mjs +23 -0
- package/dist/mode-C2EzN1uE.mjs.map +1 -0
- package/dist/page/index.d.mts +140 -0
- package/dist/page/index.d.mts.map +1 -0
- package/dist/page/index.mjs +416 -0
- package/dist/page/index.mjs.map +1 -0
- package/dist/placeholder-CmGAmqeO.d.mts +276 -0
- package/dist/placeholder-CmGAmqeO.d.mts.map +1 -0
- package/dist/placeholder-SmpOx-_v.mjs +243 -0
- package/dist/placeholder-SmpOx-_v.mjs.map +1 -0
- package/dist/plugin-utils.d.mts +58 -0
- package/dist/plugin-utils.d.mts.map +1 -0
- package/dist/plugin-utils.mjs +78 -0
- package/dist/plugin-utils.mjs.map +1 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts +22 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -0
- package/dist/plugins/adapt-sandbox-entry.mjs +113 -0
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -0
- package/dist/query-CS_iSj34.mjs +460 -0
- package/dist/query-CS_iSj34.mjs.map +1 -0
- package/dist/redirect-DIfIni3r.mjs +329 -0
- package/dist/redirect-DIfIni3r.mjs.map +1 -0
- package/dist/registry-D_w5HW4G.mjs +863 -0
- package/dist/registry-D_w5HW4G.mjs.map +1 -0
- package/dist/request-context.d.mts +49 -0
- package/dist/request-context.d.mts.map +1 -0
- package/dist/request-context.mjs +43 -0
- package/dist/request-context.mjs.map +1 -0
- package/dist/runner-C0hCbYnD.mjs +1412 -0
- package/dist/runner-C0hCbYnD.mjs.map +1 -0
- package/dist/runner-EAtf0ZIe.d.mts +27 -0
- package/dist/runner-EAtf0ZIe.d.mts.map +1 -0
- package/dist/runtime.d.mts +26 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +42 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/search-DG603UrT.mjs +9211 -0
- package/dist/search-DG603UrT.mjs.map +1 -0
- package/dist/seed/index.d.mts +3 -0
- package/dist/seed/index.mjs +15 -0
- package/dist/seo/index.d.mts +70 -0
- package/dist/seo/index.d.mts.map +1 -0
- package/dist/seo/index.mjs +70 -0
- package/dist/seo/index.mjs.map +1 -0
- package/dist/storage/local.d.mts +39 -0
- package/dist/storage/local.d.mts.map +1 -0
- package/dist/storage/local.mjs +166 -0
- package/dist/storage/local.mjs.map +1 -0
- package/dist/storage/s3.d.mts +32 -0
- package/dist/storage/s3.d.mts.map +1 -0
- package/dist/storage/s3.mjs +175 -0
- package/dist/storage/s3.mjs.map +1 -0
- package/dist/tokens-DpgrkrXK.mjs +171 -0
- package/dist/tokens-DpgrkrXK.mjs.map +1 -0
- package/dist/transport-BFGblqwG.d.mts +42 -0
- package/dist/transport-BFGblqwG.d.mts.map +1 -0
- package/dist/transport-yxiQsi8I.mjs +418 -0
- package/dist/transport-yxiQsi8I.mjs.map +1 -0
- package/dist/types-BRuPJGdV.d.mts +102 -0
- package/dist/types-BRuPJGdV.d.mts.map +1 -0
- package/dist/types-C4-fAxN3.d.mts +182 -0
- package/dist/types-C4-fAxN3.d.mts.map +1 -0
- package/dist/types-CMMN0pNg.mjs +31 -0
- package/dist/types-CMMN0pNg.mjs.map +1 -0
- package/dist/types-CUBbjgmP.mjs +16 -0
- package/dist/types-CUBbjgmP.mjs.map +1 -0
- package/dist/types-DRjfYOEv.d.mts +426 -0
- package/dist/types-DRjfYOEv.d.mts.map +1 -0
- package/dist/types-DY5zk5HN.mjs +73 -0
- package/dist/types-DY5zk5HN.mjs.map +1 -0
- package/dist/types-DaNLHo_T.d.mts +184 -0
- package/dist/types-DaNLHo_T.d.mts.map +1 -0
- package/dist/types-DvhsUmSJ.d.mts +1111 -0
- package/dist/types-DvhsUmSJ.d.mts.map +1 -0
- package/dist/validate-CpBtVMsD.d.mts +378 -0
- package/dist/validate-CpBtVMsD.d.mts.map +1 -0
- package/dist/validate-CqRJb_xU.mjs +97 -0
- package/dist/validate-CqRJb_xU.mjs.map +1 -0
- package/dist/validate-O7PWmlnq.mjs +328 -0
- package/dist/validate-O7PWmlnq.mjs.map +1 -0
- package/locals.d.ts +46 -0
- package/package.json +233 -19
- package/src/api/authorize.ts +63 -0
- package/src/api/csrf.ts +48 -0
- package/src/api/error.ts +99 -0
- package/src/api/errors.ts +445 -0
- package/src/api/escape.ts +9 -0
- package/src/api/handlers/api-tokens.ts +240 -0
- package/src/api/handlers/comments.ts +314 -0
- package/src/api/handlers/content.ts +1315 -0
- package/src/api/handlers/dashboard.ts +205 -0
- package/src/api/handlers/device-flow.ts +684 -0
- package/src/api/handlers/index.ts +163 -0
- package/src/api/handlers/manifest.ts +158 -0
- package/src/api/handlers/marketplace.ts +930 -0
- package/src/api/handlers/media.ts +207 -0
- package/src/api/handlers/menus.ts +493 -0
- package/src/api/handlers/oauth-authorization.ts +429 -0
- package/src/api/handlers/oauth-clients.ts +349 -0
- package/src/api/handlers/oauth-user-lookup.ts +39 -0
- package/src/api/handlers/plugins.ts +254 -0
- package/src/api/handlers/redirects.ts +360 -0
- package/src/api/handlers/revision.ts +145 -0
- package/src/api/handlers/schema.ts +534 -0
- package/src/api/handlers/sections.ts +289 -0
- package/src/api/handlers/seo.ts +115 -0
- package/src/api/handlers/settings.ts +49 -0
- package/src/api/handlers/snapshot.ts +350 -0
- package/src/api/handlers/taxonomies.ts +523 -0
- package/src/api/index.ts +6 -0
- package/src/api/openapi/document.ts +2368 -0
- package/src/api/openapi/index.ts +1 -0
- package/src/api/parse.ts +139 -0
- package/src/api/redirect.ts +14 -0
- package/src/api/rev.ts +67 -0
- package/src/api/schemas/auth.ts +112 -0
- package/src/api/schemas/bylines.ts +85 -0
- package/src/api/schemas/comments.ts +117 -0
- package/src/api/schemas/common.ts +89 -0
- package/src/api/schemas/content.ts +191 -0
- package/src/api/schemas/import.ts +52 -0
- package/src/api/schemas/index.ts +17 -0
- package/src/api/schemas/media.ts +116 -0
- package/src/api/schemas/menus.ts +111 -0
- package/src/api/schemas/redirects.ts +155 -0
- package/src/api/schemas/schema.ts +203 -0
- package/src/api/schemas/search.ts +63 -0
- package/src/api/schemas/sections.ts +67 -0
- package/src/api/schemas/settings.ts +63 -0
- package/src/api/schemas/setup.ts +37 -0
- package/src/api/schemas/taxonomies.ts +113 -0
- package/src/api/schemas/users.ts +96 -0
- package/src/api/schemas/widgets.ts +80 -0
- package/src/api/site-url.ts +25 -0
- package/src/api/types.ts +82 -0
- package/src/astro/index.ts +27 -0
- package/src/astro/integration/index.ts +303 -0
- package/src/astro/integration/routes.ts +834 -0
- package/src/astro/integration/runtime.ts +338 -0
- package/src/astro/integration/virtual-modules.ts +469 -0
- package/src/astro/integration/vite-config.ts +335 -0
- package/src/astro/middleware/auth.ts +743 -0
- package/src/astro/middleware/redirect.ts +89 -0
- package/src/astro/middleware/request-context.ts +129 -0
- package/src/astro/middleware/setup.ts +89 -0
- package/src/astro/middleware.ts +398 -0
- package/src/astro/routes/PluginRegistry.tsx +15 -0
- package/src/astro/routes/admin.astro +81 -0
- package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
- package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
- package/src/astro/routes/api/admin/api-tokens/[id].ts +40 -0
- package/src/astro/routes/api/admin/api-tokens/index.ts +68 -0
- package/src/astro/routes/api/admin/bylines/[id]/index.ts +87 -0
- package/src/astro/routes/api/admin/bylines/index.ts +72 -0
- package/src/astro/routes/api/admin/comments/[id]/status.ts +116 -0
- package/src/astro/routes/api/admin/comments/[id].ts +64 -0
- package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
- package/src/astro/routes/api/admin/comments/counts.ts +30 -0
- package/src/astro/routes/api/admin/comments/index.ts +46 -0
- package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +91 -0
- package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +110 -0
- package/src/astro/routes/api/admin/oauth-clients/index.ts +71 -0
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +39 -0
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +39 -0
- package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +48 -0
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +59 -0
- package/src/astro/routes/api/admin/plugins/index.ts +32 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +61 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +62 -0
- package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
- package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
- package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
- package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +61 -0
- package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
- package/src/astro/routes/api/admin/users/[id]/disable.ts +69 -0
- package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
- package/src/astro/routes/api/admin/users/[id]/index.ts +146 -0
- package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
- package/src/astro/routes/api/admin/users/index.ts +66 -0
- package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
- package/src/astro/routes/api/auth/invite/accept.ts +52 -0
- package/src/astro/routes/api/auth/invite/complete.ts +84 -0
- package/src/astro/routes/api/auth/invite/index.ts +99 -0
- package/src/astro/routes/api/auth/logout.ts +40 -0
- package/src/astro/routes/api/auth/magic-link/send.ts +89 -0
- package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
- package/src/astro/routes/api/auth/me.ts +60 -0
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +219 -0
- package/src/astro/routes/api/auth/oauth/[provider].ts +119 -0
- package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
- package/src/astro/routes/api/auth/passkey/index.ts +54 -0
- package/src/astro/routes/api/auth/passkey/options.ts +82 -0
- package/src/astro/routes/api/auth/passkey/register/options.ts +86 -0
- package/src/astro/routes/api/auth/passkey/register/verify.ts +115 -0
- package/src/astro/routes/api/auth/passkey/verify.ts +66 -0
- package/src/astro/routes/api/auth/signup/complete.ts +85 -0
- package/src/astro/routes/api/auth/signup/request.ts +77 -0
- package/src/astro/routes/api/auth/signup/verify.ts +53 -0
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +312 -0
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +54 -0
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +61 -0
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +33 -0
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +56 -0
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +54 -0
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +101 -0
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +140 -0
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +30 -0
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +56 -0
- package/src/astro/routes/api/content/[collection]/[id].ts +137 -0
- package/src/astro/routes/api/content/[collection]/index.ts +59 -0
- package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
- package/src/astro/routes/api/dashboard.ts +32 -0
- package/src/astro/routes/api/dev/emails.ts +36 -0
- package/src/astro/routes/api/import/probe.ts +47 -0
- package/src/astro/routes/api/import/wordpress/analyze.ts +510 -0
- package/src/astro/routes/api/import/wordpress/execute.ts +283 -0
- package/src/astro/routes/api/import/wordpress/media.ts +338 -0
- package/src/astro/routes/api/import/wordpress/prepare.ts +181 -0
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +393 -0
- package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
- package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +347 -0
- package/src/astro/routes/api/manifest.ts +62 -0
- package/src/astro/routes/api/mcp.ts +124 -0
- package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
- package/src/astro/routes/api/media/[id].ts +145 -0
- package/src/astro/routes/api/media/file/[key].ts +79 -0
- package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +86 -0
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
- package/src/astro/routes/api/media/providers/index.ts +30 -0
- package/src/astro/routes/api/media/upload-url.ts +137 -0
- package/src/astro/routes/api/media.ts +190 -0
- package/src/astro/routes/api/menus/[name]/items.ts +87 -0
- package/src/astro/routes/api/menus/[name]/reorder.ts +33 -0
- package/src/astro/routes/api/menus/[name].ts +65 -0
- package/src/astro/routes/api/menus/index.ts +47 -0
- package/src/astro/routes/api/oauth/authorize.ts +412 -0
- package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
- package/src/astro/routes/api/oauth/device/code.ts +51 -0
- package/src/astro/routes/api/oauth/device/token.ts +69 -0
- package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
- package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
- package/src/astro/routes/api/oauth/token.ts +184 -0
- package/src/astro/routes/api/openapi.json.ts +32 -0
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +92 -0
- package/src/astro/routes/api/redirects/404s/index.ts +72 -0
- package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
- package/src/astro/routes/api/redirects/[id].ts +84 -0
- package/src/astro/routes/api/redirects/index.ts +52 -0
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
- package/src/astro/routes/api/revisions/[revisionId]/restore.ts +58 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +76 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +52 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +32 -0
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +80 -0
- package/src/astro/routes/api/schema/collections/index.ts +47 -0
- package/src/astro/routes/api/schema/index.ts +109 -0
- package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
- package/src/astro/routes/api/schema/orphans/index.ts +26 -0
- package/src/astro/routes/api/search/enable.ts +64 -0
- package/src/astro/routes/api/search/index.ts +55 -0
- package/src/astro/routes/api/search/rebuild.ts +72 -0
- package/src/astro/routes/api/search/stats.ts +35 -0
- package/src/astro/routes/api/search/suggest.ts +53 -0
- package/src/astro/routes/api/sections/[slug].ts +84 -0
- package/src/astro/routes/api/sections/index.ts +52 -0
- package/src/astro/routes/api/settings/email.ts +150 -0
- package/src/astro/routes/api/settings.ts +67 -0
- package/src/astro/routes/api/setup/admin-verify.ts +100 -0
- package/src/astro/routes/api/setup/admin.ts +94 -0
- package/src/astro/routes/api/setup/dev-bypass.ts +199 -0
- package/src/astro/routes/api/setup/dev-reset.ts +40 -0
- package/src/astro/routes/api/setup/index.ts +126 -0
- package/src/astro/routes/api/setup/status.ts +122 -0
- package/src/astro/routes/api/snapshot.ts +75 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +95 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +69 -0
- package/src/astro/routes/api/taxonomies/index.ts +59 -0
- package/src/astro/routes/api/themes/preview.ts +77 -0
- package/src/astro/routes/api/typegen.ts +114 -0
- package/src/astro/routes/api/well-known/auth.ts +68 -0
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +44 -0
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +37 -0
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +68 -0
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +127 -0
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +80 -0
- package/src/astro/routes/api/widget-areas/[name].ts +87 -0
- package/src/astro/routes/api/widget-areas/index.ts +99 -0
- package/src/astro/routes/api/widget-components.ts +22 -0
- package/src/astro/routes/robots.txt.ts +77 -0
- package/src/astro/routes/sitemap.xml.ts +97 -0
- package/src/astro/storage/adapters.ts +74 -0
- package/src/astro/storage/index.ts +19 -0
- package/src/astro/storage/types.ts +60 -0
- package/src/astro/types.ts +346 -0
- package/src/auth/api-tokens.ts +25 -0
- package/src/auth/challenge-store.ts +80 -0
- package/src/auth/mode.ts +96 -0
- package/src/auth/oauth-state-store.ts +96 -0
- package/src/auth/passkey-config.ts +27 -0
- package/src/auth/rate-limit.ts +158 -0
- package/src/auth/scopes.ts +33 -0
- package/src/auth/types.ts +104 -0
- package/src/aws-sdk.d.ts +100 -0
- package/src/bylines/index.ts +237 -0
- package/src/cleanup.ts +153 -0
- package/src/cli/client-factory.ts +100 -0
- package/src/cli/commands/auth.ts +46 -0
- package/src/cli/commands/bundle-utils.ts +247 -0
- package/src/cli/commands/bundle.ts +609 -0
- package/src/cli/commands/content.ts +442 -0
- package/src/cli/commands/dev.ts +191 -0
- package/src/cli/commands/doctor.ts +211 -0
- package/src/cli/commands/export-seed.ts +630 -0
- package/src/cli/commands/import/wordpress.ts +1056 -0
- package/src/cli/commands/init.ts +192 -0
- package/src/cli/commands/login.ts +547 -0
- package/src/cli/commands/media.ts +165 -0
- package/src/cli/commands/menu.ts +67 -0
- package/src/cli/commands/plugin-init.ts +291 -0
- package/src/cli/commands/plugin-validate.ts +31 -0
- package/src/cli/commands/plugin.ts +33 -0
- package/src/cli/commands/publish.ts +697 -0
- package/src/cli/commands/schema.ts +233 -0
- package/src/cli/commands/search-cmd.ts +54 -0
- package/src/cli/commands/seed.ts +286 -0
- package/src/cli/commands/taxonomy.ts +128 -0
- package/src/cli/commands/types.ts +68 -0
- package/src/cli/credentials.ts +236 -0
- package/src/cli/index.ts +70 -0
- package/src/cli/output.ts +75 -0
- package/src/cli/wxr/parser.ts +969 -0
- package/src/client/cf-access.ts +193 -0
- package/src/client/index.ts +854 -0
- package/src/client/portable-text.ts +413 -0
- package/src/client/transport.ts +200 -0
- package/src/comments/moderator.ts +46 -0
- package/src/comments/notifications.ts +144 -0
- package/src/comments/query.ts +105 -0
- package/src/comments/service.ts +213 -0
- package/src/components/Break.astro +45 -0
- package/src/components/Button.astro +71 -0
- package/src/components/Buttons.astro +49 -0
- package/src/components/Code.astro +59 -0
- package/src/components/Columns.astro +59 -0
- package/src/components/CommentForm.astro +315 -0
- package/src/components/Comments.astro +232 -0
- package/src/components/Cover.astro +128 -0
- package/src/components/EmDashBodyEnd.astro +32 -0
- package/src/components/EmDashBodyStart.astro +32 -0
- package/src/components/EmDashHead.astro +53 -0
- package/src/components/EmDashImage.astro +178 -0
- package/src/components/EmDashMedia.astro +167 -0
- package/src/components/Embed.astro +128 -0
- package/src/components/File.astro +122 -0
- package/src/components/Gallery.astro +93 -0
- package/src/components/HtmlBlock.astro +33 -0
- package/src/components/Image.astro +178 -0
- package/src/components/InlineEditor.astro +27 -0
- package/src/components/InlinePortableTextEditor.tsx +1905 -0
- package/src/components/LiveSearch.astro +614 -0
- package/src/components/PortableText.astro +51 -0
- package/src/components/Pullquote.astro +51 -0
- package/src/components/Table.astro +108 -0
- package/src/components/WidgetArea.astro +22 -0
- package/src/components/WidgetRenderer.astro +72 -0
- package/src/components/index.ts +116 -0
- package/src/components/marks/Link.astro +31 -0
- package/src/components/marks/StrikeThrough.astro +7 -0
- package/src/components/marks/Subscript.astro +7 -0
- package/src/components/marks/Superscript.astro +7 -0
- package/src/components/marks/Underline.astro +7 -0
- package/src/components/widgets/Archives.astro +65 -0
- package/src/components/widgets/Categories.astro +35 -0
- package/src/components/widgets/RecentPosts.astro +51 -0
- package/src/components/widgets/Search.astro +18 -0
- package/src/components/widgets/Tags.astro +38 -0
- package/src/content/converters/index.ts +9 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +385 -0
- package/src/content/converters/prosemirror-to-portable-text.ts +413 -0
- package/src/content/converters/types.ts +120 -0
- package/src/content/index.ts +5 -0
- package/src/database/connection.ts +67 -0
- package/src/database/dialect-helpers.ts +138 -0
- package/src/database/index.ts +5 -0
- package/src/database/migrations/001_initial.ts +170 -0
- package/src/database/migrations/002_media_status.ts +26 -0
- package/src/database/migrations/003_schema_registry.ts +79 -0
- package/src/database/migrations/004_plugins.ts +62 -0
- package/src/database/migrations/005_menus.ts +67 -0
- package/src/database/migrations/006_taxonomy_defs.ts +51 -0
- package/src/database/migrations/007_widgets.ts +42 -0
- package/src/database/migrations/008_auth.ts +194 -0
- package/src/database/migrations/009_user_disabled.ts +27 -0
- package/src/database/migrations/011_sections.ts +65 -0
- package/src/database/migrations/012_search.ts +25 -0
- package/src/database/migrations/013_scheduled_publishing.ts +51 -0
- package/src/database/migrations/014_draft_revisions.ts +72 -0
- package/src/database/migrations/015_indexes.ts +82 -0
- package/src/database/migrations/016_api_tokens.ts +89 -0
- package/src/database/migrations/017_authorization_codes.ts +45 -0
- package/src/database/migrations/018_seo.ts +56 -0
- package/src/database/migrations/019_i18n.ts +618 -0
- package/src/database/migrations/020_collection_url_pattern.ts +23 -0
- package/src/database/migrations/021_remove_section_categories.ts +43 -0
- package/src/database/migrations/022_marketplace_plugin_state.ts +46 -0
- package/src/database/migrations/023_plugin_metadata.ts +33 -0
- package/src/database/migrations/024_media_placeholders.ts +32 -0
- package/src/database/migrations/025_oauth_clients.ts +28 -0
- package/src/database/migrations/026_cron_tasks.ts +49 -0
- package/src/database/migrations/027_comments.ts +87 -0
- package/src/database/migrations/028_drop_author_url.ts +9 -0
- package/src/database/migrations/029_redirects.ts +67 -0
- package/src/database/migrations/030_widen_scheduled_index.ts +48 -0
- package/src/database/migrations/031_bylines.ts +90 -0
- package/src/database/migrations/032_rate_limits.ts +39 -0
- package/src/database/migrations/runner.ts +170 -0
- package/src/database/repositories/audit.ts +294 -0
- package/src/database/repositories/byline.ts +387 -0
- package/src/database/repositories/comment.ts +458 -0
- package/src/database/repositories/content.ts +1144 -0
- package/src/database/repositories/index.ts +30 -0
- package/src/database/repositories/media.ts +347 -0
- package/src/database/repositories/options.ts +150 -0
- package/src/database/repositories/plugin-storage.ts +373 -0
- package/src/database/repositories/redirect.ts +480 -0
- package/src/database/repositories/revision.ts +200 -0
- package/src/database/repositories/seo.ts +176 -0
- package/src/database/repositories/taxonomy.ts +294 -0
- package/src/database/repositories/types.ts +132 -0
- package/src/database/repositories/user.ts +258 -0
- package/src/database/transaction.ts +54 -0
- package/src/database/types.ts +501 -0
- package/src/database/validate.ts +138 -0
- package/src/db/adapters.ts +125 -0
- package/src/db/index.ts +37 -0
- package/src/db/libsql.ts +23 -0
- package/src/db/postgres.ts +30 -0
- package/src/db/sqlite.ts +27 -0
- package/src/emdash-runtime.ts +2096 -0
- package/src/fields/boolean.ts +34 -0
- package/src/fields/datetime.ts +44 -0
- package/src/fields/file.ts +41 -0
- package/src/fields/image.ts +34 -0
- package/src/fields/index.ts +42 -0
- package/src/fields/integer.ts +50 -0
- package/src/fields/json.ts +37 -0
- package/src/fields/multiselect.ts +48 -0
- package/src/fields/number.ts +52 -0
- package/src/fields/portable-text.ts +33 -0
- package/src/fields/reference.ts +29 -0
- package/src/fields/richtext.ts +31 -0
- package/src/fields/select.ts +46 -0
- package/src/fields/slug.ts +38 -0
- package/src/fields/text.ts +55 -0
- package/src/fields/textarea.ts +52 -0
- package/src/fields/types.ts +64 -0
- package/src/i18n/config.ts +68 -0
- package/src/import/index.ts +90 -0
- package/src/import/menus.ts +436 -0
- package/src/import/registry.ts +111 -0
- package/src/import/sections.ts +103 -0
- package/src/import/settings.ts +281 -0
- package/src/import/sources/wordpress-plugin.ts +641 -0
- package/src/import/sources/wordpress-rest.ts +191 -0
- package/src/import/sources/wxr.ts +330 -0
- package/src/import/ssrf.ts +260 -0
- package/src/import/types.ts +418 -0
- package/src/import/utils.ts +412 -0
- package/src/index.ts +481 -0
- package/src/loader.ts +770 -0
- package/src/mcp/server.ts +1463 -0
- package/src/media/index.ts +32 -0
- package/src/media/local-runtime.ts +213 -0
- package/src/media/local.ts +46 -0
- package/src/media/normalize.ts +190 -0
- package/src/media/placeholder.ts +150 -0
- package/src/media/provider-loader.ts +78 -0
- package/src/media/types.ts +279 -0
- package/src/menus/index.ts +324 -0
- package/src/menus/types.ts +112 -0
- package/src/page/context.ts +93 -0
- package/src/page/fragments.ts +89 -0
- package/src/page/index.ts +58 -0
- package/src/page/jsonld.ts +94 -0
- package/src/page/metadata.ts +185 -0
- package/src/page/seo-contributions.ts +136 -0
- package/src/plugin-utils.ts +80 -0
- package/src/plugins/adapt-sandbox-entry.ts +207 -0
- package/src/plugins/context.ts +833 -0
- package/src/plugins/cron.ts +361 -0
- package/src/plugins/define-plugin.ts +259 -0
- package/src/plugins/email-console.ts +73 -0
- package/src/plugins/email.ts +209 -0
- package/src/plugins/hooks.ts +1273 -0
- package/src/plugins/index.ts +193 -0
- package/src/plugins/manager.ts +595 -0
- package/src/plugins/manifest-schema.ts +230 -0
- package/src/plugins/marketplace.ts +460 -0
- package/src/plugins/request-meta.ts +139 -0
- package/src/plugins/routes.ts +302 -0
- package/src/plugins/sandbox/index.ts +18 -0
- package/src/plugins/sandbox/noop.ts +76 -0
- package/src/plugins/sandbox/types.ts +173 -0
- package/src/plugins/scheduler/node.ts +122 -0
- package/src/plugins/scheduler/piggyback.ts +71 -0
- package/src/plugins/scheduler/types.ts +27 -0
- package/src/plugins/state.ts +208 -0
- package/src/plugins/storage-indexes.ts +326 -0
- package/src/plugins/storage-query.ts +240 -0
- package/src/plugins/types.ts +1284 -0
- package/src/preview/helpers.ts +27 -0
- package/src/preview/index.ts +40 -0
- package/src/preview/tokens.ts +279 -0
- package/src/preview/urls.ts +118 -0
- package/src/query.ts +674 -0
- package/src/redirects/patterns.ts +224 -0
- package/src/request-context.ts +67 -0
- package/src/runtime.ts +21 -0
- package/src/schema/index.ts +29 -0
- package/src/schema/query.ts +44 -0
- package/src/schema/registry.ts +965 -0
- package/src/schema/types.ts +276 -0
- package/src/schema/zod-generator.ts +413 -0
- package/src/search/fts-manager.ts +452 -0
- package/src/search/index.ts +26 -0
- package/src/search/query.ts +396 -0
- package/src/search/text-extraction.ts +162 -0
- package/src/search/types.ts +114 -0
- package/src/sections/index.ts +226 -0
- package/src/sections/types.ts +86 -0
- package/src/seed/apply.ts +1141 -0
- package/src/seed/default.ts +86 -0
- package/src/seed/index.ts +28 -0
- package/src/seed/load.ts +35 -0
- package/src/seed/types.ts +341 -0
- package/src/seed/validate.ts +642 -0
- package/src/seo/index.ts +179 -0
- package/src/settings/index.ts +203 -0
- package/src/settings/types.ts +58 -0
- package/src/storage/index.ts +28 -0
- package/src/storage/local.ts +249 -0
- package/src/storage/s3.ts +263 -0
- package/src/storage/types.ts +204 -0
- package/src/taxonomies/index.ts +309 -0
- package/src/taxonomies/types.ts +61 -0
- package/src/ui.ts +75 -0
- package/src/utils/base64.ts +73 -0
- package/src/utils/hash.ts +36 -0
- package/src/utils/sanitize.ts +20 -0
- package/src/utils/slugify.ts +29 -0
- package/src/utils/url.ts +48 -0
- package/src/virtual-modules.d.ts +111 -0
- package/src/visual-editing/editable.ts +108 -0
- package/src/visual-editing/toolbar.ts +1229 -0
- package/src/widgets/components.ts +105 -0
- package/src/widgets/index.ts +131 -0
- package/src/widgets/types.ts +81 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /_emdash/api/auth/oauth/[provider]/callback
|
|
3
|
+
*
|
|
4
|
+
* Handle OAuth callback from provider
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
export const prerender = false;
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
handleOAuthCallback,
|
|
13
|
+
OAuthError,
|
|
14
|
+
Role,
|
|
15
|
+
type OAuthConsumerConfig,
|
|
16
|
+
type RoleLevel,
|
|
17
|
+
} from "@emdash-cms/auth";
|
|
18
|
+
import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
|
|
19
|
+
|
|
20
|
+
import { createOAuthStateStore } from "#auth/oauth-state-store.js";
|
|
21
|
+
|
|
22
|
+
type ProviderName = "github" | "google";
|
|
23
|
+
|
|
24
|
+
const VALID_PROVIDERS = new Set<string>(["github", "google"]);
|
|
25
|
+
|
|
26
|
+
function isValidProvider(provider: string): provider is ProviderName {
|
|
27
|
+
return VALID_PROVIDERS.has(provider);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Safely extract a string value from an env-like record */
|
|
31
|
+
function envString(env: Record<string, unknown>, ...keys: string[]): string | undefined {
|
|
32
|
+
for (const key of keys) {
|
|
33
|
+
const val = env[key];
|
|
34
|
+
if (typeof val === "string" && val) return val;
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get OAuth config from environment variables
|
|
41
|
+
*/
|
|
42
|
+
function getOAuthConfig(env: Record<string, unknown>): OAuthConsumerConfig["providers"] {
|
|
43
|
+
const providers: OAuthConsumerConfig["providers"] = {};
|
|
44
|
+
|
|
45
|
+
// GitHub
|
|
46
|
+
const githubClientId = envString(env, "EMDASH_OAUTH_GITHUB_CLIENT_ID", "GITHUB_CLIENT_ID");
|
|
47
|
+
const githubClientSecret = envString(
|
|
48
|
+
env,
|
|
49
|
+
"EMDASH_OAUTH_GITHUB_CLIENT_SECRET",
|
|
50
|
+
"GITHUB_CLIENT_SECRET",
|
|
51
|
+
);
|
|
52
|
+
if (githubClientId && githubClientSecret) {
|
|
53
|
+
providers.github = {
|
|
54
|
+
clientId: githubClientId,
|
|
55
|
+
clientSecret: githubClientSecret,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Google
|
|
60
|
+
const googleClientId = envString(env, "EMDASH_OAUTH_GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_ID");
|
|
61
|
+
const googleClientSecret = envString(
|
|
62
|
+
env,
|
|
63
|
+
"EMDASH_OAUTH_GOOGLE_CLIENT_SECRET",
|
|
64
|
+
"GOOGLE_CLIENT_SECRET",
|
|
65
|
+
);
|
|
66
|
+
if (googleClientId && googleClientSecret) {
|
|
67
|
+
providers.google = {
|
|
68
|
+
clientId: googleClientId,
|
|
69
|
+
clientSecret: googleClientSecret,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return providers;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const GET: APIRoute = async ({ params, request, locals, session, redirect }) => {
|
|
77
|
+
const { emdash } = locals;
|
|
78
|
+
const provider = params.provider;
|
|
79
|
+
|
|
80
|
+
// Validate provider
|
|
81
|
+
if (!provider || !isValidProvider(provider)) {
|
|
82
|
+
return redirect(
|
|
83
|
+
`/_emdash/admin/login?error=invalid_provider&message=${encodeURIComponent("Invalid OAuth provider")}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!emdash?.db) {
|
|
88
|
+
return redirect(
|
|
89
|
+
`/_emdash/admin/login?error=server_error&message=${encodeURIComponent("Database not configured")}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const url = new URL(request.url);
|
|
94
|
+
const code = url.searchParams.get("code");
|
|
95
|
+
const state = url.searchParams.get("state");
|
|
96
|
+
const error = url.searchParams.get("error");
|
|
97
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
98
|
+
|
|
99
|
+
// Handle OAuth errors from provider
|
|
100
|
+
if (error) {
|
|
101
|
+
const message = errorDescription || error;
|
|
102
|
+
return redirect(
|
|
103
|
+
`/_emdash/admin/login?error=oauth_denied&message=${encodeURIComponent(message)}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Validate required params
|
|
108
|
+
if (!code || !state) {
|
|
109
|
+
return redirect(
|
|
110
|
+
`/_emdash/admin/login?error=invalid_callback&message=${encodeURIComponent("Missing code or state parameter")}`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
// Get OAuth providers from environment
|
|
116
|
+
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- locals.runtime is injected by the Cloudflare adapter at runtime; not declared on App.Locals since the adapter is optional
|
|
117
|
+
const runtimeLocals = locals as unknown as { runtime?: { env?: Record<string, unknown> } };
|
|
118
|
+
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- import.meta.env is typed as ImportMetaEnv but we need Record<string, unknown> for getOAuthConfig
|
|
119
|
+
const env = runtimeLocals.runtime?.env ?? (import.meta.env as Record<string, unknown>);
|
|
120
|
+
const providers = getOAuthConfig(env);
|
|
121
|
+
|
|
122
|
+
if (!providers[provider]) {
|
|
123
|
+
return redirect(
|
|
124
|
+
`/_emdash/admin/login?error=provider_not_configured&message=${encodeURIComponent(`OAuth provider ${provider} is not configured`)}`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const config: OAuthConsumerConfig = {
|
|
129
|
+
baseUrl: `${url.origin}/_emdash`,
|
|
130
|
+
providers,
|
|
131
|
+
canSelfSignup: async (email: string) => {
|
|
132
|
+
// Extract domain from email
|
|
133
|
+
const domain = email.split("@")[1]?.toLowerCase();
|
|
134
|
+
if (!domain) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check allowed_domains table for a matching, enabled entry
|
|
139
|
+
const entry = await emdash.db
|
|
140
|
+
.selectFrom("allowed_domains")
|
|
141
|
+
.selectAll()
|
|
142
|
+
.where("domain", "=", domain)
|
|
143
|
+
.where("enabled", "=", 1)
|
|
144
|
+
.executeTakeFirst();
|
|
145
|
+
|
|
146
|
+
if (!entry) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Map the stored role level to the Role enum
|
|
151
|
+
const roleLevel = entry.default_role;
|
|
152
|
+
const roleMap: Record<number, RoleLevel> = {
|
|
153
|
+
50: Role.ADMIN,
|
|
154
|
+
40: Role.EDITOR,
|
|
155
|
+
30: Role.AUTHOR,
|
|
156
|
+
20: Role.CONTRIBUTOR,
|
|
157
|
+
10: Role.SUBSCRIBER,
|
|
158
|
+
};
|
|
159
|
+
const role = roleMap[roleLevel] ?? Role.CONTRIBUTOR;
|
|
160
|
+
if (!roleMap[roleLevel]) {
|
|
161
|
+
console.warn(
|
|
162
|
+
`[oauth] Unknown role level ${roleLevel} for domain ${domain}, defaulting to CONTRIBUTOR`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { allowed: true, role };
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const adapter = createKyselyAdapter(emdash.db);
|
|
171
|
+
const stateStore = createOAuthStateStore(emdash.db);
|
|
172
|
+
|
|
173
|
+
const user = await handleOAuthCallback(config, adapter, provider, code, state, stateStore);
|
|
174
|
+
|
|
175
|
+
// Create session
|
|
176
|
+
if (session) {
|
|
177
|
+
session.set("user", { id: user.id });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Redirect to admin dashboard
|
|
181
|
+
return redirect("/_emdash/admin");
|
|
182
|
+
} catch (callbackError) {
|
|
183
|
+
console.error("OAuth callback error:", callbackError);
|
|
184
|
+
|
|
185
|
+
let message = "Authentication failed";
|
|
186
|
+
let errorCode = "oauth_error";
|
|
187
|
+
|
|
188
|
+
if (callbackError instanceof OAuthError) {
|
|
189
|
+
errorCode = callbackError.code;
|
|
190
|
+
|
|
191
|
+
// Map all error codes to user-friendly messages (never expose raw error.message)
|
|
192
|
+
switch (callbackError.code) {
|
|
193
|
+
case "invalid_state":
|
|
194
|
+
message = "OAuth session expired or invalid. Please try again.";
|
|
195
|
+
break;
|
|
196
|
+
case "signup_not_allowed":
|
|
197
|
+
message = "Self-signup is not allowed for your email. Please contact an administrator.";
|
|
198
|
+
break;
|
|
199
|
+
case "user_not_found":
|
|
200
|
+
message = "Your account was not found. It may have been deleted.";
|
|
201
|
+
break;
|
|
202
|
+
case "token_exchange_failed":
|
|
203
|
+
message = "Failed to complete authentication. Please try again.";
|
|
204
|
+
break;
|
|
205
|
+
case "profile_fetch_failed":
|
|
206
|
+
message = "Failed to retrieve your profile. Please try again.";
|
|
207
|
+
break;
|
|
208
|
+
default:
|
|
209
|
+
message = "Authentication failed. Please try again.";
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// For generic errors, keep the default "Authentication failed" message
|
|
214
|
+
|
|
215
|
+
return redirect(
|
|
216
|
+
`/_emdash/admin/login?error=${errorCode}&message=${encodeURIComponent(message)}`,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /_emdash/api/auth/oauth/[provider]
|
|
3
|
+
*
|
|
4
|
+
* Start OAuth flow - redirects to provider authorization URL
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
export const prerender = false;
|
|
10
|
+
|
|
11
|
+
import { createAuthorizationUrl, type OAuthConsumerConfig } from "@emdash-cms/auth";
|
|
12
|
+
|
|
13
|
+
import { createOAuthStateStore } from "#auth/oauth-state-store.js";
|
|
14
|
+
|
|
15
|
+
type ProviderName = "github" | "google";
|
|
16
|
+
|
|
17
|
+
const VALID_PROVIDERS = new Set<string>(["github", "google"]);
|
|
18
|
+
|
|
19
|
+
function isValidProvider(provider: string): provider is ProviderName {
|
|
20
|
+
return VALID_PROVIDERS.has(provider);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Safely extract a string value from an env-like record */
|
|
24
|
+
function envString(env: Record<string, unknown>, ...keys: string[]): string | undefined {
|
|
25
|
+
for (const key of keys) {
|
|
26
|
+
const val = env[key];
|
|
27
|
+
if (typeof val === "string" && val) return val;
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get OAuth config from environment variables
|
|
34
|
+
*/
|
|
35
|
+
function getOAuthConfig(env: Record<string, unknown>): OAuthConsumerConfig["providers"] {
|
|
36
|
+
const providers: OAuthConsumerConfig["providers"] = {};
|
|
37
|
+
|
|
38
|
+
// GitHub
|
|
39
|
+
const githubClientId = envString(env, "EMDASH_OAUTH_GITHUB_CLIENT_ID", "GITHUB_CLIENT_ID");
|
|
40
|
+
const githubClientSecret = envString(
|
|
41
|
+
env,
|
|
42
|
+
"EMDASH_OAUTH_GITHUB_CLIENT_SECRET",
|
|
43
|
+
"GITHUB_CLIENT_SECRET",
|
|
44
|
+
);
|
|
45
|
+
if (githubClientId && githubClientSecret) {
|
|
46
|
+
providers.github = {
|
|
47
|
+
clientId: githubClientId,
|
|
48
|
+
clientSecret: githubClientSecret,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Google
|
|
53
|
+
const googleClientId = envString(env, "EMDASH_OAUTH_GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_ID");
|
|
54
|
+
const googleClientSecret = envString(
|
|
55
|
+
env,
|
|
56
|
+
"EMDASH_OAUTH_GOOGLE_CLIENT_SECRET",
|
|
57
|
+
"GOOGLE_CLIENT_SECRET",
|
|
58
|
+
);
|
|
59
|
+
if (googleClientId && googleClientSecret) {
|
|
60
|
+
providers.google = {
|
|
61
|
+
clientId: googleClientId,
|
|
62
|
+
clientSecret: googleClientSecret,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return providers;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const GET: APIRoute = async ({ params, request, locals, redirect }) => {
|
|
70
|
+
const { emdash } = locals;
|
|
71
|
+
const provider = params.provider;
|
|
72
|
+
|
|
73
|
+
// Validate provider
|
|
74
|
+
if (!provider || !isValidProvider(provider)) {
|
|
75
|
+
return redirect(
|
|
76
|
+
`/_emdash/admin/login?error=invalid_provider&message=${encodeURIComponent("Invalid OAuth provider")}`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!emdash?.db) {
|
|
81
|
+
return redirect(
|
|
82
|
+
`/_emdash/admin/login?error=server_error&message=${encodeURIComponent("Database not configured")}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const url = new URL(request.url);
|
|
88
|
+
|
|
89
|
+
// Get OAuth providers from environment
|
|
90
|
+
// Access via locals.runtime for Cloudflare, or import.meta.env for Node
|
|
91
|
+
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- locals.runtime is injected by the Cloudflare adapter at runtime; not declared on App.Locals since the adapter is optional
|
|
92
|
+
const runtimeLocals = locals as unknown as { runtime?: { env?: Record<string, unknown> } };
|
|
93
|
+
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- import.meta.env is typed as ImportMetaEnv but we need Record<string, unknown> for getOAuthConfig
|
|
94
|
+
const env = runtimeLocals.runtime?.env ?? (import.meta.env as Record<string, unknown>);
|
|
95
|
+
const providers = getOAuthConfig(env);
|
|
96
|
+
|
|
97
|
+
if (!providers[provider]) {
|
|
98
|
+
return redirect(
|
|
99
|
+
`/_emdash/admin/login?error=provider_not_configured&message=${encodeURIComponent(`OAuth provider ${provider} is not configured`)}`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const config: OAuthConsumerConfig = {
|
|
104
|
+
baseUrl: `${url.origin}/_emdash`,
|
|
105
|
+
providers,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const stateStore = createOAuthStateStore(emdash.db);
|
|
109
|
+
|
|
110
|
+
const { url: authUrl } = await createAuthorizationUrl(config, provider, stateStore);
|
|
111
|
+
|
|
112
|
+
return redirect(authUrl);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error("OAuth initiation error:", error);
|
|
115
|
+
return redirect(
|
|
116
|
+
`/_emdash/admin/login?error=oauth_error&message=${encodeURIComponent("Failed to start OAuth flow. Please try again.")}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PATCH/DELETE /_emdash/api/auth/passkey/[id]
|
|
3
|
+
*
|
|
4
|
+
* Rename or delete a passkey
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
export const prerender = false;
|
|
10
|
+
|
|
11
|
+
import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
|
|
12
|
+
|
|
13
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
14
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
15
|
+
import { passkeyRenameBody } from "#api/schemas.js";
|
|
16
|
+
|
|
17
|
+
interface PasskeyResponse {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string | null;
|
|
20
|
+
deviceType: "singleDevice" | "multiDevice";
|
|
21
|
+
backedUp: boolean;
|
|
22
|
+
createdAt: string;
|
|
23
|
+
lastUsedAt: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* PATCH - Rename a passkey
|
|
28
|
+
*/
|
|
29
|
+
export const PATCH: APIRoute = async ({ params, request, locals }) => {
|
|
30
|
+
const { emdash, user } = locals;
|
|
31
|
+
const { id } = params;
|
|
32
|
+
|
|
33
|
+
if (!emdash?.db) {
|
|
34
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Require authentication
|
|
38
|
+
if (!user) {
|
|
39
|
+
return apiError("NOT_AUTHENTICATED", "Not authenticated", 401);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!id) {
|
|
43
|
+
return apiError("MISSING_PARAM", "Passkey ID is required", 400);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const adapter = createKyselyAdapter(emdash.db);
|
|
48
|
+
|
|
49
|
+
// Get the credential and verify ownership
|
|
50
|
+
const credential = await adapter.getCredentialById(id);
|
|
51
|
+
|
|
52
|
+
if (!credential || credential.userId !== user.id) {
|
|
53
|
+
return apiError("NOT_FOUND", "Passkey not found", 404);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Parse request body
|
|
57
|
+
const body = await parseBody(request, passkeyRenameBody);
|
|
58
|
+
if (isParseError(body)) return body;
|
|
59
|
+
|
|
60
|
+
// Update the name
|
|
61
|
+
const trimmedName = body.name.trim() || null;
|
|
62
|
+
await adapter.updateCredentialName(id, trimmedName);
|
|
63
|
+
|
|
64
|
+
// Return updated passkey info
|
|
65
|
+
const passkey: PasskeyResponse = {
|
|
66
|
+
id: credential.id,
|
|
67
|
+
name: trimmedName,
|
|
68
|
+
deviceType: credential.deviceType,
|
|
69
|
+
backedUp: credential.backedUp,
|
|
70
|
+
createdAt: credential.createdAt.toISOString(),
|
|
71
|
+
lastUsedAt: credential.lastUsedAt.toISOString(),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return apiSuccess({ passkey });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return handleError(error, "Failed to rename passkey", "PASSKEY_RENAME_ERROR");
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* DELETE - Remove a passkey
|
|
82
|
+
*/
|
|
83
|
+
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
84
|
+
const { emdash, user } = locals;
|
|
85
|
+
const { id } = params;
|
|
86
|
+
|
|
87
|
+
if (!emdash?.db) {
|
|
88
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Require authentication
|
|
92
|
+
if (!user) {
|
|
93
|
+
return apiError("NOT_AUTHENTICATED", "Not authenticated", 401);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!id) {
|
|
97
|
+
return apiError("MISSING_PARAM", "Passkey ID is required", 400);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const adapter = createKyselyAdapter(emdash.db);
|
|
102
|
+
|
|
103
|
+
// Get the credential and verify ownership
|
|
104
|
+
const credential = await adapter.getCredentialById(id);
|
|
105
|
+
|
|
106
|
+
if (!credential || credential.userId !== user.id) {
|
|
107
|
+
return apiError("NOT_FOUND", "Passkey not found", 404);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check that this isn't the last passkey
|
|
111
|
+
const count = await adapter.countCredentialsByUserId(user.id);
|
|
112
|
+
|
|
113
|
+
if (count <= 1) {
|
|
114
|
+
return apiError("LAST_PASSKEY", "Cannot remove your last passkey", 400);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Delete the passkey
|
|
118
|
+
await adapter.deleteCredential(id);
|
|
119
|
+
|
|
120
|
+
return apiSuccess({ success: true });
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return handleError(error, "Failed to delete passkey", "PASSKEY_DELETE_ERROR");
|
|
123
|
+
}
|
|
124
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /_emdash/api/auth/passkey
|
|
3
|
+
*
|
|
4
|
+
* List all passkeys for the authenticated user
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
export const prerender = false;
|
|
10
|
+
|
|
11
|
+
import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
|
|
12
|
+
|
|
13
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
14
|
+
|
|
15
|
+
interface PasskeyResponse {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string | null;
|
|
18
|
+
deviceType: "singleDevice" | "multiDevice";
|
|
19
|
+
backedUp: boolean;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
lastUsedAt: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const GET: APIRoute = async ({ locals }) => {
|
|
25
|
+
const { emdash, user } = locals;
|
|
26
|
+
|
|
27
|
+
if (!emdash?.db) {
|
|
28
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Require authentication
|
|
32
|
+
if (!user) {
|
|
33
|
+
return apiError("NOT_AUTHENTICATED", "Not authenticated", 401);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const adapter = createKyselyAdapter(emdash.db);
|
|
38
|
+
const credentials = await adapter.getCredentialsByUserId(user.id);
|
|
39
|
+
|
|
40
|
+
// Map to public response format (exclude sensitive fields)
|
|
41
|
+
const passkeys: PasskeyResponse[] = credentials.map((cred) => ({
|
|
42
|
+
id: cred.id,
|
|
43
|
+
name: cred.name,
|
|
44
|
+
deviceType: cred.deviceType,
|
|
45
|
+
backedUp: cred.backedUp,
|
|
46
|
+
createdAt: cred.createdAt.toISOString(),
|
|
47
|
+
lastUsedAt: cred.lastUsedAt.toISOString(),
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
return apiSuccess({ items: passkeys });
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return handleError(error, "Failed to list passkeys", "PASSKEY_LIST_ERROR");
|
|
53
|
+
}
|
|
54
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /_emdash/api/auth/passkey/options
|
|
3
|
+
*
|
|
4
|
+
* Get authentication options for passkey login.
|
|
5
|
+
*
|
|
6
|
+
* Rate limited: 10 requests per minute per IP.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { APIRoute } from "astro";
|
|
10
|
+
|
|
11
|
+
export const prerender = false;
|
|
12
|
+
|
|
13
|
+
import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
|
|
14
|
+
import { generateAuthenticationOptions } from "@emdash-cms/auth/passkey";
|
|
15
|
+
|
|
16
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
17
|
+
import { isParseError, parseOptionalBody } from "#api/parse.js";
|
|
18
|
+
import { passkeyOptionsBody } from "#api/schemas.js";
|
|
19
|
+
import { createChallengeStore, cleanupExpiredChallenges } from "#auth/challenge-store.js";
|
|
20
|
+
import { getPasskeyConfig } from "#auth/passkey-config.js";
|
|
21
|
+
import { checkRateLimit, getClientIp, rateLimitResponse } from "#auth/rate-limit.js";
|
|
22
|
+
import { OptionsRepository } from "#db/repositories/options.js";
|
|
23
|
+
|
|
24
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
25
|
+
const { emdash } = locals;
|
|
26
|
+
|
|
27
|
+
if (!emdash?.db) {
|
|
28
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Fire-and-forget cleanup of expired challenges -- prevents accumulation
|
|
33
|
+
void cleanupExpiredChallenges(emdash.db).catch(() => {});
|
|
34
|
+
|
|
35
|
+
// Parse body before rate limiting so malformed requests don't consume slots
|
|
36
|
+
const body = await parseOptionalBody(request, passkeyOptionsBody, {});
|
|
37
|
+
if (isParseError(body)) return body;
|
|
38
|
+
|
|
39
|
+
// Rate limit: 10 requests per 60 seconds per IP
|
|
40
|
+
const ip = getClientIp(request);
|
|
41
|
+
const rateLimit = await checkRateLimit(emdash.db, ip, "passkey/options", 10, 60);
|
|
42
|
+
if (!rateLimit.allowed) {
|
|
43
|
+
return rateLimitResponse(60);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const adapter = createKyselyAdapter(emdash.db);
|
|
47
|
+
|
|
48
|
+
// Get credentials to allow
|
|
49
|
+
let credentials: Awaited<ReturnType<typeof adapter.getCredentialsByUserId>> = [];
|
|
50
|
+
|
|
51
|
+
if (body.email) {
|
|
52
|
+
// Get credentials for specific user
|
|
53
|
+
const user = await adapter.getUserByEmail(body.email);
|
|
54
|
+
if (user) {
|
|
55
|
+
credentials = await adapter.getCredentialsByUserId(user.id);
|
|
56
|
+
}
|
|
57
|
+
// Don't reveal if user exists - just return empty allowCredentials
|
|
58
|
+
}
|
|
59
|
+
// If no email provided, allowCredentials will be undefined (allow any discoverable credential)
|
|
60
|
+
|
|
61
|
+
// Get passkey config
|
|
62
|
+
const url = new URL(request.url);
|
|
63
|
+
const options = new OptionsRepository(emdash.db);
|
|
64
|
+
const siteName = (await options.get<string>("emdash:site_title")) ?? undefined;
|
|
65
|
+
const passkeyConfig = getPasskeyConfig(url, siteName);
|
|
66
|
+
|
|
67
|
+
// Generate authentication options
|
|
68
|
+
const challengeStore = createChallengeStore(emdash.db);
|
|
69
|
+
const authOptions = await generateAuthenticationOptions(
|
|
70
|
+
passkeyConfig,
|
|
71
|
+
credentials,
|
|
72
|
+
challengeStore,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return apiSuccess({
|
|
76
|
+
success: true,
|
|
77
|
+
options: authOptions,
|
|
78
|
+
});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return handleError(error, "Failed to generate passkey options", "PASSKEY_OPTIONS_ERROR");
|
|
81
|
+
}
|
|
82
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /_emdash/api/auth/passkey/register/options
|
|
3
|
+
*
|
|
4
|
+
* Get WebAuthn registration options for adding a new passkey (authenticated user)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
export const prerender = false;
|
|
10
|
+
|
|
11
|
+
import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
|
|
12
|
+
import { generateRegistrationOptions } from "@emdash-cms/auth/passkey";
|
|
13
|
+
|
|
14
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
15
|
+
import { isParseError, parseOptionalBody } from "#api/parse.js";
|
|
16
|
+
import { passkeyRegisterOptionsBody } from "#api/schemas.js";
|
|
17
|
+
import { createChallengeStore } from "#auth/challenge-store.js";
|
|
18
|
+
import { getPasskeyConfig } from "#auth/passkey-config.js";
|
|
19
|
+
import { OptionsRepository } from "#db/repositories/options.js";
|
|
20
|
+
|
|
21
|
+
const MAX_PASSKEYS = 10;
|
|
22
|
+
|
|
23
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
24
|
+
const { emdash, user } = locals;
|
|
25
|
+
|
|
26
|
+
if (!emdash?.db) {
|
|
27
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Require authentication
|
|
31
|
+
if (!user) {
|
|
32
|
+
return apiError("NOT_AUTHENTICATED", "Not authenticated", 401);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const adapter = createKyselyAdapter(emdash.db);
|
|
37
|
+
|
|
38
|
+
// Check passkey limit
|
|
39
|
+
const count = await adapter.countCredentialsByUserId(user.id);
|
|
40
|
+
if (count >= MAX_PASSKEYS) {
|
|
41
|
+
return apiError("PASSKEY_LIMIT", `Maximum of ${MAX_PASSKEYS} passkeys allowed`, 400);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Parse optional name from request
|
|
45
|
+
const body = await parseOptionalBody(request, passkeyRegisterOptionsBody, {});
|
|
46
|
+
if (isParseError(body)) return body;
|
|
47
|
+
|
|
48
|
+
// Get existing credentials for excludeCredentials
|
|
49
|
+
const existingCredentials = await adapter.getCredentialsByUserId(user.id);
|
|
50
|
+
|
|
51
|
+
// Get passkey config
|
|
52
|
+
const url = new URL(request.url);
|
|
53
|
+
const optionsRepo = new OptionsRepository(emdash.db);
|
|
54
|
+
const siteName = (await optionsRepo.get<string>("emdash:site_title")) ?? undefined;
|
|
55
|
+
const passkeyConfig = getPasskeyConfig(url, siteName);
|
|
56
|
+
|
|
57
|
+
// Generate registration options
|
|
58
|
+
const challengeStore = createChallengeStore(emdash.db);
|
|
59
|
+
const registrationOptions = await generateRegistrationOptions(
|
|
60
|
+
passkeyConfig,
|
|
61
|
+
{ id: user.id, email: user.email, name: user.name },
|
|
62
|
+
existingCredentials,
|
|
63
|
+
challengeStore,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Store the passkey name in the challenge metadata if provided
|
|
67
|
+
// We'll retrieve it during verification
|
|
68
|
+
if (body.name) {
|
|
69
|
+
// Store name with challenge for later retrieval
|
|
70
|
+
// The challenge store will need this when verifying
|
|
71
|
+
await optionsRepo.set(`emdash:passkey_pending:${user.id}`, {
|
|
72
|
+
name: body.name,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return apiSuccess({
|
|
77
|
+
options: registrationOptions,
|
|
78
|
+
});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return handleError(
|
|
81
|
+
error,
|
|
82
|
+
"Failed to generate registration options",
|
|
83
|
+
"PASSKEY_REGISTER_OPTIONS_ERROR",
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
};
|