@sonicjs-cms/core 3.0.0-beta.14 → 3.0.0-beta.16

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.
Files changed (87) hide show
  1. package/dist/{app-Bo0X1OWX.d.ts → app-01M0dIwt.d.ts} +1 -1
  2. package/dist/{app-Do66yCcV.d.cts → app-aj39TGHB.d.cts} +1 -1
  3. package/dist/{chunk-SIPBHWMG.cjs → chunk-3XFA3E2F.cjs} +4 -4
  4. package/dist/{chunk-SIPBHWMG.cjs.map → chunk-3XFA3E2F.cjs.map} +1 -1
  5. package/dist/{chunk-QNPT6BQP.js → chunk-7P2IJM4K.js} +3 -3
  6. package/dist/{chunk-QNPT6BQP.js.map → chunk-7P2IJM4K.js.map} +1 -1
  7. package/dist/{chunk-32SDMKZ7.js → chunk-HYO2MSFI.js} +2 -2
  8. package/dist/{chunk-32SDMKZ7.js.map → chunk-HYO2MSFI.js.map} +1 -1
  9. package/dist/{chunk-H5FB6GKR.cjs → chunk-IA24HACT.cjs} +2 -2
  10. package/dist/{chunk-H5FB6GKR.cjs.map → chunk-IA24HACT.cjs.map} +1 -1
  11. package/dist/{chunk-75JKPNUZ.js → chunk-IQZA2OY5.js} +3 -3
  12. package/dist/{chunk-75JKPNUZ.js.map → chunk-IQZA2OY5.js.map} +1 -1
  13. package/dist/{chunk-5L6EMI7A.js → chunk-IXUNNFZZ.js} +6 -5
  14. package/dist/chunk-IXUNNFZZ.js.map +1 -0
  15. package/dist/{chunk-U7T5EOHJ.js → chunk-JRMBHDGM.js} +4 -4
  16. package/dist/{chunk-U7T5EOHJ.js.map → chunk-JRMBHDGM.js.map} +1 -1
  17. package/dist/{chunk-INE5E3SL.js → chunk-KIB7K5LV.js} +27 -2
  18. package/dist/chunk-KIB7K5LV.js.map +1 -0
  19. package/dist/{chunk-ZTEJHSYS.cjs → chunk-KMCLNO4K.cjs} +5 -5
  20. package/dist/{chunk-ZTEJHSYS.cjs.map → chunk-KMCLNO4K.cjs.map} +1 -1
  21. package/dist/{chunk-L3KHLEHL.cjs → chunk-KMM45BTY.cjs} +53 -15
  22. package/dist/chunk-KMM45BTY.cjs.map +1 -0
  23. package/dist/{chunk-4YRYGDCQ.cjs → chunk-KOXFYFOF.cjs} +4 -4
  24. package/dist/{chunk-4YRYGDCQ.cjs.map → chunk-KOXFYFOF.cjs.map} +1 -1
  25. package/dist/{chunk-WIV3R2MJ.cjs → chunk-KZIBF2EV.cjs} +27 -2
  26. package/dist/chunk-KZIBF2EV.cjs.map +1 -0
  27. package/dist/{chunk-G5LC7MYH.cjs → chunk-LXYDJU6U.cjs} +12 -11
  28. package/dist/chunk-LXYDJU6U.cjs.map +1 -0
  29. package/dist/{chunk-O32IZ6JS.js → chunk-PA2UIFB7.js} +43 -5
  30. package/dist/chunk-PA2UIFB7.js.map +1 -0
  31. package/dist/{chunk-SSOP7G4R.cjs → chunk-SC2WZFNQ.cjs} +309 -243
  32. package/dist/chunk-SC2WZFNQ.cjs.map +1 -0
  33. package/dist/{chunk-HNUGMALL.js → chunk-UOFRE5PY.js} +104 -39
  34. package/dist/chunk-UOFRE5PY.js.map +1 -0
  35. package/dist/{chunk-IESEVHXL.js → chunk-VVYVFCML.js} +12 -3
  36. package/dist/chunk-VVYVFCML.js.map +1 -0
  37. package/dist/{chunk-HIKBY7MS.cjs → chunk-XWVDSM2L.cjs} +12 -3
  38. package/dist/chunk-XWVDSM2L.cjs.map +1 -0
  39. package/dist/config-SM2UWSDN.js +6 -0
  40. package/dist/{config-PLJTDINX.js.map → config-SM2UWSDN.js.map} +1 -1
  41. package/dist/config-YCRSU7ZP.cjs +19 -0
  42. package/dist/{config-FLOUCYHH.cjs.map → config-YCRSU7ZP.cjs.map} +1 -1
  43. package/dist/{define-plugin-BzNHc1ZI.d.ts → define-plugin-BYIey6sp.d.ts} +1 -1
  44. package/dist/{define-plugin-IWDKYaVm.d.cts → define-plugin-DsKiGu1q.d.cts} +1 -1
  45. package/dist/index.cjs +775 -297
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.d.cts +194 -7
  48. package/dist/index.d.ts +194 -7
  49. package/dist/index.js +527 -59
  50. package/dist/index.js.map +1 -1
  51. package/dist/middleware.cjs +33 -33
  52. package/dist/middleware.d.cts +2 -2
  53. package/dist/middleware.d.ts +2 -2
  54. package/dist/middleware.js +4 -4
  55. package/dist/migrations-F4LBAHQN.js +4 -0
  56. package/dist/{migrations-DOPZXFK3.js.map → migrations-F4LBAHQN.js.map} +1 -1
  57. package/dist/migrations-KMCULV6H.cjs +13 -0
  58. package/dist/{migrations-TYYC66P4.cjs.map → migrations-KMCULV6H.cjs.map} +1 -1
  59. package/dist/plugins.cjs +40 -40
  60. package/dist/plugins.d.cts +2 -2
  61. package/dist/plugins.d.ts +2 -2
  62. package/dist/plugins.js +3 -3
  63. package/dist/routes.cjs +27 -27
  64. package/dist/routes.d.cts +2 -2
  65. package/dist/routes.d.ts +2 -2
  66. package/dist/routes.js +7 -7
  67. package/dist/services.cjs +18 -18
  68. package/dist/services.js +2 -2
  69. package/dist/{types-Dea1eNxU.d.cts → types-DT7XN5TX.d.cts} +1 -1
  70. package/dist/{types-Dea1eNxU.d.ts → types-DT7XN5TX.d.ts} +1 -1
  71. package/dist/utils.cjs +3 -3
  72. package/dist/utils.js +1 -1
  73. package/package.json +2 -2
  74. package/dist/chunk-5L6EMI7A.js.map +0 -1
  75. package/dist/chunk-G5LC7MYH.cjs.map +0 -1
  76. package/dist/chunk-HIKBY7MS.cjs.map +0 -1
  77. package/dist/chunk-HNUGMALL.js.map +0 -1
  78. package/dist/chunk-IESEVHXL.js.map +0 -1
  79. package/dist/chunk-INE5E3SL.js.map +0 -1
  80. package/dist/chunk-L3KHLEHL.cjs.map +0 -1
  81. package/dist/chunk-O32IZ6JS.js.map +0 -1
  82. package/dist/chunk-SSOP7G4R.cjs.map +0 -1
  83. package/dist/chunk-WIV3R2MJ.cjs.map +0 -1
  84. package/dist/config-FLOUCYHH.cjs +0 -19
  85. package/dist/config-PLJTDINX.js +0 -6
  86. package/dist/migrations-DOPZXFK3.js +0 -4
  87. package/dist/migrations-TYYC66P4.cjs +0 -13
