emdash 0.7.0 → 0.9.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-Di31kZ28.d.mts → adapters-DoNJiveC.d.mts} +1 -1
- package/dist/{adapters-Di31kZ28.d.mts.map → adapters-DoNJiveC.d.mts.map} +1 -1
- package/dist/{apply-5uslYdUu.mjs → apply-BzltprvY.mjs} +90 -139
- package/dist/apply-BzltprvY.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 +194 -17
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +6 -7
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +34 -57
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +17 -12
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.d.mts.map +1 -1
- package/dist/astro/middleware/request-context.mjs +9 -6
- 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 +301 -165
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/types.d.mts +34 -10
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{base64-MBPo9ozB.mjs → base64-BRICGH2l.mjs} +1 -1
- package/dist/{base64-MBPo9ozB.mjs.map → base64-BRICGH2l.mjs.map} +1 -1
- package/dist/{byline-C4OVd8b3.mjs → byline-BSaNL1w7.mjs} +5 -5
- package/dist/byline-BSaNL1w7.mjs.map +1 -0
- package/dist/bylines-CvJ3PYz2.mjs +113 -0
- package/dist/bylines-CvJ3PYz2.mjs.map +1 -0
- package/dist/cache-C6N_hhN7.mjs +65 -0
- package/dist/cache-C6N_hhN7.mjs.map +1 -0
- package/dist/{chunks-HGz06Soa.mjs → chunks-NBQVDOci.mjs} +8 -2
- package/dist/{chunks-HGz06Soa.mjs.map → chunks-NBQVDOci.mjs.map} +1 -1
- package/dist/cli/index.mjs +229 -31
- 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 +3 -3
- package/dist/client/index.mjs.map +1 -1
- package/dist/{config-BXwuX8Bx.mjs → config-BI0V3ICQ.mjs} +1 -1
- package/dist/{config-BXwuX8Bx.mjs.map → config-BI0V3ICQ.mjs.map} +1 -1
- package/dist/{content-D7J5y73J.mjs → content-8lOYF0pr.mjs} +43 -28
- package/dist/content-8lOYF0pr.mjs.map +1 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +2 -2
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/libsql.d.mts.map +1 -1
- package/dist/db/libsql.mjs +7 -2
- package/dist/db/libsql.mjs.map +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.d.mts.map +1 -1
- package/dist/db/sqlite.mjs +8 -3
- package/dist/db/sqlite.mjs.map +1 -1
- package/dist/{db-errors-D0UT85nC.mjs → db-errors-WRezodiz.mjs} +1 -1
- package/dist/{db-errors-D0UT85nC.mjs.map → db-errors-WRezodiz.mjs.map} +1 -1
- package/dist/{default-CME5YdZ3.mjs → default-D8ksjWhO.mjs} +1 -1
- package/dist/{default-CME5YdZ3.mjs.map → default-D8ksjWhO.mjs.map} +1 -1
- package/dist/{dialect-helpers-DhTzaUxP.mjs → dialect-helpers-BKCvISIQ.mjs} +19 -2
- package/dist/dialect-helpers-BKCvISIQ.mjs.map +1 -0
- package/dist/{error-CiYn9yDu.mjs → error-D_-tqP-I.mjs} +1 -1
- package/dist/error-D_-tqP-I.mjs.map +1 -0
- package/dist/{index-De6_Xv3v.d.mts → index-BFRaVcD6.d.mts} +243 -40
- package/dist/index-BFRaVcD6.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +29 -25
- package/dist/{load-CBcmDIot.mjs → load-DDqMMvZL.mjs} +2 -2
- package/dist/{load-CBcmDIot.mjs.map → load-DDqMMvZL.mjs.map} +1 -1
- package/dist/{loader-DeiBJEMe.mjs → loader-CKLbBnhK.mjs} +32 -10
- package/dist/loader-CKLbBnhK.mjs.map +1 -0
- package/dist/{manifest-schema-V30qsMft.mjs → manifest-schema-DqWNC3lM.mjs} +45 -3
- package/dist/manifest-schema-DqWNC3lM.mjs.map +1 -0
- 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/media/local-runtime.mjs +3 -3
- package/dist/{media-DqHVh136.mjs → media-BW32b4gi.mjs} +4 -7
- package/dist/media-BW32b4gi.mjs.map +1 -0
- package/dist/{mode-CpNnGkPz.mjs → mode-ier8jbBk.mjs} +1 -1
- package/dist/mode-ier8jbBk.mjs.map +1 -0
- package/dist/options-BVp3UsTS.mjs +117 -0
- package/dist/options-BVp3UsTS.mjs.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/{placeholder-tzpqGWII.d.mts → placeholder-BE4o_2dc.d.mts} +1 -1
- package/dist/{placeholder-tzpqGWII.d.mts.map → placeholder-BE4o_2dc.d.mts.map} +1 -1
- package/dist/{placeholder-C-fk5hYI.mjs → placeholder-CIJejMlK.mjs} +1 -1
- package/dist/placeholder-CIJejMlK.mjs.map +1 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.mjs +6 -5
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
- package/dist/public-url-DByxYjUw.mjs +51 -0
- package/dist/public-url-DByxYjUw.mjs.map +1 -0
- package/dist/{query-g4Ug-9j9.mjs → query-Cg9ZKRQ0.mjs} +114 -16
- package/dist/query-Cg9ZKRQ0.mjs.map +1 -0
- package/dist/{redirect-CN0Rt9Ob.mjs → redirect-BhUBKRc1.mjs} +13 -8
- package/dist/redirect-BhUBKRc1.mjs.map +1 -0
- package/dist/{registry-Ci3WxVAr.mjs → registry-Dw70ChxB.mjs} +69 -11
- package/dist/registry-Dw70ChxB.mjs.map +1 -0
- package/dist/{request-cache-DiR961CV.mjs → request-cache-B-bmkipQ.mjs} +1 -1
- package/dist/request-cache-B-bmkipQ.mjs.map +1 -0
- package/dist/runner-Bnoj7vjK.d.mts +44 -0
- package/dist/runner-Bnoj7vjK.d.mts.map +1 -0
- package/dist/{runner-tQ7BJ4T7.mjs → runner-C7ADox5q.mjs} +185 -55
- package/dist/{runner-tQ7BJ4T7.mjs.map → runner-C7ADox5q.mjs.map} +1 -1
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +4 -4
- package/dist/{search-B0effn3j.mjs → search-dOGEccMa.mjs} +341 -152
- package/dist/search-dOGEccMa.mjs.map +1 -0
- package/dist/secrets-CW3reAnU.mjs +314 -0
- package/dist/secrets-CW3reAnU.mjs.map +1 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +15 -14
- 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.d.mts.map +1 -1
- package/dist/storage/s3.mjs +4 -4
- package/dist/storage/s3.mjs.map +1 -1
- package/dist/{taxonomies-K2z0Uhnj.mjs → taxonomies-ZlRtD6AG.mjs} +14 -7
- package/dist/taxonomies-ZlRtD6AG.mjs.map +1 -0
- package/dist/{tokens-BFPFx3CA.mjs → tokens-D7zMmWi2.mjs} +2 -2
- package/dist/{tokens-BFPFx3CA.mjs.map → tokens-D7zMmWi2.mjs.map} +1 -1
- package/dist/{transport-BykRfpyy.mjs → transport-BeMCmin1.mjs} +6 -5
- package/dist/{transport-BykRfpyy.mjs.map → transport-BeMCmin1.mjs.map} +1 -1
- package/dist/{transport-H4Iwx7tC.d.mts → transport-DNEfeMaU.d.mts} +1 -1
- package/dist/{transport-H4Iwx7tC.d.mts.map → transport-DNEfeMaU.d.mts.map} +1 -1
- package/dist/types-4fVtCIm0.mjs +68 -0
- package/dist/types-4fVtCIm0.mjs.map +1 -0
- package/dist/{types-CnZYHyLW.d.mts → types-BSyXeCFW.d.mts} +24 -2
- package/dist/{types-CnZYHyLW.d.mts.map → types-BSyXeCFW.d.mts.map} +1 -1
- package/dist/{types-DgrIP0tF.d.mts → types-BuBIptGk.d.mts} +80 -106
- package/dist/types-BuBIptGk.d.mts.map +1 -0
- package/dist/{types-BH2L167P.mjs → types-CDbKp7ND.mjs} +1 -1
- package/dist/{types-BH2L167P.mjs.map → types-CDbKp7ND.mjs.map} +1 -1
- package/dist/{types-DDS4MxsT.mjs → types-CIOg5AR8.mjs} +1 -1
- package/dist/{types-DDS4MxsT.mjs.map → types-CIOg5AR8.mjs.map} +1 -1
- package/dist/{types-6CUZRrZP.d.mts → types-CJsYGpco.d.mts} +24 -2
- package/dist/{types-6CUZRrZP.d.mts.map → types-CJsYGpco.d.mts.map} +1 -1
- package/dist/types-CRxNbK-Z.mjs +68 -0
- package/dist/types-CRxNbK-Z.mjs.map +1 -0
- package/dist/{types-C2v0c34j.d.mts → types-CrtWgIvl.d.mts} +1 -1
- package/dist/{types-C2v0c34j.d.mts.map → types-CrtWgIvl.d.mts.map} +1 -1
- package/dist/{types-CFWjXmus.d.mts → types-M78DQ1lx.d.mts} +1 -1
- package/dist/{types-CFWjXmus.d.mts.map → types-M78DQ1lx.d.mts.map} +1 -1
- package/dist/{validate-CqsNItbt.mjs → validate-Baqf0slj.mjs} +3 -3
- package/dist/{validate-CqsNItbt.mjs.map → validate-Baqf0slj.mjs.map} +1 -1
- package/dist/{validate-kM8Pjuf7.d.mts → validate-BfQh_C_y.d.mts} +4 -4
- package/dist/{validate-kM8Pjuf7.d.mts.map → validate-BfQh_C_y.d.mts.map} +1 -1
- package/dist/validation-BfEI7tNe.mjs +144 -0
- package/dist/validation-BfEI7tNe.mjs.map +1 -0
- package/dist/version-DoxrVdYf.mjs +7 -0
- package/dist/{version-BnTKdfam.mjs.map → version-DoxrVdYf.mjs.map} +1 -1
- package/dist/zod-generator-CC0xNe_K.mjs +132 -0
- package/dist/zod-generator-CC0xNe_K.mjs.map +1 -0
- package/locals.d.ts +1 -6
- package/package.json +21 -7
- package/src/api/auth-storage.ts +37 -0
- package/src/api/error.ts +6 -0
- package/src/api/errors.ts +8 -0
- package/src/api/handlers/comments.ts +19 -4
- package/src/api/handlers/content.ts +151 -4
- package/src/api/handlers/device-flow.ts +5 -0
- package/src/api/handlers/index.ts +2 -0
- package/src/api/handlers/marketplace.ts +11 -4
- package/src/api/handlers/media.ts +8 -1
- package/src/api/handlers/menus.ts +160 -21
- package/src/api/handlers/oauth-authorization.ts +72 -33
- package/src/api/handlers/redirects.ts +16 -3
- package/src/api/handlers/revision.ts +23 -14
- package/src/api/handlers/sections.ts +8 -1
- package/src/api/handlers/taxonomies.ts +131 -22
- package/src/api/handlers/validation.ts +212 -0
- package/src/api/openapi/document.ts +4 -1
- package/src/api/public-url.ts +54 -5
- package/src/api/route-utils.ts +14 -0
- package/src/api/schemas/comments.ts +2 -2
- package/src/api/schemas/common.ts +1 -1
- package/src/api/schemas/content.ts +17 -0
- package/src/api/schemas/sections.ts +3 -3
- package/src/api/schemas/setup.ts +8 -0
- package/src/api/schemas/users.ts +1 -1
- package/src/api/schemas/widgets.ts +12 -10
- package/src/api/setup-complete.ts +40 -0
- package/src/api/types.ts +5 -1
- package/src/astro/integration/index.ts +30 -2
- package/src/astro/integration/routes.ts +28 -0
- package/src/astro/integration/runtime.ts +49 -1
- package/src/astro/integration/virtual-modules.ts +73 -2
- package/src/astro/integration/vite-config.ts +49 -13
- package/src/astro/middleware/auth.ts +34 -6
- package/src/astro/middleware/redirect.ts +29 -16
- package/src/astro/middleware/request-context.ts +15 -5
- package/src/astro/middleware.ts +41 -10
- package/src/astro/routes/PluginRegistry.tsx +10 -1
- package/src/astro/routes/api/auth/invite/complete.ts +6 -1
- package/src/astro/routes/api/auth/mode.ts +57 -0
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +23 -3
- package/src/astro/routes/api/auth/oauth/[provider].ts +10 -4
- package/src/astro/routes/api/auth/passkey/register/verify.ts +6 -1
- package/src/astro/routes/api/auth/passkey/verify.ts +6 -1
- package/src/astro/routes/api/auth/signup/complete.ts +6 -1
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +2 -2
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +34 -12
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +32 -2
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -2
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +8 -4
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id].ts +12 -0
- package/src/astro/routes/api/content/[collection]/index.ts +1 -9
- package/src/astro/routes/api/import/wordpress/execute.ts +3 -1
- package/src/astro/routes/api/import/wordpress/media.ts +2 -7
- package/src/astro/routes/api/import/wordpress/prepare.ts +9 -0
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +3 -1
- package/src/astro/routes/api/manifest.ts +62 -45
- package/src/astro/routes/api/media/[id]/confirm.ts +10 -1
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +12 -3
- package/src/astro/routes/api/openapi.json.ts +27 -10
- package/src/astro/routes/api/redirects/404s/index.ts +10 -4
- package/src/astro/routes/api/redirects/404s/summary.ts +4 -2
- package/src/astro/routes/api/redirects/[id].ts +10 -4
- package/src/astro/routes/api/redirects/index.ts +7 -3
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +1 -1
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -2
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -1
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -1
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +2 -2
- package/src/astro/routes/api/schema/collections/index.ts +1 -1
- package/src/astro/routes/api/search/index.ts +10 -2
- package/src/astro/routes/api/sections/[slug].ts +10 -4
- package/src/astro/routes/api/sections/index.ts +7 -3
- package/src/astro/routes/api/settings/email.ts +4 -9
- package/src/astro/routes/api/setup/admin-verify.ts +6 -1
- package/src/astro/routes/api/setup/admin.ts +8 -2
- package/src/astro/routes/api/setup/index.ts +2 -2
- package/src/astro/routes/api/setup/status.ts +3 -1
- package/src/astro/routes/api/snapshot.ts +44 -18
- package/src/astro/routes/api/taxonomies/index.ts +0 -1
- package/src/astro/routes/api/themes/preview.ts +11 -5
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +4 -1
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +4 -1
- package/src/astro/routes/api/widget-areas/[name].ts +4 -1
- package/src/astro/routes/api/widget-areas/index.ts +4 -1
- package/src/astro/types.ts +32 -3
- package/src/auth/allowed-origins.ts +168 -0
- package/src/auth/mode.ts +15 -3
- package/src/auth/passkey-config.ts +35 -13
- package/src/auth/providers/github-admin.tsx +29 -0
- package/src/auth/providers/github.ts +31 -0
- package/src/auth/providers/google-admin.tsx +44 -0
- package/src/auth/providers/google.ts +31 -0
- package/src/auth/types.ts +114 -4
- package/src/bylines/index.ts +37 -88
- package/src/cli/commands/auth.ts +28 -6
- package/src/cli/commands/bundle-utils.ts +11 -2
- package/src/cli/commands/bundle.ts +31 -9
- package/src/cli/commands/content.ts +13 -0
- package/src/cli/commands/login.ts +8 -1
- package/src/cli/commands/publish.ts +24 -0
- package/src/cli/commands/secrets.ts +183 -0
- package/src/cli/credentials.ts +1 -1
- package/src/cli/index.ts +5 -1
- package/src/client/index.ts +4 -4
- package/src/client/transport.ts +17 -7
- package/src/components/Break.astro +2 -2
- package/src/components/EmDashHead.astro +18 -13
- package/src/components/EmDashImage.astro +7 -6
- package/src/components/Embed.astro +1 -1
- package/src/components/Gallery.astro +6 -4
- package/src/components/Image.astro +9 -4
- package/src/components/InlinePortableTextEditor.tsx +106 -19
- package/src/components/LiveSearch.astro +5 -14
- package/src/config/secrets.ts +528 -0
- package/src/database/dialect-helpers.ts +50 -0
- package/src/database/migrations/034_published_at_index.ts +1 -1
- package/src/database/migrations/035_bounded_404_log.ts +56 -39
- package/src/database/migrations/runner.ts +156 -23
- package/src/database/repositories/audit.ts +6 -8
- package/src/database/repositories/byline.ts +6 -8
- package/src/database/repositories/comment.ts +12 -16
- package/src/database/repositories/content.ts +76 -52
- package/src/database/repositories/index.ts +1 -1
- package/src/database/repositories/media.ts +10 -13
- package/src/database/repositories/plugin-storage.ts +4 -6
- package/src/database/repositories/redirect.ts +26 -19
- package/src/database/repositories/taxonomy.ts +40 -3
- package/src/database/repositories/types.ts +57 -8
- package/src/database/repositories/user.ts +6 -8
- package/src/db/libsql.ts +1 -3
- package/src/db/sqlite.ts +2 -5
- package/src/emdash-runtime.ts +388 -247
- package/src/index.ts +14 -1
- package/src/loader.ts +30 -6
- package/src/mcp/server.ts +781 -141
- package/src/media/normalize.ts +1 -1
- package/src/media/url.ts +78 -0
- package/src/page/site-identity.ts +58 -0
- package/src/plugins/adapt-sandbox-entry.ts +22 -10
- package/src/plugins/context.ts +13 -10
- package/src/plugins/define-plugin.ts +40 -12
- package/src/plugins/email-console.ts +10 -3
- package/src/plugins/hooks.ts +34 -19
- package/src/plugins/index.ts +9 -0
- package/src/plugins/manifest-schema.ts +49 -2
- package/src/plugins/types.ts +174 -13
- package/src/preview/urls.ts +23 -3
- package/src/query.ts +149 -6
- package/src/redirects/cache.ts +38 -18
- package/src/request-cache.ts +3 -0
- package/src/schema/registry.ts +97 -5
- package/src/schema/zod-generator.ts +27 -5
- package/src/search/fts-manager.ts +0 -2
- package/src/search/query.ts +111 -26
- package/src/search/types.ts +8 -1
- package/src/sections/index.ts +7 -9
- package/src/seed/apply.ts +2 -0
- package/src/settings/index.ts +80 -6
- package/src/settings/types.ts +23 -1
- package/src/storage/s3.ts +12 -6
- package/src/taxonomies/index.ts +11 -1
- package/src/virtual-modules.d.ts +21 -1
- package/src/widgets/index.ts +1 -1
- package/dist/apply-5uslYdUu.mjs.map +0 -1
- package/dist/byline-C4OVd8b3.mjs.map +0 -1
- package/dist/bylines-hPTW79hw.mjs +0 -157
- package/dist/bylines-hPTW79hw.mjs.map +0 -1
- package/dist/cache-BkKBuIvS.mjs +0 -56
- package/dist/cache-BkKBuIvS.mjs.map +0 -1
- package/dist/chunk-ClPoSABd.mjs +0 -21
- package/dist/content-D7J5y73J.mjs.map +0 -1
- package/dist/dialect-helpers-DhTzaUxP.mjs.map +0 -1
- package/dist/error-CiYn9yDu.mjs.map +0 -1
- package/dist/index-De6_Xv3v.d.mts.map +0 -1
- package/dist/loader-DeiBJEMe.mjs.map +0 -1
- package/dist/manifest-schema-V30qsMft.mjs.map +0 -1
- package/dist/media-DqHVh136.mjs.map +0 -1
- package/dist/mode-CpNnGkPz.mjs.map +0 -1
- package/dist/placeholder-C-fk5hYI.mjs.map +0 -1
- package/dist/query-g4Ug-9j9.mjs.map +0 -1
- package/dist/redirect-CN0Rt9Ob.mjs.map +0 -1
- package/dist/registry-Ci3WxVAr.mjs.map +0 -1
- package/dist/request-cache-DiR961CV.mjs.map +0 -1
- package/dist/runner-BR2xKwhn.d.mts +0 -34
- package/dist/runner-BR2xKwhn.d.mts.map +0 -1
- package/dist/search-B0effn3j.mjs.map +0 -1
- package/dist/taxonomies-K2z0Uhnj.mjs.map +0 -1
- package/dist/types-CMMN0pNg.mjs +0 -31
- package/dist/types-CMMN0pNg.mjs.map +0 -1
- package/dist/types-DgrIP0tF.d.mts.map +0 -1
- package/dist/version-BnTKdfam.mjs +0 -7
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolution and validation of multi-origin passkey verification.
|
|
3
|
+
*
|
|
4
|
+
* `allowedOrigins` lets one EmDash deployment accept passkey assertions from
|
|
5
|
+
* several hostnames sharing the same `rpId` (e.g. apex + preview/staging
|
|
6
|
+
* subdomains under one registrable parent). Origins come from two sources:
|
|
7
|
+
*
|
|
8
|
+
* - `EmDashConfig.allowedOrigins` (declared in `astro.config.mjs`)
|
|
9
|
+
* - `EMDASH_ALLOWED_ORIGINS` (comma-separated runtime env var)
|
|
10
|
+
*
|
|
11
|
+
* Sources are merged (union of permissions, deduplicated). Each entry is
|
|
12
|
+
* validated against `siteUrl` to fail loud on dead config the browser would
|
|
13
|
+
* never honor.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { getEnvAllowedOrigins } from "../api/public-url.js";
|
|
17
|
+
import type { EmDashConfig } from "../astro/integration/runtime.js";
|
|
18
|
+
|
|
19
|
+
export type AllowedOriginSource = "config.allowedOrigins" | "EMDASH_ALLOWED_ORIGINS";
|
|
20
|
+
|
|
21
|
+
export interface TaggedOrigin {
|
|
22
|
+
/** Raw entry as declared by the operator. */
|
|
23
|
+
origin: string;
|
|
24
|
+
/** Where the entry came from (used for source-attributed errors). */
|
|
25
|
+
source: AllowedOriginSource;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Collect raw allowedOrigins from config and env, source-tagged.
|
|
30
|
+
*
|
|
31
|
+
* Returns raw values — the caller is expected to pass the result through
|
|
32
|
+
* `validateAllowedOrigins()` before use in passkey verification.
|
|
33
|
+
*/
|
|
34
|
+
export function getConfiguredAllowedOrigins(config?: EmDashConfig): TaggedOrigin[] {
|
|
35
|
+
const tagged: TaggedOrigin[] = [];
|
|
36
|
+
if (config?.allowedOrigins) {
|
|
37
|
+
for (const origin of config.allowedOrigins) {
|
|
38
|
+
if (origin) tagged.push({ origin, source: "config.allowedOrigins" });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const origin of getEnvAllowedOrigins()) {
|
|
42
|
+
tagged.push({ origin, source: "EMDASH_ALLOWED_ORIGINS" });
|
|
43
|
+
}
|
|
44
|
+
return tagged;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validate per-entry shape rules (no `siteUrl` needed):
|
|
49
|
+
* - parses as `URL`
|
|
50
|
+
* - protocol is `http:` or `https:`
|
|
51
|
+
* - hostname has no trailing dot (`example.com.` rejected)
|
|
52
|
+
* - hostname has no empty labels (`foo..example.com` rejected)
|
|
53
|
+
*
|
|
54
|
+
* Returns the deduplicated, normalized origin form (`URL.origin`) of every
|
|
55
|
+
* input, in input order. Throws on the first violation with a source-tagged
|
|
56
|
+
* error message.
|
|
57
|
+
*/
|
|
58
|
+
export function validateOriginShape(tagged: TaggedOrigin[]): string[] {
|
|
59
|
+
const normalized: string[] = [];
|
|
60
|
+
const seen = new Set<string>();
|
|
61
|
+
for (const { origin, source } of tagged) {
|
|
62
|
+
let parsed: URL;
|
|
63
|
+
try {
|
|
64
|
+
parsed = new URL(origin);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
throw configError(source, `invalid URL: "${origin}"`, e);
|
|
67
|
+
}
|
|
68
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
69
|
+
throw configError(
|
|
70
|
+
source,
|
|
71
|
+
`origin must be http or https: "${origin}" (got ${parsed.protocol})`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (parsed.hostname.endsWith(".")) {
|
|
75
|
+
throw configError(
|
|
76
|
+
source,
|
|
77
|
+
`hostname has a trailing dot: "${origin}". Remove the trailing dot — assertion origins from the browser do not include it.`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (parsed.hostname.split(".").includes("")) {
|
|
81
|
+
throw configError(source, `hostname has empty labels: "${origin}"`);
|
|
82
|
+
}
|
|
83
|
+
if (!seen.has(parsed.origin)) {
|
|
84
|
+
seen.add(parsed.origin);
|
|
85
|
+
normalized.push(parsed.origin);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return normalized;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validate the effective merged allowedOrigins set against `siteUrl`.
|
|
93
|
+
*
|
|
94
|
+
* Performs `validateOriginShape()` plus the siteUrl-dependent rules:
|
|
95
|
+
* - Rule A: non-empty origins ⇒ `siteUrl` is set
|
|
96
|
+
* - `siteUrl` hostname is not an IP literal (multi-origin requires a domain)
|
|
97
|
+
* - `siteUrl` hostname has no trailing dot (cannot match assertion origins)
|
|
98
|
+
* - Rule B: each origin's hostname is `siteHost` exactly or a subdomain
|
|
99
|
+
*
|
|
100
|
+
* Throws on first violation. Returns the deduplicated normalized origins.
|
|
101
|
+
*
|
|
102
|
+
* Use this at the runtime chokepoint (where config + env are merged into the
|
|
103
|
+
* effective set). At Astro integration init, prefer `validateOriginShape()`
|
|
104
|
+
* for shape-only checks on `config.allowedOrigins`, since `siteUrl` may be
|
|
105
|
+
* supplied at runtime via `EMDASH_SITE_URL`.
|
|
106
|
+
*/
|
|
107
|
+
export function validateAllowedOrigins(
|
|
108
|
+
siteUrl: string | undefined,
|
|
109
|
+
tagged: TaggedOrigin[],
|
|
110
|
+
): string[] {
|
|
111
|
+
const normalized = validateOriginShape(tagged);
|
|
112
|
+
if (normalized.length === 0) return normalized;
|
|
113
|
+
|
|
114
|
+
if (!siteUrl) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`EmDash config error: allowedOrigins is set (${normalized.length} ${
|
|
117
|
+
normalized.length === 1 ? "entry" : "entries"
|
|
118
|
+
}) but siteUrl is not. Without a canonical siteUrl, rpId is derived from the request hostname, defeating multi-origin passkeys. Set siteUrl in astro.config.mjs or via EMDASH_SITE_URL.`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let siteHost: string;
|
|
123
|
+
try {
|
|
124
|
+
siteHost = new URL(siteUrl).hostname;
|
|
125
|
+
} catch (e) {
|
|
126
|
+
throw new Error(`EmDash config error: siteUrl is not a valid URL: "${siteUrl}"`, {
|
|
127
|
+
cause: e,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (siteHost.endsWith(".")) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`EmDash config error: siteUrl "${siteUrl}" has a trailing-dot hostname, which cannot match assertion origins. Remove the trailing dot when using allowedOrigins.`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (isIPLiteralHostname(siteHost)) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`EmDash config error: siteUrl "${siteUrl}" uses an IP-literal hostname. Multi-origin passkeys require a domain-based siteUrl — IP addresses cannot have valid subdomains for WebAuthn rpId.`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const { origin, source } of tagged) {
|
|
143
|
+
const h = new URL(origin).hostname;
|
|
144
|
+
if (h !== siteHost && !h.endsWith("." + siteHost)) {
|
|
145
|
+
throw configError(
|
|
146
|
+
source,
|
|
147
|
+
`"${origin}" is not a subdomain of siteUrl "${siteUrl}". Allowed origins must be the same hostname as siteUrl or a subdomain of it.`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return normalized;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function configError(source: AllowedOriginSource, detail: string, cause?: unknown): Error {
|
|
156
|
+
const err = new Error(`EmDash config error in ${source}: ${detail}`);
|
|
157
|
+
if (cause !== undefined) (err as Error & { cause?: unknown }).cause = cause;
|
|
158
|
+
return err;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const IPV4_DOTTED_DECIMAL_RE = /^\d+(\.\d+){3}$/;
|
|
162
|
+
|
|
163
|
+
function isIPLiteralHostname(h: string): boolean {
|
|
164
|
+
// IPv6 hostnames are bracketed by URL.hostname, e.g. "[::1]"
|
|
165
|
+
if (h.startsWith("[")) return true;
|
|
166
|
+
// IPv4 dotted-decimal
|
|
167
|
+
return IPV4_DOTTED_DECIMAL_RE.test(h);
|
|
168
|
+
}
|
package/src/auth/mode.ts
CHANGED
|
@@ -6,9 +6,21 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { EmDashConfig } from "../astro/integration/runtime.js";
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
AuthDescriptor,
|
|
11
|
+
AuthProviderDescriptor,
|
|
12
|
+
AuthRouteDescriptor,
|
|
13
|
+
AuthResult,
|
|
14
|
+
ExternalAuthConfig,
|
|
15
|
+
} from "./types.js";
|
|
10
16
|
|
|
11
|
-
export type {
|
|
17
|
+
export type {
|
|
18
|
+
AuthDescriptor,
|
|
19
|
+
AuthProviderDescriptor,
|
|
20
|
+
AuthRouteDescriptor,
|
|
21
|
+
AuthResult,
|
|
22
|
+
ExternalAuthConfig,
|
|
23
|
+
};
|
|
12
24
|
|
|
13
25
|
/**
|
|
14
26
|
* Passkey auth mode (default)
|
|
@@ -59,7 +71,7 @@ export function getAuthMode(
|
|
|
59
71
|
): AuthMode {
|
|
60
72
|
const auth = config?.auth;
|
|
61
73
|
|
|
62
|
-
// Check for AuthDescriptor (
|
|
74
|
+
// Check for AuthDescriptor (transparent external auth like Cloudflare Access)
|
|
63
75
|
if (auth && "entrypoint" in auth && auth.entrypoint) {
|
|
64
76
|
return {
|
|
65
77
|
type: "external",
|
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
export interface PasskeyConfig {
|
|
10
10
|
rpName: string;
|
|
11
11
|
rpId: string;
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Accepted client-data origins. First entry is the canonical/preferred origin;
|
|
14
|
+
* additional entries support multi-origin deployments (e.g. apex + preview
|
|
15
|
+
* subdomain sharing the same `rpId`). See `allowedOrigins` parameter.
|
|
16
|
+
*/
|
|
17
|
+
origins: string[];
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
/**
|
|
@@ -18,10 +23,22 @@ export interface PasskeyConfig {
|
|
|
18
23
|
* @param url The request URL (typically `new URL(Astro.request.url)` or `new URL(request.url)`)
|
|
19
24
|
* @param siteName Optional site name for rpName (defaults to hostname from `url` or public origin)
|
|
20
25
|
* @param siteUrl Optional browser-facing origin (see `EmDashConfig.siteUrl`).
|
|
21
|
-
* When set, **origin** and **rpId** are taken from this URL
|
|
26
|
+
* When set, the canonical **origin** and **rpId** are taken from this URL.
|
|
27
|
+
* @param allowedOrigins Optional list of additional accepted origins for verification.
|
|
28
|
+
* Each must share `rpId` with the canonical origin (WebAuthn requirement).
|
|
29
|
+
* Typical use: apex + preview subdomain on the same registrable domain.
|
|
22
30
|
* @throws If `siteUrl` is non-empty but not parseable by `new URL()`.
|
|
23
31
|
*/
|
|
24
|
-
export function getPasskeyConfig(
|
|
32
|
+
export function getPasskeyConfig(
|
|
33
|
+
url: URL,
|
|
34
|
+
siteName?: string,
|
|
35
|
+
siteUrl?: string,
|
|
36
|
+
allowedOrigins?: string[],
|
|
37
|
+
): PasskeyConfig {
|
|
38
|
+
let rpName: string;
|
|
39
|
+
let rpId: string;
|
|
40
|
+
let canonicalOrigin: string;
|
|
41
|
+
|
|
25
42
|
if (siteUrl) {
|
|
26
43
|
let publicUrl: URL;
|
|
27
44
|
try {
|
|
@@ -29,16 +46,21 @@ export function getPasskeyConfig(url: URL, siteName?: string, siteUrl?: string):
|
|
|
29
46
|
} catch (e) {
|
|
30
47
|
throw new Error(`Invalid siteUrl: "${siteUrl}"`, { cause: e });
|
|
31
48
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
rpName = siteName || publicUrl.hostname;
|
|
50
|
+
rpId = publicUrl.hostname;
|
|
51
|
+
canonicalOrigin = publicUrl.origin;
|
|
52
|
+
} else {
|
|
53
|
+
rpName = siteName || url.hostname;
|
|
54
|
+
rpId = url.hostname;
|
|
55
|
+
canonicalOrigin = url.origin;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const origins = [canonicalOrigin];
|
|
59
|
+
if (allowedOrigins) {
|
|
60
|
+
for (const extra of allowedOrigins) {
|
|
61
|
+
if (extra && !origins.includes(extra)) origins.push(extra);
|
|
62
|
+
}
|
|
37
63
|
}
|
|
38
64
|
|
|
39
|
-
return {
|
|
40
|
-
rpName: siteName || url.hostname,
|
|
41
|
-
rpId: url.hostname,
|
|
42
|
-
origin: url.origin,
|
|
43
|
-
};
|
|
65
|
+
return { rpName, rpId, origins };
|
|
44
66
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub OAuth Admin Components
|
|
3
|
+
*
|
|
4
|
+
* LoginButton for the login page, rendered via the auth provider virtual module.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { LinkButton } from "@cloudflare/kumo";
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
|
|
10
|
+
function GitHubIcon({ className }: { className?: string }) {
|
|
11
|
+
return (
|
|
12
|
+
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
|
|
13
|
+
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
|
14
|
+
</svg>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function LoginButton() {
|
|
19
|
+
return (
|
|
20
|
+
<LinkButton
|
|
21
|
+
href="/_emdash/api/auth/oauth/github"
|
|
22
|
+
variant="outline"
|
|
23
|
+
className="w-full justify-center"
|
|
24
|
+
>
|
|
25
|
+
<GitHubIcon className="h-5 w-5" />
|
|
26
|
+
<span>GitHub</span>
|
|
27
|
+
</LinkButton>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub OAuth Auth Provider
|
|
3
|
+
*
|
|
4
|
+
* Returns an AuthProviderDescriptor for GitHub OAuth login.
|
|
5
|
+
* Credentials are read from environment variables at runtime.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { github } from "emdash/auth/providers/github";
|
|
10
|
+
*
|
|
11
|
+
* emdash({
|
|
12
|
+
* authProviders: [github()],
|
|
13
|
+
* })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { AuthProviderDescriptor } from "../types.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Configure GitHub OAuth as an auth provider.
|
|
21
|
+
*
|
|
22
|
+
* Requires `EMDASH_OAUTH_GITHUB_CLIENT_ID` and `EMDASH_OAUTH_GITHUB_CLIENT_SECRET`
|
|
23
|
+
* (or `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`) environment variables.
|
|
24
|
+
*/
|
|
25
|
+
export function github(): AuthProviderDescriptor {
|
|
26
|
+
return {
|
|
27
|
+
id: "github",
|
|
28
|
+
label: "GitHub",
|
|
29
|
+
adminEntry: "emdash/auth/providers/github-admin",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google OAuth Admin Components
|
|
3
|
+
*
|
|
4
|
+
* LoginButton for the login page, rendered via the auth provider virtual module.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { LinkButton } from "@cloudflare/kumo";
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
|
|
10
|
+
function GoogleIcon({ className }: { className?: string }) {
|
|
11
|
+
return (
|
|
12
|
+
<svg className={className} viewBox="0 0 24 24">
|
|
13
|
+
<path
|
|
14
|
+
fill="#4285F4"
|
|
15
|
+
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
|
16
|
+
/>
|
|
17
|
+
<path
|
|
18
|
+
fill="#34A853"
|
|
19
|
+
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
|
20
|
+
/>
|
|
21
|
+
<path
|
|
22
|
+
fill="#FBBC05"
|
|
23
|
+
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
|
24
|
+
/>
|
|
25
|
+
<path
|
|
26
|
+
fill="#EA4335"
|
|
27
|
+
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
|
28
|
+
/>
|
|
29
|
+
</svg>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function LoginButton() {
|
|
34
|
+
return (
|
|
35
|
+
<LinkButton
|
|
36
|
+
href="/_emdash/api/auth/oauth/google"
|
|
37
|
+
variant="outline"
|
|
38
|
+
className="w-full justify-center"
|
|
39
|
+
>
|
|
40
|
+
<GoogleIcon className="h-5 w-5" />
|
|
41
|
+
<span>Google</span>
|
|
42
|
+
</LinkButton>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google OAuth Auth Provider
|
|
3
|
+
*
|
|
4
|
+
* Returns an AuthProviderDescriptor for Google OAuth login.
|
|
5
|
+
* Credentials are read from environment variables at runtime.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { google } from "emdash/auth/providers/google";
|
|
10
|
+
*
|
|
11
|
+
* emdash({
|
|
12
|
+
* authProviders: [google()],
|
|
13
|
+
* })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { AuthProviderDescriptor } from "../types.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Configure Google OAuth as an auth provider.
|
|
21
|
+
*
|
|
22
|
+
* Requires `EMDASH_OAUTH_GOOGLE_CLIENT_ID` and `EMDASH_OAUTH_GOOGLE_CLIENT_SECRET`
|
|
23
|
+
* (or `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET`) environment variables.
|
|
24
|
+
*/
|
|
25
|
+
export function google(): AuthProviderDescriptor {
|
|
26
|
+
return {
|
|
27
|
+
id: "google",
|
|
28
|
+
label: "Google",
|
|
29
|
+
adminEntry: "emdash/auth/providers/google-admin",
|
|
30
|
+
};
|
|
31
|
+
}
|
package/src/auth/types.ts
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
* Auth Provider Types
|
|
3
3
|
*
|
|
4
4
|
* Defines the interfaces for pluggable authentication providers.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Two systems coexist:
|
|
7
|
+
* - `AuthDescriptor` — transparent auth (Cloudflare Access) that authenticates
|
|
8
|
+
* every request via headers/cookies. No login UI needed.
|
|
9
|
+
* - `AuthProviderDescriptor` — pluggable login methods (GitHub, Google,
|
|
10
|
+
* AT Protocol, etc.) that appear as options on the login page and setup
|
|
11
|
+
* wizard. Passkey is built-in; providers are additive.
|
|
6
12
|
*/
|
|
7
13
|
|
|
8
14
|
/**
|
|
@@ -22,10 +28,10 @@ export interface AuthResult {
|
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
/**
|
|
25
|
-
* Auth descriptor
|
|
31
|
+
* Auth descriptor — transparent auth providers (e.g., Cloudflare Access).
|
|
26
32
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
33
|
+
* These authenticate every request via headers/cookies. No login UI needed.
|
|
34
|
+
* The module's `authenticate()` function is called by middleware on each request.
|
|
29
35
|
*/
|
|
30
36
|
export interface AuthDescriptor {
|
|
31
37
|
/**
|
|
@@ -64,6 +70,110 @@ export interface AuthProviderModule {
|
|
|
64
70
|
authenticate(request: Request, config: unknown): Promise<AuthResult>;
|
|
65
71
|
}
|
|
66
72
|
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Pluggable Auth Providers (additive login methods)
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Descriptor for a pluggable auth provider.
|
|
79
|
+
*
|
|
80
|
+
* Auth providers appear as login options on the login page and setup wizard.
|
|
81
|
+
* They coexist with passkey (which is built-in) and with each other.
|
|
82
|
+
* Any provider can be used to create the initial admin account.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* // astro.config.ts
|
|
87
|
+
* import { atproto } from "@emdash-cms/auth-atproto";
|
|
88
|
+
*
|
|
89
|
+
* emdash({
|
|
90
|
+
* authProviders: [atproto(), github(), google()],
|
|
91
|
+
* })
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export interface AuthProviderDescriptor {
|
|
95
|
+
/** Unique provider ID (e.g., "github", "atproto") */
|
|
96
|
+
id: string;
|
|
97
|
+
|
|
98
|
+
/** Human-readable label for UI (e.g., "GitHub", "AT Protocol") */
|
|
99
|
+
label: string;
|
|
100
|
+
|
|
101
|
+
/** Provider-specific config (JSON-serializable) */
|
|
102
|
+
config?: unknown;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Module exporting React components for the admin UI.
|
|
106
|
+
* Statically imported at build time via virtual module.
|
|
107
|
+
*
|
|
108
|
+
* The module should export components matching `AuthProviderAdminExports`.
|
|
109
|
+
*/
|
|
110
|
+
adminEntry?: string;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Astro route handlers this provider needs injected at build time.
|
|
114
|
+
* Used for login initiation, OAuth callbacks, well-known endpoints, etc.
|
|
115
|
+
*/
|
|
116
|
+
routes?: AuthRouteDescriptor[];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* URL prefixes/paths that should bypass auth middleware.
|
|
120
|
+
* Added to the public routes set so login/callback endpoints work
|
|
121
|
+
* for unauthenticated users.
|
|
122
|
+
*/
|
|
123
|
+
publicRoutes?: string[];
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Storage collections for persistent auth state (e.g., OAuth sessions).
|
|
127
|
+
* Same format as plugin storage — collections are stored in the shared
|
|
128
|
+
* `_plugin_storage` table namespaced under `auth:<providerId>`.
|
|
129
|
+
*
|
|
130
|
+
* Access via `getAuthProviderStorage()` from `emdash/api/route-utils`.
|
|
131
|
+
*/
|
|
132
|
+
storage?: Record<
|
|
133
|
+
string,
|
|
134
|
+
{ indexes?: Array<string | string[]>; uniqueIndexes?: Array<string | string[]> }
|
|
135
|
+
>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A route that an auth provider needs injected into the Astro app.
|
|
140
|
+
*/
|
|
141
|
+
export interface AuthRouteDescriptor {
|
|
142
|
+
/** URL pattern (e.g., "/_emdash/api/auth/atproto/login") */
|
|
143
|
+
pattern: string;
|
|
144
|
+
/** Module specifier for the Astro route handler */
|
|
145
|
+
entrypoint: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Expected exports from an auth provider's `adminEntry` module.
|
|
150
|
+
*
|
|
151
|
+
* All exports are optional. Providers export whichever components
|
|
152
|
+
* make sense for their auth flow.
|
|
153
|
+
*/
|
|
154
|
+
export interface AuthProviderAdminExports {
|
|
155
|
+
/**
|
|
156
|
+
* Compact button for the login page (icon + label).
|
|
157
|
+
* Used for providers with a simple redirect flow (GitHub, Google).
|
|
158
|
+
* Rendered in the "Or continue with" section.
|
|
159
|
+
*/
|
|
160
|
+
LoginButton?: import("react").ComponentType;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Full login form for providers that need custom input.
|
|
164
|
+
* Used for providers like AT Protocol that need a handle field.
|
|
165
|
+
* Rendered as an expandable section on the login page.
|
|
166
|
+
*/
|
|
167
|
+
LoginForm?: import("react").ComponentType;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Setup wizard step for creating the admin account via this provider.
|
|
171
|
+
* When present, this provider appears as an option in the setup wizard's
|
|
172
|
+
* "Create admin account" step.
|
|
173
|
+
*/
|
|
174
|
+
SetupStep?: import("react").ComponentType<{ onComplete: () => void }>;
|
|
175
|
+
}
|
|
176
|
+
|
|
67
177
|
/**
|
|
68
178
|
* Configuration options common to external auth providers
|
|
69
179
|
*/
|