@sonicjs-cms/core 3.0.0-beta.14 → 3.0.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-Bo0X1OWX.d.ts → app-01M0dIwt.d.ts} +1 -1
- package/dist/{app-Do66yCcV.d.cts → app-aj39TGHB.d.cts} +1 -1
- package/dist/{chunk-H5FB6GKR.cjs → chunk-4AB3PQHF.cjs} +2 -2
- package/dist/{chunk-H5FB6GKR.cjs.map → chunk-4AB3PQHF.cjs.map} +1 -1
- package/dist/{chunk-INE5E3SL.js → chunk-4ENXIP4M.js} +27 -2
- package/dist/chunk-4ENXIP4M.js.map +1 -0
- package/dist/{chunk-5L6EMI7A.js → chunk-75WXXLAU.js} +4 -4
- package/dist/{chunk-5L6EMI7A.js.map → chunk-75WXXLAU.js.map} +1 -1
- package/dist/{chunk-SSOP7G4R.cjs → chunk-BDQOPRA3.cjs} +220 -219
- package/dist/chunk-BDQOPRA3.cjs.map +1 -0
- package/dist/{chunk-U7T5EOHJ.js → chunk-CE6POA7A.js} +4 -4
- package/dist/{chunk-U7T5EOHJ.js.map → chunk-CE6POA7A.js.map} +1 -1
- package/dist/{chunk-G5LC7MYH.cjs → chunk-FVI3H6RG.cjs} +10 -10
- package/dist/{chunk-G5LC7MYH.cjs.map → chunk-FVI3H6RG.cjs.map} +1 -1
- package/dist/{chunk-WIV3R2MJ.cjs → chunk-FXZSZ7C7.cjs} +27 -2
- package/dist/chunk-FXZSZ7C7.cjs.map +1 -0
- package/dist/{chunk-O32IZ6JS.js → chunk-GQXVDFIG.js} +43 -5
- package/dist/chunk-GQXVDFIG.js.map +1 -0
- package/dist/{chunk-75JKPNUZ.js → chunk-IQZA2OY5.js} +3 -3
- package/dist/{chunk-75JKPNUZ.js.map → chunk-IQZA2OY5.js.map} +1 -1
- package/dist/{chunk-QNPT6BQP.js → chunk-JRLADH5B.js} +3 -3
- package/dist/{chunk-QNPT6BQP.js.map → chunk-JRLADH5B.js.map} +1 -1
- package/dist/{chunk-ZTEJHSYS.cjs → chunk-KMCLNO4K.cjs} +5 -5
- package/dist/{chunk-ZTEJHSYS.cjs.map → chunk-KMCLNO4K.cjs.map} +1 -1
- package/dist/{chunk-HNUGMALL.js → chunk-L3VIH6U4.js} +15 -15
- package/dist/chunk-L3VIH6U4.js.map +1 -0
- package/dist/{chunk-SIPBHWMG.cjs → chunk-PK3ODTAL.cjs} +4 -4
- package/dist/{chunk-SIPBHWMG.cjs.map → chunk-PK3ODTAL.cjs.map} +1 -1
- package/dist/{chunk-4YRYGDCQ.cjs → chunk-PTTFLUEX.cjs} +4 -4
- package/dist/{chunk-4YRYGDCQ.cjs.map → chunk-PTTFLUEX.cjs.map} +1 -1
- package/dist/{chunk-32SDMKZ7.js → chunk-VOAP3KFU.js} +2 -2
- package/dist/{chunk-32SDMKZ7.js.map → chunk-VOAP3KFU.js.map} +1 -1
- package/dist/{chunk-IESEVHXL.js → chunk-VVYVFCML.js} +12 -3
- package/dist/chunk-VVYVFCML.js.map +1 -0
- package/dist/{chunk-HIKBY7MS.cjs → chunk-XWVDSM2L.cjs} +12 -3
- package/dist/chunk-XWVDSM2L.cjs.map +1 -0
- package/dist/{chunk-L3KHLEHL.cjs → chunk-ZBDDZYA3.cjs} +53 -15
- package/dist/chunk-ZBDDZYA3.cjs.map +1 -0
- package/dist/config-SM2UWSDN.js +6 -0
- package/dist/{config-PLJTDINX.js.map → config-SM2UWSDN.js.map} +1 -1
- package/dist/config-YCRSU7ZP.cjs +19 -0
- package/dist/{config-FLOUCYHH.cjs.map → config-YCRSU7ZP.cjs.map} +1 -1
- package/dist/{define-plugin-BzNHc1ZI.d.ts → define-plugin-BYIey6sp.d.ts} +1 -1
- package/dist/{define-plugin-IWDKYaVm.d.cts → define-plugin-DsKiGu1q.d.cts} +1 -1
- package/dist/index.cjs +754 -296
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -6
- package/dist/index.d.ts +17 -6
- package/dist/index.js +512 -56
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +33 -33
- package/dist/middleware.d.cts +2 -2
- package/dist/middleware.d.ts +2 -2
- package/dist/middleware.js +4 -4
- package/dist/migrations-6R2N6G2P.js +4 -0
- package/dist/{migrations-DOPZXFK3.js.map → migrations-6R2N6G2P.js.map} +1 -1
- package/dist/migrations-X3UX24QG.cjs +13 -0
- package/dist/{migrations-TYYC66P4.cjs.map → migrations-X3UX24QG.cjs.map} +1 -1
- package/dist/plugins.cjs +40 -40
- package/dist/plugins.d.cts +2 -2
- package/dist/plugins.d.ts +2 -2
- package/dist/plugins.js +3 -3
- package/dist/routes.cjs +27 -27
- package/dist/routes.d.cts +2 -2
- package/dist/routes.d.ts +2 -2
- package/dist/routes.js +7 -7
- package/dist/services.cjs +18 -18
- package/dist/services.js +2 -2
- package/dist/{types-Dea1eNxU.d.cts → types-DT7XN5TX.d.cts} +1 -1
- package/dist/{types-Dea1eNxU.d.ts → types-DT7XN5TX.d.ts} +1 -1
- package/dist/utils.cjs +3 -3
- package/dist/utils.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-HIKBY7MS.cjs.map +0 -1
- package/dist/chunk-HNUGMALL.js.map +0 -1
- package/dist/chunk-IESEVHXL.js.map +0 -1
- package/dist/chunk-INE5E3SL.js.map +0 -1
- package/dist/chunk-L3KHLEHL.cjs.map +0 -1
- package/dist/chunk-O32IZ6JS.js.map +0 -1
- package/dist/chunk-SSOP7G4R.cjs.map +0 -1
- package/dist/chunk-WIV3R2MJ.cjs.map +0 -1
- package/dist/config-FLOUCYHH.cjs +0 -19
- package/dist/config-PLJTDINX.js +0 -6
- package/dist/migrations-DOPZXFK3.js +0 -4
- package/dist/migrations-TYYC66P4.cjs +0 -13
package/dist/index.js
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import { createAuth } from './chunk-
|
|
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-
|
|
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-
|
|
4
|
-
import './chunk-
|
|
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-L3VIH6U4.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-L3VIH6U4.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-
|
|
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-
|
|
11
|
+
import { requireAuth, DocumentTypeRegistry, requireRbac, requireRole, getJwtExpirySecondsFromDb, AuthManager, metricsMiddleware, bootstrapMiddleware, securityHeadersMiddleware, csrfProtection } from './chunk-GQXVDFIG.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-GQXVDFIG.js';
|
|
13
13
|
import { RbacService, DocumentsService } from './chunk-PDC7DLOV.js';
|
|
14
14
|
export { 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-
|
|
17
|
-
export { PluginBootstrapService, PluginService as PluginServiceClass, getAvailableCollectionNames, loadCollectionConfig, loadCollectionConfigs, registerCollections, validateCollectionConfig } from './chunk-
|
|
18
|
-
export { MigrationService } from './chunk-
|
|
16
|
+
import { PluginService, PLUGIN_REGISTRY, invalidateTenantCache, getCollectionRegistry, TENANT_COOKIE, MULTI_TENANT_PLUGIN_ID, collectionRecordToRow, tenantMiddleware } from './chunk-4ENXIP4M.js';
|
|
17
|
+
export { PluginBootstrapService, PluginService as PluginServiceClass, getAvailableCollectionNames, loadCollectionConfig, loadCollectionConfigs, registerCollections, validateCollectionConfig } from './chunk-4ENXIP4M.js';
|
|
18
|
+
export { MigrationService } from './chunk-VOAP3KFU.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-
|
|
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-
|
|
26
|
-
import { definePlugin, setPluginDefinitions } from './chunk-
|
|
27
|
-
export { applySchemaDefaults, definePlugin, isDefinedPlugin, parseConfigSchema, parseFormDataToSettings, renderSchemaFields } from './chunk-
|
|
24
|
+
import { TurnstileService, HookSystemImpl, createPluginWirer, registerPluginRoutes, setPluginMenu } from './chunk-75WXXLAU.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-75WXXLAU.js';
|
|
26
|
+
import { definePlugin, setPluginDefinitions } from './chunk-JRLADH5B.js';
|
|
27
|
+
export { applySchemaDefaults, definePlugin, isDefinedPlugin, parseConfigSchema, parseFormDataToSettings, renderSchemaFields } from './chunk-JRLADH5B.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-
|
|
34
|
-
export { SONICJS_VERSION, getCoreVersion } from './chunk-
|
|
33
|
+
import { getCoreVersion, package_default } from './chunk-CE6POA7A.js';
|
|
34
|
+
export { SONICJS_VERSION, getCoreVersion } from './chunk-CE6POA7A.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 <key></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
|
|
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
|
|
7387
|
-
|
|
7388
|
-
|
|
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
|
|
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 { ...
|
|
7852
|
+
return { ...DEFAULT_SETTINGS4, ...settings };
|
|
7402
7853
|
}
|
|
7403
7854
|
} catch {
|
|
7404
7855
|
}
|
|
7405
|
-
return
|
|
7856
|
+
return DEFAULT_SETTINGS4;
|
|
7406
7857
|
}
|
|
7407
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7907
|
+
adminRoutes5.get("/settings", async (c) => {
|
|
7457
7908
|
const db = c.env.DB;
|
|
7458
7909
|
const user = c.get("user");
|
|
7459
|
-
const settings = await
|
|
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
|
-
|
|
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
|
|
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 { ...
|
|
8174
|
+
return { ...DEFAULT_SETTINGS4, ...settings };
|
|
7724
8175
|
}
|
|
7725
8176
|
} catch {
|
|
7726
8177
|
}
|
|
7727
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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",
|
|
8477
|
+
app2.route("/admin/plugins/stripe", adminRoutes5);
|
|
8027
8478
|
app2.route("/api/stripe", apiRoutes3);
|
|
8028
8479
|
},
|
|
8029
8480
|
menu: [
|
|
@@ -11113,16 +11564,16 @@ function pluginMenuMiddleware() {
|
|
|
11113
11564
|
};
|
|
11114
11565
|
}
|
|
11115
11566
|
init_admin_layout_catalyst_template();
|
|
11116
|
-
var
|
|
11117
|
-
|
|
11118
|
-
|
|
11567
|
+
var adminRoutes6 = new Hono();
|
|
11568
|
+
adminRoutes6.use("*", requireAuth());
|
|
11569
|
+
adminRoutes6.use("*", async (c, next) => {
|
|
11119
11570
|
const user = c.get("user");
|
|
11120
11571
|
if (user?.role !== "admin") {
|
|
11121
11572
|
return c.text("Access denied", 403);
|
|
11122
11573
|
}
|
|
11123
11574
|
return next();
|
|
11124
11575
|
});
|
|
11125
|
-
|
|
11576
|
+
adminRoutes6.get("/", async (c) => {
|
|
11126
11577
|
const user = c.get("user");
|
|
11127
11578
|
const db = c.env.DB;
|
|
11128
11579
|
let totalRequests = 0;
|
|
@@ -11478,7 +11929,7 @@ var analyticsPlugin = definePlugin({
|
|
|
11478
11929
|
author: { name: "SonicJS Team", email: "team@sonicjs.com" },
|
|
11479
11930
|
register(app2) {
|
|
11480
11931
|
app2.route("/api/events", apiRoutes4);
|
|
11481
|
-
app2.route("/admin/analytics",
|
|
11932
|
+
app2.route("/admin/analytics", adminRoutes6);
|
|
11482
11933
|
},
|
|
11483
11934
|
menu: [
|
|
11484
11935
|
{
|
|
@@ -11760,8 +12211,8 @@ apiRoutes5.delete("/:id", async (c) => {
|
|
|
11760
12211
|
return c.json({ success: false, error: "Failed to delete variable" }, 500);
|
|
11761
12212
|
}
|
|
11762
12213
|
});
|
|
11763
|
-
var
|
|
11764
|
-
|
|
12214
|
+
var adminRoutes7 = new Hono();
|
|
12215
|
+
adminRoutes7.use("*", async (c, next) => {
|
|
11765
12216
|
try {
|
|
11766
12217
|
const db = c.env?.DB;
|
|
11767
12218
|
if (db) {
|
|
@@ -11772,7 +12223,7 @@ adminRoutes6.use("*", async (c, next) => {
|
|
|
11772
12223
|
}
|
|
11773
12224
|
await next();
|
|
11774
12225
|
});
|
|
11775
|
-
|
|
12226
|
+
adminRoutes7.get("/", async (c) => {
|
|
11776
12227
|
const db = c.env.DB;
|
|
11777
12228
|
let variables = [];
|
|
11778
12229
|
try {
|
|
@@ -11802,7 +12253,7 @@ adminRoutes6.get("/", async (c) => {
|
|
|
11802
12253
|
}
|
|
11803
12254
|
return c.html(renderAdminPage(variables, { editorActive, activeEditorName, enableEditorIntegration }));
|
|
11804
12255
|
});
|
|
11805
|
-
|
|
12256
|
+
adminRoutes7.put("/:id", async (c) => {
|
|
11806
12257
|
const db = c.env.DB;
|
|
11807
12258
|
const id = c.req.param("id");
|
|
11808
12259
|
let body;
|
|
@@ -11818,7 +12269,7 @@ adminRoutes6.put("/:id", async (c) => {
|
|
|
11818
12269
|
}
|
|
11819
12270
|
return c.html('<span class="text-green-400 text-xs">Saved</span>');
|
|
11820
12271
|
});
|
|
11821
|
-
|
|
12272
|
+
adminRoutes7.post("/:id/toggle", async (c) => {
|
|
11822
12273
|
const db = c.env.DB;
|
|
11823
12274
|
const id = c.req.param("id");
|
|
11824
12275
|
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 +12277,7 @@ adminRoutes6.post("/:id/toggle", async (c) => {
|
|
|
11826
12277
|
c.header("HX-Redirect", "/admin/global-variables");
|
|
11827
12278
|
return c.body(null, 204);
|
|
11828
12279
|
});
|
|
11829
|
-
|
|
12280
|
+
adminRoutes7.post("/", async (c) => {
|
|
11830
12281
|
const db = c.env.DB;
|
|
11831
12282
|
const form = await c.req.parseBody();
|
|
11832
12283
|
const key = (form.key || "").trim();
|
|
@@ -11847,7 +12298,7 @@ adminRoutes6.post("/", async (c) => {
|
|
|
11847
12298
|
c.header("HX-Redirect", "/admin/global-variables");
|
|
11848
12299
|
return c.body(null, 204);
|
|
11849
12300
|
});
|
|
11850
|
-
|
|
12301
|
+
adminRoutes7.post("/settings/editor-integration", async (c) => {
|
|
11851
12302
|
const db = c.env.DB;
|
|
11852
12303
|
try {
|
|
11853
12304
|
const row = await db.prepare("SELECT settings FROM plugins WHERE id = 'global-variables'").first();
|
|
@@ -11859,7 +12310,7 @@ adminRoutes6.post("/settings/editor-integration", async (c) => {
|
|
|
11859
12310
|
return c.json({ success: false, error: "Failed to update setting" }, 500);
|
|
11860
12311
|
}
|
|
11861
12312
|
});
|
|
11862
|
-
|
|
12313
|
+
adminRoutes7.delete("/:id", async (c) => {
|
|
11863
12314
|
const db = c.env.DB;
|
|
11864
12315
|
await db.prepare("DELETE FROM global_variables WHERE id = ?").bind(c.req.param("id")).run();
|
|
11865
12316
|
invalidateVariablesCache();
|
|
@@ -12190,7 +12641,7 @@ var globalVariablesPlugin = definePlugin({
|
|
|
12190
12641
|
capabilities: ["hooks.content:subscribe"],
|
|
12191
12642
|
register(app2) {
|
|
12192
12643
|
app2.route("/api/global-variables", apiRoutes5);
|
|
12193
|
-
app2.route("/admin/global-variables",
|
|
12644
|
+
app2.route("/admin/global-variables", adminRoutes7);
|
|
12194
12645
|
},
|
|
12195
12646
|
menu: [
|
|
12196
12647
|
{ label: "Global Variables", path: "/admin/global-variables", icon: "bolt", order: 45, permissions: ["global-variables:view"] }
|
|
@@ -12511,8 +12962,8 @@ apiRoutes6.delete("/:id", async (c) => {
|
|
|
12511
12962
|
return c.json({ success: false, error: "Failed to delete shortcode" }, 500);
|
|
12512
12963
|
}
|
|
12513
12964
|
});
|
|
12514
|
-
var
|
|
12515
|
-
|
|
12965
|
+
var adminRoutes8 = new Hono();
|
|
12966
|
+
adminRoutes8.use("*", async (c, next) => {
|
|
12516
12967
|
try {
|
|
12517
12968
|
const db = c.env?.DB;
|
|
12518
12969
|
if (db) {
|
|
@@ -12523,7 +12974,7 @@ adminRoutes7.use("*", async (c, next) => {
|
|
|
12523
12974
|
}
|
|
12524
12975
|
await next();
|
|
12525
12976
|
});
|
|
12526
|
-
|
|
12977
|
+
adminRoutes8.get("/", async (c) => {
|
|
12527
12978
|
const db = c.env.DB;
|
|
12528
12979
|
let shortcodes = [];
|
|
12529
12980
|
try {
|
|
@@ -12553,7 +13004,7 @@ adminRoutes7.get("/", async (c) => {
|
|
|
12553
13004
|
}
|
|
12554
13005
|
return c.html(renderAdminPage2(shortcodes, { editorActive, activeEditorName, enableEditorIntegration }));
|
|
12555
13006
|
});
|
|
12556
|
-
|
|
13007
|
+
adminRoutes8.post("/", async (c) => {
|
|
12557
13008
|
const db = c.env.DB;
|
|
12558
13009
|
const form = await c.req.parseBody();
|
|
12559
13010
|
const name = (form.name || "").trim();
|
|
@@ -12574,7 +13025,7 @@ adminRoutes7.post("/", async (c) => {
|
|
|
12574
13025
|
c.header("HX-Redirect", "/admin/shortcodes");
|
|
12575
13026
|
return c.body(null, 204);
|
|
12576
13027
|
});
|
|
12577
|
-
|
|
13028
|
+
adminRoutes8.post("/settings/editor-integration", async (c) => {
|
|
12578
13029
|
const db = c.env.DB;
|
|
12579
13030
|
try {
|
|
12580
13031
|
const row = await db.prepare("SELECT settings FROM plugins WHERE id = 'shortcodes'").first();
|
|
@@ -12586,7 +13037,7 @@ adminRoutes7.post("/settings/editor-integration", async (c) => {
|
|
|
12586
13037
|
return c.json({ success: false, error: "Failed to update setting" }, 500);
|
|
12587
13038
|
}
|
|
12588
13039
|
});
|
|
12589
|
-
|
|
13040
|
+
adminRoutes8.delete("/:id", async (c) => {
|
|
12590
13041
|
await c.env.DB.prepare("DELETE FROM shortcodes WHERE id = ?").bind(c.req.param("id")).run();
|
|
12591
13042
|
return c.html("");
|
|
12592
13043
|
});
|
|
@@ -12797,7 +13248,7 @@ var shortcodesPlugin = definePlugin({
|
|
|
12797
13248
|
capabilities: ["hooks.content:subscribe"],
|
|
12798
13249
|
register(app2) {
|
|
12799
13250
|
app2.route("/api/shortcodes", apiRoutes6);
|
|
12800
|
-
app2.route("/admin/shortcodes",
|
|
13251
|
+
app2.route("/admin/shortcodes", adminRoutes8);
|
|
12801
13252
|
},
|
|
12802
13253
|
menu: [
|
|
12803
13254
|
{ label: "Shortcodes", path: "/admin/shortcodes", icon: "bolt", order: 46, permissions: ["shortcodes:view"] }
|
|
@@ -12906,6 +13357,9 @@ var helloWorldPlugin = definePlugin({
|
|
|
12906
13357
|
console.info("\u274C Hello World plugin deactivated");
|
|
12907
13358
|
}
|
|
12908
13359
|
});
|
|
13360
|
+
function createHelloWorldPlugin() {
|
|
13361
|
+
return helloWorldPlugin;
|
|
13362
|
+
}
|
|
12909
13363
|
|
|
12910
13364
|
// src/plugins/core-plugins/multi-tenant-plugin/manifest.json
|
|
12911
13365
|
var manifest_default2 = {
|
|
@@ -16079,6 +16533,7 @@ function createSonicJSApp(config = {}) {
|
|
|
16079
16533
|
const magicLinkPlugin = createMagicLinkAuthPlugin();
|
|
16080
16534
|
const corePluginsBeforeCatchAll = [
|
|
16081
16535
|
securityAuditPlugin,
|
|
16536
|
+
apiKeysPlugin,
|
|
16082
16537
|
aiSearchPlugin,
|
|
16083
16538
|
oauthProvidersPlugin,
|
|
16084
16539
|
userProfilesPlugin,
|
|
@@ -16220,6 +16675,7 @@ function createSonicJSApp(config = {}) {
|
|
|
16220
16675
|
}
|
|
16221
16676
|
await next();
|
|
16222
16677
|
});
|
|
16678
|
+
app2.use("*", apiKeyAuthMiddleware());
|
|
16223
16679
|
if (config.middleware?.afterAuth) {
|
|
16224
16680
|
for (const middleware of config.middleware.afterAuth) {
|
|
16225
16681
|
app2.use("*", middleware);
|
|
@@ -19947,6 +20403,6 @@ function createDb(d1) {
|
|
|
19947
20403
|
// src/index.ts
|
|
19948
20404
|
var VERSION = package_default.version;
|
|
19949
20405
|
|
|
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 };
|
|
20406
|
+
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
20407
|
//# sourceMappingURL=index.js.map
|
|
19952
20408
|
//# sourceMappingURL=index.js.map
|