package/dist/index.js CHANGED
@@ -1,37 +1,37 @@
1
- import { createAuth } from './chunk-75JKPNUZ.js';
2
- import { getEmailService, hasEmailService, setEmailService, invalidateVariablesCache, resolveVariablesInObject, resolveDocScope, denyIfNotAllowed, DocumentRepository, dispatchHookEvent, getCustomData, renderTable, getVariablesCached, setVariablesCache, TenantService, isValidMemberRole, renderConfirmationDialog, getConfirmationDialogScript, VALID_MEMBER_ROLES, userProfilesPlugin, lexicalEditorPlugin, api_media_default, api_system_default, apiDocumentsRoutes, api_default, adminDocumentsRoutes, admin_api_default, adminCollectionsRoutes, adminSettingsRoutes, router2, admin_content_default, adminMediaRoutes, router, adminPluginRoutes, adminLogsRoutes, userRoutes, auth_default, test_cleanup_default } from './chunk-HNUGMALL.js';
3
- export { ROUTES_INFO, admin_api_default as adminApiRoutes, adminCheckboxRoutes, adminCollectionsRoutes, admin_content_default as adminContentRoutes, router as adminDashboardRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, userRoutes as adminUsersRoutes, api_content_crud_default as apiContentCrudRoutes, api_media_default as apiMediaRoutes, api_default as apiRoutes, api_system_default as apiSystemRoutes, auth_default as authRoutes, createUserProfilesPlugin, defineUserProfile, getUserProfileConfig, userProfilesPlugin } from './chunk-HNUGMALL.js';
4
- import './chunk-IESEVHXL.js';
1
+ import { createAuth } from './chunk-IQZA2OY5.js';
2
+ import { getEmailService, hasEmailService, setEmailService, invalidateVariablesCache, resolveVariablesInObject, resolveDocScope, denyIfNotAllowed, DocumentRepository, dispatchHookEvent, getCustomData, createDocumentSchema, renderTable, getVariablesCached, setVariablesCache, TenantService, isValidMemberRole, renderConfirmationDialog, getConfirmationDialogScript, VALID_MEMBER_ROLES, userProfilesPlugin, lexicalEditorPlugin, api_media_default, api_system_default, apiDocumentsRoutes, api_default, adminDocumentsRoutes, admin_api_default, adminCollectionsRoutes, adminSettingsRoutes, router2, admin_content_default, adminMediaRoutes, router, adminPluginRoutes, adminLogsRoutes, userRoutes, auth_default, test_cleanup_default } from './chunk-UOFRE5PY.js';
3
+ export { DocumentRepository, ROUTES_INFO, admin_api_default as adminApiRoutes, adminCheckboxRoutes, adminCollectionsRoutes, admin_content_default as adminContentRoutes, router as adminDashboardRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, userRoutes as adminUsersRoutes, api_content_crud_default as apiContentCrudRoutes, api_media_default as apiMediaRoutes, api_default as apiRoutes, api_system_default as apiSystemRoutes, auth_default as authRoutes, createUserProfilesPlugin, defineUserProfile, getUserProfileConfig, userProfilesPlugin } from './chunk-UOFRE5PY.js';
4
+ import './chunk-VVYVFCML.js';
5
5
  import './chunk-RMRJGMDE.js';
6
6
  import { SettingsService, setAppInstance } from './chunk-RQ6N3FTV.js';
7
7
  export { Logger, getLogger, initLogger } from './chunk-RQ6N3FTV.js';
8
8
  import { getAllCacheStats, CACHE_CONFIGS, getCacheService, clearAllCaches, parseCacheKey } from './chunk-K25XHMM3.js';
9
9
  import { schema_exports } from './chunk-AI663NBO.js';
10
10
  export { apiTokens, content, contentVersions, insertContentSchema, insertLogConfigSchema, insertMediaSchema, insertPluginActivityLogSchema, insertPluginAssetSchema, insertPluginHookSchema, insertPluginRouteSchema, insertPluginSchema, insertSystemLogSchema, insertUserSchema, insertWorkflowHistorySchema, logConfig, media, pluginActivityLog, pluginAssets, pluginHooks, pluginRoutes, plugins, selectContentSchema, selectLogConfigSchema, selectMediaSchema, selectPluginActivityLogSchema, selectPluginAssetSchema, selectPluginHookSchema, selectPluginRouteSchema, selectPluginSchema, selectSystemLogSchema, selectUserSchema, selectWorkflowHistorySchema, systemLogs, users, workflowHistory } from './chunk-AI663NBO.js';
11
- import { requireAuth, DocumentTypeRegistry, requireRbac, requireRole, getJwtExpirySecondsFromDb, AuthManager, metricsMiddleware, bootstrapMiddleware, securityHeadersMiddleware, csrfProtection } from './chunk-O32IZ6JS.js';
12
- export { AuthManager, PermissionManager, bootstrapDocumentTypes, bootstrapMiddleware, cacheHeaders, compressionMiddleware, detailedLoggingMiddleware, getActivePlugins, isPluginActive, logActivity, loggingMiddleware, optionalAuth, performanceLoggingMiddleware, requireActivePlugin, requireActivePlugins, requireAnyPermission, requireAuth, requirePermission, requireRole, securityHeadersMiddleware as securityHeaders, securityLoggingMiddleware } from './chunk-O32IZ6JS.js';
11
+ import { requireAuth, DocumentTypeRegistry, requireRbac, requireRole, getJwtExpirySecondsFromDb, AuthManager, metricsMiddleware, bootstrapMiddleware, securityHeadersMiddleware, csrfProtection } from './chunk-PA2UIFB7.js';
12
+ export { AuthManager, PermissionManager, bootstrapDocumentTypes, bootstrapMiddleware, cacheHeaders, compressionMiddleware, detailedLoggingMiddleware, getActivePlugins, isPluginActive, logActivity, loggingMiddleware, optionalAuth, performanceLoggingMiddleware, requireActivePlugin, requireActivePlugins, requireAnyPermission, requireAuth, requirePermission, requireRole, securityHeadersMiddleware as securityHeaders, securityLoggingMiddleware } from './chunk-PA2UIFB7.js';
13
13
  import { RbacService, DocumentsService } from './chunk-PDC7DLOV.js';
14
- export { RbacService } from './chunk-PDC7DLOV.js';
14
+ export { DocumentsService, RbacService } from './chunk-PDC7DLOV.js';
15
15
  import { nanoid } from './chunk-GCDZZNIN.js';
16
- import { PluginService, PLUGIN_REGISTRY, invalidateTenantCache, getCollectionRegistry, TENANT_COOKIE, MULTI_TENANT_PLUGIN_ID, collectionRecordToRow, tenantMiddleware } from './chunk-INE5E3SL.js';
17
- export { PluginBootstrapService, PluginService as PluginServiceClass, getAvailableCollectionNames, loadCollectionConfig, loadCollectionConfigs, registerCollections, validateCollectionConfig } from './chunk-INE5E3SL.js';
18
- export { MigrationService } from './chunk-32SDMKZ7.js';
16
+ import { PluginService, PLUGIN_REGISTRY, invalidateTenantCache, getCollectionRegistry, TENANT_COOKIE, MULTI_TENANT_PLUGIN_ID, collectionRecordToRow, tenantMiddleware } from './chunk-KIB7K5LV.js';
17
+ export { PluginBootstrapService, PluginService as PluginServiceClass, getAvailableCollectionNames, loadCollectionConfig, loadCollectionConfigs, registerCollections, validateCollectionConfig } from './chunk-KIB7K5LV.js';
18
+ export { MigrationService } from './chunk-HYO2MSFI.js';
19
19
  export { renderFilterBar } from './chunk-JQISFW6U.js';
20
20
  import { renderAdminLayout } from './chunk-OBA2RYZN.js';
21
21
  export { getConfirmationDialogScript, renderAlert, renderConfirmationDialog, renderForm, renderFormField, renderPagination, renderTable } from './chunk-OBA2RYZN.js';
22
22
  import { init_admin_layout_catalyst_template, renderAdminLayoutCatalyst } from './chunk-5V62WT6M.js';
23
23
  export { renderAdminLayoutCatalyst } from './chunk-5V62WT6M.js';
