@sonicjs-cms/core 2.19.0 → 3.0.0-beta.10
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 +52 -52
- package/dist/admin-documents-form.template-DDSH6ROU.js +6 -0
- package/dist/{admin-layout-catalyst.template-UMTIN66R.js.map → admin-documents-form.template-DDSH6ROU.js.map} +1 -1
- package/dist/admin-documents-form.template-LSZKGA5J.cjs +19 -0
- package/dist/{admin-layout-catalyst.template-HFD37TY5.cjs.map → admin-documents-form.template-LSZKGA5J.cjs.map} +1 -1
- package/dist/{filter-bar.template-DlVYMk-T.d.cts → admin-layout-catalyst.template-DrwDUfsE.d.cts} +25 -1
- package/dist/{filter-bar.template-DlVYMk-T.d.ts → admin-layout-catalyst.template-DrwDUfsE.d.ts} +25 -1
- package/dist/admin-layout-catalyst.template-KDHKVLXR.cjs +21 -0
- package/dist/admin-layout-catalyst.template-KDHKVLXR.cjs.map +1 -0
- package/dist/admin-layout-catalyst.template-YQ4EMF2J.js +7 -0
- package/dist/admin-layout-catalyst.template-YQ4EMF2J.js.map +1 -0
- package/dist/app-Bo0X1OWX.d.ts +1268 -0
- package/dist/app-Do66yCcV.d.cts +1268 -0
- package/dist/cache-DDARE4QE.js +4 -0
- package/dist/cache-DDARE4QE.js.map +1 -0
- package/dist/cache-LVYS4BPL.cjs +33 -0
- package/dist/cache-LVYS4BPL.cjs.map +1 -0
- package/dist/chunk-2CB4KY7I.cjs +771 -0
- package/dist/chunk-2CB4KY7I.cjs.map +1 -0
- package/dist/{chunk-ABB34XUS.cjs → chunk-3KYKEXV7.cjs} +667 -19
- package/dist/chunk-3KYKEXV7.cjs.map +1 -0
- package/dist/chunk-4BTBSXMR.cjs +912 -0
- package/dist/chunk-4BTBSXMR.cjs.map +1 -0
- package/dist/{chunk-55RDMDOP.js → chunk-5V62WT6M.js} +181 -57
- package/dist/chunk-5V62WT6M.js.map +1 -0
- package/dist/{chunk-OCL3HMEG.js → chunk-6OC6MF3C.js} +7004 -9807
- package/dist/chunk-6OC6MF3C.js.map +1 -0
- package/dist/chunk-AI663NBO.js +821 -0
- package/dist/chunk-AI663NBO.js.map +1 -0
- package/dist/chunk-ALDRXTUO.js +273 -0
- package/dist/chunk-ALDRXTUO.js.map +1 -0
- package/dist/{chunk-TFNTM3OA.js → chunk-ATUPB6MN.js} +645 -15
- package/dist/chunk-ATUPB6MN.js.map +1 -0
- package/dist/chunk-BLMTL57B.js +767 -0
- package/dist/chunk-BLMTL57B.js.map +1 -0
- package/dist/{chunk-4ZSNJDLS.cjs → chunk-CRGUD4KC.cjs} +9 -9
- package/dist/chunk-CRGUD4KC.cjs.map +1 -0
- package/dist/chunk-F67UK75A.cjs +158 -0
- package/dist/chunk-F67UK75A.cjs.map +1 -0
- package/dist/chunk-GCDZZNIN.js +192 -0
- package/dist/chunk-GCDZZNIN.js.map +1 -0
- package/dist/chunk-HIKBY7MS.cjs +70 -0
- package/dist/chunk-HIKBY7MS.cjs.map +1 -0
- package/dist/{chunk-4NPCDK6B.js → chunk-IDCZBF35.js} +557 -90
- package/dist/chunk-IDCZBF35.js.map +1 -0
- package/dist/chunk-IESEVHXL.js +66 -0
- package/dist/chunk-IESEVHXL.js.map +1 -0
- package/dist/chunk-IGADDMXH.js +387 -0
- package/dist/chunk-IGADDMXH.js.map +1 -0
- package/dist/chunk-IHTXB7AT.cjs +276 -0
- package/dist/chunk-IHTXB7AT.cjs.map +1 -0
- package/dist/chunk-IVPRUGTY.js +242 -0
- package/dist/chunk-IVPRUGTY.js.map +1 -0
- package/dist/{chunk-JZVHLLSI.cjs → chunk-IXUHXTHW.cjs} +2 -151
- package/dist/chunk-IXUHXTHW.cjs.map +1 -0
- package/dist/chunk-J6JTWD2A.cjs +100 -0
- package/dist/chunk-J6JTWD2A.cjs.map +1 -0
- package/dist/chunk-JEQ7FLOD.cjs +199 -0
- package/dist/chunk-JEQ7FLOD.cjs.map +1 -0
- package/dist/{chunk-ON5ZMSU4.js → chunk-JQISFW6U.js} +3 -3
- package/dist/chunk-JQISFW6U.js.map +1 -0
- package/dist/chunk-K25XHMM3.js +566 -0
- package/dist/chunk-K25XHMM3.js.map +1 -0
- package/dist/{chunk-UYJ6TJHX.cjs → chunk-K623Q6WD.cjs} +181 -56
- package/dist/chunk-K623Q6WD.cjs.map +1 -0
- package/dist/{chunk-7A4CB7T3.cjs → chunk-MUNO67TT.cjs} +561 -91
- package/dist/chunk-MUNO67TT.cjs.map +1 -0
- package/dist/chunk-N32OWET6.cjs +327 -0
- package/dist/chunk-N32OWET6.cjs.map +1 -0
- package/dist/chunk-NUKJ54GA.cjs +245 -0
- package/dist/chunk-NUKJ54GA.cjs.map +1 -0
- package/dist/{chunk-XWIA3HVX.js → chunk-OBA2RYZN.js} +6 -1249
- package/dist/chunk-OBA2RYZN.js.map +1 -0
- package/dist/chunk-PMGOBS6X.cjs +408 -0
- package/dist/chunk-PMGOBS6X.cjs.map +1 -0
- package/dist/{chunk-OHYBNCVL.cjs → chunk-PXNTCCPE.cjs} +10 -1256
- package/dist/chunk-PXNTCCPE.cjs.map +1 -0
- package/dist/chunk-PYVFXCSD.js +1828 -0
- package/dist/chunk-PYVFXCSD.js.map +1 -0
- package/dist/{chunk-BU7SFHGP.js → chunk-QZGABF2M.js} +3 -149
- package/dist/chunk-QZGABF2M.js.map +1 -0
- package/dist/{chunk-E4YFJBM2.cjs → chunk-R4ILO3W6.cjs} +876 -829
- package/dist/chunk-R4ILO3W6.cjs.map +1 -0
- package/dist/chunk-RMRJGMDE.js +323 -0
- package/dist/chunk-RMRJGMDE.js.map +1 -0
- package/dist/chunk-RNZFGN4R.js +88 -0
- package/dist/chunk-RNZFGN4R.js.map +1 -0
- package/dist/chunk-RQ6N3FTV.js +900 -0
- package/dist/chunk-RQ6N3FTV.js.map +1 -0
- package/dist/{chunk-R4FOLLFB.cjs → chunk-TO6EY4P7.cjs} +8730 -11520
- package/dist/chunk-TO6EY4P7.cjs.map +1 -0
- package/dist/chunk-V464XBYS.js +154 -0
- package/dist/chunk-V464XBYS.js.map +1 -0
- package/dist/chunk-YA3TJ65D.cjs +575 -0
- package/dist/chunk-YA3TJ65D.cjs.map +1 -0
- package/dist/chunk-YP7GW2G5.cjs +866 -0
- package/dist/chunk-YP7GW2G5.cjs.map +1 -0
- package/dist/{collection-config-B4PG-AaF.d.cts → collection-config-JgHOpFCG.d.cts} +30 -2
- package/dist/{collection-config-B4PG-AaF.d.ts → collection-config-JgHOpFCG.d.ts} +30 -2
- package/dist/config-HFXANXCC.js +6 -0
- package/dist/config-HFXANXCC.js.map +1 -0
- package/dist/config-ON6FNMYX.cjs +19 -0
- package/dist/config-ON6FNMYX.cjs.map +1 -0
- package/dist/define-plugin-BzNHc1ZI.d.ts +1321 -0
- package/dist/define-plugin-IWDKYaVm.d.cts +1321 -0
- package/dist/document-projection-TDWRJX3Z.cjs +13 -0
- package/dist/document-projection-TDWRJX3Z.cjs.map +1 -0
- package/dist/document-projection-YYMC6I4U.js +4 -0
- package/dist/document-projection-YYMC6I4U.js.map +1 -0
- package/dist/index.cjs +13736 -4326
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +331 -493
- package/dist/index.d.ts +331 -493
- package/dist/index.js +13455 -4067
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +38 -32
- package/dist/middleware.d.cts +50 -7
- package/dist/middleware.d.ts +50 -7
- package/dist/middleware.js +9 -3
- package/dist/migrations-2XHQEGOQ.cjs +13 -0
- package/dist/{migrations-566IIPS2.cjs.map → migrations-2XHQEGOQ.cjs.map} +1 -1
- package/dist/migrations-PE3CDVSM.js +4 -0
- package/dist/{migrations-H5IXZNCO.js.map → migrations-PE3CDVSM.js.map} +1 -1
- package/dist/{plugin-bootstrap-DfVerYV4.d.cts → plugin-bootstrap-B8ThJU21.d.cts} +4315 -1661
- package/dist/{plugin-bootstrap-P_ciLp_C.d.ts → plugin-bootstrap-qu8hJgUt.d.ts} +4315 -1661
- package/dist/plugins.cjs +171 -12
- package/dist/plugins.d.cts +36 -2
- package/dist/plugins.d.ts +36 -2
- package/dist/plugins.js +5 -2
- package/dist/rbac-O73MFKDA.js +5 -0
- package/dist/rbac-O73MFKDA.js.map +1 -0
- package/dist/rbac-VONLJJKB.cjs +14 -0
- package/dist/rbac-VONLJJKB.cjs.map +1 -0
- package/dist/routes.cjs +42 -46
- package/dist/routes.d.cts +56 -146
- package/dist/routes.d.ts +56 -146
- package/dist/routes.js +18 -10
- package/dist/services.cjs +43 -76
- package/dist/services.d.cts +93 -55
- package/dist/services.d.ts +93 -55
- package/dist/services.js +6 -3
- package/dist/{telemetry-B9vIV4wh.d.cts → telemetry-Cku1ax74.d.cts} +1 -1
- package/dist/{telemetry-B9vIV4wh.d.ts → telemetry-Cku1ax74.d.ts} +1 -1
- package/dist/templates.cjs +17 -29
- package/dist/templates.d.cts +2 -89
- package/dist/templates.d.ts +2 -89
- package/dist/templates.js +3 -3
- package/dist/types-Dea1eNxU.d.cts +286 -0
- package/dist/types-Dea1eNxU.d.ts +286 -0
- package/dist/types.d.cts +2 -2
- package/dist/types.d.ts +2 -2
- package/dist/utils.cjs +21 -20
- package/dist/utils.d.cts +2 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +3 -2
- package/migrations/0001_core.sql +184 -0
- package/migrations/0002_documents.sql +163 -0
- package/package.json +12 -7
- package/dist/admin-layout-catalyst.template-HFD37TY5.cjs +0 -17
- package/dist/admin-layout-catalyst.template-UMTIN66R.js +0 -7
- package/dist/app-C9esKLmh.d.cts +0 -112
- package/dist/app-C9esKLmh.d.ts +0 -112
- package/dist/chunk-4NPCDK6B.js.map +0 -1
- package/dist/chunk-4ZSNJDLS.cjs.map +0 -1
- package/dist/chunk-55RDMDOP.js.map +0 -1
- package/dist/chunk-635JAMSE.cjs +0 -653
- package/dist/chunk-635JAMSE.cjs.map +0 -1
- package/dist/chunk-7A4CB7T3.cjs.map +0 -1
- package/dist/chunk-ABB34XUS.cjs.map +0 -1
- package/dist/chunk-BU7SFHGP.js.map +0 -1
- package/dist/chunk-E4YFJBM2.cjs.map +0 -1
- package/dist/chunk-EXNEW5US.js +0 -648
- package/dist/chunk-EXNEW5US.js.map +0 -1
- package/dist/chunk-JZV22DEV.js +0 -1783
- package/dist/chunk-JZV22DEV.js.map +0 -1
- package/dist/chunk-JZVHLLSI.cjs.map +0 -1
- package/dist/chunk-OCL3HMEG.js.map +0 -1
- package/dist/chunk-OHYBNCVL.cjs.map +0 -1
- package/dist/chunk-ON5ZMSU4.js.map +0 -1
- package/dist/chunk-QFWHAFEO.js +0 -1843
- package/dist/chunk-QFWHAFEO.js.map +0 -1
- package/dist/chunk-R4FOLLFB.cjs.map +0 -1
- package/dist/chunk-RLMUFFUD.cjs +0 -2219
- package/dist/chunk-RLMUFFUD.cjs.map +0 -1
- package/dist/chunk-TFNTM3OA.js.map +0 -1
- package/dist/chunk-UYJ6TJHX.cjs.map +0 -1
- package/dist/chunk-WAEQXGCX.cjs +0 -1898
- package/dist/chunk-WAEQXGCX.cjs.map +0 -1
- package/dist/chunk-XWIA3HVX.js.map +0 -1
- package/dist/chunk-ZYAYUIZE.js +0 -2217
- package/dist/chunk-ZYAYUIZE.js.map +0 -1
- package/dist/migrations-566IIPS2.cjs +0 -13
- package/dist/migrations-H5IXZNCO.js +0 -4
- package/dist/plugin-manager-BoM3Q7o7.d.cts +0 -328
- package/dist/plugin-manager-Efx9RyDX.d.ts +0 -328
- package/migrations/001_initial_schema.sql +0 -170
- package/migrations/002_faq_plugin.sql +0 -86
- package/migrations/003_stage5_enhancements.sql +0 -121
- package/migrations/004_stage6_user_management.sql +0 -183
- package/migrations/005_stage7_workflow_automation.sql +0 -294
- package/migrations/006_plugin_system.sql +0 -155
- package/migrations/007_demo_login_plugin.sql +0 -23
- package/migrations/008_fix_slug_validation.sql +0 -22
- package/migrations/009_system_logging.sql +0 -57
- package/migrations/011_config_managed_collections.sql +0 -15
- package/migrations/012_testimonials_plugin.sql +0 -80
- package/migrations/013_code_examples_plugin.sql +0 -177
- package/migrations/014_fix_plugin_registry.sql +0 -88
- package/migrations/015_add_remaining_plugins.sql +0 -89
- package/migrations/016_remove_duplicate_cache_plugin.sql +0 -17
- package/migrations/017_auth_configurable_fields.sql +0 -49
- package/migrations/018_settings_table.sql +0 -23
- package/migrations/019_remove_blog_posts_collection.sql +0 -15
- package/migrations/020_add_email_plugin.sql +0 -22
- package/migrations/021_add_magic_link_auth_plugin.sql +0 -42
- package/migrations/022_add_tinymce_plugin.sql +0 -25
- package/migrations/023_add_easy_mdx_plugin.sql +0 -25
- package/migrations/024_add_quill_editor_plugin.sql +0 -25
- package/migrations/025_add_easymde_plugin.sql +0 -25
- package/migrations/026_add_otp_login.sql +0 -42
- package/migrations/027_fix_slug_field_type.sql +0 -18
- package/migrations/028_fix_slug_field_type_in_schemas.sql +0 -30
- package/migrations/029_add_forms_system.sql +0 -184
- package/migrations/030_add_turnstile_to_forms.sql +0 -14
- package/migrations/031_ai_search_plugin.sql +0 -45
- package/migrations/032_user_profiles.sql +0 -37
- package/migrations/033_form_content_integration.sql +0 -19
- package/migrations/034_security_audit_plugin.sql +0 -27
- package/migrations/035_user_profiles_data_column.sql +0 -16
- package/migrations/036_analytics_events.sql +0 -22
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/templates/pages/admin-documents-form.template.ts","../src/templates/components/alert.template.ts"],"names":["init_admin_layout_catalyst_template","escapeHtml","renderAdminLayoutCatalyst"],"mappings":"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","file":"chunk-N32OWET6.cjs","sourcesContent":["import { renderAdminLayoutCatalyst } from '../layouts/admin-layout-catalyst.template'\nimport { renderAlert } from '../components/alert.template'\nimport { escapeHtml } from '../../utils/sanitize'\nimport type { Document, DocumentType, QueryableField } from '../../schemas/document'\n\nexport interface DocumentFormData {\n docType: DocumentType\n doc?: Document\n publishedDoc?: Document | null // Published revision when different from current draft\n isEdit: boolean\n errors?: Record<string, string>\n message?: string\n messageType?: 'success' | 'error' | 'warning' | 'info'\n user?: { name: string; email: string; role: string }\n version?: string\n versioningEnabled?: boolean // Whether versioning is opt-in for this document type\n}\n\nfunction inputClass(error?: string): string {\n const base = 'block w-full rounded-lg border bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white placeholder:text-zinc-400 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors'\n return error\n ? `${base} border-red-400 dark:border-red-500`\n : `${base} border-zinc-300 dark:border-zinc-700`\n}\n\nfunction renderFieldInput(field: QueryableField, value: unknown, error?: string): string {\n const id = `data_${field.name}`\n const name = `data[${field.name}]`\n const label = field.name.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase())\n const strVal = value != null ? String(value) : ''\n\n let input: string\n if (field.type === 'integer' || field.type === 'number') {\n input = `<input type=\"number\" id=\"${id}\" name=\"${name}\" value=\"${escapeHtml(strVal)}\"\n step=\"${field.type === 'integer' ? '1' : 'any'}\"\n class=\"${inputClass(error)}\">`\n } else if (field.type === 'boolean') {\n // Hidden 'false' before the checkbox: an unchecked checkbox submits nothing, so without this a\n // boolean could never be set back to false (D15). When checked, the checkbox value wins.\n input = `<div class=\"flex items-center gap-2\">\n <input type=\"hidden\" name=\"${name}\" value=\"false\">\n <input type=\"checkbox\" id=\"${id}\" name=\"${name}\" value=\"true\" ${strVal === 'true' ? 'checked' : ''}\n class=\"h-4 w-4 rounded border-zinc-300 dark:border-zinc-600 text-blue-600 focus:ring-blue-500\">\n <span class=\"text-sm text-zinc-700 dark:text-zinc-300\">${label}</span>\n </div>`\n return `\n <div>\n ${input}\n ${error ? `<p class=\"mt-1 text-xs text-red-500\">${escapeHtml(error)}</p>` : ''}\n </div>`\n } else if (field.kind === 'facet') {\n // Multi-value: comma-separated list\n const arrVal = Array.isArray(value) ? (value as string[]).join(', ') : strVal\n input = `<input type=\"text\" id=\"${id}\" name=\"${name}\" value=\"${escapeHtml(arrVal)}\"\n placeholder=\"Comma-separated values\"\n class=\"${inputClass(error)}\">`\n } else {\n input = `<input type=\"text\" id=\"${id}\" name=\"${name}\" value=\"${escapeHtml(strVal)}\"\n class=\"${inputClass(error)}\">`\n }\n\n return `\n <div>\n <label for=\"${id}\" class=\"block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1\">\n ${label}\n </label>\n ${input}\n ${error ? `<p class=\"mt-1 text-xs text-red-500\">${escapeHtml(error)}</p>` : ''}\n </div>`\n}\n\nfunction renderRemainingFields(\n allData: Record<string, unknown>,\n queryableFields: QueryableField[],\n errors: Record<string, string>,\n): string {\n const knownNames = new Set(queryableFields.map(f => f.name))\n const remainingKeys = Object.keys(allData).filter(k => !knownNames.has(k))\n if (remainingKeys.length === 0) return ''\n\n const inputs = remainingKeys.map(key => {\n const val = allData[key]\n const id = `data_${key}`\n const name = `data[${key}]`\n const label = key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase())\n const strVal = typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val ?? '')\n const isMultiline = strVal.includes('\\n') || strVal.length > 100\n\n return `\n <div>\n <label for=\"${id}\" class=\"block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1\">${escapeHtml(label)}</label>\n ${isMultiline\n ? `<textarea id=\"${id}\" name=\"${name}\" rows=\"4\" class=\"${inputClass(errors[key])}\">${escapeHtml(strVal)}</textarea>`\n : `<input type=\"text\" id=\"${id}\" name=\"${name}\" value=\"${escapeHtml(strVal)}\" class=\"${inputClass(errors[key])}\">`\n }\n ${errors[key] ? `<p class=\"mt-1 text-xs text-red-500\">${escapeHtml(errors[key])}</p>` : ''}\n </div>`\n }).join('')\n\n return `\n <div class=\"border-t border-zinc-200 dark:border-zinc-700 pt-6\">\n <h3 class=\"text-sm font-medium text-zinc-500 dark:text-zinc-400 mb-4\">Additional Fields</h3>\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">${inputs}</div>\n </div>`\n}\n\nexport function renderDocumentFormPage(data: DocumentFormData): string {\n const { docType, doc, publishedDoc, isEdit, errors = {} } = data\n const queryableFields = docType.queryableFields ?? []\n const docData = (doc?.data ?? {}) as Record<string, unknown>\n\n const isAdmin = data.user?.role === 'admin'\n const isEditor = isAdmin || data.user?.role === 'editor'\n\n const hasNewerDraft = isEdit && doc && !doc.isPublished && publishedDoc\n const isPublishedAndDraft = isEdit && doc?.isPublished && doc?.isCurrentDraft\n\n // Real document CRUD lives under /admin/content/documents/:typeId/... (admin-content.ts), NOT\n // /admin/documents/ui (those are GET redirects only). Edit/save POSTs to :rootId; create to /new.\n const formAction = isEdit\n ? `/admin/content/documents/${escapeHtml(docType.id)}/${escapeHtml(doc!.rootId)}`\n : `/admin/content/documents/${escapeHtml(docType.id)}/new`\n\n const publishBannerHtml = (() => {\n if (!isEdit || !doc) return ''\n if (isPublishedAndDraft) {\n return renderAlert({\n type: 'success',\n message: 'This document is live. Saving creates a new draft. Use \"Publish\" to push changes live.',\n })\n }\n if (hasNewerDraft) {\n return renderAlert({\n type: 'info',\n message: `A published version (v${publishedDoc!.versionNumber}) is still live. This is an unpublished draft (v${doc.versionNumber}).`,\n })\n }\n return ''\n })()\n\n // Reference-kind fields are intentionally not rendered yet (D27) — fine for FAQ/testimonial, which\n // have none. When media references land (Phase 6), render a root-id picker here.\n const queryableInputs = queryableFields\n .filter(f => f.kind !== 'reference')\n .map(f => renderFieldInput(f, docData[f.name], errors[`data.${f.name}`]))\n .join('')\n\n const remainingHtml = renderRemainingFields(docData, queryableFields, errors)\n\n const content = `\n <div class=\"w-full px-4 sm:px-6 lg:px-8 py-6 space-y-6\">\n <!-- Header -->\n <div class=\"flex flex-col sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <div class=\"flex items-center gap-2 text-sm text-zinc-500 dark:text-zinc-400 mb-1\">\n <a href=\"/admin/content\" class=\"hover:text-zinc-950 dark:hover:text-white\">Content</a>\n <svg class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\" clip-rule=\"evenodd\"/></svg>\n <a href=\"/admin/content?model=doc:${escapeHtml(docType.id)}\" class=\"hover:text-zinc-950 dark:hover:text-white\">${escapeHtml(docType.displayName)}</a>\n <svg class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\" clip-rule=\"evenodd\"/></svg>\n <span class=\"text-zinc-950 dark:text-white font-medium\">${isEdit ? 'Edit' : 'New'}</span>\n </div>\n <h1 class=\"text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8\">\n ${isEdit ? `Edit ${escapeHtml(docType.displayName)}` : `New ${escapeHtml(docType.displayName)}`}\n </h1>\n ${isEdit && doc ? `<p class=\"mt-1 text-xs text-zinc-500 dark:text-zinc-400\">v${doc.versionNumber} · root: <code class=\"font-mono\">${escapeHtml(doc.rootId)}</code></p>` : ''}\n </div>\n\n <!-- Publish controls (edit mode, versioning types only) -->\n ${isEdit && doc && isEditor && data.versioningEnabled ? `\n <div class=\"mt-4 sm:mt-0 flex gap-2\">\n ${!doc.isPublished ? `\n <form method=\"POST\" action=\"/admin/content/documents/${escapeHtml(docType.id)}/${escapeHtml(doc.id)}/publish\">\n <button type=\"submit\"\n class=\"inline-flex items-center rounded-lg bg-green-600 px-3.5 py-2.5 text-sm font-semibold text-white hover:bg-green-500 transition-colors shadow-sm\">\n Publish\n </button>\n </form>` : `\n <form method=\"POST\" action=\"/admin/content/documents/${escapeHtml(docType.id)}/${escapeHtml(doc.id)}/unpublish\">\n <button type=\"submit\"\n class=\"inline-flex items-center rounded-lg bg-amber-500 px-3.5 py-2.5 text-sm font-semibold text-white hover:bg-amber-400 transition-colors shadow-sm\">\n Unpublish\n </button>\n </form>`}\n </div>` : ''}\n </div>\n\n ${publishBannerHtml}\n ${data.message ? renderAlert({ type: data.messageType ?? 'info', message: data.message, dismissible: true }) : ''}\n\n <!-- Form -->\n <form method=\"POST\" action=\"${formAction}\">\n ${isEdit ? `<input type=\"hidden\" name=\"_method\" value=\"PUT\">` : ''}\n\n <div class=\"relative rounded-xl\">\n <div class=\"absolute inset-0 bg-gradient-to-br from-blue-500/5 to-purple-500/5 dark:from-blue-400/10 dark:to-purple-400/10 rounded-xl\"></div>\n <div class=\"relative bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 rounded-xl p-6 space-y-6\">\n\n <!-- Standard fields -->\n <div>\n <h3 class=\"text-sm font-semibold text-zinc-700 dark:text-zinc-200 mb-4\">Document</h3>\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label for=\"title\" class=\"block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1\">Title</label>\n <input type=\"text\" id=\"title\" name=\"title\" value=\"${escapeHtml(doc?.title ?? '')}\"\n class=\"${inputClass(errors.title)}\">\n ${errors.title ? `<p class=\"mt-1 text-xs text-red-500\">${escapeHtml(errors.title)}</p>` : ''}\n </div>\n <div>\n <label for=\"slug\" class=\"block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1\">Slug</label>\n <input type=\"text\" id=\"slug\" name=\"slug\" value=\"${escapeHtml(doc?.slug ?? '')}\"\n placeholder=\"auto-generated-if-empty\"\n class=\"${inputClass(errors.slug)}\">\n ${errors.slug ? `<p class=\"mt-1 text-xs text-red-500\">${escapeHtml(errors.slug)}</p>` : ''}\n </div>\n </div>\n </div>\n\n <!-- Queryable data fields -->\n ${queryableFields.length > 0 ? `\n <div class=\"border-t border-zinc-200 dark:border-zinc-700 pt-6\">\n <h3 class=\"text-sm font-semibold text-zinc-700 dark:text-zinc-200 mb-4\">Content</h3>\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n ${queryableInputs}\n </div>\n </div>` : ''}\n\n <!-- Remaining data fields not in queryable fields -->\n ${remainingHtml}\n\n <!-- Actions -->\n <div class=\"border-t border-zinc-200 dark:border-zinc-700 pt-6 flex items-center justify-between\">\n <a href=\"/admin/content?model=doc:${escapeHtml(docType.id)}\"\n class=\"inline-flex items-center rounded-lg bg-white dark:bg-zinc-800 px-3.5 py-2.5 text-sm font-semibold text-zinc-700 dark:text-zinc-200 ring-1 ring-inset ring-zinc-300 dark:ring-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors\">\n Cancel\n </a>\n <div class=\"flex gap-3\">\n <button type=\"submit\"\n class=\"inline-flex items-center rounded-lg bg-zinc-950 dark:bg-white px-3.5 py-2.5 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors shadow-sm\">\n ${isEdit ? (data.versioningEnabled ? 'Save Draft' : 'Update') : 'Create'}\n </button>\n </div>\n </div>\n </div>\n </div>\n </form>\n\n <!-- Version history (edit mode, versioning opt-in only) -->\n ${isEdit && doc && data.versioningEnabled ? `\n <details class=\"group\">\n <summary class=\"cursor-pointer text-sm text-zinc-500 dark:text-zinc-400 hover:text-zinc-950 dark:hover:text-white flex items-center gap-2 py-2\">\n <svg class=\"h-4 w-4 transition-transform group-open:rotate-90\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\" clip-rule=\"evenodd\"/>\n </svg>\n Version history\n </summary>\n <div class=\"mt-3 rounded-xl bg-white dark:bg-zinc-900 ring-1 ring-zinc-950/5 dark:ring-white/10 overflow-hidden\">\n <div id=\"version-history-placeholder\" class=\"px-6 py-4 text-sm text-zinc-500 dark:text-zinc-400\"\n hx-get=\"/admin/versioning/${escapeHtml(doc.rootId)}\"\n hx-trigger=\"revealed\"\n hx-swap=\"outerHTML\">\n Loading version history…\n </div>\n </div>\n </details>` : ''}\n </div>\n `\n\n return renderAdminLayoutCatalyst({\n title: `${isEdit ? 'Edit' : 'New'} ${docType.displayName} — Documents`,\n currentPath: '/admin/content',\n user: data.user,\n version: data.version,\n content,\n })\n}\n\n// ─── Version history fragment (HTMX target) ──────────────────────────────────\n\nexport interface VersionHistoryData {\n versions: Array<{\n id: string\n versionNumber: number\n isCurrentDraft: boolean\n isPublished: boolean\n status: string\n updatedAt: number\n createdBy: string | null\n }>\n docType: DocumentType\n rootId: string\n}\n\nexport function renderVersionHistoryFragment(data: VersionHistoryData): string {\n if (data.versions.length === 0) {\n return `<div class=\"px-6 py-4 text-sm text-zinc-500 dark:text-zinc-400\">No versions found.</div>`\n }\n\n const rows = data.versions.map(v => `\n <div class=\"flex items-center justify-between px-6 py-3 border-b border-zinc-100 dark:border-zinc-800 last:border-0\">\n <div class=\"flex items-center gap-3\">\n <span class=\"text-sm font-medium text-zinc-950 dark:text-white\">v${v.versionNumber}</span>\n ${v.isPublished ? `<span class=\"inline-flex items-center rounded-md bg-green-50 dark:bg-green-500/10 px-1.5 py-0.5 text-xs font-medium text-green-700 dark:text-green-400\">live</span>` : ''}\n ${v.isCurrentDraft ? `<span class=\"inline-flex items-center rounded-md bg-blue-50 dark:bg-blue-500/10 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:text-blue-400\">draft</span>` : ''}\n </div>\n <div class=\"flex items-center gap-4 text-xs text-zinc-500 dark:text-zinc-400\">\n <span>${v.createdBy ?? '—'}</span>\n <span>${new Date(v.updatedAt * 1000).toLocaleString('en-US', { dateStyle: 'short', timeStyle: 'short' })}</span>\n </div>\n </div>\n `).join('')\n\n return `<div>${rows}</div>`\n}\n","import { escapeHtml } from '../../utils/sanitize'\n\nexport type AlertType = 'success' | 'error' | 'warning' | 'info'\n\nexport interface AlertData {\n type: AlertType\n title?: string\n message: string\n dismissible?: boolean\n className?: string\n icon?: boolean\n}\n\nexport function renderAlert(data: AlertData): string {\n const typeClasses = {\n success: 'bg-green-50 dark:bg-green-500/10 border border-green-600/20 dark:border-green-500/20',\n error: 'bg-error/10 border border-red-600/20 dark:border-red-500/20',\n warning: 'bg-amber-50 dark:bg-amber-500/10 border border-amber-600/20 dark:border-amber-500/20',\n info: 'bg-blue-50 dark:bg-blue-500/10 border border-blue-600/20 dark:border-blue-500/20'\n }\n\n const iconClasses = {\n success: 'text-green-600 dark:text-green-400',\n error: 'text-red-600 dark:text-red-400',\n warning: 'text-amber-600 dark:text-amber-400',\n info: 'text-blue-600 dark:text-blue-400'\n }\n\n const textClasses = {\n success: 'text-green-900 dark:text-green-300',\n error: 'text-red-900 dark:text-red-300',\n warning: 'text-amber-900 dark:text-amber-300',\n info: 'text-blue-900 dark:text-blue-300'\n }\n\n const messageTextClasses = {\n success: 'text-green-700 dark:text-green-400',\n error: 'text-red-700 dark:text-red-400',\n warning: 'text-amber-700 dark:text-amber-400',\n info: 'text-blue-700 dark:text-blue-400'\n }\n\n const icons = {\n success: `<path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\" clip-rule=\"evenodd\" />`,\n error: `<path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z\" clip-rule=\"evenodd\" />`,\n warning: `<path fill-rule=\"evenodd\" d=\"M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z\" clip-rule=\"evenodd\" />`,\n info: `<path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z\" clip-rule=\"evenodd\" />`\n }\n\n return `\n <div class=\"rounded-lg p-4 ${typeClasses[data.type]} ${data.className || ''}\" ${data.dismissible ? 'id=\"dismissible-alert\"' : ''}>\n <div class=\"flex\">\n ${data.icon !== false ? `\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 ${iconClasses[data.type]}\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n ${icons[data.type]}\n </svg>\n </div>\n ` : ''}\n <div class=\"${data.icon !== false ? 'ml-3' : ''}\">\n ${data.title ? `\n <h3 class=\"text-sm font-semibold ${textClasses[data.type]}\">\n ${escapeHtml(data.title)}\n </h3>\n ` : ''}\n <div class=\"${data.title ? 'mt-1 text-sm' : 'text-sm'} ${messageTextClasses[data.type]}\">\n <p>${escapeHtml(data.message)}</p>\n </div>\n </div>\n ${data.dismissible ? `\n <div class=\"ml-auto pl-3\">\n <div class=\"-mx-1.5 -my-1.5\">\n <button\n type=\"button\"\n class=\"inline-flex rounded-md p-1.5 ${iconClasses[data.type]} hover:bg-opacity-20 focus:outline-none focus:ring-2 focus:ring-offset-2\"\n onclick=\"document.getElementById('dismissible-alert').remove()\"\n >\n <span class=\"sr-only\">Dismiss</span>\n <svg class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z\" clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n </div>\n ` : ''}\n </div>\n </div>\n `\n}\n\nexport function renderSuccessAlert(message: string, title?: string): string {\n return renderAlert({ type: 'success', message, title })\n}\n\nexport function renderErrorAlert(message: string, title?: string): string {\n return renderAlert({ type: 'error', message, title })\n}\n\nexport function renderWarningAlert(message: string, title?: string): string {\n return renderAlert({ type: 'warning', message, title })\n}\n\nexport function renderInfoAlert(message: string, title?: string): string {\n return renderAlert({ type: 'info', message, title })\n}\n"]}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkHIKBY7MS_cjs = require('./chunk-HIKBY7MS.cjs');
|
|
4
|
+
var chunkYP7GW2G5_cjs = require('./chunk-YP7GW2G5.cjs');
|
|
5
|
+
var betterAuth = require('better-auth');
|
|
6
|
+
var betterAuthCloudflare = require('better-auth-cloudflare');
|
|
7
|
+
var crypto$1 = require('better-auth/crypto');
|
|
8
|
+
var api = require('better-auth/api');
|
|
9
|
+
var magicLink = require('better-auth/plugins/magic-link');
|
|
10
|
+
var emailOtp = require('better-auth/plugins/email-otp');
|
|
11
|
+
var organization = require('better-auth/plugins/organization');
|
|
12
|
+
var d1 = require('drizzle-orm/d1');
|
|
13
|
+
|
|
14
|
+
async function sendViaEmailPlugin(db, to, subject, html) {
|
|
15
|
+
try {
|
|
16
|
+
const row = await db.prepare("SELECT settings FROM plugins WHERE id = 'email'").first();
|
|
17
|
+
if (row?.settings) {
|
|
18
|
+
const { apiKey, fromEmail, fromName } = JSON.parse(row.settings);
|
|
19
|
+
if (apiKey && fromEmail) {
|
|
20
|
+
await fetch("https://api.resend.com/emails", {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
from: `${fromName ?? "SonicJS"} <${fromEmail}>`,
|
|
25
|
+
to: [to],
|
|
26
|
+
subject,
|
|
27
|
+
html
|
|
28
|
+
})
|
|
29
|
+
});
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
console.log(`[email-dev] To:${to} | Subject:${subject}`);
|
|
36
|
+
}
|
|
37
|
+
async function verifyLegacyPbkdf2(password, stored) {
|
|
38
|
+
const parts = stored.split(":");
|
|
39
|
+
if (parts.length !== 4) return false;
|
|
40
|
+
const iterations = parseInt(parts[1], 10);
|
|
41
|
+
const saltBytes = parts[2].match(/.{2}/g);
|
|
42
|
+
if (!saltBytes || !Number.isFinite(iterations)) return false;
|
|
43
|
+
const salt = new Uint8Array(saltBytes.map((b) => parseInt(b, 16)));
|
|
44
|
+
const km = await crypto.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveBits"]);
|
|
45
|
+
const bits = await crypto.subtle.deriveBits({ name: "PBKDF2", salt, iterations, hash: "SHA-256" }, km, 256);
|
|
46
|
+
const actual = Array.from(new Uint8Array(bits)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
47
|
+
const expected = parts[3];
|
|
48
|
+
if (actual.length !== expected.length) return false;
|
|
49
|
+
let diff = 0;
|
|
50
|
+
for (let i = 0; i < actual.length; i++) diff |= actual.charCodeAt(i) ^ expected.charCodeAt(i);
|
|
51
|
+
return diff === 0;
|
|
52
|
+
}
|
|
53
|
+
function getDefaultAuthOptions(env, requestBaseURL) {
|
|
54
|
+
const db = d1.drizzle(env.DB);
|
|
55
|
+
return {
|
|
56
|
+
secret: env.BETTER_AUTH_SECRET,
|
|
57
|
+
baseURL: env.BETTER_AUTH_URL || requestBaseURL,
|
|
58
|
+
appName: "SonicJS",
|
|
59
|
+
...betterAuthCloudflare.withCloudflare(
|
|
60
|
+
{
|
|
61
|
+
autoDetectIpAddress: true,
|
|
62
|
+
geolocationTracking: false,
|
|
63
|
+
cf: {},
|
|
64
|
+
d1: {
|
|
65
|
+
db,
|
|
66
|
+
options: {
|
|
67
|
+
// Keys MUST match modelName values — BA resolves by modelName, not by JS variable name.
|
|
68
|
+
schema: { auth_user: chunkYP7GW2G5_cjs.authUser, auth_session: chunkYP7GW2G5_cjs.authSession, auth_account: chunkYP7GW2G5_cjs.authAccount, auth_verification: chunkYP7GW2G5_cjs.authVerification, auth_tenant: chunkYP7GW2G5_cjs.authTenant, auth_tenant_member: chunkYP7GW2G5_cjs.authTenantMember, auth_tenant_invitation: chunkYP7GW2G5_cjs.authTenantInvitation, auth_tenant_team: chunkYP7GW2G5_cjs.authTenantTeam }
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
kv: env.CACHE_KV
|
|
72
|
+
// session secondary storage → getSession skips D1
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
basePath: "/auth",
|
|
76
|
+
emailAndPassword: {
|
|
77
|
+
enabled: true,
|
|
78
|
+
autoSignIn: true,
|
|
79
|
+
// Transparent migration of SonicJS legacy PBKDF2 hashes: verify against
|
|
80
|
+
// the old format on login, then re-hash to scrypt and persist. No
|
|
81
|
+
// mass-rehash, no forced password resets.
|
|
82
|
+
password: {
|
|
83
|
+
verify: async ({ hash, password }) => {
|
|
84
|
+
if (hash.startsWith("pbkdf2:")) {
|
|
85
|
+
const ok = await verifyLegacyPbkdf2(password, hash);
|
|
86
|
+
if (ok) {
|
|
87
|
+
const upgraded = await crypto$1.hashPassword(password);
|
|
88
|
+
await env.DB.prepare(
|
|
89
|
+
"UPDATE auth_account SET password = ?, updated_at = ? WHERE password = ? AND provider_id = 'credential'"
|
|
90
|
+
).bind(upgraded, Math.floor(Date.now() / 1e3), hash).run();
|
|
91
|
+
}
|
|
92
|
+
return ok;
|
|
93
|
+
}
|
|
94
|
+
return crypto$1.verifyPassword({ hash, password });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
user: {
|
|
99
|
+
modelName: "auth_user",
|
|
100
|
+
// Field-mapping values are Drizzle *property keys* (camelCase), which
|
|
101
|
+
// already match Better Auth's defaults for emailVerified/createdAt/
|
|
102
|
+
// updatedAt. Only `image` differs (SonicJS uses `avatar`).
|
|
103
|
+
fields: {
|
|
104
|
+
image: "avatar"
|
|
105
|
+
},
|
|
106
|
+
additionalFields: {
|
|
107
|
+
role: { type: "string", required: false, defaultValue: "viewer", input: false },
|
|
108
|
+
firstName: { type: "string", required: false, defaultValue: "", input: true },
|
|
109
|
+
lastName: { type: "string", required: false, defaultValue: "", input: true },
|
|
110
|
+
isSuperAdmin: { type: "boolean", required: false, defaultValue: false, input: false }
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
session: {
|
|
114
|
+
modelName: "auth_session",
|
|
115
|
+
// Drizzle property keys already match Better Auth defaults (userId,
|
|
116
|
+
// expiresAt, ipAddress, …) — no field overrides needed.
|
|
117
|
+
expiresIn: 60 * 60 * 24 * 7,
|
|
118
|
+
// 7 days
|
|
119
|
+
updateAge: 60 * 60 * 24
|
|
120
|
+
// refresh once per day
|
|
121
|
+
},
|
|
122
|
+
account: { modelName: "auth_account" },
|
|
123
|
+
verification: { modelName: "auth_verification" },
|
|
124
|
+
databaseHooks: {
|
|
125
|
+
user: {
|
|
126
|
+
create: {
|
|
127
|
+
before: async (userData) => {
|
|
128
|
+
const isFirst = await chunkHIKBY7MS_cjs.isFirstUserRegistration(env.DB);
|
|
129
|
+
if (!isFirst) {
|
|
130
|
+
const enabled = await chunkHIKBY7MS_cjs.isRegistrationEnabled(env.DB);
|
|
131
|
+
if (!enabled) {
|
|
132
|
+
throw new api.APIError("BAD_REQUEST", { message: "Registration is currently disabled." });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const d = userData;
|
|
136
|
+
const name = (d.name ?? "User").toString();
|
|
137
|
+
const parts = name.trim().split(/\s+/);
|
|
138
|
+
const firstName = d.firstName || parts[0] || "User";
|
|
139
|
+
const lastName = d.lastName || parts.slice(1).join(" ") || firstName;
|
|
140
|
+
return { data: { ...userData, name, firstName, lastName, role: "viewer" } };
|
|
141
|
+
},
|
|
142
|
+
after: async (user) => {
|
|
143
|
+
try {
|
|
144
|
+
const { RbacService } = await import('./rbac-VONLJJKB.cjs');
|
|
145
|
+
const rbac = new RbacService(env.DB);
|
|
146
|
+
const roleName = await rbac.countPortalAdmins(user.id) === 0 ? "admin" : "viewer";
|
|
147
|
+
await rbac.addUserRoleByName(user.id, roleName);
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
),
|
|
156
|
+
// ── Phase 4: BA-native login methods ─────────────────────────────────────
|
|
157
|
+
// Magic-link and Email-OTP replace the standalone SonicJS plugins that
|
|
158
|
+
// minted JWT cookies. Social providers replace the bespoke oauth-providers
|
|
159
|
+
// plugin. All are gated on the relevant env vars / email service config
|
|
160
|
+
// so they activate only when configured.
|
|
161
|
+
plugins: [
|
|
162
|
+
// Magic-link passwordless auth. Sends a one-time link to the user's inbox;
|
|
163
|
+
// the link resolves to a BA session. Requires a working email service.
|
|
164
|
+
magicLink.magicLink({
|
|
165
|
+
sendMagicLink: async ({ email, url }, _request) => {
|
|
166
|
+
await sendViaEmailPlugin(
|
|
167
|
+
env.DB,
|
|
168
|
+
email,
|
|
169
|
+
"Your sign-in link",
|
|
170
|
+
`<div style="font-family:sans-serif;max-width:600px">
|
|
171
|
+
<h2>Sign in to SonicJS</h2>
|
|
172
|
+
<p>Click the link below to sign in. Expires in 15 minutes.</p>
|
|
173
|
+
<p><a href="${url}" style="background:#465FFF;color:#fff;padding:12px 24px;border-radius:6px;text-decoration:none">Sign in</a></p>
|
|
174
|
+
<p style="color:#666;font-size:12px">Or copy: ${url}</p>
|
|
175
|
+
</div>`
|
|
176
|
+
);
|
|
177
|
+
},
|
|
178
|
+
expiresIn: 15 * 60
|
|
179
|
+
}),
|
|
180
|
+
// Email OTP — 6-digit code sent to inbox. Replaces the otp-login-plugin.
|
|
181
|
+
emailOtp.emailOTP({
|
|
182
|
+
sendVerificationOTP: async (params, _request) => {
|
|
183
|
+
await sendViaEmailPlugin(
|
|
184
|
+
env.DB,
|
|
185
|
+
params.email,
|
|
186
|
+
"Your sign-in code",
|
|
187
|
+
`<div style="font-family:sans-serif;max-width:600px">
|
|
188
|
+
<h2>Your one-time code</h2>
|
|
189
|
+
<p style="font-size:36px;font-weight:bold;letter-spacing:8px;color:#465FFF">${params.otp}</p>
|
|
190
|
+
<p style="color:#666">Expires in 10 minutes. Do not share this code.</p>
|
|
191
|
+
</div>`
|
|
192
|
+
);
|
|
193
|
+
},
|
|
194
|
+
otpLength: 6,
|
|
195
|
+
expiresIn: 10 * 60
|
|
196
|
+
}),
|
|
197
|
+
organization.organization({
|
|
198
|
+
schema: {
|
|
199
|
+
organization: {
|
|
200
|
+
modelName: "auth_tenant",
|
|
201
|
+
additionalFields: {
|
|
202
|
+
status: { type: "string", required: false, defaultValue: "active", input: true },
|
|
203
|
+
domain: { type: "string", required: false, input: true },
|
|
204
|
+
notes: { type: "string", required: false, defaultValue: "", input: true }
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
member: {
|
|
208
|
+
modelName: "auth_tenant_member",
|
|
209
|
+
fields: { organizationId: "tenant_id" }
|
|
210
|
+
},
|
|
211
|
+
invitation: {
|
|
212
|
+
modelName: "auth_tenant_invitation",
|
|
213
|
+
fields: { organizationId: "tenant_id" }
|
|
214
|
+
},
|
|
215
|
+
team: {
|
|
216
|
+
modelName: "auth_tenant_team",
|
|
217
|
+
fields: { organizationId: "tenant_id" }
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
],
|
|
222
|
+
// ── Phase 4: Social providers ─────────────────────────────────────────
|
|
223
|
+
// Activated when the relevant env vars are set. Replaces the bespoke
|
|
224
|
+
// oauth-providers SonicJS plugin. Set via wrangler secret put / .dev.vars.
|
|
225
|
+
socialProviders: {
|
|
226
|
+
...env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET ? { github: { clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET } } : {},
|
|
227
|
+
...env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET ? { google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET } } : {}
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function createAuth(env, extendBetterAuth, requestBaseURL) {
|
|
232
|
+
if (!env.BETTER_AUTH_SECRET || env.BETTER_AUTH_SECRET.length < 16) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
"BETTER_AUTH_SECRET is missing or too short. Set it as a Wrangler secret (wrangler secret put BETTER_AUTH_SECRET) or in a gitignored .dev.vars for local dev. Refusing to initialize auth without a strong signing secret."
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
const defaults = getDefaultAuthOptions(env, requestBaseURL);
|
|
238
|
+
const options = extendBetterAuth ? extendBetterAuth(defaults) : defaults;
|
|
239
|
+
return betterAuth.betterAuth(options);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
exports.createAuth = createAuth;
|
|
243
|
+
exports.getDefaultAuthOptions = getDefaultAuthOptions;
|
|
244
|
+
//# sourceMappingURL=chunk-NUKJ54GA.cjs.map
|
|
245
|
+
//# sourceMappingURL=chunk-NUKJ54GA.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth/config.ts"],"names":["drizzle","withCloudflare","authUser","authSession","authAccount","authVerification","authTenant","authTenantMember","authTenantInvitation","authTenantTeam","baHashPassword","baVerifyPassword","isFirstUserRegistration","isRegistrationEnabled","APIError","magicLink","emailOTP","organization","betterAuth"],"mappings":"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","file":"chunk-NUKJ54GA.cjs","sourcesContent":["/**\n * Better Auth configuration for SonicJS — via the better-auth-cloudflare shim.\n *\n * A fresh auth instance is built per request (Workers lifecycle). The existing\n * `auth_user` table is used as Better Auth's user model. Legacy SonicJS PBKDF2\n * hashes are verified and transparently upgraded to scrypt on first login. KV\n * (CACHE_KV) is used as session secondary storage so getSession does not hit D1\n * on every request.\n *\n * Extend via config.auth.extendBetterAuth in createSonicJSApp() to add social\n * providers, magic link, 2FA, etc.\n */\n/** Send an email via the SonicJS email plugin (Resend).\n * Loads apiKey/fromEmail/fromName from the `plugins` table at runtime.\n * Falls back to console.log when the plugin is unconfigured (local dev). */\nasync function sendViaEmailPlugin(\n db: D1Database,\n to: string,\n subject: string,\n html: string\n): Promise<void> {\n try {\n const row = (await db\n .prepare(\"SELECT settings FROM plugins WHERE id = 'email'\")\n .first()) as { settings: string } | null\n if (row?.settings) {\n const { apiKey, fromEmail, fromName } = JSON.parse(row.settings) as {\n apiKey?: string; fromEmail?: string; fromName?: string\n }\n if (apiKey && fromEmail) {\n await fetch('https://api.resend.com/emails', {\n method: 'POST',\n headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n from: `${fromName ?? 'SonicJS'} <${fromEmail}>`,\n to: [to],\n subject,\n html,\n }),\n })\n return\n }\n }\n } catch { /* fall through to dev log */ }\n console.log(`[email-dev] To:${to} | Subject:${subject}`)\n}\n\nimport { betterAuth } from 'better-auth'\nimport { withCloudflare } from 'better-auth-cloudflare'\nimport { hashPassword as baHashPassword, verifyPassword as baVerifyPassword } from 'better-auth/crypto'\nimport { APIError } from 'better-auth/api'\nimport { magicLink } from 'better-auth/plugins/magic-link'\nimport { emailOTP } from 'better-auth/plugins/email-otp'\nimport { organization } from 'better-auth/plugins/organization'\nimport { drizzle } from 'drizzle-orm/d1'\nimport { authUser, authSession, authAccount, authVerification, authTenant, authTenantMember, authTenantInvitation, authTenantTeam } from '../db/schema'\nimport { isRegistrationEnabled, isFirstUserRegistration } from '../services/auth-validation'\nimport type { Bindings } from '../app'\n\n/**\n * Verify a password against a SonicJS legacy PBKDF2 hash:\n * pbkdf2:<iterations>:<saltHex>:<hashHex> (PBKDF2-SHA256, 256-bit)\n * Mirrors AuthManager.verifyPassword in middleware/auth.ts.\n */\nasync function verifyLegacyPbkdf2(password: string, stored: string): Promise<boolean> {\n const parts = stored.split(':')\n if (parts.length !== 4) return false\n const iterations = parseInt(parts[1]!, 10)\n const saltBytes = parts[2]!.match(/.{2}/g)\n if (!saltBytes || !Number.isFinite(iterations)) return false\n const salt = new Uint8Array(saltBytes.map((b) => parseInt(b, 16)))\n const km = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveBits'])\n const bits = await crypto.subtle.deriveBits({ name: 'PBKDF2', salt, iterations, hash: 'SHA-256' }, km, 256)\n const actual = Array.from(new Uint8Array(bits)).map((b) => b.toString(16).padStart(2, '0')).join('')\n const expected = parts[3]!\n if (actual.length !== expected.length) return false\n let diff = 0\n for (let i = 0; i < actual.length; i++) diff |= actual.charCodeAt(i) ^ expected.charCodeAt(i)\n return diff === 0\n}\n\n/**\n * Build the default Better Auth options used by SonicJS (through the CF shim).\n * Exported so apps can extend via config.auth.extendBetterAuth.\n */\nexport function getDefaultAuthOptions(env: Bindings, requestBaseURL?: string) {\n const db = drizzle(env.DB)\n\n return {\n secret: env.BETTER_AUTH_SECRET,\n baseURL: env.BETTER_AUTH_URL || requestBaseURL,\n appName: 'SonicJS',\n ...withCloudflare(\n {\n autoDetectIpAddress: true,\n geolocationTracking: false,\n cf: {},\n d1: {\n db,\n options: {\n // Keys MUST match modelName values — BA resolves by modelName, not by JS variable name.\n schema: { auth_user: authUser, auth_session: authSession, auth_account: authAccount, auth_verification: authVerification, auth_tenant: authTenant, auth_tenant_member: authTenantMember, auth_tenant_invitation: authTenantInvitation, auth_tenant_team: authTenantTeam },\n },\n },\n kv: env.CACHE_KV, // session secondary storage → getSession skips D1\n },\n {\n basePath: '/auth',\n emailAndPassword: {\n enabled: true,\n autoSignIn: true,\n // Transparent migration of SonicJS legacy PBKDF2 hashes: verify against\n // the old format on login, then re-hash to scrypt and persist. No\n // mass-rehash, no forced password resets.\n password: {\n verify: async ({ hash, password }: { hash: string; password: string }) => {\n if (hash.startsWith('pbkdf2:')) {\n const ok = await verifyLegacyPbkdf2(password, hash)\n if (ok) {\n const upgraded = await baHashPassword(password)\n await env.DB.prepare(\n \"UPDATE auth_account SET password = ?, updated_at = ? WHERE password = ? AND provider_id = 'credential'\"\n )\n .bind(upgraded, Math.floor(Date.now() / 1000), hash)\n .run()\n }\n return ok\n }\n return baVerifyPassword({ hash, password })\n },\n },\n },\n user: {\n modelName: 'auth_user',\n // Field-mapping values are Drizzle *property keys* (camelCase), which\n // already match Better Auth's defaults for emailVerified/createdAt/\n // updatedAt. Only `image` differs (SonicJS uses `avatar`).\n fields: {\n image: 'avatar',\n },\n additionalFields: {\n role: { type: 'string', required: false, defaultValue: 'viewer', input: false },\n firstName: { type: 'string', required: false, defaultValue: '', input: true },\n lastName: { type: 'string', required: false, defaultValue: '', input: true },\n isSuperAdmin: { type: 'boolean', required: false, defaultValue: false, input: false },\n },\n },\n session: {\n modelName: 'auth_session',\n // Drizzle property keys already match Better Auth defaults (userId,\n // expiresAt, ipAddress, …) — no field overrides needed.\n expiresIn: 60 * 60 * 24 * 7, // 7 days\n updateAge: 60 * 60 * 24, // refresh once per day\n },\n account: { modelName: 'auth_account' },\n verification: { modelName: 'auth_verification' },\n databaseHooks: {\n user: {\n create: {\n before: async (userData: Record<string, unknown>) => {\n const isFirst = await isFirstUserRegistration(env.DB)\n if (!isFirst) {\n const enabled = await isRegistrationEnabled(env.DB)\n if (!enabled) {\n throw new APIError('BAD_REQUEST', { message: 'Registration is currently disabled.' })\n }\n }\n const d = userData as {\n name?: string; email?: string; firstName?: string; lastName?: string\n }\n const name = (d.name ?? 'User').toString()\n const parts = name.trim().split(/\\s+/)\n // Prefer explicitly-provided fields (registration form); fall back\n // to values derived from name/email.\n const firstName = d.firstName || parts[0] || 'User'\n const lastName = d.lastName || parts.slice(1).join(' ') || firstName\n return { data: { ...userData, name, firstName, lastName, role: 'viewer' } }\n },\n after: async (user: { id: string }) => {\n // Assign dynamic RBAC membership. The first real user receives\n // Administrator so fresh installs can enter the portal; later\n // self-registered users receive Viewer.\n try {\n // RBAC roles/assignments are document-backed (services/rbac.ts).\n // First real portal admin → Administrator; later users → Viewer.\n // setUserRoles (via addUserRoleByName) also projects auth_user.role.\n const { RbacService } = await import('../services/rbac')\n const rbac = new RbacService(env.DB)\n const roleName = (await rbac.countPortalAdmins(user.id)) === 0 ? 'admin' : 'viewer'\n await rbac.addUserRoleByName(user.id, roleName)\n } catch {\n /* rbac docs may not be seeded yet on older schemas — non-fatal */\n }\n },\n },\n },\n },\n }\n ),\n\n // ── Phase 4: BA-native login methods ─────────────────────────────────────\n // Magic-link and Email-OTP replace the standalone SonicJS plugins that\n // minted JWT cookies. Social providers replace the bespoke oauth-providers\n // plugin. All are gated on the relevant env vars / email service config\n // so they activate only when configured.\n\n plugins: [\n // Magic-link passwordless auth. Sends a one-time link to the user's inbox;\n // the link resolves to a BA session. Requires a working email service.\n magicLink({\n sendMagicLink: async ({ email, url }: { email: string; url: string }, _request: any) => {\n await sendViaEmailPlugin(\n env.DB, email,\n 'Your sign-in link',\n `<div style=\"font-family:sans-serif;max-width:600px\">\n <h2>Sign in to SonicJS</h2>\n <p>Click the link below to sign in. Expires in 15 minutes.</p>\n <p><a href=\"${url}\" style=\"background:#465FFF;color:#fff;padding:12px 24px;border-radius:6px;text-decoration:none\">Sign in</a></p>\n <p style=\"color:#666;font-size:12px\">Or copy: ${url}</p>\n </div>`\n )\n },\n expiresIn: 15 * 60,\n }),\n\n // Email OTP — 6-digit code sent to inbox. Replaces the otp-login-plugin.\n emailOTP({\n sendVerificationOTP: async (params: { email: string; otp: string; type: string }, _request: any) => {\n await sendViaEmailPlugin(\n env.DB, params.email,\n 'Your sign-in code',\n `<div style=\"font-family:sans-serif;max-width:600px\">\n <h2>Your one-time code</h2>\n <p style=\"font-size:36px;font-weight:bold;letter-spacing:8px;color:#465FFF\">${params.otp}</p>\n <p style=\"color:#666\">Expires in 10 minutes. Do not share this code.</p>\n </div>`\n )\n },\n otpLength: 6,\n expiresIn: 10 * 60,\n }),\n\n organization({\n schema: {\n organization: {\n modelName: 'auth_tenant',\n additionalFields: {\n status: { type: 'string', required: false, defaultValue: 'active', input: true },\n domain: { type: 'string', required: false, input: true },\n notes: { type: 'string', required: false, defaultValue: '', input: true },\n },\n },\n member: {\n modelName: 'auth_tenant_member',\n fields: { organizationId: 'tenant_id' },\n },\n invitation: {\n modelName: 'auth_tenant_invitation',\n fields: { organizationId: 'tenant_id' },\n },\n team: {\n modelName: 'auth_tenant_team',\n fields: { organizationId: 'tenant_id' },\n },\n },\n }),\n ],\n\n // ── Phase 4: Social providers ─────────────────────────────────────────\n // Activated when the relevant env vars are set. Replaces the bespoke\n // oauth-providers SonicJS plugin. Set via wrangler secret put / .dev.vars.\n socialProviders: {\n ...(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET\n ? { github: { clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET } }\n : {}),\n ...(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET\n ? { google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET } }\n : {}),\n },\n }\n}\n\nexport type BetterAuthDefaultOptions = ReturnType<typeof getDefaultAuthOptions>\nexport type ExtendBetterAuth = (opts: BetterAuthDefaultOptions) => BetterAuthDefaultOptions\n\n/** Create a Better Auth instance for this request. */\nexport function createAuth(env: Bindings, extendBetterAuth?: ExtendBetterAuth, requestBaseURL?: string) {\n // Hard-fail rather than sign sessions with an undefined/blank secret. The\n // secret must be provided via `wrangler secret put BETTER_AUTH_SECRET`\n // (prod/preview) or a gitignored `.dev.vars` (local) — never committed.\n if (!env.BETTER_AUTH_SECRET || env.BETTER_AUTH_SECRET.length < 16) {\n throw new Error(\n 'BETTER_AUTH_SECRET is missing or too short. Set it as a Wrangler secret ' +\n '(wrangler secret put BETTER_AUTH_SECRET) or in a gitignored .dev.vars for local dev. ' +\n 'Refusing to initialize auth without a strong signing secret.'\n )\n }\n const defaults = getDefaultAuthOptions(env, requestBaseURL)\n const options = extendBetterAuth ? extendBetterAuth(defaults) : defaults\n return betterAuth(options as Parameters<typeof betterAuth>[0])\n}\n\nexport type SonicJSAuth = ReturnType<typeof createAuth>\n"]}
|