@beechcms/api 0.5.0 → 0.6.0-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/dashboard/Searching.svg +1 -0
- package/assets/dashboard/assets/index-BSjk0Sx9.js +634 -0
- package/assets/dashboard/assets/index-kRCYVN2B.css +1 -0
- package/assets/dashboard/index.html +2 -2
- package/assets/dashboard/noResult.svg +43 -0
- package/assets/dashboard/working.svg +1 -0
- package/migrations/0000_v040_base.sql +27 -0
- package/package.json +13 -9
- package/src/auth/{in-memory-hash-provider.ts → __fixtures__/in-memory-hash-provider.ts} +4 -0
- package/src/auth/{static-token-service.ts → __fixtures__/static-token-service.ts} +4 -0
- package/src/auth/bcrypt-hash-provider.ts +6 -2
- package/src/auth/constants.ts +4 -0
- package/src/auth/generate-refresh-token.test.ts +8 -4
- package/src/auth/hash-provider.test.ts +14 -5
- package/src/auth/jose-token-service.ts +7 -0
- package/src/auth/jwt-claims-passthrough.test.ts +87 -0
- package/src/auth/login.test.ts +9 -1
- package/src/auth/login.ts +5 -1
- package/src/auth/refresh.ts +7 -1
- package/src/auth/token-service.test.ts +9 -1
- package/src/factory.ts +37 -16
- package/src/features/automations/__tests__/action-executors.test.ts +59 -72
- package/src/features/automations/__tests__/automation-runner.test.ts +4 -0
- package/src/features/automations/__tests__/automation-runner.utils.test.ts +4 -0
- package/src/features/automations/__tests__/automations.handler.test.ts +7 -3
- package/src/features/automations/__tests__/automations.repository.test.ts +4 -0
- package/src/features/automations/__tests__/automations.schema.test.ts +92 -2
- package/src/features/automations/__tests__/context-resolver.test.ts +4 -0
- package/src/features/automations/__tests__/cron-runner.test.ts +11 -7
- package/src/features/automations/__tests__/cron-runner.utils.test.ts +4 -0
- package/src/features/automations/__tests__/set-variable.executor.test.ts +77 -0
- package/src/features/automations/__tests__/template-grammar.test.ts +4 -0
- package/src/features/automations/__tests__/webhook.executor.test.ts +142 -0
- package/src/features/automations/__tests__/when-evaluator.test.ts +4 -0
- package/src/features/automations/__tests__/when-pushdown.test.ts +5 -1
- package/src/features/automations/action-executors/create-entry.executor.ts +4 -0
- package/src/features/automations/action-executors/edit-field.executor.ts +4 -0
- package/src/features/automations/action-executors/index.ts +5 -1
- package/src/features/automations/action-executors/send-mail.executor.ts +13 -2
- package/src/features/automations/action-executors/set-variable.executor.ts +15 -2
- package/src/features/automations/action-executors/webhook.executor.ts +55 -13
- package/src/features/automations/automation-runner.ts +4 -0
- package/src/features/automations/automation-runner.utils.ts +4 -0
- package/src/features/automations/automations.handler.ts +4 -0
- package/src/features/automations/automations.schema.ts +19 -2
- package/src/features/automations/context-resolver.ts +4 -0
- package/src/features/automations/cron-runner.ts +4 -0
- package/src/features/automations/cron-runner.utils.ts +4 -0
- package/src/features/automations/filter-translation.ts +4 -0
- package/src/features/automations/index.ts +4 -0
- package/src/features/automations/template-grammar.ts +4 -0
- package/src/features/automations/var-access-resolver.ts +4 -0
- package/src/features/automations/when-evaluator.ts +4 -0
- package/src/features/automations/when-pushdown.ts +4 -0
- package/src/features/backrefs/__tests__/backrefs.handler.test.ts +359 -0
- package/src/features/backrefs/backrefs.handler.ts +168 -0
- package/src/features/backrefs/d1-backref.repository.ts +84 -0
- package/src/features/backrefs/index.ts +5 -0
- package/src/features/content/constants.ts +6 -0
- package/src/features/content/handlers/bulk.handler.ts +185 -0
- package/src/features/content/handlers/create.ts +19 -55
- package/src/features/content/handlers/delete.ts +12 -37
- package/src/features/content/handlers/facets.ts +4 -0
- package/src/features/content/handlers/get.ts +4 -0
- package/src/features/content/handlers/helpers.test.ts +180 -0
- package/src/features/content/handlers/helpers.ts +93 -0
- package/src/features/content/handlers/list.ts +105 -19
- package/src/features/content/handlers/update.ts +19 -64
- package/src/features/content/index.ts +6 -0
- package/src/features/draft/draft.handler.ts +33 -8
- package/src/features/draft/draft.middleware.ts +4 -0
- package/src/features/draft/index.ts +4 -0
- package/src/features/email/email.provider.ts +4 -0
- package/src/features/email/email.service.ts +50 -45
- package/src/features/email/email.types.ts +12 -1
- package/src/features/email/index.ts +4 -0
- package/src/features/email/providers/resend.ts +4 -0
- package/src/features/email/providers/smtp.ts +55 -0
- package/src/features/email/templates/automation-mail.ts +4 -0
- package/src/features/email/templates/password-changed.ts +4 -0
- package/src/features/email/templates/password-reset.ts +4 -0
- package/src/features/email/templates/shell.ts +4 -0
- package/src/features/notifications/index.ts +4 -0
- package/src/features/notifications/notifications.handler.ts +4 -0
- package/src/features/password-reset/index.ts +5 -1
- package/src/features/password-reset/request.ts +12 -2
- package/src/features/password-reset/reset.ts +12 -2
- package/src/features/rotate-field/index.ts +4 -0
- package/src/features/rotate-field/rotate-field.handler.ts +4 -0
- package/src/features/rotate-field/rotate-field.schema.ts +4 -0
- package/src/features/schema/schema.handler.ts +118 -3
- package/src/features/seeds/index.ts +5 -0
- package/src/features/seeds/seeds.handler.test.ts +632 -0
- package/src/features/seeds/seeds.handler.ts +693 -0
- package/src/features/settings/__tests__/settings.handler.test.ts +555 -0
- package/src/features/settings/settings.handler.ts +98 -8
- package/src/features/setup/index.ts +124 -11
- package/src/features/stats/index.ts +4 -0
- package/src/features/stats/stats.handler.ts +11 -21
- package/src/features/webhooks/index.ts +63 -0
- package/src/index.ts +28 -19
- package/src/media-utils.ts +4 -0
- package/src/middleware/auth-providers.middleware.ts +13 -0
- package/src/middleware/observability.middleware.ts +21 -3
- package/src/middleware/rate-limit.middleware.ts +4 -0
- package/src/middleware/repository.middleware.ts +25 -2
- package/src/middleware/seed-registry.middleware.test.ts +97 -0
- package/src/middleware/seed-registry.middleware.ts +21 -0
- package/src/middleware/storage.middleware.ts +4 -0
- package/src/middleware.ts +5 -7
- package/src/public/access-policy.ts +4 -0
- package/src/public/api-key-middleware.ts +4 -0
- package/src/public/cache-utils.ts +4 -0
- package/src/public/entry-projection.ts +4 -0
- package/src/public/idempotency.ts +4 -0
- package/src/public/index.ts +4 -0
- package/src/public/problem-details.ts +71 -0
- package/src/public/public-add.ts +4 -0
- package/src/public/public-edit.ts +4 -0
- package/src/public/public-errors.ts +4 -0
- package/src/public/public-read.ts +4 -0
- package/src/public/public-routes.ts +4 -0
- package/src/public/query-builder.test.ts +4 -0
- package/src/public/query-builder.ts +4 -0
- package/src/public/rate-limit-middleware.ts +4 -0
- package/src/public/read-list.ts +4 -0
- package/src/public/read-single.ts +4 -0
- package/src/public/response-builder.ts +4 -0
- package/src/public/sanitize.ts +4 -0
- package/src/public/slug-utils.ts +4 -0
- package/src/rate-limit/cloudflare-rate-limiter.test.ts +4 -0
- package/src/rate-limit/cloudflare-rate-limiter.ts +4 -0
- package/src/rate-limit/in-memory-rate-limiter.test.ts +4 -0
- package/src/rate-limit/in-memory-rate-limiter.ts +4 -0
- package/src/rate-limit/no-op-rate-limiter.test.ts +4 -0
- package/src/rate-limit/no-op-rate-limiter.ts +4 -0
- package/src/search-utils.test.ts +4 -0
- package/src/search-utils.ts +4 -0
- package/src/search.ts +4 -0
- package/src/shared/apply-policies.test.ts +4 -0
- package/src/shared/apply-policies.ts +4 -0
- package/src/shared/automations.repository.d1.ts +4 -0
- package/src/shared/background-notification-service.test.ts +4 -0
- package/src/shared/background-notification-service.ts +4 -0
- package/src/shared/base.repository.d1.ts +4 -0
- package/src/shared/content-utils.test.ts +4 -0
- package/src/shared/content-utils.ts +4 -0
- package/src/shared/content.repository.d1.test.ts +147 -1
- package/src/shared/content.repository.d1.ts +535 -66
- package/src/shared/d1-activity-log.repository.test.ts +4 -0
- package/src/shared/d1-activity-log.repository.ts +4 -0
- package/src/shared/d1-activity-logger.test.ts +4 -0
- package/src/shared/d1-activity-logger.ts +4 -0
- package/src/shared/d1-analytics.repository.test.ts +4 -0
- package/src/shared/d1-analytics.repository.ts +4 -0
- package/src/shared/d1-content-scan.repository.test.ts +9 -5
- package/src/shared/d1-content-scan.repository.ts +15 -7
- package/src/shared/d1-notification.repository.test.ts +4 -0
- package/src/shared/d1-notification.repository.ts +4 -0
- package/src/shared/d1-password-reset-token.repository.test.ts +4 -0
- package/src/shared/d1-password-reset-token.repository.ts +4 -0
- package/src/shared/d1-search.repository.test.ts +4 -0
- package/src/shared/d1-search.repository.ts +4 -0
- package/src/shared/d1-session.repository.test.ts +4 -0
- package/src/shared/d1-session.repository.ts +4 -0
- package/src/shared/d1-setup-checklist.repository.ts +36 -0
- package/src/shared/d1-user.repository.test.ts +8 -4
- package/src/shared/d1-user.repository.ts +15 -5
- package/src/shared/d1-widget.repository.test.ts +4 -0
- package/src/shared/d1-widget.repository.ts +4 -0
- package/src/shared/demo-data-sql.ts +54 -0
- package/src/shared/demo-data.repository.d1.ts +20 -0
- package/src/shared/execution-context-scheduler.ts +4 -0
- package/src/shared/fixed-clock.ts +4 -0
- package/src/shared/fts-sync.ts +4 -0
- package/src/shared/idempotency.repository.d1.test.ts +4 -0
- package/src/shared/idempotency.repository.d1.ts +4 -0
- package/src/shared/in-memory-activity-logger.ts +4 -0
- package/src/shared/in-memory-notification-service.ts +4 -0
- package/src/shared/in-memory-seed.repository.ts +51 -0
- package/src/shared/media.repository.d1.test.ts +4 -0
- package/src/shared/media.repository.d1.ts +4 -0
- package/src/shared/qstash-notification-service.test.ts +67 -0
- package/src/shared/qstash-notification-service.ts +55 -0
- package/src/shared/query-utils.ts +4 -0
- package/src/shared/request-utils.ts +4 -0
- package/src/shared/schema-mutator.d1.ts +64 -0
- package/src/shared/seed-layout.repository.d1.test.ts +114 -0
- package/src/shared/seed-layout.repository.d1.ts +62 -0
- package/src/shared/seed-registry-cache.test.ts +68 -0
- package/src/shared/seed-registry-cache.ts +49 -0
- package/src/shared/seed.repository.d1.test.ts +143 -0
- package/src/shared/seed.repository.d1.ts +98 -0
- package/src/shared/sequential-id-generator.ts +11 -0
- package/src/shared/site-settings.repository.d1.ts +53 -0
- package/src/shared/storage/factory.ts +16 -15
- package/src/shared/storage/s3-bucket.ts +34 -2
- package/src/shared/storage-utils.ts +4 -0
- package/src/shared/system-stats.repository.d1.test.ts +4 -0
- package/src/shared/system-stats.repository.d1.ts +5 -1
- package/src/types.ts +23 -3
- package/src/upload.ts +134 -123
- package/src/widget.ts +6 -2
- package/assets/dashboard/assets/index-B1mUfgiK.css +0 -1
- package/assets/dashboard/assets/index-CHxxc_id.js +0 -629
- package/src/shared/storage/r2-binding-bucket.ts +0 -81
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="673.818" viewBox="0 0 800 673.818" xmlns:xlink="http://www.w3.org/1999/xlink" role="img" artist="Katerina Limpitsouni" source="https://undraw.co/"><g transform="translate(-826.017 -264.531)"><g transform="matrix(0.946, -0.326, 0.326, 0.946, 826.017, 424.075)"><path d="M114.77,145.533H13.015A13.03,13.03,0,0,1,0,132.514V13.015A13.03,13.03,0,0,1,13.015,0h94.666l20.1,16.024v116.49A13.03,13.03,0,0,1,114.77,145.533Z" transform="translate(0 0)" fill="#e6e6e6"/><path d="M9.269,0A9.284,9.284,0,0,0,0,9.269v119.5a9.284,9.284,0,0,0,9.269,9.269H111.023a9.284,9.284,0,0,0,9.269-9.269V14.082L102.624,0Z" transform="translate(3.746 3.746)" fill="#fff"/><path d="M48.495,4.335H2.168A2.168,2.168,0,1,1,2.168,0H48.495a2.168,2.168,0,0,1,0,4.335Z" transform="translate(46.483 34.16)" fill="#FF6584"/><path d="M57.435,4.335H2.168A2.168,2.168,0,1,1,2.168,0H57.435a2.168,2.168,0,1,1,0,4.335Z" transform="translate(46.483 41.475)" fill="#FF6584"/><path d="M48.493,0H2.167a2.167,2.167,0,0,0,0,4.334H48.493a2.167,2.167,0,1,0,0-4.334Z" transform="translate(46.486 66.94)" fill="#ccc"/><path d="M57.434,0H2.167a2.167,2.167,0,0,0,0,4.334H57.434a2.167,2.167,0,1,0,0-4.334Z" transform="translate(46.486 74.257)" fill="#ccc"/><path d="M48.494,4.334H2.167A2.167,2.167,0,0,1,2.167,0H48.494a2.168,2.168,0,1,1,0,4.335Z" transform="translate(46.483 99.722)" fill="#ccc"/><path d="M57.435,4.335H2.168A2.168,2.168,0,1,1,2.168,0H57.435a2.168,2.168,0,1,1,0,4.335Z" transform="translate(46.483 107.037)" fill="#ccc"/><circle cx="7.661" cy="7.661" r="7.661" transform="translate(21.698 32.325)" fill="#FF6584"/><path d="M15.32,7.661a7.661,7.661,0,0,1-7.66,7.663,1.645,1.645,0,0,1-.2-.007,7.66,7.66,0,1,1,7.856-7.655Z" transform="translate(22.486 65.103)" fill="#FF6584"/><circle cx="7.661" cy="7.661" r="7.661" transform="translate(21.698 97.887)" fill="#FF6584"/><path d="M20.083,16.235H4.334A4.335,4.335,0,0,1,0,11.9V.269A.271.271,0,0,1,.439.058L20.251,15.752a.271.271,0,0,1-.168.483Z" transform="translate(107.31 0.305)" fill="#e6e6e6"/></g><circle cx="1.547" cy="1.547" r="1.547" transform="translate(991.127 861.128)" opacity="0.2"/><g transform="matrix(0.998, 0.07, -0.07, 0.998, 1403.442, 449.522)"><rect width="223.119" height="45.534" rx="16" fill="#e6e6e6"/><path d="M0,22.773V.006H8.061a8.379,8.379,0,0,1,3.931.817A5.449,5.449,0,0,1,14.3,3.008a6.165,6.165,0,0,1,.756,3.033,5.036,5.036,0,0,1-.519,2.422,4.2,4.2,0,0,1-1.366,1.521,6.345,6.345,0,0,1-1.84.84v.214a4.44,4.44,0,0,1,2.13.733,5.359,5.359,0,0,1,1.771,1.91,5.993,5.993,0,0,1,.71,3.026,6.107,6.107,0,0,1-.787,3.086,5.417,5.417,0,0,1-2.466,2.178,10.177,10.177,0,0,1-4.366.8Zm2.9-2.567H8.244a5.564,5.564,0,0,0,3.778-1.025,3.263,3.263,0,0,0,1.122-2.5,4.009,4.009,0,0,0-.581-2.108,4.249,4.249,0,0,0-1.646-1.545,5.188,5.188,0,0,0-2.526-.581H2.9ZM2.9,10H7.909a4.641,4.641,0,0,0,3.1-1.07A3.516,3.516,0,0,0,12.275,6.1a3.4,3.4,0,0,0-1.03-2.5A4.451,4.451,0,0,0,8,2.574H2.9Z" transform="translate(27.321 11.378)" fill="#090814"/><g transform="translate(86.515 11.333)"><path d="M1.29,10.6a1.275,1.275,0,0,1-.911-.388,1.347,1.347,0,0,1,0-1.874L8.144.388a1.273,1.273,0,0,1,1.83,0,1.347,1.347,0,0,1,0,1.874L2.205,10.21a1.275,1.275,0,0,1-.915.388Z" transform="translate(6.475 6.136)"/><path d="M10.353,15.412a1.278,1.278,0,0,1-.978-.455A1.347,1.347,0,0,1,9.5,13.087l.655-.583C12.3,10.278,13.3,5.638,11.422,3.716s-6.407-.9-8.583,1.292l-.569.672a1.274,1.274,0,0,1-1.821.137A1.347,1.347,0,0,1,.315,3.948l.6-.717q.031-.036.064-.071A11.089,11.089,0,0,1,6.907.192,6.98,6.98,0,0,1,13.253,1.84a7.386,7.386,0,0,1,1.61,6.5,11.558,11.558,0,0,1-2.905,6.069q-.032.033-.066.064l-.691.615A1.272,1.272,0,0,1,10.353,15.412Z" transform="translate(7.765 0)"/><path d="M7.015,15.355a6.455,6.455,0,0,1-4.72-1.785A8.057,8.057,0,0,1,0,7.945C0,5.6,2.337,2.262,3.588,1L3.643.943,4.326.33A1.274,1.274,0,0,1,6.147.444a1.347,1.347,0,0,1-.114,1.87l-.65.587c-1.228,1.252-2.8,3.91-2.8,5.042a5.359,5.359,0,0,0,1.527,3.742c1.884,1.907,6.42.911,8.63-1.258L13.2,9.8a1.275,1.275,0,0,1,1.811-.273,1.346,1.346,0,0,1,.263,1.851l-.513.707a1.317,1.317,0,0,1-.129.151,11.217,11.217,0,0,1-5.96,2.968,10.1,10.1,0,0,1-1.656.147Z" transform="translate(0 7.463)"/></g><rect width="2.277" height="45.534" transform="translate(122.943 0)" fill="#fff"/><path d="M0,22.775,2.786,6.235H5.495L2.71,22.775ZM4.608,3.528a1.582,1.582,0,0,1-1.235-.521,1.417,1.417,0,0,1-.342-1.243A1.961,1.961,0,0,1,3.8.512,2.237,2.237,0,0,1,5.215,0a1.611,1.611,0,0,1,1.252.51,1.42,1.42,0,0,1,.342,1.252,1.982,1.982,0,0,1-.771,1.243A2.237,2.237,0,0,1,4.608,3.528Z" transform="translate(61.471 11.376)"/><g transform="translate(138.88 11.384)"><path d="M2.277,0A2.277,2.277,0,1,1,0,2.277,2.277,2.277,0,0,1,2.277,0Z" transform="translate(11.384 4.553)" fill="#090814"/><path d="M4.553,0h13.66a4.553,4.553,0,0,1,4.553,4.553v13.66a4.553,4.553,0,0,1-4.553,4.553H4.553A4.553,4.553,0,0,1,0,18.214V4.553A4.553,4.553,0,0,1,4.553,0Zm13.66,20.491a2.277,2.277,0,0,0,2.277-2.277V4.553a2.277,2.277,0,0,0-2.277-2.277H4.553A2.277,2.277,0,0,0,2.277,4.553v13.66a2.277,2.277,0,0,0,2.277,2.277Z" transform="translate(0 0)"/><path d="M15.937,8.728a1.138,1.138,0,0,1-.8-.333L9.448,2.71a1.165,1.165,0,0,0-1.821,0L1.943,8.394a1.138,1.138,0,1,1-1.61-1.61L6.025,1.093l.016-.015a3.431,3.431,0,0,1,4.994,0l.016.015,5.692,5.692a1.138,1.138,0,0,1-.806,1.943Z" transform="translate(0 8.348)"/><path d="M9.107,6.451a1.138,1.138,0,0,1-.8-.333L4.895,2.71a1.165,1.165,0,0,0-1.821,0l-1.13,1.13a1.138,1.138,0,1,1-1.61-1.61L1.472,1.093l.016-.015a3.431,3.431,0,0,1,4.994,0l.016.015L9.913,4.508a1.138,1.138,0,0,1-.806,1.943Z" transform="translate(12.522 10.624)"/></g><g transform="translate(179.86 11.387)"><path d="M3.131,22.763A3.193,3.193,0,0,1,0,19.511V9.754C0,4.664,2.584,1.309,7.447.045a1.562,1.562,0,0,1,1.9,1.183A1.631,1.631,0,0,1,8.207,3.2c-3.486.911-5.076,2.971-5.076,6.554V11.38h4.7a3.187,3.187,0,0,1,3.123,3.009l.008.244v4.879a3.193,3.193,0,0,1-3.13,3.252Z" transform="translate(14.087 0)"/><path d="M3.131,22.762A3.193,3.193,0,0,1,0,19.51V9.753C0,4.664,2.584,1.309,7.447.045a1.562,1.562,0,0,1,1.9,1.183A1.631,1.631,0,0,1,8.207,3.2c-3.486.911-5.076,2.971-5.076,6.554v1.624h4.7a3.187,3.187,0,0,1,3.123,3.009l.008.244V19.51a3.193,3.193,0,0,1-3.13,3.252Z" transform="translate(0 0.001)"/></g></g><path d="M843.21,705.353H432.577A28.17,28.17,0,0,0,404.44,733.49a27.852,27.852,0,0,0,2.03,10.467,28.139,28.139,0,0,0,26.107,17.679H843.21A28.139,28.139,0,0,0,867.493,747.7a27.17,27.17,0,0,0,1.834-3.742,27.856,27.856,0,0,0,2.03-10.467,28.178,28.178,0,0,0-28.146-28.137Z" transform="translate(576.882 171.013)" fill="#d6d6e3"/><rect width="59.815" height="70.309" transform="translate(1186.541 620.026)" fill="#ed9da0"/><path d="M422.732,358.755h6.253a11.464,11.464,0,0,1,11.461,11.464H411.268a11.464,11.464,0,0,1,11.464-11.464Z" transform="translate(784.75 215.89)" fill="#ccc"/><path d="M64.587,0c27.538,0,60.456,41.809,60.456,41.809s4.131,14.646,4.131,22.779A64.587,64.587,0,1,1,64.587,0Z" transform="translate(1152.034 510.51)" fill="#ed9da0"/><path d="M461.292,766.982H818.543c-2.391-27.251-3.948-131.421-3.948-138.276,0-30.239-24.743-63.238-24.743-63.238l-8.856-4.895L707.032,537.5l-19.141-24.865a21.287,21.287,0,0,0-17.047-8.324l-60.464.455a21.349,21.349,0,0,0-14.343,5.683L558.595,545.18,503.682,571.4l-.093-.093-.569.427-8.15,5.87-5.236,3.788s-13.762,16.5-24.754,65.986C463.226,654.823,462.066,751.175,461.292,766.982Z" transform="translate(579.69 161.084)" fill="#f2f2f2"/><path d="M905.823,742.141c-.86.785-1.73,1.562-2.6,2.338q-4.939,4.392-10.018,8.633-7.675,6.4-15.668,12.408H396.608q-14.685-11.028-28.3-23.385Z" transform="translate(575.098 172.829)" fill="#d6d6e3"/><path d="M523.094,689.248H346.3l3.741-17.772H519.349Z" transform="translate(781.541 231.335)" fill="#090814"/><path d="M867.982,674.4h-46a7.106,7.106,0,0,0-5.454,2.526,10.3,10.3,0,0,0-1.945-.187,10.911,10.911,0,1,0,0,21.822,10.783,10.783,0,0,0,4.749-1.1l.77,9.427,3.05,37.266a6.81,6.81,0,0,0,.579,1.338,7.172,7.172,0,0,0,6.3,3.742H859.94a7.2,7.2,0,0,0,6.3-3.742,7.389,7.389,0,0,0,.569-1.338l8.045-60.447a7.193,7.193,0,0,0-6.876-9.307Zm-53.4,21.047a7.8,7.8,0,0,1,0-15.6c.14,0,.271.038.412.047a7.141,7.141,0,0,0,.114,3.807l3.292,10.7a7.671,7.671,0,0,1-3.817,1.038Z" transform="translate(596.598 169.485)" fill="#FF6584"/><path d="M789.056,485.931H489.161a9.73,9.73,0,0,0-9.72,9.72V698.02a9.73,9.73,0,0,0,9.72,9.72H789.056a9.73,9.73,0,0,0,9.72-9.72V495.651A9.73,9.73,0,0,0,789.056,485.931Z" transform="translate(574.701 153.388)" fill="#3f3d56"/><path d="M661.816,635.978H593.691a3.635,3.635,0,0,0-3.629,3.643V747.239h75.394V639.616a3.637,3.637,0,0,0-3.643-3.643Zm-33.833,61.147a8.082,8.082,0,0,1-8.045-8.045V676.672a8.045,8.045,0,1,1,16.089,0V689.08a8.082,8.082,0,0,1-8.045,8.045Z" transform="translate(586.049 167.586)" fill="#090814"/><path d="M589.839,738.717V749.1a2.034,2.034,0,0,0,2.03,2.03h71.806a2.04,2.04,0,0,0,2.03-2.03V738.717Z" transform="translate(586.038 172.661)" fill="#3f3d56"/><circle cx="4.598" cy="4.598" r="4.598" transform="translate(1349.885 840.806)" fill="#2f2e41"/><path d="M523.543,669.493a1.609,1.609,0,0,0-1.609,1.609v8.278a1.609,1.609,0,0,0,3.218,0V671.1a1.609,1.609,0,0,0-1.609-1.609Z" transform="translate(582.685 169.242)" fill="#2f2e41"/><path d="M531.868,669.493a1.609,1.609,0,0,0-1.609,1.609v8.278a1.609,1.609,0,0,0,3.218,0V671.1a1.608,1.608,0,0,0-1.608-1.609Z" transform="translate(583.095 169.242)" fill="#2f2e41"/><path d="M540.2,669.493a1.609,1.609,0,0,0-1.609,1.609v8.278a1.609,1.609,0,0,0,3.218,0V671.1a1.609,1.609,0,0,0-1.61-1.609Z" transform="translate(583.508 169.242)" fill="#2f2e41"/><path d="M548.519,669.493a1.609,1.609,0,0,0-1.609,1.609v8.278a1.609,1.609,0,0,0,3.218,0V671.1a1.609,1.609,0,0,0-1.609-1.609Z" transform="translate(583.918 169.242)" fill="#2f2e41"/><path d="M24.32,0C37.751,0,53.8,20.726,53.8,34.158S37.751,48.64,24.32,48.64A24.32,24.32,0,0,1,24.32,0Z" transform="translate(1115.087 867.265)" fill="#ed9da0"/><path d="M25.434,0a24.32,24.32,0,1,1,0,48.64C12,48.64-8.048,37.751-8.048,24.32S12,0,25.434,0Z" transform="translate(1283.879 871.942)" fill="#ed9da0"/><path d="M974.16,446.985l1.3-7-17.507-3.249s-24.762-22.707-53.171-6.247S863.917,455.5,863.917,455.5l-13.1,12.056,9.854,5.451-12.454,8.554,10.5,1.95-8.953,9.2,14.752,29.9s2.755-26.654,23.763-22.755S939,480.3,939,480.3l45.373,48.265s.38-21.56,12.644-19.334C997.015,509.232,1012.025,457.635,974.16,446.985Z" transform="translate(284.224 63.235)" fill="#090814"/><g transform="translate(1130.828 265.531)"><path d="M220.083,139.917H3.6a2.608,2.608,0,0,1-2.6-2.6V3.6A2.608,2.608,0,0,1,3.6,1H220.083a2.608,2.608,0,0,1,2.6,2.6V137.312A2.608,2.608,0,0,1,220.083,139.917Z" transform="translate(-1 -1)" fill="#fff"/><path d="M220.083,139.917H3.6a2.608,2.608,0,0,1-2.6-2.6V3.6A2.608,2.608,0,0,1,3.6,1H220.083a2.608,2.608,0,0,1,2.6,2.6V137.312A2.608,2.608,0,0,1,220.083,139.917Z" transform="translate(-1 -1)" fill="none" stroke="#d7d7d7" stroke-miterlimit="10" stroke-width="2"/><path d="M154.417,132.04H20.131a2.442,2.442,0,0,1-2.6-2.231V56.941a2.442,2.442,0,0,1,2.6-2.231H154.417a2.442,2.442,0,0,1,2.6,2.231V129.81a2.442,2.442,0,0,1-2.6,2.231Z" transform="translate(-11.415 -38.09)" fill="#FF6584"/><g transform="translate(4.382 5.21)"><path d="M29.971,25.959h-9.84a2.608,2.608,0,0,1-2.6-2.6V22.2a2.608,2.608,0,0,1,2.6-2.6h9.84a2.608,2.608,0,0,1,2.6,2.6v1.158a2.608,2.608,0,0,1-2.6,2.6Z" transform="translate(-17.278 -19.592)" fill="#e6e6e6"/><path d="M662.185,25.959H656.4a2.608,2.608,0,0,1-2.6-2.6V22.2a2.608,2.608,0,0,1,2.6-2.6h5.788a2.608,2.608,0,0,1,2.6,2.6v1.158a2.608,2.608,0,0,1-2.6,2.6Z" transform="translate(-475.267 -19.592)" fill="#e6e6e6"/><path d="M342.355,412.772H280.422a2.607,2.607,0,0,1-2.6-2.6V389.909a2.607,2.607,0,0,1,2.6-2.6h61.934a2.607,2.607,0,0,1,2.6,2.6v20.259A2.608,2.608,0,0,1,342.355,412.772Z" transform="translate(-204.637 -284.275)" fill="#e6e6e6"/><path d="M81.177,412.266H19.243a2.608,2.608,0,0,1-2.6-2.6V389.4a2.608,2.608,0,0,1,2.6-2.6H81.177a2.608,2.608,0,0,1,2.6,2.6v20.259a2.608,2.608,0,0,1-2.6,2.6Z" transform="translate(-16.639 -283.91)" fill="#e6e6e6"/><path d="M600.58,387.3a2.608,2.608,0,0,1,2.6,2.6v20.259a2.608,2.608,0,0,1-2.6,2.6H538.647a2.607,2.607,0,0,1-2.6-2.6V389.909a2.607,2.607,0,0,1,2.6-2.6" transform="translate(-390.509 -284.275)" fill="#e6e6e6"/><g transform="translate(146.69 39.555)"><path d="M541.62,166.264a1.449,1.449,0,0,0-1.447,1.447v1.158a1.449,1.449,0,0,0,1.447,1.447h33.572a1.449,1.449,0,0,0,1.447-1.447v-1.158a1.449,1.449,0,0,0-1.447-1.447Z" transform="translate(-540.174 -166.264)" fill="#e6e6e6"/><path d="M541.62,199.317a1.447,1.447,0,0,0,0,2.894h51.515a1.447,1.447,0,0,0,0-2.894Z" transform="translate(-540.174 -190.056)" fill="#e6e6e6"/><path d="M541.62,228.238a1.447,1.447,0,0,0,0,2.894h51.515a1.447,1.447,0,0,0,0-2.894Z" transform="translate(-540.174 -210.873)" fill="#e6e6e6"/></g><path d="M703.5,25.959h-5.788a2.608,2.608,0,0,1-2.6-2.6V22.2a2.608,2.608,0,0,1,2.6-2.6H703.5a2.607,2.607,0,0,1,2.6,2.6v1.158A2.607,2.607,0,0,1,703.5,25.959Z" transform="translate(-505.007 -19.592)" fill="#e6e6e6"/><path d="M744.817,25.959h-5.788a2.608,2.608,0,0,1-2.6-2.6V22.2a2.608,2.608,0,0,1,2.6-2.6h5.788a2.608,2.608,0,0,1,2.6,2.6v1.158a2.608,2.608,0,0,1-2.6,2.6Z" transform="translate(-534.746 -19.592)" fill="#e6e6e6"/></g></g></g></svg>
|
|
@@ -30,6 +30,7 @@ CREATE TABLE IF NOT EXISTS users (
|
|
|
30
30
|
password_hash TEXT NOT NULL,
|
|
31
31
|
role TEXT NOT NULL DEFAULT 'editor' CHECK (role IN ('admin', 'editor')),
|
|
32
32
|
name TEXT,
|
|
33
|
+
surname TEXT,
|
|
33
34
|
avatar_url TEXT,
|
|
34
35
|
notification_prefs TEXT NOT NULL DEFAULT '{}',
|
|
35
36
|
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
@@ -236,3 +237,29 @@ CREATE TABLE IF NOT EXISTS automations (
|
|
|
236
237
|
|
|
237
238
|
CREATE INDEX IF NOT EXISTS idx_automations_seed_slug ON automations(seed_slug);
|
|
238
239
|
CREATE INDEX IF NOT EXISTS idx_automations_enabled ON automations(enabled);
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
-- =============================================================================
|
|
243
|
+
-- 12. SITE SETTINGS
|
|
244
|
+
-- Key-value store for site-level configuration (title, locale, timezone…).
|
|
245
|
+
-- =============================================================================
|
|
246
|
+
|
|
247
|
+
CREATE TABLE IF NOT EXISTS site_settings (
|
|
248
|
+
key TEXT NOT NULL PRIMARY KEY,
|
|
249
|
+
value TEXT NOT NULL
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
-- =============================================================================
|
|
254
|
+
-- 13. SEED LAYOUTS
|
|
255
|
+
-- Persists per-Seed custom editor form layouts (sprint 04a).
|
|
256
|
+
-- layout column stores a FormLayout JSON blob.
|
|
257
|
+
-- Reset = DELETE the row; client recomputes the generated default.
|
|
258
|
+
-- =============================================================================
|
|
259
|
+
|
|
260
|
+
CREATE TABLE IF NOT EXISTS seed_layouts (
|
|
261
|
+
slug TEXT NOT NULL PRIMARY KEY,
|
|
262
|
+
layout TEXT NOT NULL, -- JSON of FormLayout
|
|
263
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
264
|
+
updated_by TEXT NOT NULL -- users.id of the writer
|
|
265
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beechcms/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0-preview.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/factory.ts"
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"dev": "wrangler dev --port 8789",
|
|
17
17
|
"build": "tsc -p tsconfig.build.json --noEmit",
|
|
18
|
-
"db:migrate:local": "
|
|
19
|
-
"db:reset:local": "node -e \"require('fs').rmSync('.wrangler/state', {recursive:true,force:true})\" &&
|
|
18
|
+
"db:migrate:local": "node ./scripts/bootstrap-d1.mjs",
|
|
19
|
+
"db:reset:local": "node -e \"require('fs').rmSync('.wrangler/state', {recursive:true,force:true})\" && node ./scripts/bootstrap-d1.mjs",
|
|
20
20
|
"deploy": "wrangler deploy --minify",
|
|
21
21
|
"cf-typegen": "wrangler types --env-interface CloudflareBindings",
|
|
22
22
|
"test": "vitest run",
|
|
@@ -24,16 +24,20 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@aws-sdk/client-s3": "^3.995.0",
|
|
27
|
-
"@
|
|
27
|
+
"@aws-sdk/s3-request-presigner": "^3.995.0",
|
|
28
|
+
"@beechcms/core": "^0.6.0-preview.1",
|
|
29
|
+
"@upstash/qstash": "^2.11.0",
|
|
28
30
|
"bcryptjs": "^2.4.3",
|
|
29
|
-
"hono": "^4.
|
|
31
|
+
"hono": "^4.12.21",
|
|
30
32
|
"jose": "^6.1.3"
|
|
31
33
|
},
|
|
32
34
|
"devDependencies": {
|
|
33
35
|
"@cloudflare/workers-types": "^4.20260213.0",
|
|
34
36
|
"@types/bcryptjs": "^2.4.6",
|
|
35
|
-
"@vitest/coverage-v8": "^
|
|
36
|
-
"vitest": "^
|
|
37
|
-
"wrangler": "^4.
|
|
38
|
-
}
|
|
37
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
38
|
+
"vitest": "^4.1.0",
|
|
39
|
+
"wrangler": "^4.98.0"
|
|
40
|
+
},
|
|
41
|
+
"license": "BUSL-1.1",
|
|
42
|
+
"licenseFile": "../../LICENSE"
|
|
39
43
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
1
5
|
import type { ITokenService, IssueTokenOptions, JwtClaims } from '@beechcms/core'
|
|
2
6
|
|
|
3
7
|
const TEST_TOKEN_PREFIX = 'test:'
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
1
5
|
import bcrypt from 'bcryptjs'
|
|
2
6
|
import type { IHashProvider } from '@beechcms/core'
|
|
3
7
|
|
|
4
|
-
const
|
|
8
|
+
export const BCRYPT_SALT_ROUNDS = 10
|
|
5
9
|
|
|
6
10
|
export class BcryptHashProvider implements IHashProvider {
|
|
7
11
|
private readonly saltRounds: number
|
|
8
12
|
|
|
9
|
-
constructor(saltRounds: number =
|
|
13
|
+
constructor(saltRounds: number = BCRYPT_SALT_ROUNDS) {
|
|
10
14
|
this.saltRounds = saltRounds
|
|
11
15
|
}
|
|
12
16
|
|
package/src/auth/constants.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
1
5
|
/** API auth error messages - used by handlers and tests */
|
|
2
6
|
export const AUTH_ERRORS = {
|
|
3
7
|
INVALID_REQUEST: 'Invalid request',
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
1
5
|
import { describe, it, expect } from 'vitest'
|
|
2
6
|
import { generateRefreshToken } from './refresh'
|
|
3
7
|
|
|
@@ -6,10 +10,10 @@ describe('generateRefreshToken', () => {
|
|
|
6
10
|
expect(generateRefreshToken().length).toBeGreaterThan(0)
|
|
7
11
|
})
|
|
8
12
|
|
|
9
|
-
it('returns a
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
)
|
|
13
|
+
it('returns a 64-character hex string (256-bit / 32 bytes of entropy)', () => {
|
|
14
|
+
const token = generateRefreshToken()
|
|
15
|
+
expect(token).toHaveLength(64)
|
|
16
|
+
expect(token).toMatch(/^[0-9a-f]+$/)
|
|
13
17
|
})
|
|
14
18
|
|
|
15
19
|
it('returns a different token on every call', () => {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
1
5
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { InMemoryHashProvider } from './in-memory-hash-provider'
|
|
3
|
-
import { BcryptHashProvider } from './bcrypt-hash-provider'
|
|
6
|
+
import { InMemoryHashProvider } from './__fixtures__/in-memory-hash-provider'
|
|
7
|
+
import { BcryptHashProvider, BCRYPT_SALT_ROUNDS } from './bcrypt-hash-provider'
|
|
8
|
+
import { DUMMY_PASSWORD_HASH } from './login'
|
|
4
9
|
|
|
5
10
|
describe('InMemoryHashProvider', () => {
|
|
6
11
|
const provider = new InMemoryHashProvider()
|
|
@@ -39,8 +44,12 @@ describe('BcryptHashProvider', () => {
|
|
|
39
44
|
})
|
|
40
45
|
|
|
41
46
|
it('verify returns false against the DUMMY_PASSWORD_HASH for any plaintext', async () => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
expect(await provider.verify('password123', DUMMY_PASSWORD_HASH)).toBe(false)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('DUMMY_PASSWORD_HASH bcrypt cost matches BCRYPT_SALT_ROUNDS — timing oracle invariant', () => {
|
|
51
|
+
const match = DUMMY_PASSWORD_HASH.match(/^\$2[aby]\$(\d+)\$/)
|
|
52
|
+
expect(match).not.toBeNull()
|
|
53
|
+
expect(Number(match![1])).toBe(BCRYPT_SALT_ROUNDS)
|
|
45
54
|
})
|
|
46
55
|
})
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
1
5
|
import { SignJWT, jwtVerify } from 'jose'
|
|
2
6
|
import type { ITokenService, IssueTokenOptions, JwtClaims, IClock } from '@beechcms/core'
|
|
3
7
|
|
|
@@ -17,6 +21,9 @@ export class JoseTokenService implements ITokenService {
|
|
|
17
21
|
|
|
18
22
|
constructor(secret: string, config: JoseTokenServiceConfig, clock: IClock) {
|
|
19
23
|
this.secretBytes = new TextEncoder().encode(secret)
|
|
24
|
+
if (this.secretBytes.length < 32) {
|
|
25
|
+
throw new Error('JWT secret must be at least 32 bytes (256-bit)')
|
|
26
|
+
}
|
|
20
27
|
this.config = config
|
|
21
28
|
this.clock = clock
|
|
22
29
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sprint 04-pre Part B5 — JWT claims pass-through test.
|
|
7
|
+
*
|
|
8
|
+
* Verifies that `role` and `surname` claims issued in a JWT token are
|
|
9
|
+
* preserved end-to-end through the authMiddleware and available on
|
|
10
|
+
* `context.get('jwtPayload')`. Without the JwtClaims unification these
|
|
11
|
+
* fields would be type-erased and come back undefined.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, it, expect } from 'vitest'
|
|
15
|
+
import { Hono } from 'hono'
|
|
16
|
+
import { SystemClock } from '@beechcms/core'
|
|
17
|
+
import type { JwtClaims } from '@beechcms/core'
|
|
18
|
+
import { JoseTokenService } from './jose-token-service'
|
|
19
|
+
import { authMiddleware } from '../middleware'
|
|
20
|
+
import type { Env, Variables } from '../types'
|
|
21
|
+
|
|
22
|
+
const TEST_SECRET = 'super-secret-key-used-only-in-the-vitest-suite-min-length'
|
|
23
|
+
|
|
24
|
+
describe('JWT claims pass-through (sprint 04-pre)', () => {
|
|
25
|
+
const tokenService = new JoseTokenService(TEST_SECRET, {}, SystemClock)
|
|
26
|
+
|
|
27
|
+
async function buildApp() {
|
|
28
|
+
const app = new Hono<{ Bindings: Env; Variables: Variables }>()
|
|
29
|
+
|
|
30
|
+
app.use('*', async (c, next) => {
|
|
31
|
+
c.set('tokenService', tokenService)
|
|
32
|
+
await next()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
app.use('/protected', authMiddleware())
|
|
36
|
+
|
|
37
|
+
app.get('/protected', (c) => {
|
|
38
|
+
const payload = c.get('jwtPayload')
|
|
39
|
+
return c.json(payload)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return app
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
it('role and surname claims survive the authMiddleware round-trip', async () => {
|
|
46
|
+
const app = await buildApp()
|
|
47
|
+
|
|
48
|
+
const claims: JwtClaims = {
|
|
49
|
+
sub: 'user-123',
|
|
50
|
+
email: 'admin@example.com',
|
|
51
|
+
name: 'Admin',
|
|
52
|
+
surname: 'User',
|
|
53
|
+
role: 'admin',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const token = await tokenService.issue(claims)
|
|
57
|
+
|
|
58
|
+
const res = await app.request('/protected', {
|
|
59
|
+
method: 'GET',
|
|
60
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
expect(res.status).toBe(200)
|
|
64
|
+
const body = await res.json<JwtClaims>()
|
|
65
|
+
expect(body.sub).toBe('user-123')
|
|
66
|
+
expect(body.role).toBe('admin')
|
|
67
|
+
expect(body.surname).toBe('User')
|
|
68
|
+
expect(body.email).toBe('admin@example.com')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('a token without role/surname returns undefined for those fields', async () => {
|
|
72
|
+
const app = await buildApp()
|
|
73
|
+
|
|
74
|
+
const token = await tokenService.issue({ sub: 'user-456', email: 'plain@example.com' })
|
|
75
|
+
|
|
76
|
+
const res = await app.request('/protected', {
|
|
77
|
+
method: 'GET',
|
|
78
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
expect(res.status).toBe(200)
|
|
82
|
+
const body = await res.json<JwtClaims>()
|
|
83
|
+
expect(body.sub).toBe('user-456')
|
|
84
|
+
expect(body.role).toBeUndefined()
|
|
85
|
+
expect(body.surname).toBeUndefined()
|
|
86
|
+
})
|
|
87
|
+
})
|
package/src/auth/login.test.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
1
5
|
import { describe, it, expect } from 'vitest'
|
|
2
6
|
import { parseLoginBody, validateLoginInput, verifyPassword, DUMMY_PASSWORD_HASH } from './login'
|
|
3
|
-
import { InMemoryHashProvider } from './in-memory-hash-provider'
|
|
7
|
+
import { InMemoryHashProvider } from './__fixtures__/in-memory-hash-provider'
|
|
4
8
|
|
|
5
9
|
describe('parseLoginBody', () => {
|
|
6
10
|
it('returns credentials for a valid object body', () => {
|
|
@@ -69,6 +73,10 @@ describe('validateLoginInput', () => {
|
|
|
69
73
|
it('accepts passwords at the upper boundary (exactly 128 characters)', () => {
|
|
70
74
|
expect(validateLoginInput('user@test.com', 'a'.repeat(128))).toBe(true)
|
|
71
75
|
})
|
|
76
|
+
|
|
77
|
+
it('rejects a password made only of whitespace', () => {
|
|
78
|
+
expect(validateLoginInput('user@test.com', ' '.repeat(8))).toBe(false)
|
|
79
|
+
})
|
|
72
80
|
})
|
|
73
81
|
|
|
74
82
|
describe('verifyPassword', () => {
|
package/src/auth/login.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
1
5
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
6
|
import type { IHashProvider } from '@beechcms/core'
|
|
3
7
|
|
|
@@ -53,7 +57,7 @@ export function validateLoginInput(email: string, password: string): boolean {
|
|
|
53
57
|
return (
|
|
54
58
|
email.length <= MAX_EMAIL_LENGTH &&
|
|
55
59
|
EMAIL_REGEX.test(email) &&
|
|
56
|
-
password.length >= MIN_PASSWORD_LENGTH &&
|
|
60
|
+
password.trim().length >= MIN_PASSWORD_LENGTH &&
|
|
57
61
|
password.length <= MAX_PASSWORD_LENGTH
|
|
58
62
|
)
|
|
59
63
|
}
|
package/src/auth/refresh.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
1
5
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
6
|
|
|
3
7
|
export function generateRefreshToken(): string {
|
|
4
|
-
|
|
8
|
+
const bytes = new Uint8Array(32)
|
|
9
|
+
crypto.getRandomValues(bytes)
|
|
10
|
+
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('')
|
|
5
11
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
1
5
|
import { describe, it, expect } from 'vitest'
|
|
2
6
|
import { SystemClock } from '@beechcms/core'
|
|
3
7
|
import { JoseTokenService } from './jose-token-service'
|
|
4
|
-
import { StaticTokenService } from './static-token-service'
|
|
8
|
+
import { StaticTokenService } from './__fixtures__/static-token-service'
|
|
5
9
|
|
|
6
10
|
const TEST_SECRET = 'super-secret-key-used-only-in-the-vitest-suite-min-length'
|
|
7
11
|
|
|
@@ -47,6 +51,10 @@ describe('JoseTokenService', () => {
|
|
|
47
51
|
const claims = await service.verify(token)
|
|
48
52
|
expect(claims?.sub).toBe('user-1')
|
|
49
53
|
})
|
|
54
|
+
|
|
55
|
+
it('throws if the secret is shorter than 32 bytes', () => {
|
|
56
|
+
expect(() => new JoseTokenService('short-secret', {}, SystemClock)).toThrow('JWT secret must be at least 32 bytes')
|
|
57
|
+
})
|
|
50
58
|
})
|
|
51
59
|
|
|
52
60
|
describe('StaticTokenService', () => {
|
package/src/factory.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
1
5
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
6
|
import { Hono } from 'hono'
|
|
3
7
|
import { cors } from 'hono/cors'
|
|
4
8
|
import { getCookie, setCookie, deleteCookie } from 'hono/cookie'
|
|
5
9
|
import type { Seed, ContentRepository, IdempotencyRepository, BeechBucket, MediaRepository, SystemStatsRepository } from '@beechcms/core'
|
|
6
|
-
import { sha256hex, SystemClock, SystemIdGenerator
|
|
10
|
+
import { sha256hex, SystemClock, SystemIdGenerator } from '@beechcms/core'
|
|
7
11
|
import type { Env, Variables } from './types'
|
|
8
12
|
import { getClientIp } from './shared/request-utils'
|
|
9
13
|
|
|
@@ -25,14 +29,20 @@ import { setupApp } from './features/setup'
|
|
|
25
29
|
import { draftApp } from './features/draft'
|
|
26
30
|
import { settingsApp } from './features/settings/settings.handler'
|
|
27
31
|
import { schemaApp } from './features/schema/schema.handler'
|
|
32
|
+
import { seedsApp } from './features/seeds'
|
|
28
33
|
import { notificationsApp } from './features/notifications'
|
|
29
34
|
import { automationsApp } from './features/automations'
|
|
30
35
|
import { statsApp } from './features/stats'
|
|
36
|
+
import { backrefsApp } from './features/backrefs'
|
|
37
|
+
import { webhooksApp } from './features/webhooks'
|
|
31
38
|
import { uploadRoutes, serveMediaHandler } from './upload'
|
|
32
39
|
import { publicRoutes, apiKeyMiddleware, publicRateLimitMiddleware } from './public'
|
|
33
40
|
import { searchRouter } from "./search"
|
|
41
|
+
import type { ISeedRepository } from '@beechcms/core'
|
|
34
42
|
import { repositoryMiddleware } from './middleware/repository.middleware'
|
|
43
|
+
import { seedRegistryMiddleware } from './middleware/seed-registry.middleware'
|
|
35
44
|
import { storageMiddleware } from './middleware/storage.middleware'
|
|
45
|
+
import { InMemorySeedRepository } from './shared/in-memory-seed.repository'
|
|
36
46
|
import { authProvidersMiddleware } from './middleware/auth-providers.middleware'
|
|
37
47
|
import { rateLimiterMiddleware } from './middleware/rate-limit.middleware'
|
|
38
48
|
import { observabilityMiddleware } from './middleware/observability.middleware'
|
|
@@ -44,6 +54,10 @@ export interface BeechConfig {
|
|
|
44
54
|
bucket?: BeechBucket
|
|
45
55
|
mediaRepository?: MediaRepository
|
|
46
56
|
systemStatsRepository?: SystemStatsRepository
|
|
57
|
+
// Optional override: inject a custom ISeedRepository (e.g. InMemorySeedRepository for tests).
|
|
58
|
+
// When omitted and seeds is non-empty, an InMemorySeedRepository is created automatically
|
|
59
|
+
// for backward compatibility.
|
|
60
|
+
seedRepository?: ISeedRepository
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
// --- Costanti e helper ---
|
|
@@ -86,32 +100,34 @@ function extractPublicSeed(path: string): string {
|
|
|
86
100
|
}
|
|
87
101
|
|
|
88
102
|
/**
|
|
89
|
-
* Builds a fully configured Hono app
|
|
90
|
-
*
|
|
103
|
+
* Builds a fully configured Hono app. Seeds are hydrated from D1 at runtime via
|
|
104
|
+
* seedRegistryMiddleware; config.seeds is retained for back-compat but no longer
|
|
105
|
+
* drives request handling.
|
|
91
106
|
*/
|
|
92
107
|
export function createBeechApp(config: BeechConfig): Hono<{ Bindings: Env; Variables: Variables }> {
|
|
108
|
+
// Back-compat shim: when callers (e.g. existing tests) pass seeds without a custom
|
|
109
|
+
// seedRepository, wrap them in an InMemorySeedRepository so the middleware can serve
|
|
110
|
+
// them without touching D1. Production index.ts passes seeds:[] so D1SeedRepository
|
|
111
|
+
// is used as the live path.
|
|
93
112
|
const seedsArray = Array.isArray(config.seeds) ? config.seeds : Object.values(config.seeds)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const seedRegistry = new SeedRegistry(validSeeds)
|
|
113
|
+
const seedRepository = config.seedRepository
|
|
114
|
+
?? (seedsArray.length > 0 ? new InMemorySeedRepository(seedsArray) : undefined)
|
|
97
115
|
|
|
98
116
|
const app = new Hono<{ Bindings: Env; Variables: Variables }>()
|
|
99
117
|
|
|
100
|
-
// 1.
|
|
101
|
-
app.use('*', async (context, next) => {
|
|
102
|
-
context.set('getSeed', (slug: string) => seedRegistry.get(slug))
|
|
103
|
-
context.set('seedRegistry', seedRegistry)
|
|
104
|
-
await next()
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
// 1.1 Repository Injection
|
|
118
|
+
// 1. Repository Injection (must be first — seedRegistryMiddleware depends on seedRepository)
|
|
108
119
|
app.use('*', repositoryMiddleware({
|
|
109
120
|
repository: config.repository,
|
|
110
121
|
idempotencyRepository: config.idempotencyRepository,
|
|
111
122
|
mediaRepository: config.mediaRepository,
|
|
112
123
|
systemStatsRepository: config.systemStatsRepository,
|
|
124
|
+
seedRepository,
|
|
113
125
|
}))
|
|
114
126
|
|
|
127
|
+
// 2. Seed Registry Hydration (D1-backed, version-token-cached per isolate)
|
|
128
|
+
app.use('*', seedRegistryMiddleware())
|
|
129
|
+
|
|
130
|
+
// 3. Storage / Auth / Rate Limiting / Observability
|
|
115
131
|
app.use('*', storageMiddleware({
|
|
116
132
|
bucket: config.bucket,
|
|
117
133
|
}))
|
|
@@ -211,7 +227,7 @@ export function createBeechApp(config: BeechConfig): Hono<{ Bindings: Env; Varia
|
|
|
211
227
|
|
|
212
228
|
if (!user || !isValid) return context.json({ error: AUTH_ERRORS.INVALID_CREDENTIALS }, 401)
|
|
213
229
|
|
|
214
|
-
const accessToken = await context.get('tokenService').issue({ sub: user.id, email: user.email, name: user.name ?? undefined })
|
|
230
|
+
const accessToken = await context.get('tokenService').issue({ sub: user.id, email: user.email, name: user.name ?? undefined, surname: user.surname ?? undefined, role: user.role })
|
|
215
231
|
const refreshToken = generateRefreshToken()
|
|
216
232
|
const refreshTokenHash = await sha256hex(refreshToken)
|
|
217
233
|
const nowSeconds = SystemClock.nowSeconds()
|
|
@@ -251,7 +267,7 @@ export function createBeechApp(config: BeechConfig): Hono<{ Bindings: Env; Varia
|
|
|
251
267
|
|
|
252
268
|
// Issue new tokens before revoking the old one: if saveRefreshToken fails,
|
|
253
269
|
// the old token stays valid and the user is not locked out.
|
|
254
|
-
const newAccessToken = await context.get('tokenService').issue({ sub: user.id, email: user.email, name: user.name ?? undefined })
|
|
270
|
+
const newAccessToken = await context.get('tokenService').issue({ sub: user.id, email: user.email, name: user.name ?? undefined, surname: user.surname ?? undefined, role: user.role })
|
|
255
271
|
const newRefreshToken = generateRefreshToken()
|
|
256
272
|
const newRefreshTokenHash = await sha256hex(newRefreshToken)
|
|
257
273
|
|
|
@@ -297,10 +313,12 @@ export function createBeechApp(config: BeechConfig): Hono<{ Bindings: Env; Varia
|
|
|
297
313
|
|
|
298
314
|
apiProtected.route('/settings', settingsApp)
|
|
299
315
|
apiProtected.route('/schema', schemaApp)
|
|
316
|
+
apiProtected.route('/seeds', seedsApp)
|
|
300
317
|
apiProtected.route('/content', notificationsApp)
|
|
301
318
|
apiProtected.route('/content', statsApp)
|
|
302
319
|
apiProtected.route('/content', rotateFieldApp)
|
|
303
320
|
apiProtected.route('/content', draftApp)
|
|
321
|
+
apiProtected.route('/content', backrefsApp)
|
|
304
322
|
apiProtected.route('/content', contentFeature)
|
|
305
323
|
apiProtected.route('/widget', widgetApp)
|
|
306
324
|
apiProtected.route('/automations', automationsApp)
|
|
@@ -313,6 +331,9 @@ export function createBeechApp(config: BeechConfig): Hono<{ Bindings: Env; Varia
|
|
|
313
331
|
apiPublic.route('/', publicRoutes)
|
|
314
332
|
app.route('/api/v1/public', apiPublic)
|
|
315
333
|
|
|
334
|
+
// Webhooks API (public, verified via signature)
|
|
335
|
+
app.route('/api/webhooks', webhooksApp)
|
|
336
|
+
|
|
316
337
|
app.get('/api/media/:key', (context) => serveMediaHandler(context))
|
|
317
338
|
app.route('/api', apiProtected)
|
|
318
339
|
app.route('/api/search', searchRouter)
|