24
- import { TurnstileService, HookSystemImpl, createPluginWirer, registerPluginRoutes, setPluginMenu } from './chunk-5L6EMI7A.js';
25
- export { HookSystemImpl, HookUtils, PluginDependencyCycleError, PluginManager as PluginManagerClass, PluginRegistryImpl, PluginValidator as PluginValidatorClass, RegisterPluginsError, ScopedHookSystem as ScopedHookSystemClass, collectCronSchedules, collectCrons, createScheduledHandler, dispatchCronTick, getPluginMenu, registerPlugins, resetPluginMenu, resolvePluginMenuItems, setPluginMenu, topoSort } from './chunk-5L6EMI7A.js';
26
- import { definePlugin, setPluginDefinitions } from './chunk-QNPT6BQP.js';
27
- export { applySchemaDefaults, definePlugin, isDefinedPlugin, parseConfigSchema, parseFormDataToSettings, renderSchemaFields } from './chunk-QNPT6BQP.js';
24
+ import { TurnstileService, HookSystemImpl, createPluginWirer, registerPluginRoutes, setPluginMenu, resolvePluginMenuItems } from './chunk-IXUNNFZZ.js';
25
+ export { HookSystemImpl, HookUtils, PluginDependencyCycleError, PluginManager as PluginManagerClass, PluginRegistryImpl, PluginValidator as PluginValidatorClass, RegisterPluginsError, ScopedHookSystem as ScopedHookSystemClass, collectCronSchedules, collectCrons, createScheduledHandler, dispatchCronTick, getPluginMenu, registerPlugins, resetPluginMenu, resolvePluginMenuItems, setPluginMenu, topoSort } from './chunk-IXUNNFZZ.js';
26
+ import { definePlugin, setPluginDefinitions } from './chunk-7P2IJM4K.js';
27
+ export { applySchemaDefaults, definePlugin, isDefinedPlugin, parseConfigSchema, parseFormDataToSettings, renderSchemaFields } from './chunk-7P2IJM4K.js';
28
28
  import { setHookSystem } from './chunk-RNZFGN4R.js';
29
29
  export { getHookSystem, getTypedHooks, hasHookSystem, resetHookSystem, setHookSystem } from './chunk-RNZFGN4R.js';
30
30
  export { QueryFilterBuilder, TemplateRenderer, buildQuery, renderTemplate, templateRenderer } from './chunk-QZGABF2M.js';
31
31
  export { metricsTracker } from './chunk-FICTAGD4.js';
32
32
  import './chunk-X7ZAEI5S.js';
33
- import { getCoreVersion, package_default } from './chunk-U7T5EOHJ.js';
34
- export { SONICJS_VERSION, getCoreVersion } from './chunk-U7T5EOHJ.js';
33
+ import { getCoreVersion, package_default } from './chunk-JRMBHDGM.js';
34
+ export { SONICJS_VERSION, getCoreVersion } from './chunk-JRMBHDGM.js';
35
35
  import { escapeHtml, sanitizeInput } from './chunk-TQABQWOP.js';
36
36
  export { escapeHtml, sanitizeInput, sanitizeObject } from './chunk-TQABQWOP.js';
37
37
  export { HOOKS } from './chunk-QXOZI5Q2.js';
@@ -6646,6 +6646,457 @@ var securityAuditPlugin = definePlugin({
6646
6646
  deactivate: async () => console.log("[SecurityAudit] Plugin deactivated")
6647
6647
  });
6648
6648
 
6649
+ // src/plugins/core-plugins/api-keys-plugin/services/api-key-service.ts
6650
+ var API_KEY_QUERYABLE = [
6651
+ { name: "keyHash", kind: "scalar", type: "text", column: "q_apikey_hash" },
6652
+ { name: "userId", kind: "scalar", type: "text", column: "q_apikey_user_id" },
6653
+ { name: "revoked", kind: "scalar", type: "integer", column: "q_apikey_revoked" }
6654
+ ];
6655
+ var KEY_PREFIX = "sk_";
6656
+ var KEY_BYTES = 24;
6657
+ var DISPLAY_PREFIX_LEN = KEY_PREFIX.length + 8;
6658
+ function generateApiKeySecret() {
6659
+ const bytes = new Uint8Array(KEY_BYTES);
6660
+ crypto.getRandomValues(bytes);
6661
+ let hex = "";
6662
+ for (const b of bytes) hex += b.toString(16).padStart(2, "0");
6663
+ return KEY_PREFIX + hex;
6664
+ }
6665
+ async function hashApiKey(secret) {
6666
+ const data = new TextEncoder().encode(secret);
6667
+ const digest = await crypto.subtle.digest("SHA-256", data);
6668
+ const view = new Uint8Array(digest);
6669
+ let hex = "";
6670
+ for (const b of view) hex += b.toString(16).padStart(2, "0");
6671
+ return hex;
6672
+ }
6673
+ var ApiKeyService = class {
6674
+ constructor(db, tenantId = "default") {
6675
+ this.db = db;
6676
+ this.tenantId = tenantId;
6677
+ }
6678
+ docService() {
6679
+ return new DocumentsService(this.db, {
6680
+ queryableFields: API_KEY_QUERYABLE,
6681
+ tenantId: this.tenantId,
6682
+ maxVersionsPerRoot: 1
6683
+ });
6684
+ }
6685
+ /** Count a user's active (non-revoked) keys — used to enforce the per-user cap. */
6686
+ async countForUser(userId) {
6687
+ const row = await this.db.prepare(
6688
+ `SELECT COUNT(*) AS c FROM documents
6689
+ WHERE type_id = 'api_key' AND tenant_id = ? AND is_current_draft = 1
6690
+ AND deleted_at IS NULL AND q_apikey_user_id = ? AND q_apikey_revoked = 0`
6691
+ ).bind(this.tenantId, userId).first();
6692
+ return row?.c ?? 0;
6693
+ }
6694
+ /**
6695
+ * Mint a new key for a user. The returned `key` is the only time the plaintext
6696
+ * exists outside the caller — it is hashed before storage.
6697
+ */
6698
+ async create(input) {
6699
+ const name = input.name.trim() || "API Key";
6700
+ const secret = generateApiKeySecret();
6701
+ const keyHash = await hashApiKey(secret);
6702
+ const prefix = secret.slice(0, DISPLAY_PREFIX_LEN);
6703
+ const expiresAt = input.expiresAt ?? null;
6704
+ const doc = await this.docService().create(
6705
+ createDocumentSchema.parse({
6706
+ typeId: "api_key",
6707
+ tenantId: this.tenantId,
6708
+ title: name,
6709
+ ownerId: input.userId,
6710
+ publishOnCreate: true,
6711
+ data: {
6712
+ name,
6713
+ userId: input.userId,
6714
+ keyHash,
6715
+ keyPrefix: prefix,
6716
+ revoked: 0,
6717
+ lastUsedAt: null,
6718
+ expiresAt
6719
+ }
6720
+ }),
6721
+ input.userId
6722
+ );
6723
+ return { id: doc.rootId, key: secret, name, prefix, expiresAt };
6724
+ }
6725
+ /** List a user's non-revoked keys (metadata only — never the secret or hash). */
6726
+ async list(userId) {
6727
+ const res = await this.db.prepare(
6728
+ `SELECT root_id, data, created_at FROM documents
6729
+ WHERE type_id = 'api_key' AND tenant_id = ? AND is_current_draft = 1
6730
+ AND deleted_at IS NULL AND q_apikey_user_id = ? AND q_apikey_revoked = 0
6731
+ ORDER BY created_at DESC`
6732
+ ).bind(this.tenantId, userId).all();
6733
+ return (res.results ?? []).map((row) => {
6734
+ const d = JSON.parse(row.data);
6735
+ return {
6736
+ id: row.root_id,
6737
+ name: d.name ?? "API Key",
6738
+ prefix: d.keyPrefix ?? "",
6739
+ createdAt: row.created_at,
6740
+ lastUsedAt: d.lastUsedAt ?? null,
6741
+ expiresAt: d.expiresAt ?? null
6742
+ };
6743
+ });
6744
+ }
6745
+ /**
6746
+ * Revoke (hard-delete) a key owned by `userId`. Credentials are erased, not
6747
+ * soft-flagged. Returns false if the key does not exist or is not owned by
6748
+ * the caller (no information leak about other users' keys).
6749
+ */
6750
+ async revoke(rootId, userId) {
6751
+ const row = await this.db.prepare(
6752
+ `SELECT q_apikey_user_id AS owner FROM documents
6753
+ WHERE root_id = ? AND tenant_id = ? AND type_id = 'api_key' AND is_current_draft = 1 AND deleted_at IS NULL`
6754
+ ).bind(rootId, this.tenantId).first();
6755
+ if (!row || row.owner !== userId) return false;
6756
+ await this.docService().erase(rootId, this.tenantId);
6757
+ return true;
6758
+ }
6759
+ /**
6760
+ * Resolve a presented secret to its owning user, or null. Rejects unknown,
6761
+ * revoked, expired keys and inactive users. Best-effort updates lastUsedAt.
6762
+ */
6763
+ async resolve(secret) {
6764
+ if (!secret || !secret.startsWith(KEY_PREFIX)) return null;
6765
+ const keyHash = await hashApiKey(secret);
6766
+ const keyRow2 = await this.db.prepare(
6767
+ `SELECT root_id, data FROM documents
6768
+ WHERE type_id = 'api_key' AND tenant_id = ? AND is_current_draft = 1 AND deleted_at IS NULL
6769
+ AND q_apikey_hash = ? AND q_apikey_revoked = 0
6770
+ LIMIT 1`
6771
+ ).bind(this.tenantId, keyHash).first();
6772
+ if (!keyRow2) return null;
6773
+ const d = JSON.parse(keyRow2.data);
6774
+ if (d.expiresAt != null && Date.now() > Number(d.expiresAt)) return null;
6775
+ const user = await this.db.prepare(`SELECT id, email, role, is_active, is_super_admin FROM auth_user WHERE id = ?`).bind(d.userId).first();
6776
+ if (!user || user.is_active !== 1) return null;
6777
+ try {
6778
+ await this.touchLastUsed(keyRow2.root_id, d);
6779
+ } catch {
6780
+ }
6781
+ return {
6782
+ userId: user.id,
6783
+ email: user.email,
6784
+ role: user.role ?? "viewer",
6785
+ isSuperAdmin: user.is_super_admin === 1
6786
+ };
6787
+ }
6788
+ /** Update lastUsedAt in-place (single-version type, no history churn). */
6789
+ async touchLastUsed(rootId, data) {
6790
+ const next = JSON.stringify({ ...data, lastUsedAt: Date.now() });
6791
+ await this.db.prepare(
6792
+ `UPDATE documents SET data = ? WHERE root_id = ? AND tenant_id = ? AND type_id = 'api_key' AND is_current_draft = 1`
6793
+ ).bind(next, rootId, this.tenantId).run();
6794
+ }
6795
+ };
6796
+
6797
+ // src/plugins/core-plugins/api-keys-plugin/components/keys-page.ts
6798
+ init_admin_layout_catalyst_template();
6799
+ function fmtDate(ms) {
6800
+ if (!ms) return "\u2014";
6801
+ try {
6802
+ return new Date(ms).toISOString().slice(0, 10);
6803
+ } catch {
6804
+ return "\u2014";
6805
+ }
6806
+ }
6807
+ function keyRow(k) {
6808
+ const name = escapeHtml(k.name);
6809
+ const prefix = escapeHtml(k.prefix);
6810
+ const id = escapeHtml(k.id);
6811
+ return `
6812
+ <tr class="border-t border-zinc-950/5 dark:border-white/10" data-key-id="${id}">
6813
+ <td class="py-3 pr-4 text-sm font-medium text-zinc-950 dark:text-white">${name}</td>
6814
+ <td class="py-3 pr-4 text-sm font-mono text-zinc-500 dark:text-zinc-400">${prefix}\u2026</td>
6815
+ <td class="py-3 pr-4 text-sm text-zinc-500 dark:text-zinc-400">${fmtDate(k.createdAt ? k.createdAt * 1e3 : null)}</td>
6816
+ <td class="py-3 pr-4 text-sm text-zinc-500 dark:text-zinc-400">${k.lastUsedAt ? fmtDate(k.lastUsedAt) : "Never"}</td>
6817
+ <td class="py-3 pr-4 text-sm text-zinc-500 dark:text-zinc-400">${k.expiresAt ? fmtDate(k.expiresAt) : "Never"}</td>
6818
+ <td class="py-3 text-right">
6819
+ <button type="button" onclick="revokeApiKey('${id}', '${name.replace(/'/g, "\\'")}')"
6820
+ class="rounded-lg bg-red-50 dark:bg-red-900/20 px-3 py-1.5 text-sm font-medium text-red-700 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/40 ring-1 ring-red-200 dark:ring-red-800 transition-colors">
6821
+ Revoke
6822
+ </button>
6823
+ </td>
6824
+ </tr>`;
6825
+ }
6826
+ function renderApiKeysPage(data) {
6827
+ const { keys, user, version, dynamicMenuItems } = data;
6828
+ const rows = keys.length ? keys.map(keyRow).join("") : `<tr><td colspan="6" class="py-8 text-center text-sm text-zinc-500 dark:text-zinc-400">No API keys yet. Create one to enable programmatic access.</td></tr>`;
6829
+ const content2 = `
6830
+ <div>
6831
+ <div class="sm:flex sm:items-center sm:justify-between mb-6">
6832
+ <div class="sm:flex-auto">
6833
+ <h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">API Keys</h1>
6834
+ <p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">
6835
+ Long-lived secrets for headless / server-to-server REST access. Send a key as the
6836
+ <code class="font-mono">x-api-key</code> header or <code class="font-mono">Authorization: Bearer &lt;key&gt;</code>.
6837
+ The secret is shown once at creation \u2014 store it now.
6838
+ </p>
6839
+ </div>
6840
+ <div class="mt-4 sm:mt-0 sm:ml-16">
6841
+ <button type="button" onclick="document.getElementById('create-key-modal').classList.remove('hidden')"
6842
+ class="inline-flex items-center justify-center rounded-lg bg-cyan-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-cyan-500 transition-colors shadow-sm">
6843
+ Create API key
6844
+ </button>
6845
+ </div>
6846
+ </div>
6847
+
6848
+ <!-- Newly-created secret banner (populated by JS, shown once) -->
6849
+ <div id="new-key-banner" class="hidden mb-6 rounded-xl bg-emerald-50 dark:bg-emerald-900/20 p-4 ring-1 ring-emerald-200 dark:ring-emerald-800">
6850
+ <p class="text-sm font-medium text-emerald-800 dark:text-emerald-300 mb-2">
6851
+ Copy your new key now \u2014 it will not be shown again.
6852
+ </p>
6853
+ <div class="flex items-center gap-2">
6854
+ <code id="new-key-value" class="flex-1 rounded-lg bg-white dark:bg-zinc-900 px-3 py-2 text-sm font-mono text-zinc-950 dark:text-white ring-1 ring-zinc-950/10 dark:ring-white/10 break-all"></code>
6855
+ <button type="button" onclick="copyNewKey()"
6856
+ class="rounded-lg bg-emerald-600 px-3 py-2 text-sm font-semibold text-white hover:bg-emerald-500 transition-colors">
6857
+ Copy
6858
+ </button>
6859
+ </div>
6860
+ </div>
6861
+
6862
+ <div class="rounded-xl bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl p-6 ring-1 ring-zinc-950/5 dark:ring-white/10 shadow-sm overflow-x-auto">
6863
+ <table class="w-full text-left">
6864
+ <thead>
6865
+ <tr class="text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
6866
+ <th class="pb-2 pr-4">Name</th>
6867
+ <th class="pb-2 pr-4">Prefix</th>
6868
+ <th class="pb-2 pr-4">Created</th>
6869
+ <th class="pb-2 pr-4">Last used</th>
6870
+ <th class="pb-2 pr-4">Expires</th>
6871
+ <th class="pb-2 text-right">Actions</th>
6872
+ </tr>
6873
+ </thead>
6874
+ <tbody id="keys-tbody">${rows}</tbody>
6875
+ </table>
6876
+ </div>
6877
+ </div>
6878
+
6879
+ <!-- Create modal -->
6880
+ <div id="create-key-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
6881
+ <div class="w-full max-w-md rounded-xl bg-white dark:bg-zinc-900 p-6 ring-1 ring-zinc-950/10 dark:ring-white/10 shadow-xl">
6882
+ <h2 class="text-base font-semibold text-zinc-950 dark:text-white mb-4">Create API key</h2>
6883
+ <label class="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1">Name</label>
6884
+ <input id="new-key-name" type="text" placeholder="e.g. CI deploy token"
6885
+ class="w-full rounded-lg border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white mb-4 ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10">
6886
+ <label class="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1">Expires in (days, optional)</label>
6887
+ <input id="new-key-expiry" type="number" min="1" max="3650" placeholder="Never"
6888
+ class="w-full rounded-lg border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white mb-4 ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10">
6889
+ <div class="flex justify-end gap-2">
6890
+ <button type="button" onclick="document.getElementById('create-key-modal').classList.add('hidden')"
6891
+ class="rounded-lg bg-white dark:bg-zinc-800 px-4 py-2 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10">
6892
+ Cancel
6893
+ </button>
6894
+ <button type="button" onclick="createApiKey()"
6895
+ class="rounded-lg bg-cyan-600 px-4 py-2 text-sm font-semibold text-white hover:bg-cyan-500 transition-colors">
6896
+ Create
6897
+ </button>
6898
+ </div>
6899
+ </div>
6900
+ </div>
6901
+
6902
+ <script>
6903
+ // CSRF: the csrf_token cookie is JS-readable (httpOnly:false); echo it back
6904
+ // as X-CSRF-Token on state-changing requests (signed double-submit).
6905
+ function csrfHeader() {
6906
+ const m = document.cookie.match(/(?:^|; )csrf_token=([^;]+)/)
6907
+ return m ? { 'X-CSRF-Token': decodeURIComponent(m[1]) } : {}
6908
+ }
6909
+ async function createApiKey() {
6910
+ const name = document.getElementById('new-key-name').value.trim()
6911
+ if (!name) { window.showNotification && window.showNotification('Name is required', 'error'); return }
6912
+ const expiry = parseInt(document.getElementById('new-key-expiry').value, 10)
6913
+ const body = { name }
6914
+ if (Number.isFinite(expiry) && expiry > 0) body.expiresInDays = expiry
6915
+ const res = await fetch('/admin/plugins/api-keys/api/keys', {
6916
+ method: 'POST',
6917
+ headers: { 'Content-Type': 'application/json', ...csrfHeader() },
6918
+ body: JSON.stringify(body),
6919
+ })
6920
+ if (!res.ok) {
6921
+ const e = await res.json().catch(() => ({}))
6922
+ window.showNotification && window.showNotification(e.error || 'Failed to create key', 'error')
6923
+ return
6924
+ }
6925
+ const { apiKey } = await res.json()
6926
+ document.getElementById('create-key-modal').classList.add('hidden')
6927
+ document.getElementById('new-key-value').textContent = apiKey.key
6928
+ document.getElementById('new-key-banner').classList.remove('hidden')
6929
+ setTimeout(() => location.reload(), 1500)
6930
+ }
6931
+ function copyNewKey() {
6932
+ const v = document.getElementById('new-key-value').textContent
6933
+ navigator.clipboard.writeText(v).then(() => window.showNotification && window.showNotification('Copied', 'success'))
6934
+ }
6935
+ async function revokeApiKey(id, name) {
6936
+ if (!confirm('Revoke "' + name + '"? This cannot be undone and will break any client using it.')) return
6937
+ const res = await fetch('/admin/plugins/api-keys/api/keys/' + id, { method: 'DELETE', headers: { ...csrfHeader() } })
6938
+ if (res.ok) {
6939
+ const row = document.querySelector('[data-key-id="' + id + '"]')
6940
+ if (row) row.remove()
6941
+ window.showNotification && window.showNotification('Key revoked', 'success')
6942
+ } else {
6943
+ window.showNotification && window.showNotification('Failed to revoke', 'error')
6944
+ }
6945
+ }
6946
+ </script>`;
6947
+ const layoutData = {
6948
+ title: "API Keys",
6949
+ pageTitle: "API Keys",
6950
+ currentPath: "/admin/plugins/api-keys",
6951
+ user,
6952
+ content: content2,
6953
+ version,
6954
+ dynamicMenuItems
6955
+ };
6956
+ return renderAdminLayoutCatalyst(layoutData);
6957
+ }
6958
+
6959
+ // src/plugins/core-plugins/api-keys-plugin/types.ts
6960
+ var DEFAULT_SETTINGS3 = {
6961
+ maxKeysPerUser: 50,
6962
+ defaultExpiryDays: 0
6963
+ };
6964
+
6965
+ // src/plugins/core-plugins/api-keys-plugin/routes/admin.ts
6966
+ var adminRoutes4 = new Hono();
6967
+ var DAY_MS = 24 * 60 * 60 * 1e3;
6968
+ var createSchema = z.object({
6969
+ name: z.string().min(1).max(100),
6970
+ // Optional lifetime in days; omitted/null → plugin default (which may be "never").
6971
+ expiresInDays: z.number().int().positive().max(3650).nullish()
6972
+ });
6973
+ async function getSettings3(db) {
6974
+ try {
6975
+ const plugin2 = await new PluginService(db).getPlugin("api-keys");
6976
+ if (plugin2?.settings) {
6977
+ const s = typeof plugin2.settings === "string" ? JSON.parse(plugin2.settings) : plugin2.settings;
6978
+ return { ...DEFAULT_SETTINGS3, ...s };
6979
+ }
6980
+ } catch {
6981
+ }
6982
+ return DEFAULT_SETTINGS3;
6983
+ }
6984
+ adminRoutes4.use("*", requireAuth());
6985
+ adminRoutes4.use("*", async (c, next) => {
6986
+ const user = c.get("user");
6987
+ if (user?.role !== "admin") {
6988
+ return c.json({ error: "Access denied" }, 403);
6989
+ }
6990
+ return next();
6991
+ });
6992
+ adminRoutes4.get("/", async (c) => {
6993
+ const user = c.get("user");
6994
+ const tenantId = c.get("tenantId") || "default";
6995
+ const keys = await new ApiKeyService(c.env.DB, tenantId).list(user.userId);
6996
+ return c.html(
6997
+ renderApiKeysPage({
6998
+ keys,
6999
+ user: { name: user.email, email: user.email, role: user.role },
7000
+ version: c.get("appVersion"),
7001
+ dynamicMenuItems: c.get("pluginMenuItems")
7002
+ })
7003
+ );
7004
+ });
7005
+ adminRoutes4.get("/api/keys", async (c) => {
7006
+ const user = c.get("user");
7007
+ const tenantId = c.get("tenantId") || "default";
7008
+ const keys = await new ApiKeyService(c.env.DB, tenantId).list(user.userId);
7009
+ return c.json({ keys });
7010
+ });
7011
+ adminRoutes4.post("/api/keys", async (c) => {
7012
+ const user = c.get("user");
7013
+ const tenantId = c.get("tenantId") || "default";
7014
+ const body = await c.req.json().catch(() => ({}));
7015
+ const parsed = createSchema.safeParse(body);
7016
+ if (!parsed.success) {
7017
+ return c.json({ error: "Invalid request", details: parsed.error.flatten() }, 400);
7018
+ }
7019
+ const settings = await getSettings3(c.env.DB);
7020
+ const svc = new ApiKeyService(c.env.DB, tenantId);
7021
+ if (await svc.countForUser(user.userId) >= settings.maxKeysPerUser) {
7022
+ return c.json({ error: `API key limit reached (max ${settings.maxKeysPerUser})` }, 409);
7023
+ }
7024
+ const days = parsed.data.expiresInDays ?? (settings.defaultExpiryDays > 0 ? settings.defaultExpiryDays : null);
7025
+ const expiresAt = days ? Date.now() + days * DAY_MS : null;
7026
+ const created = await svc.create({ userId: user.userId, name: parsed.data.name, expiresAt });
7027
+ return c.json({ apiKey: created }, 201);
7028
+ });
7029
+ adminRoutes4.delete("/api/keys/:id", async (c) => {
7030
+ const user = c.get("user");
7031
+ const tenantId = c.get("tenantId") || "default";
7032
+ const ok = await new ApiKeyService(c.env.DB, tenantId).revoke(c.req.param("id") ?? "", user.userId);
7033
+ if (!ok) return c.json({ error: "API key not found" }, 404);
7034
+ return c.json({ success: true });
7035
+ });
7036
+
7037
+ // src/plugins/core-plugins/api-keys-plugin/middleware/api-key-auth.ts
7038
+ function apiKeyAuthMiddleware() {
7039
+ return async (c, next) => {
7040
+ if (!c.get("user")) {
7041
+ const authz = c.req.header("authorization") || "";
7042
+ const bearer = authz.toLowerCase().startsWith("bearer ") ? authz.slice(7).trim() : "";
7043
+ const presented = c.req.header("x-api-key") || (bearer.startsWith("sk_") ? bearer : "");
7044
+ if (presented) {
7045
+ try {
7046
+ const tenantId = c.get("tenantId") || "default";
7047
+ const resolved = await new ApiKeyService(c.env.DB, tenantId).resolve(presented);
7048
+ if (resolved) {
7049
+ c.set("user", {
7050
+ userId: resolved.userId,
7051
+ email: resolved.email,
7052
+ role: resolved.role,
7053
+ isSuperAdmin: resolved.isSuperAdmin,
7054
+ // API keys are stateless bearer creds — no session window.
7055
+ exp: 0,
7056
+ iat: 0
7057
+ });
7058
+ }
7059
+ } catch {
7060
+ }
7061
+ }
7062
+ }
7063
+ await next();
7064
+ };
7065
+ }
7066
+
7067
+ // src/plugins/core-plugins/api-keys-plugin/index.ts
7068
+ var KEY_ICON = `<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/></svg>`;
7069
+ var apiKeysPlugin = definePlugin({
7070
+ id: "api-keys",
7071
+ version: "1.0.0-beta.1",
7072
+ name: "API Keys",
7073
+ description: "Programmatic API access keys for headless / server-to-server REST access.",
7074
+ sonicjsVersionRange: "^3.0.0",
7075
+ author: { name: "SonicJS Team" },
7076
+ register(app2) {
7077
+ app2.route("/admin/plugins/api-keys", adminRoutes4);
7078
+ },
7079
+ menu: [
7080
+ { label: "API Keys", path: "/admin/plugins/api-keys", icon: KEY_ICON, order: 86 }
7081
+ ],
7082
+ configSchema: {
7083
+ maxKeysPerUser: {
7084
+ type: "number",
7085
+ label: "Max keys per user",
7086
+ description: "Hard cap on active keys a single user may hold.",
7087
+ default: 50
7088
+ },
7089
+ defaultExpiryDays: {
7090
+ type: "number",
7091
+ label: "Default expiry (days)",
7092
+ description: "Lifetime applied to new keys when no explicit expiry is given. 0 = never expires.",
7093
+ default: 0
7094
+ }
7095
+ },
7096
+ activate: async () => console.log("[ApiKeys] Plugin activated"),
7097
+ deactivate: async () => console.log("[ApiKeys] Plugin deactivated")
7098
+ });
7099
+
6649
7100
  // src/plugins/core-plugins/stripe-plugin/services/subscription-service.ts
6650
7101
  var SubscriptionService = class {
6651
7102
  constructor(db) {
@@ -7373,7 +7824,7 @@ function renderEventPagination(page, totalPages, type, status) {
7373
7824
  }
7374
7825
 
7375
7826
  // src/plugins/core-plugins/stripe-plugin/types.ts
7376
- var DEFAULT_SETTINGS3 = {
7827
+ var DEFAULT_SETTINGS4 = {
7377
7828
  stripePublishableKey: "",
7378
7829
  stripeSecretKey: "",
7379
7830
  stripeWebhookSecret: "",
@@ -7383,28 +7834,28 @@ var DEFAULT_SETTINGS3 = {
7383
7834
  };
7384
7835
 
7385
7836
  // src/plugins/core-plugins/stripe-plugin/routes/admin.ts
7386
- var adminRoutes4 = new Hono();
7387
- adminRoutes4.use("*", requireAuth());
7388
- adminRoutes4.use("*", async (c, next) => {
7837
+ var adminRoutes5 = new Hono();
7838
+ adminRoutes5.use("*", requireAuth());
7839
+ adminRoutes5.use("*", async (c, next) => {
7389
7840
  const user = c.get("user");
7390
7841
  if (user?.role !== "admin") {
7391
7842
  return c.text("Access denied", 403);
7392
7843
  }
7393
7844
  return next();
7394
7845
  });
7395
- async function getSettings3(db) {
7846
+ async function getSettings4(db) {
7396
7847
  try {
7397
7848
  const pluginService = new PluginService(db);
7398
7849
  const plugin2 = await pluginService.getPlugin("stripe");
7399
7850
  if (plugin2?.settings) {
7400
7851
  const settings = typeof plugin2.settings === "string" ? JSON.parse(plugin2.settings) : plugin2.settings;
7401
- return { ...DEFAULT_SETTINGS3, ...settings };
7852
+ return { ...DEFAULT_SETTINGS4, ...settings };
7402
7853
  }
7403
7854
  } catch {
7404
7855
  }
7405
- return DEFAULT_SETTINGS3;
7856
+ return DEFAULT_SETTINGS4;
7406
7857
  }
7407
- adminRoutes4.get("/", async (c) => {
7858
+ adminRoutes5.get("/", async (c) => {
7408
7859
  const db = c.env.DB;
7409
7860
  const user = c.get("user");
7410
7861
  const subscriptionService = new SubscriptionService(db);
@@ -7427,7 +7878,7 @@ adminRoutes4.get("/", async (c) => {
7427
7878
  });
7428
7879
  return c.html(html4);
7429
7880
  });
7430
- adminRoutes4.get("/events", async (c) => {
7881
+ adminRoutes5.get("/events", async (c) => {
7431
7882
  const db = c.env.DB;
7432
7883
  const user = c.get("user");
7433
7884
  const eventService = new StripeEventService(db);
@@ -7453,10 +7904,10 @@ adminRoutes4.get("/events", async (c) => {
7453
7904
  });
7454
7905
  return c.html(html4);
7455
7906
  });
7456
- adminRoutes4.get("/settings", async (c) => {
7907
+ adminRoutes5.get("/settings", async (c) => {
7457
7908
  const db = c.env.DB;
7458
7909
  const user = c.get("user");
7459
- const settings = await getSettings3(db);
7910
+ const settings = await getSettings4(db);
7460
7911
  const { renderAdminLayoutCatalyst: renderAdminLayoutCatalyst2 } = await import('./admin-layout-catalyst.template-YQ4EMF2J.js');
7461
7912
  const content2 = `
7462
7913
  <div>
@@ -7563,7 +8014,7 @@ adminRoutes4.get("/settings", async (c) => {
7563
8014
  dynamicMenuItems: c.get("pluginMenuItems")
7564
8015
  }));
7565
8016
  });
7566
- adminRoutes4.post("/settings", async (c) => {
8017
+ adminRoutes5.post("/settings", async (c) => {
7567
8018
  try {
7568
8019
  const body = await c.req.json();
7569
8020
  const db = c.env.DB;
@@ -7714,17 +8165,17 @@ function timingSafeEqual(a, b) {
7714
8165
 
7715
8166
  // src/plugins/core-plugins/stripe-plugin/routes/api.ts
7716
8167
  var apiRoutes3 = new Hono();
7717
- async function getSettings4(db) {
8168
+ async function getSettings5(db) {
7718
8169
  try {
7719
8170
  const pluginService = new PluginService(db);
7720
8171
  const plugin2 = await pluginService.getPlugin("stripe");
7721
8172
  if (plugin2?.settings) {
7722
8173
  const settings = typeof plugin2.settings === "string" ? JSON.parse(plugin2.settings) : plugin2.settings;
7723
- return { ...DEFAULT_SETTINGS3, ...settings };
8174
+ return { ...DEFAULT_SETTINGS4, ...settings };
7724
8175
  }
7725
8176
  } catch {
7726
8177
  }
7727
- return DEFAULT_SETTINGS3;
8178
+ return DEFAULT_SETTINGS4;
7728
8179
  }
7729
8180
  function mapStripeStatus(status) {
7730
8181
  const map = {
@@ -7741,7 +8192,7 @@ function mapStripeStatus(status) {
7741
8192
  }
7742
8193
  apiRoutes3.post("/webhook", async (c) => {
7743
8194
  const db = c.env.DB;
7744
- const settings = await getSettings4(db);
8195
+ const settings = await getSettings5(db);
7745
8196
  if (!settings.stripeWebhookSecret) {
7746
8197
  return c.json({ error: "Webhook secret not configured" }, 500);
7747
8198
  }
@@ -7871,7 +8322,7 @@ apiRoutes3.post("/create-checkout-session", requireAuth(), async (c) => {
7871
8322
  const db = c.env.DB;
7872
8323
  const user = c.get("user");
7873
8324
  if (!user) return c.json({ error: "Unauthorized" }, 401);
7874
- const settings = await getSettings4(db);
8325
+ const settings = await getSettings5(db);
7875
8326
  if (!settings.stripeSecretKey) {
7876
8327
  return c.json({ error: "Stripe not configured" }, 500);
7877
8328
  }
@@ -7948,7 +8399,7 @@ apiRoutes3.post("/sync-subscriptions", requireAuth(), async (c) => {
7948
8399
  const user = c.get("user");
7949
8400
  if (user?.role !== "admin") return c.json({ error: "Access denied" }, 403);
7950
8401
  const db = c.env.DB;
7951
- const settings = await getSettings4(db);
8402
+ const settings = await getSettings5(db);
7952
8403
  if (!settings.stripeSecretKey) {
7953
8404
  return c.json({ error: "Stripe secret key not configured" }, 400);
7954
8405
  }
@@ -8023,7 +8474,7 @@ var stripePlugin = definePlugin({
8023
8474
  sonicjsVersionRange: "^3.0.0",
8024
8475
  author: { name: "SonicJS Team" },
8025
8476
  register(app2) {
8026
- app2.route("/admin/plugins/stripe", adminRoutes4);
8477
+ app2.route("/admin/plugins/stripe", adminRoutes5);
8027
8478
  app2.route("/api/stripe", apiRoutes3);
8028
8479
  },
8029
8480
  menu: [
@@ -11095,7 +11546,16 @@ function pluginMenuMiddleware() {
11095
11546
  }
11096
11547
  } catch {
11097
11548
  }
11098
- c.set("pluginMenuItems", activeMenuItems.map((m) => ({ label: m.label, path: m.path, icon: resolveIcon(m.icon) || "" })));
11549
+ const user = c.get("user");
11550
+ const singletonItems = resolvePluginMenuItems(user ? { role: user.role } : void 0);
11551
+ const existingPaths = new Set(activeMenuItems.map((i) => i.path));
11552
+ for (const item of singletonItems) {
11553
+ if (!existingPaths.has(item.path)) {
11554
+ activeMenuItems.push({ label: item.label, path: item.path, icon: item.icon, order: item.order });
11555
+ }
11556
+ }
11557
+ activeMenuItems.sort((a, b) => a.order - b.order);
11558
+ c.set("pluginMenuItems", activeMenuItems.map((m) => ({ label: m.label, path: m.path, icon: resolveIcon(m.icon) || m.icon || "" })));
11099
11559
  await next();
11100
11560
  const finalItems = c.get("pluginMenuItems") ?? [];
11101
11561
  if (finalItems.length > 0 && c.res.headers.get("content-type")?.includes("text/html")) {
@@ -11113,16 +11573,16 @@ function pluginMenuMiddleware() {
11113
11573
  };
11114
11574
  }
11115
11575
  init_admin_layout_catalyst_template();
11116
- var adminRoutes5 = new Hono();
11117
- adminRoutes5.use("*", requireAuth());
11118
- adminRoutes5.use("*", async (c, next) => {
11576
+ var adminRoutes6 = new Hono();
11577
+ adminRoutes6.use("*", requireAuth());
11578
+ adminRoutes6.use("*", async (c, next) => {
11119
11579
  const user = c.get("user");
11120
11580
  if (user?.role !== "admin") {
11121
11581
  return c.text("Access denied", 403);
11122
11582
  }
11123
11583
  return next();
11124
11584
  });
11125
- adminRoutes5.get("/", async (c) => {
11585
+ adminRoutes6.get("/", async (c) => {
11126
11586
  const user = c.get("user");
11127
11587
  const db = c.env.DB;
11128
11588
  let totalRequests = 0;
@@ -11478,7 +11938,7 @@ var analyticsPlugin = definePlugin({
11478
11938
  author: { name: "SonicJS Team", email: "team@sonicjs.com" },
11479
11939
  register(app2) {
11480
11940
  app2.route("/api/events", apiRoutes4);
11481
- app2.route("/admin/analytics", adminRoutes5);
11941
+ app2.route("/admin/analytics", adminRoutes6);
11482
11942
  },
11483
11943
  menu: [
11484
11944
  {
@@ -11760,8 +12220,8 @@ apiRoutes5.delete("/:id", async (c) => {
11760
12220
  return c.json({ success: false, error: "Failed to delete variable" }, 500);
11761
12221
  }
11762
12222
  });
11763
- var adminRoutes6 = new Hono();
11764
- adminRoutes6.use("*", async (c, next) => {
12223
+ var adminRoutes7 = new Hono();
12224
+ adminRoutes7.use("*", async (c, next) => {
11765
12225
  try {
11766
12226
  const db = c.env?.DB;
11767
12227
  if (db) {
@@ -11772,7 +12232,7 @@ adminRoutes6.use("*", async (c, next) => {
11772
12232
  }
11773
12233
  await next();
11774
12234
  });
11775
- adminRoutes6.get("/", async (c) => {
12235
+ adminRoutes7.get("/", async (c) => {
11776
12236
  const db = c.env.DB;
11777
12237
  let variables = [];
11778
12238
  try {
@@ -11802,7 +12262,7 @@ adminRoutes6.get("/", async (c) => {
11802
12262
  }
11803
12263
  return c.html(renderAdminPage(variables, { editorActive, activeEditorName, enableEditorIntegration }));
11804
12264
  });
11805
- adminRoutes6.put("/:id", async (c) => {
12265
+ adminRoutes7.put("/:id", async (c) => {
11806
12266
  const db = c.env.DB;
11807
12267
  const id = c.req.param("id");
11808
12268
  let body;
@@ -11818,7 +12278,7 @@ adminRoutes6.put("/:id", async (c) => {
11818
12278
  }
11819
12279
  return c.html('<span class="text-green-400 text-xs">Saved</span>');
11820
12280
  });
11821
- adminRoutes6.post("/:id/toggle", async (c) => {
12281
+ adminRoutes7.post("/:id/toggle", async (c) => {
11822
12282
  const db = c.env.DB;
11823
12283
  const id = c.req.param("id");
11824
12284
  await db.prepare("UPDATE global_variables SET is_active = CASE WHEN is_active = 1 THEN 0 ELSE 1 END, updated_at = strftime('%s', 'now') WHERE id = ?").bind(id).run();
@@ -11826,7 +12286,7 @@ adminRoutes6.post("/:id/toggle", async (c) => {
11826
12286
  c.header("HX-Redirect", "/admin/global-variables");
11827
12287
  return c.body(null, 204);
11828
12288
  });
11829
- adminRoutes6.post("/", async (c) => {
12289
+ adminRoutes7.post("/", async (c) => {
11830
12290
  const db = c.env.DB;
11831
12291
  const form = await c.req.parseBody();
11832
12292
  const key = (form.key || "").trim();
@@ -11847,7 +12307,7 @@ adminRoutes6.post("/", async (c) => {
11847
12307
  c.header("HX-Redirect", "/admin/global-variables");
11848
12308
  return c.body(null, 204);
11849
12309
  });
11850
- adminRoutes6.post("/settings/editor-integration", async (c) => {
12310
+ adminRoutes7.post("/settings/editor-integration", async (c) => {
11851
12311
  const db = c.env.DB;
11852
12312
  try {
11853
12313
  const row = await db.prepare("SELECT settings FROM plugins WHERE id = 'global-variables'").first();
@@ -11859,7 +12319,7 @@ adminRoutes6.post("/settings/editor-integration", async (c) => {
11859
12319
  return c.json({ success: false, error: "Failed to update setting" }, 500);
11860
12320
  }
11861
12321
  });
11862
- adminRoutes6.delete("/:id", async (c) => {
12322
+ adminRoutes7.delete("/:id", async (c) => {
11863
12323
  const db = c.env.DB;
11864
12324
  await db.prepare("DELETE FROM global_variables WHERE id = ?").bind(c.req.param("id")).run();
11865
12325
  invalidateVariablesCache();
@@ -12190,7 +12650,7 @@ var globalVariablesPlugin = definePlugin({
12190
12650
  capabilities: ["hooks.content:subscribe"],
12191
12651
  register(app2) {
12192
12652
  app2.route("/api/global-variables", apiRoutes5);
12193
- app2.route("/admin/global-variables", adminRoutes6);
12653
+ app2.route("/admin/global-variables", adminRoutes7);
12194
12654
  },
12195
12655
  menu: [
12196
12656
  { label: "Global Variables", path: "/admin/global-variables", icon: "bolt", order: 45, permissions: ["global-variables:view"] }
@@ -12511,8 +12971,8 @@ apiRoutes6.delete("/:id", async (c) => {
12511
12971
  return c.json({ success: false, error: "Failed to delete shortcode" }, 500);
12512
12972
  }
12513
12973
  });
12514
- var adminRoutes7 = new Hono();
12515
- adminRoutes7.use("*", async (c, next) => {
12974
+ var adminRoutes8 = new Hono();
12975
+ adminRoutes8.use("*", async (c, next) => {
12516
12976
  try {
12517
12977
  const db = c.env?.DB;
12518
12978
  if (db) {
@@ -12523,7 +12983,7 @@ adminRoutes7.use("*", async (c, next) => {
12523
12983
  }
12524
12984
  await next();
12525
12985
  });
12526
- adminRoutes7.get("/", async (c) => {
12986
+ adminRoutes8.get("/", async (c) => {
12527
12987
  const db = c.env.DB;
12528
12988
  let shortcodes = [];
12529
12989
  try {
@@ -12553,7 +13013,7 @@ adminRoutes7.get("/", async (c) => {
12553
13013
  }
12554
13014
  return c.html(renderAdminPage2(shortcodes, { editorActive, activeEditorName, enableEditorIntegration }));
12555
13015
  });
12556
- adminRoutes7.post("/", async (c) => {
13016
+ adminRoutes8.post("/", async (c) => {
12557
13017
  const db = c.env.DB;
12558
13018
  const form = await c.req.parseBody();
12559
13019
  const name = (form.name || "").trim();
@@ -12574,7 +13034,7 @@ adminRoutes7.post("/", async (c) => {
12574
13034
  c.header("HX-Redirect", "/admin/shortcodes");
12575
13035
  return c.body(null, 204);
12576
13036
  });
12577
- adminRoutes7.post("/settings/editor-integration", async (c) => {
13037
+ adminRoutes8.post("/settings/editor-integration", async (c) => {
12578
13038
  const db = c.env.DB;
12579
13039
  try {
12580
13040
  const row = await db.prepare("SELECT settings FROM plugins WHERE id = 'shortcodes'").first();
@@ -12586,7 +13046,7 @@ adminRoutes7.post("/settings/editor-integration", async (c) => {
12586
13046
  return c.json({ success: false, error: "Failed to update setting" }, 500);
12587
13047
  }
12588
13048
  });
12589
- adminRoutes7.delete("/:id", async (c) => {
13049
+ adminRoutes8.delete("/:id", async (c) => {
12590
13050
  await c.env.DB.prepare("DELETE FROM shortcodes WHERE id = ?").bind(c.req.param("id")).run();
12591
13051
  return c.html("");
12592
13052
  });
@@ -12797,7 +13257,7 @@ var shortcodesPlugin = definePlugin({
12797
13257
  capabilities: ["hooks.content:subscribe"],
12798
13258
  register(app2) {
12799
13259
  app2.route("/api/shortcodes", apiRoutes6);
12800
- app2.route("/admin/shortcodes", adminRoutes7);
13260
+ app2.route("/admin/shortcodes", adminRoutes8);
12801
13261
  },
12802
13262
  menu: [
12803
13263
  { label: "Shortcodes", path: "/admin/shortcodes", icon: "bolt", order: 46, permissions: ["shortcodes:view"] }
@@ -12906,6 +13366,9 @@ var helloWorldPlugin = definePlugin({
12906
13366
  console.info("\u274C Hello World plugin deactivated");
12907
13367
  }
12908
13368
  });
13369
+ function createHelloWorldPlugin() {
13370
+ return helloWorldPlugin;
13371
+ }
12909
13372
 
12910
13373
  // src/plugins/core-plugins/multi-tenant-plugin/manifest.json
12911
13374
  var manifest_default2 = {
@@ -16079,6 +16542,7 @@ function createSonicJSApp(config = {}) {
16079
16542
  const magicLinkPlugin = createMagicLinkAuthPlugin();
16080
16543
  const corePluginsBeforeCatchAll = [
16081
16544
  securityAuditPlugin,
16545
+ apiKeysPlugin,
16082
16546
  aiSearchPlugin,
16083
16547
  oauthProvidersPlugin,
16084
16548
  userProfilesPlugin,
@@ -16220,6 +16684,7 @@ function createSonicJSApp(config = {}) {
16220
16684
  }
16221
16685
  await next();
16222
16686
  });
16687
+ app2.use("*", apiKeyAuthMiddleware());
16223
16688
  if (config.middleware?.afterAuth) {
16224
16689
  for (const middleware of config.middleware.afterAuth) {
16225
16690
  app2.use("*", middleware);
@@ -16318,7 +16783,10 @@ function createSonicJSApp(config = {}) {
16318
16783
  ...corePluginsAfterCatchAll,
16319
16784
  ...config.plugins?.register ?? []
16320
16785
  ];
16321
- setPluginMenu(allMountedPlugins.flatMap((p) => p.menu ?? []));
16786
+ const userPlugins = (config.plugins?.register ?? []).filter(
16787
+ (p) => !PLUGIN_REGISTRY[p.id]
16788
+ );
16789
+ setPluginMenu(userPlugins.flatMap((p) => p.menu ?? []));
16322
16790
  setPluginDefinitions(allMountedPlugins);
16323
16791
  }
16324
16792
  app2.get("/favicon.svg", (c) => {
@@ -19947,6 +20415,6 @@ function createDb(d1) {
19947
20415
  // src/index.ts
19948
20416
  var VERSION = package_default.version;
19949
20417
 
19950
- export { BUILT_IN_PROVIDERS, CloudflareEmailProvider2 as CloudflareEmailProvider, OAuthService, VERSION, createDb, createOAuthProvidersPlugin, createRedirectPlugin, createSonicJSApp, emailReconciliationPlugin, generateTriggersComment, oauthProvidersPlugin, parseCronTriggers, redirectPlugin, setupCoreMiddleware, setupCoreRoutes, site_settings_collection_default as siteSettingsCollection, updateWranglerTriggers };
20418
+ export { BUILT_IN_PROVIDERS, CloudflareEmailProvider2 as CloudflareEmailProvider, OAuthService, VERSION, createDb, createHelloWorldPlugin, createOAuthProvidersPlugin, createRedirectPlugin, createSonicJSApp, emailReconciliationPlugin, generateTriggersComment, helloWorldPlugin, oauthProvidersPlugin, parseCronTriggers, redirectPlugin, setupCoreMiddleware, setupCoreRoutes, site_settings_collection_default as siteSettingsCollection, updateWranglerTriggers };
19951
20419
  //# sourceMappingURL=index.js.map
19952
20420
  //# sourceMappingURL=index.js.map