@dyrected/core 2.6.2 → 2.6.4
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/__tests__/access-features.test.d.ts +2 -0
- package/dist/__tests__/access-features.test.d.ts.map +1 -0
- package/dist/__tests__/access-features.test.js +159 -0
- package/dist/__tests__/access-features.test.js.map +1 -0
- package/dist/__tests__/access-parity.test.d.ts +2 -0
- package/dist/__tests__/access-parity.test.d.ts.map +1 -0
- package/dist/__tests__/access-parity.test.js +338 -0
- package/dist/__tests__/access-parity.test.js.map +1 -0
- package/dist/__tests__/access.test.d.ts +2 -0
- package/dist/__tests__/access.test.d.ts.map +1 -0
- package/dist/__tests__/access.test.js +137 -0
- package/dist/__tests__/access.test.js.map +1 -0
- package/dist/__tests__/admin-auth.test.d.ts +2 -0
- package/dist/__tests__/admin-auth.test.d.ts.map +1 -0
- package/dist/__tests__/admin-auth.test.js +440 -0
- package/dist/__tests__/admin-auth.test.js.map +1 -0
- package/dist/__tests__/app.test.d.ts +2 -0
- package/dist/__tests__/app.test.d.ts.map +1 -0
- package/dist/__tests__/app.test.js +112 -0
- package/dist/__tests__/app.test.js.map +1 -0
- package/dist/__tests__/audit.test.d.ts +2 -0
- package/dist/__tests__/audit.test.d.ts.map +1 -0
- package/dist/__tests__/audit.test.js +125 -0
- package/dist/__tests__/audit.test.js.map +1 -0
- package/dist/__tests__/auth-hydration.test.d.ts +2 -0
- package/dist/__tests__/auth-hydration.test.d.ts.map +1 -0
- package/dist/__tests__/auth-hydration.test.js +90 -0
- package/dist/__tests__/auth-hydration.test.js.map +1 -0
- package/dist/__tests__/auth-invite.test.d.ts +2 -0
- package/dist/__tests__/auth-invite.test.d.ts.map +1 -0
- package/dist/__tests__/auth-invite.test.js +152 -0
- package/dist/__tests__/auth-invite.test.js.map +1 -0
- package/dist/__tests__/auth-lockout.test.d.ts +2 -0
- package/dist/__tests__/auth-lockout.test.d.ts.map +1 -0
- package/dist/__tests__/auth-lockout.test.js +154 -0
- package/dist/__tests__/auth-lockout.test.js.map +1 -0
- package/dist/__tests__/auth-sessions.test.d.ts +2 -0
- package/dist/__tests__/auth-sessions.test.d.ts.map +1 -0
- package/dist/__tests__/auth-sessions.test.js +118 -0
- package/dist/__tests__/auth-sessions.test.js.map +1 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +402 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/define-field.test.d.ts +2 -0
- package/dist/__tests__/define-field.test.d.ts.map +1 -0
- package/dist/__tests__/define-field.test.js +121 -0
- package/dist/__tests__/define-field.test.js.map +1 -0
- package/dist/__tests__/deleteMany.test.d.ts +2 -0
- package/dist/__tests__/deleteMany.test.d.ts.map +1 -0
- package/dist/__tests__/deleteMany.test.js +75 -0
- package/dist/__tests__/deleteMany.test.js.map +1 -0
- package/dist/__tests__/depth.test.d.ts +2 -0
- package/dist/__tests__/depth.test.d.ts.map +1 -0
- package/dist/__tests__/depth.test.js +115 -0
- package/dist/__tests__/depth.test.js.map +1 -0
- package/dist/__tests__/dynamic-options.test.d.ts +2 -0
- package/dist/__tests__/dynamic-options.test.d.ts.map +1 -0
- package/dist/__tests__/dynamic-options.test.js +168 -0
- package/dist/__tests__/dynamic-options.test.js.map +1 -0
- package/dist/__tests__/email.service.test.d.ts +2 -0
- package/dist/__tests__/email.service.test.d.ts.map +1 -0
- package/dist/__tests__/email.service.test.js +52 -0
- package/dist/__tests__/email.service.test.js.map +1 -0
- package/dist/__tests__/field-inference.test-types.d.ts +161 -0
- package/dist/__tests__/field-inference.test-types.d.ts.map +1 -0
- package/dist/__tests__/field-inference.test-types.js +87 -0
- package/dist/__tests__/field-inference.test-types.js.map +1 -0
- package/dist/__tests__/fixtures/editorial-workflow.d.ts +14 -0
- package/dist/__tests__/fixtures/editorial-workflow.d.ts.map +1 -0
- package/dist/__tests__/fixtures/editorial-workflow.js +12 -0
- package/dist/__tests__/fixtures/editorial-workflow.js.map +1 -0
- package/dist/__tests__/hooks.test.d.ts +2 -0
- package/dist/__tests__/hooks.test.d.ts.map +1 -0
- package/dist/__tests__/hooks.test.js +320 -0
- package/dist/__tests__/hooks.test.js.map +1 -0
- package/dist/__tests__/mocks.d.ts +75 -0
- package/dist/__tests__/mocks.d.ts.map +1 -0
- package/dist/__tests__/mocks.js +170 -0
- package/dist/__tests__/mocks.js.map +1 -0
- package/dist/__tests__/observability.test.d.ts +2 -0
- package/dist/__tests__/observability.test.d.ts.map +1 -0
- package/dist/__tests__/observability.test.js +233 -0
- package/dist/__tests__/observability.test.js.map +1 -0
- package/dist/__tests__/openapi.test.d.ts +2 -0
- package/dist/__tests__/openapi.test.d.ts.map +1 -0
- package/dist/__tests__/openapi.test.js +105 -0
- package/dist/__tests__/openapi.test.js.map +1 -0
- package/dist/__tests__/parse-sort.test.d.ts +2 -0
- package/dist/__tests__/parse-sort.test.d.ts.map +1 -0
- package/dist/__tests__/parse-sort.test.js +24 -0
- package/dist/__tests__/parse-sort.test.js.map +1 -0
- package/dist/__tests__/preferences.test.d.ts +2 -0
- package/dist/__tests__/preferences.test.d.ts.map +1 -0
- package/dist/__tests__/preferences.test.js +168 -0
- package/dist/__tests__/preferences.test.js.map +1 -0
- package/dist/__tests__/router.test.d.ts +2 -0
- package/dist/__tests__/router.test.d.ts.map +1 -0
- package/dist/__tests__/router.test.js +70 -0
- package/dist/__tests__/router.test.js.map +1 -0
- package/dist/__tests__/search.test.d.ts +2 -0
- package/dist/__tests__/search.test.d.ts.map +1 -0
- package/dist/__tests__/search.test.js +174 -0
- package/dist/__tests__/search.test.js.map +1 -0
- package/dist/__tests__/upload-validation.test.d.ts +2 -0
- package/dist/__tests__/upload-validation.test.d.ts.map +1 -0
- package/dist/__tests__/upload-validation.test.js +41 -0
- package/dist/__tests__/upload-validation.test.js.map +1 -0
- package/dist/__tests__/where.test.d.ts +2 -0
- package/dist/__tests__/where.test.d.ts.map +1 -0
- package/dist/__tests__/where.test.js +105 -0
- package/dist/__tests__/where.test.js.map +1 -0
- package/dist/__tests__/workflow.test.d.ts +2 -0
- package/dist/__tests__/workflow.test.d.ts.map +1 -0
- package/dist/__tests__/workflow.test.js +684 -0
- package/dist/__tests__/workflow.test.js.map +1 -0
- package/dist/app-config-C9VJ6rTv.d.cts +2092 -0
- package/dist/app-config-C9VJ6rTv.d.ts +2092 -0
- package/dist/app-config-CYV70eoF.d.cts +2100 -0
- package/dist/app-config-CYV70eoF.d.ts +2100 -0
- package/dist/app.d.ts +35 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +164 -0
- package/dist/app.js.map +1 -0
- package/dist/auth/access.d.ts +5 -0
- package/dist/auth/access.d.ts.map +1 -0
- package/dist/auth/access.js +59 -0
- package/dist/auth/access.js.map +1 -0
- package/dist/auth/jexl.d.ts +13 -0
- package/dist/auth/jexl.d.ts.map +1 -0
- package/dist/auth/jexl.js +30 -0
- package/dist/auth/jexl.js.map +1 -0
- package/dist/auth/lockout.d.ts +11 -0
- package/dist/auth/lockout.d.ts.map +1 -0
- package/dist/auth/lockout.js +37 -0
- package/dist/auth/lockout.js.map +1 -0
- package/dist/auth/password.d.ts +10 -0
- package/dist/auth/password.d.ts.map +1 -0
- package/dist/auth/password.js +28 -0
- package/dist/auth/password.js.map +1 -0
- package/dist/auth/sessions.d.ts +39 -0
- package/dist/auth/sessions.d.ts.map +1 -0
- package/dist/auth/sessions.js +126 -0
- package/dist/auth/sessions.js.map +1 -0
- package/dist/auth/token.d.ts +23 -0
- package/dist/auth/token.d.ts.map +1 -0
- package/dist/auth/token.js +40 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/chunk-23NM4LDU.js +2883 -0
- package/dist/chunk-2TKDCCIR.js +2622 -0
- package/dist/chunk-3SFITPX4.js +2628 -0
- package/dist/chunk-CQVTQ67D.js +2632 -0
- package/dist/chunk-ELHRRPKX.js +2863 -0
- package/dist/chunk-HRLSFTLR.js +3061 -0
- package/dist/chunk-QTIAQBNH.js +3097 -0
- package/dist/chunk-T2DGC4MD.js +3008 -0
- package/dist/chunk-TLU77TLE.js +3203 -0
- package/dist/chunk-YEBCT77K.js +3269 -0
- package/dist/chunk-YXEKNN5X.js +3085 -0
- package/dist/controllers/admin-auth.controller.d.ts +71 -0
- package/dist/controllers/admin-auth.controller.d.ts.map +1 -0
- package/dist/controllers/admin-auth.controller.js +450 -0
- package/dist/controllers/admin-auth.controller.js.map +1 -0
- package/dist/controllers/audit.controller.d.ts +42 -0
- package/dist/controllers/audit.controller.d.ts.map +1 -0
- package/dist/controllers/audit.controller.js +149 -0
- package/dist/controllers/audit.controller.js.map +1 -0
- package/dist/controllers/auth.controller.d.ts +143 -0
- package/dist/controllers/auth.controller.d.ts.map +1 -0
- package/dist/controllers/auth.controller.js +572 -0
- package/dist/controllers/auth.controller.js.map +1 -0
- package/dist/controllers/collection.controller.d.ts +170 -0
- package/dist/controllers/collection.controller.d.ts.map +1 -0
- package/dist/controllers/collection.controller.js +1033 -0
- package/dist/controllers/collection.controller.js.map +1 -0
- package/dist/controllers/global.controller.d.ts +20 -0
- package/dist/controllers/global.controller.d.ts.map +1 -0
- package/dist/controllers/global.controller.js +182 -0
- package/dist/controllers/global.controller.js.map +1 -0
- package/dist/controllers/media.controller.d.ts +39 -0
- package/dist/controllers/media.controller.d.ts.map +1 -0
- package/dist/controllers/media.controller.js +178 -0
- package/dist/controllers/media.controller.js.map +1 -0
- package/dist/controllers/preview.controller.d.ts +37 -0
- package/dist/controllers/preview.controller.d.ts.map +1 -0
- package/dist/controllers/preview.controller.js +48 -0
- package/dist/controllers/preview.controller.js.map +1 -0
- package/dist/index.cjs +707 -27
- package/dist/index.d.cts +77 -5
- package/dist/index.d.ts +77 -5
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -1
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +19 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +157 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/rate-limit.d.ts +9 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +75 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/network.d.ts +6 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/network.js +70 -0
- package/dist/network.js.map +1 -0
- package/dist/observability.d.ts +113 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +497 -0
- package/dist/observability.js.map +1 -0
- package/dist/router.d.ts +8 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +704 -0
- package/dist/router.js.map +1 -0
- package/dist/server.cjs +861 -54
- package/dist/server.d.cts +9 -1
- package/dist/server.d.ts +9 -1
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +189 -26
- package/dist/server.js.map +1 -0
- package/dist/services/audit.service.d.ts +23 -0
- package/dist/services/audit.service.d.ts.map +1 -0
- package/dist/services/audit.service.js +39 -0
- package/dist/services/audit.service.js.map +1 -0
- package/dist/services/defaults.service.d.ts +8 -0
- package/dist/services/defaults.service.d.ts.map +1 -0
- package/dist/services/defaults.service.js +55 -0
- package/dist/services/defaults.service.js.map +1 -0
- package/dist/services/email-template.d.ts +46 -0
- package/dist/services/email-template.d.ts.map +1 -0
- package/dist/services/email-template.js +88 -0
- package/dist/services/email-template.js.map +1 -0
- package/dist/services/email.service.d.ts +34 -0
- package/dist/services/email.service.d.ts.map +1 -0
- package/dist/services/email.service.js +129 -0
- package/dist/services/email.service.js.map +1 -0
- package/dist/services/media.service.d.ts +20 -0
- package/dist/services/media.service.d.ts.map +1 -0
- package/dist/services/media.service.js +49 -0
- package/dist/services/media.service.js.map +1 -0
- package/dist/services/population.service.d.ts +20 -0
- package/dist/services/population.service.d.ts.map +1 -0
- package/dist/services/population.service.js +224 -0
- package/dist/services/population.service.js.map +1 -0
- package/dist/types/access.d.ts +48 -0
- package/dist/types/access.d.ts.map +1 -0
- package/dist/types/access.js +2 -0
- package/dist/types/access.js.map +1 -0
- package/dist/types/adapters.d.ts +152 -0
- package/dist/types/adapters.d.ts.map +1 -0
- package/dist/types/adapters.js +2 -0
- package/dist/types/adapters.js.map +1 -0
- package/dist/types/admin-auth.d.ts +132 -0
- package/dist/types/admin-auth.d.ts.map +1 -0
- package/dist/types/admin-auth.js +2 -0
- package/dist/types/admin-auth.js.map +1 -0
- package/dist/types/admin.d.ts +86 -0
- package/dist/types/admin.d.ts.map +1 -0
- package/dist/types/admin.js +2 -0
- package/dist/types/admin.js.map +1 -0
- package/dist/types/app-config.d.ts +279 -0
- package/dist/types/app-config.d.ts.map +1 -0
- package/dist/types/app-config.js +2 -0
- package/dist/types/app-config.js.map +1 -0
- package/dist/types/documents.d.ts +65 -0
- package/dist/types/documents.d.ts.map +1 -0
- package/dist/types/documents.js +2 -0
- package/dist/types/documents.js.map +1 -0
- package/dist/types/hooks.d.ts +122 -0
- package/dist/types/hooks.d.ts.map +1 -0
- package/dist/types/hooks.js +2 -0
- package/dist/types/hooks.js.map +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/request.d.ts +42 -0
- package/dist/types/request.d.ts.map +1 -0
- package/dist/types/request.js +2 -0
- package/dist/types/request.js.map +1 -0
- package/dist/types/schema-config.d.ts +443 -0
- package/dist/types/schema-config.d.ts.map +1 -0
- package/dist/types/schema-config.js +2 -0
- package/dist/types/schema-config.js.map +1 -0
- package/dist/types/schema-core.d.ts +628 -0
- package/dist/types/schema-core.d.ts.map +1 -0
- package/dist/types/schema-core.js +2 -0
- package/dist/types/schema-core.js.map +1 -0
- package/dist/types/schema-inference.d.ts +73 -0
- package/dist/types/schema-inference.d.ts.map +1 -0
- package/dist/types/schema-inference.js +2 -0
- package/dist/types/schema-inference.js.map +1 -0
- package/dist/types/workflows.d.ts +121 -0
- package/dist/types/workflows.d.ts.map +1 -0
- package/dist/types/workflows.js +7 -0
- package/dist/types/workflows.js.map +1 -0
- package/dist/utils/access-control.d.ts +35 -0
- package/dist/utils/access-control.d.ts.map +1 -0
- package/dist/utils/access-control.js +161 -0
- package/dist/utils/access-control.js.map +1 -0
- package/dist/utils/admin-auth.d.ts +5 -0
- package/dist/utils/admin-auth.d.ts.map +1 -0
- package/dist/utils/admin-auth.js +39 -0
- package/dist/utils/admin-auth.js.map +1 -0
- package/dist/utils/block-references.d.ts +10 -0
- package/dist/utils/block-references.d.ts.map +1 -0
- package/dist/utils/block-references.js +100 -0
- package/dist/utils/block-references.js.map +1 -0
- package/dist/utils/collection-search.d.ts +11 -0
- package/dist/utils/collection-search.d.ts.map +1 -0
- package/dist/utils/collection-search.js +204 -0
- package/dist/utils/collection-search.js.map +1 -0
- package/dist/utils/config.d.ts +8 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +336 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/hooks.d.ts +41 -0
- package/dist/utils/hooks.d.ts.map +1 -0
- package/dist/utils/hooks.js +177 -0
- package/dist/utils/hooks.js.map +1 -0
- package/dist/utils/openapi.d.ts +6 -0
- package/dist/utils/openapi.d.ts.map +1 -0
- package/dist/utils/openapi.js +981 -0
- package/dist/utils/openapi.js.map +1 -0
- package/dist/utils/parse-sort.d.ts +43 -0
- package/dist/utils/parse-sort.d.ts.map +1 -0
- package/dist/utils/parse-sort.js +70 -0
- package/dist/utils/parse-sort.js.map +1 -0
- package/dist/utils/parse-where.d.ts +69 -0
- package/dist/utils/parse-where.d.ts.map +1 -0
- package/dist/utils/parse-where.js +202 -0
- package/dist/utils/parse-where.js.map +1 -0
- package/dist/utils/readonly-db.d.ts +9 -0
- package/dist/utils/readonly-db.d.ts.map +1 -0
- package/dist/utils/readonly-db.js +21 -0
- package/dist/utils/readonly-db.js.map +1 -0
- package/dist/utils/swagger.d.ts +11 -0
- package/dist/utils/swagger.d.ts.map +1 -0
- package/dist/utils/swagger.js +62 -0
- package/dist/utils/swagger.js.map +1 -0
- package/dist/utils/upload-validation.d.ts +23 -0
- package/dist/utils/upload-validation.d.ts.map +1 -0
- package/dist/utils/upload-validation.js +46 -0
- package/dist/utils/upload-validation.js.map +1 -0
- package/dist/utils/where-sanitizer.d.ts +10 -0
- package/dist/utils/where-sanitizer.d.ts.map +1 -0
- package/dist/utils/where-sanitizer.js +67 -0
- package/dist/utils/where-sanitizer.js.map +1 -0
- package/dist/workflows.d.ts +86 -0
- package/dist/workflows.d.ts.map +1 -0
- package/dist/workflows.js +405 -0
- package/dist/workflows.js.map +1 -0
- package/package.json +1 -1
package/dist/server.cjs
CHANGED
|
@@ -1064,21 +1064,629 @@ function createSessionId() {
|
|
|
1064
1064
|
return globalThis.crypto?.randomUUID?.() ? globalThis.crypto.randomUUID() : `sess_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
1065
1065
|
}
|
|
1066
1066
|
|
|
1067
|
+
// src/utils/hooks.ts
|
|
1068
|
+
var import_jexl3 = __toESM(require("jexl"), 1);
|
|
1069
|
+
|
|
1070
|
+
// src/utils/declarative-hooks.ts
|
|
1071
|
+
var import_jexl2 = __toESM(require("jexl"), 1);
|
|
1072
|
+
var SURFACE_ROOTS = {
|
|
1073
|
+
"collection.beforeRead": ["req", "user", "query"],
|
|
1074
|
+
"collection.afterRead": ["req", "user", "doc"],
|
|
1075
|
+
"collection.beforeChange": ["req", "user", "data", "doc", "operation"],
|
|
1076
|
+
"global.beforeRead": ["req", "user", "query"],
|
|
1077
|
+
"global.afterRead": ["req", "user", "doc"],
|
|
1078
|
+
"global.beforeChange": ["req", "user", "data", "doc", "operation"],
|
|
1079
|
+
"field.beforeChange": ["value", "data", "originalDoc", "user"],
|
|
1080
|
+
"field.admin.onChange": ["value", "siblingData", "data"]
|
|
1081
|
+
};
|
|
1082
|
+
var ACCESS_ROOTS = ["user", "req", "doc", "data", "id", "config"];
|
|
1083
|
+
var ADMIN_CONDITION_RESERVED_ROOTS = ["data", "siblingData", "user", "id"];
|
|
1084
|
+
var PREVIEW_URL_RESERVED_ROOTS = ["siteUrl", "id"];
|
|
1085
|
+
var COMPILE_CACHE = /* @__PURE__ */ new Map();
|
|
1086
|
+
function compileExpression(expression) {
|
|
1087
|
+
const cached = COMPILE_CACHE.get(expression);
|
|
1088
|
+
if (cached) return cached;
|
|
1089
|
+
const compiled = import_jexl2.default.compile(expression);
|
|
1090
|
+
const next = { ast: compiled._ast };
|
|
1091
|
+
COMPILE_CACHE.set(expression, next);
|
|
1092
|
+
return next;
|
|
1093
|
+
}
|
|
1094
|
+
function isPlainObject(value) {
|
|
1095
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1096
|
+
}
|
|
1097
|
+
function quoteSegment(value) {
|
|
1098
|
+
return `["${value.replace(/"/g, '\\"')}"]`;
|
|
1099
|
+
}
|
|
1100
|
+
function fieldSegment(field, index) {
|
|
1101
|
+
return typeof field.name === "string" && field.name.length > 0 ? `fields${quoteSegment(field.name)}` : `fields[${index}]`;
|
|
1102
|
+
}
|
|
1103
|
+
function blockSegment(block, index) {
|
|
1104
|
+
return typeof block.slug === "string" && block.slug.length > 0 ? `blocks${quoteSegment(block.slug)}` : `blocks[${index}]`;
|
|
1105
|
+
}
|
|
1106
|
+
function collectionSegment(collection, index) {
|
|
1107
|
+
return typeof collection.slug === "string" && collection.slug.length > 0 ? `collections${quoteSegment(collection.slug)}` : `collections[${index}]`;
|
|
1108
|
+
}
|
|
1109
|
+
function globalSegment(global, index) {
|
|
1110
|
+
return typeof global.slug === "string" && global.slug.length > 0 ? `globals${quoteSegment(global.slug)}` : `globals[${index}]`;
|
|
1111
|
+
}
|
|
1112
|
+
function colorize(value, code, enabled) {
|
|
1113
|
+
return enabled ? `\x1B[${code}m${value}\x1B[0m` : value;
|
|
1114
|
+
}
|
|
1115
|
+
function collectRootIdentifiers(node, roots, seen) {
|
|
1116
|
+
if (!node || typeof node !== "object" || seen.has(node)) return;
|
|
1117
|
+
seen.add(node);
|
|
1118
|
+
const candidate = node;
|
|
1119
|
+
if (candidate.type === "Identifier") {
|
|
1120
|
+
if (candidate.from) {
|
|
1121
|
+
collectRootIdentifiers(candidate.from, roots, seen);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
if (typeof candidate.value === "string" && candidate.value !== "null") {
|
|
1125
|
+
roots.add(candidate.value);
|
|
1126
|
+
}
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
if (Array.isArray(node)) {
|
|
1130
|
+
for (const entry of node) collectRootIdentifiers(entry, roots, seen);
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
for (const value of Object.values(node)) {
|
|
1134
|
+
if (Array.isArray(value)) {
|
|
1135
|
+
for (const entry of value) collectRootIdentifiers(entry, roots, seen);
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
if (value && typeof value === "object") {
|
|
1139
|
+
collectRootIdentifiers(value, roots, seen);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
function getInvalidRoots(expression, allowedRoots) {
|
|
1144
|
+
const { ast } = compileExpression(expression);
|
|
1145
|
+
const roots = /* @__PURE__ */ new Set();
|
|
1146
|
+
collectRootIdentifiers(ast, roots, /* @__PURE__ */ new Set());
|
|
1147
|
+
const allowed = new Set(allowedRoots);
|
|
1148
|
+
return [...roots].filter((root) => !allowed.has(root));
|
|
1149
|
+
}
|
|
1150
|
+
function validateDeclarativeHookExpression(expression, surface, path) {
|
|
1151
|
+
try {
|
|
1152
|
+
const invalidRoots = getInvalidRoots(expression, SURFACE_ROOTS[surface]);
|
|
1153
|
+
if (invalidRoots.length === 0) return [];
|
|
1154
|
+
return [
|
|
1155
|
+
{
|
|
1156
|
+
path,
|
|
1157
|
+
surface,
|
|
1158
|
+
expression,
|
|
1159
|
+
message: `uses unsupported context ${invalidRoots.map((root) => `"${root}"`).join(", ")}. Allowed context: ${SURFACE_ROOTS[surface].map((root) => `"${root}"`).join(", ")}`
|
|
1160
|
+
}
|
|
1161
|
+
];
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
return [
|
|
1164
|
+
{
|
|
1165
|
+
path,
|
|
1166
|
+
surface,
|
|
1167
|
+
expression,
|
|
1168
|
+
message: `has invalid syntax: ${error.message}`
|
|
1169
|
+
}
|
|
1170
|
+
];
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
function validateDeclarativeAccessExpression(expression, surface, path) {
|
|
1174
|
+
try {
|
|
1175
|
+
const invalidRoots = getInvalidRoots(expression, ACCESS_ROOTS);
|
|
1176
|
+
if (invalidRoots.length === 0) return [];
|
|
1177
|
+
return [
|
|
1178
|
+
{
|
|
1179
|
+
path,
|
|
1180
|
+
surface,
|
|
1181
|
+
expression,
|
|
1182
|
+
message: `uses unsupported context ${invalidRoots.map((root) => `"${root}"`).join(", ")}. Allowed context: ${ACCESS_ROOTS.map((root) => `"${root}"`).join(", ")}`
|
|
1183
|
+
}
|
|
1184
|
+
];
|
|
1185
|
+
} catch (error) {
|
|
1186
|
+
return [
|
|
1187
|
+
{
|
|
1188
|
+
path,
|
|
1189
|
+
surface,
|
|
1190
|
+
expression,
|
|
1191
|
+
message: `has invalid syntax: ${error.message}`
|
|
1192
|
+
}
|
|
1193
|
+
];
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
function validateAdminConditionExpression(expression, path, allowedRoots) {
|
|
1197
|
+
try {
|
|
1198
|
+
const invalidRoots = getInvalidRoots(expression, allowedRoots);
|
|
1199
|
+
if (invalidRoots.length === 0) return [];
|
|
1200
|
+
return [
|
|
1201
|
+
{
|
|
1202
|
+
path,
|
|
1203
|
+
surface: "field.admin.condition",
|
|
1204
|
+
expression,
|
|
1205
|
+
message: `uses unsupported context ${invalidRoots.map((root) => `"${root}"`).join(", ")}. Allowed context: ${allowedRoots.map((root) => `"${root}"`).join(", ")}`
|
|
1206
|
+
}
|
|
1207
|
+
];
|
|
1208
|
+
} catch (error) {
|
|
1209
|
+
return [
|
|
1210
|
+
{
|
|
1211
|
+
path,
|
|
1212
|
+
surface: "field.admin.condition",
|
|
1213
|
+
expression,
|
|
1214
|
+
message: `has invalid syntax: ${error.message}`
|
|
1215
|
+
}
|
|
1216
|
+
];
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
function validatePreviewUrlExpression(expression, path, allowedRoots) {
|
|
1220
|
+
try {
|
|
1221
|
+
const invalidRoots = getInvalidRoots(expression, allowedRoots);
|
|
1222
|
+
if (invalidRoots.length === 0) return [];
|
|
1223
|
+
return [
|
|
1224
|
+
{
|
|
1225
|
+
path,
|
|
1226
|
+
surface: "collection.admin.previewUrl",
|
|
1227
|
+
expression,
|
|
1228
|
+
message: `uses unsupported context ${invalidRoots.map((root) => `"${root}"`).join(", ")}. Allowed context: ${allowedRoots.map((root) => `"${root}"`).join(", ")}`
|
|
1229
|
+
}
|
|
1230
|
+
];
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
return [
|
|
1233
|
+
{
|
|
1234
|
+
path,
|
|
1235
|
+
surface: "collection.admin.previewUrl",
|
|
1236
|
+
expression,
|
|
1237
|
+
message: `has invalid syntax: ${error.message}`
|
|
1238
|
+
}
|
|
1239
|
+
];
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
function assertValidDeclarativeHookExpression(expression, surface, path) {
|
|
1243
|
+
const issues = validateDeclarativeHookExpression(expression, surface, path);
|
|
1244
|
+
if (issues.length === 0) return;
|
|
1245
|
+
throw new Error(`Invalid declarative hook at ${path}: ${issues[0].message}`);
|
|
1246
|
+
}
|
|
1247
|
+
function assertValidDeclarativeHookResult(result, surface, path) {
|
|
1248
|
+
if (isPlainObject(result)) return;
|
|
1249
|
+
throw new Error(`Invalid declarative hook result at ${path}: ${surface} expressions must return an object`);
|
|
1250
|
+
}
|
|
1251
|
+
function collectFieldIssues(fields, path, issues) {
|
|
1252
|
+
fields.forEach((field, index) => {
|
|
1253
|
+
const fieldPath = `${path}.${fieldSegment(field, index)}`;
|
|
1254
|
+
field.hooks?.beforeChange?.forEach((hook, hookIndex) => {
|
|
1255
|
+
if (typeof hook !== "string") return;
|
|
1256
|
+
issues.push(
|
|
1257
|
+
...validateDeclarativeHookExpression(
|
|
1258
|
+
hook,
|
|
1259
|
+
"field.beforeChange",
|
|
1260
|
+
`${fieldPath}.hooks.beforeChange[${hookIndex}]`
|
|
1261
|
+
)
|
|
1262
|
+
);
|
|
1263
|
+
});
|
|
1264
|
+
field.hooks?.afterRead?.forEach((hook, hookIndex) => {
|
|
1265
|
+
if (typeof hook !== "string") return;
|
|
1266
|
+
issues.push({
|
|
1267
|
+
path: `${fieldPath}.hooks.afterRead[${hookIndex}]`,
|
|
1268
|
+
surface: "unsupported",
|
|
1269
|
+
expression: hook,
|
|
1270
|
+
message: "declarative strings are not supported on field hooks.afterRead"
|
|
1271
|
+
});
|
|
1272
|
+
});
|
|
1273
|
+
if (typeof field.admin?.hooks?.onChange === "string") {
|
|
1274
|
+
issues.push(
|
|
1275
|
+
...validateDeclarativeHookExpression(
|
|
1276
|
+
field.admin.hooks.onChange,
|
|
1277
|
+
"field.admin.onChange",
|
|
1278
|
+
`${fieldPath}.admin.hooks.onChange`
|
|
1279
|
+
)
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
if (typeof field.admin?.hooks?.options === "string") {
|
|
1283
|
+
issues.push({
|
|
1284
|
+
path: `${fieldPath}.admin.hooks.options`,
|
|
1285
|
+
surface: "unsupported",
|
|
1286
|
+
expression: field.admin.hooks.options,
|
|
1287
|
+
message: "declarative strings are not supported on field admin.hooks.options"
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
if (Array.isArray(field.fields)) {
|
|
1291
|
+
collectFieldIssues(field.fields, fieldPath, issues);
|
|
1292
|
+
}
|
|
1293
|
+
if (Array.isArray(field.blocks)) {
|
|
1294
|
+
field.blocks.forEach((block, blockIndex) => {
|
|
1295
|
+
if (!Array.isArray(block.fields)) return;
|
|
1296
|
+
collectFieldIssues(block.fields, `${fieldPath}.${blockSegment(block, blockIndex)}`, issues);
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
function collectFieldNames(fields) {
|
|
1302
|
+
return fields.flatMap(
|
|
1303
|
+
(field) => typeof field.name === "string" && field.name.length > 0 ? [field.name] : []
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
function collectAdminConditionIssues(fields, path, rootFieldNames, issues) {
|
|
1307
|
+
const scopeFieldNames = collectFieldNames(fields);
|
|
1308
|
+
const allowedRoots = [.../* @__PURE__ */ new Set([...ADMIN_CONDITION_RESERVED_ROOTS, ...rootFieldNames, ...scopeFieldNames])];
|
|
1309
|
+
fields.forEach((field, index) => {
|
|
1310
|
+
const fieldPath = `${path}.${fieldSegment(field, index)}`;
|
|
1311
|
+
if (typeof field.admin?.condition === "string") {
|
|
1312
|
+
issues.push(
|
|
1313
|
+
...validateAdminConditionExpression(
|
|
1314
|
+
field.admin.condition,
|
|
1315
|
+
`${fieldPath}.admin.condition`,
|
|
1316
|
+
allowedRoots
|
|
1317
|
+
)
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
if (Array.isArray(field.fields)) {
|
|
1321
|
+
collectAdminConditionIssues(field.fields, fieldPath, rootFieldNames, issues);
|
|
1322
|
+
}
|
|
1323
|
+
if (Array.isArray(field.blocks)) {
|
|
1324
|
+
field.blocks.forEach((block, blockIndex) => {
|
|
1325
|
+
if (!Array.isArray(block.fields)) return;
|
|
1326
|
+
collectAdminConditionIssues(
|
|
1327
|
+
block.fields,
|
|
1328
|
+
`${fieldPath}.${blockSegment(block, blockIndex)}`,
|
|
1329
|
+
rootFieldNames,
|
|
1330
|
+
issues
|
|
1331
|
+
);
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
function collectFieldAccessIssues(fields, path, issues) {
|
|
1337
|
+
fields.forEach((field, index) => {
|
|
1338
|
+
const fieldPath = `${path}.${fieldSegment(field, index)}`;
|
|
1339
|
+
if (typeof field.access?.read === "string") {
|
|
1340
|
+
issues.push(...validateDeclarativeAccessExpression(field.access.read, "field.access", `${fieldPath}.access.read`));
|
|
1341
|
+
}
|
|
1342
|
+
if (typeof field.access?.create === "string") {
|
|
1343
|
+
issues.push(
|
|
1344
|
+
...validateDeclarativeAccessExpression(field.access.create, "field.access", `${fieldPath}.access.create`)
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
if (typeof field.access?.update === "string") {
|
|
1348
|
+
issues.push(
|
|
1349
|
+
...validateDeclarativeAccessExpression(field.access.update, "field.access", `${fieldPath}.access.update`)
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
if (Array.isArray(field.fields)) {
|
|
1353
|
+
collectFieldAccessIssues(field.fields, fieldPath, issues);
|
|
1354
|
+
}
|
|
1355
|
+
if (Array.isArray(field.blocks)) {
|
|
1356
|
+
field.blocks.forEach((block, blockIndex) => {
|
|
1357
|
+
if (!Array.isArray(block.fields)) return;
|
|
1358
|
+
collectFieldAccessIssues(
|
|
1359
|
+
block.fields,
|
|
1360
|
+
`${fieldPath}.${blockSegment(block, blockIndex)}`,
|
|
1361
|
+
issues
|
|
1362
|
+
);
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
function collectHookIssues(hooks, scope, path, issues) {
|
|
1368
|
+
if (!hooks) return;
|
|
1369
|
+
const supported = {
|
|
1370
|
+
beforeRead: `${scope}.beforeRead`,
|
|
1371
|
+
afterRead: `${scope}.afterRead`,
|
|
1372
|
+
beforeChange: `${scope}.beforeChange`
|
|
1373
|
+
};
|
|
1374
|
+
for (const [key, value] of Object.entries(hooks)) {
|
|
1375
|
+
if (!Array.isArray(value)) continue;
|
|
1376
|
+
value.forEach((hook, index) => {
|
|
1377
|
+
if (typeof hook !== "string") return;
|
|
1378
|
+
const surface = supported[key];
|
|
1379
|
+
if (!surface) {
|
|
1380
|
+
issues.push({
|
|
1381
|
+
path: `${path}.${key}[${index}]`,
|
|
1382
|
+
surface: "unsupported",
|
|
1383
|
+
expression: hook,
|
|
1384
|
+
message: `declarative strings are not supported on ${scope} hooks.${key}`
|
|
1385
|
+
});
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
issues.push(...validateDeclarativeHookExpression(hook, surface, `${path}.${key}[${index}]`));
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
function collectDeclarativeHookValidationIssues(config) {
|
|
1393
|
+
const issues = [];
|
|
1394
|
+
config.blocks?.forEach((block, blockIndex) => {
|
|
1395
|
+
if (!Array.isArray(block.fields)) return;
|
|
1396
|
+
collectFieldIssues(
|
|
1397
|
+
block.fields,
|
|
1398
|
+
blockSegment(block, blockIndex),
|
|
1399
|
+
issues
|
|
1400
|
+
);
|
|
1401
|
+
});
|
|
1402
|
+
config.collections.forEach((collection, collectionIndex) => {
|
|
1403
|
+
const collectionPath = collectionSegment(collection, collectionIndex);
|
|
1404
|
+
collectHookIssues(
|
|
1405
|
+
collection.hooks,
|
|
1406
|
+
"collection",
|
|
1407
|
+
`${collectionPath}.hooks`,
|
|
1408
|
+
issues
|
|
1409
|
+
);
|
|
1410
|
+
collectFieldIssues(collection.fields, collectionPath, issues);
|
|
1411
|
+
});
|
|
1412
|
+
config.globals?.forEach((global, globalIndex) => {
|
|
1413
|
+
const globalPath = globalSegment(global, globalIndex);
|
|
1414
|
+
collectHookIssues(
|
|
1415
|
+
global.hooks,
|
|
1416
|
+
"global",
|
|
1417
|
+
`${globalPath}.hooks`,
|
|
1418
|
+
issues
|
|
1419
|
+
);
|
|
1420
|
+
collectFieldIssues(global.fields, globalPath, issues);
|
|
1421
|
+
});
|
|
1422
|
+
return issues;
|
|
1423
|
+
}
|
|
1424
|
+
function collectDeclarativeAccessValidationIssues(config) {
|
|
1425
|
+
const issues = [];
|
|
1426
|
+
Object.entries(config.accessPolicies ?? {}).forEach(([name, value]) => {
|
|
1427
|
+
if (typeof value !== "string") return;
|
|
1428
|
+
issues.push(
|
|
1429
|
+
...validateDeclarativeAccessExpression(value, "config.accessPolicy", `accessPolicies.${name}`)
|
|
1430
|
+
);
|
|
1431
|
+
});
|
|
1432
|
+
config.blocks?.forEach((block, blockIndex) => {
|
|
1433
|
+
if (!Array.isArray(block.fields)) return;
|
|
1434
|
+
collectFieldAccessIssues(
|
|
1435
|
+
block.fields,
|
|
1436
|
+
blockSegment(block, blockIndex),
|
|
1437
|
+
issues
|
|
1438
|
+
);
|
|
1439
|
+
});
|
|
1440
|
+
config.collections.forEach((collection, collectionIndex) => {
|
|
1441
|
+
const collectionPath = collectionSegment(collection, collectionIndex);
|
|
1442
|
+
if (typeof collection.access?.read === "string") {
|
|
1443
|
+
issues.push(
|
|
1444
|
+
...validateDeclarativeAccessExpression(
|
|
1445
|
+
collection.access.read,
|
|
1446
|
+
"collection.access",
|
|
1447
|
+
`${collectionPath}.access.read`
|
|
1448
|
+
)
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
if (typeof collection.access?.create === "string") {
|
|
1452
|
+
issues.push(
|
|
1453
|
+
...validateDeclarativeAccessExpression(
|
|
1454
|
+
collection.access.create,
|
|
1455
|
+
"collection.access",
|
|
1456
|
+
`${collectionPath}.access.create`
|
|
1457
|
+
)
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
if (typeof collection.access?.update === "string") {
|
|
1461
|
+
issues.push(
|
|
1462
|
+
...validateDeclarativeAccessExpression(
|
|
1463
|
+
collection.access.update,
|
|
1464
|
+
"collection.access",
|
|
1465
|
+
`${collectionPath}.access.update`
|
|
1466
|
+
)
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
if (typeof collection.access?.delete === "string") {
|
|
1470
|
+
issues.push(
|
|
1471
|
+
...validateDeclarativeAccessExpression(
|
|
1472
|
+
collection.access.delete,
|
|
1473
|
+
"collection.access",
|
|
1474
|
+
`${collectionPath}.access.delete`
|
|
1475
|
+
)
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
if (typeof collection.access?.readAudit === "string") {
|
|
1479
|
+
issues.push(
|
|
1480
|
+
...validateDeclarativeAccessExpression(
|
|
1481
|
+
collection.access.readAudit,
|
|
1482
|
+
"collection.access",
|
|
1483
|
+
`${collectionPath}.access.readAudit`
|
|
1484
|
+
)
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
collectFieldAccessIssues(collection.fields, collectionPath, issues);
|
|
1488
|
+
});
|
|
1489
|
+
config.globals?.forEach((global, globalIndex) => {
|
|
1490
|
+
const globalPath = globalSegment(global, globalIndex);
|
|
1491
|
+
if (typeof global.access?.read === "string") {
|
|
1492
|
+
issues.push(
|
|
1493
|
+
...validateDeclarativeAccessExpression(global.access.read, "global.access", `${globalPath}.access.read`)
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1496
|
+
if (typeof global.access?.update === "string") {
|
|
1497
|
+
issues.push(
|
|
1498
|
+
...validateDeclarativeAccessExpression(
|
|
1499
|
+
global.access.update,
|
|
1500
|
+
"global.access",
|
|
1501
|
+
`${globalPath}.access.update`
|
|
1502
|
+
)
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
collectFieldAccessIssues(global.fields, globalPath, issues);
|
|
1506
|
+
});
|
|
1507
|
+
return issues;
|
|
1508
|
+
}
|
|
1509
|
+
function collectAdminConditionValidationIssues(config) {
|
|
1510
|
+
const issues = [];
|
|
1511
|
+
config.blocks?.forEach((block, blockIndex) => {
|
|
1512
|
+
if (!Array.isArray(block.fields)) return;
|
|
1513
|
+
collectAdminConditionIssues(
|
|
1514
|
+
block.fields,
|
|
1515
|
+
blockSegment(block, blockIndex),
|
|
1516
|
+
collectFieldNames(block.fields),
|
|
1517
|
+
issues
|
|
1518
|
+
);
|
|
1519
|
+
});
|
|
1520
|
+
config.collections.forEach((collection, collectionIndex) => {
|
|
1521
|
+
const collectionPath = collectionSegment(collection, collectionIndex);
|
|
1522
|
+
const rootFieldNames = collectFieldNames(collection.fields);
|
|
1523
|
+
collectAdminConditionIssues(collection.fields, collectionPath, rootFieldNames, issues);
|
|
1524
|
+
});
|
|
1525
|
+
config.globals?.forEach((global, globalIndex) => {
|
|
1526
|
+
const globalPath = globalSegment(global, globalIndex);
|
|
1527
|
+
const rootFieldNames = collectFieldNames(global.fields);
|
|
1528
|
+
collectAdminConditionIssues(global.fields, globalPath, rootFieldNames, issues);
|
|
1529
|
+
});
|
|
1530
|
+
return issues;
|
|
1531
|
+
}
|
|
1532
|
+
function collectPreviewUrlValidationIssues(config) {
|
|
1533
|
+
const issues = [];
|
|
1534
|
+
config.collections.forEach((collection, collectionIndex) => {
|
|
1535
|
+
if (typeof collection.admin?.previewUrl !== "string") return;
|
|
1536
|
+
const collectionPath = collectionSegment(collection, collectionIndex);
|
|
1537
|
+
const allowedRoots = [
|
|
1538
|
+
.../* @__PURE__ */ new Set([...PREVIEW_URL_RESERVED_ROOTS, ...collectFieldNames(collection.fields)])
|
|
1539
|
+
];
|
|
1540
|
+
issues.push(
|
|
1541
|
+
...validatePreviewUrlExpression(
|
|
1542
|
+
collection.admin.previewUrl,
|
|
1543
|
+
`${collectionPath}.admin.previewUrl`,
|
|
1544
|
+
allowedRoots
|
|
1545
|
+
)
|
|
1546
|
+
);
|
|
1547
|
+
});
|
|
1548
|
+
return issues;
|
|
1549
|
+
}
|
|
1550
|
+
function assertValidAdminConditionsInConfig(config, source = "config") {
|
|
1551
|
+
const diagnostics = collectAdminConditionValidationIssues(config).map((issue) => ({
|
|
1552
|
+
severity: "error",
|
|
1553
|
+
source: "adminCondition",
|
|
1554
|
+
path: issue.path,
|
|
1555
|
+
surface: issue.surface,
|
|
1556
|
+
message: issue.message,
|
|
1557
|
+
expression: issue.expression
|
|
1558
|
+
}));
|
|
1559
|
+
if (diagnostics.length === 0) return;
|
|
1560
|
+
throw new ConfigValidationError(source, diagnostics);
|
|
1561
|
+
}
|
|
1562
|
+
function assertValidPreviewUrlsInConfig(config, source = "config") {
|
|
1563
|
+
const diagnostics = collectPreviewUrlValidationIssues(config).map((issue) => ({
|
|
1564
|
+
severity: "error",
|
|
1565
|
+
source: "previewUrl",
|
|
1566
|
+
path: issue.path,
|
|
1567
|
+
surface: issue.surface,
|
|
1568
|
+
message: issue.message,
|
|
1569
|
+
expression: issue.expression
|
|
1570
|
+
}));
|
|
1571
|
+
if (diagnostics.length === 0) return;
|
|
1572
|
+
throw new ConfigValidationError(source, diagnostics);
|
|
1573
|
+
}
|
|
1574
|
+
function collectConfigDiagnostics(config) {
|
|
1575
|
+
const hookIssues = collectDeclarativeHookValidationIssues(config).map((issue) => ({
|
|
1576
|
+
severity: "error",
|
|
1577
|
+
source: "declarativeHook",
|
|
1578
|
+
path: issue.path,
|
|
1579
|
+
surface: issue.surface,
|
|
1580
|
+
message: issue.message,
|
|
1581
|
+
expression: issue.expression
|
|
1582
|
+
}));
|
|
1583
|
+
const accessIssues = collectDeclarativeAccessValidationIssues(config).map((issue) => ({
|
|
1584
|
+
severity: "error",
|
|
1585
|
+
source: "declarativeAccess",
|
|
1586
|
+
path: issue.path,
|
|
1587
|
+
surface: issue.surface,
|
|
1588
|
+
message: issue.message,
|
|
1589
|
+
expression: issue.expression
|
|
1590
|
+
}));
|
|
1591
|
+
const adminConditionIssues = collectAdminConditionValidationIssues(config).map((issue) => ({
|
|
1592
|
+
severity: "error",
|
|
1593
|
+
source: "adminCondition",
|
|
1594
|
+
path: issue.path,
|
|
1595
|
+
surface: issue.surface,
|
|
1596
|
+
message: issue.message,
|
|
1597
|
+
expression: issue.expression
|
|
1598
|
+
}));
|
|
1599
|
+
const previewUrlIssues = collectPreviewUrlValidationIssues(config).map((issue) => ({
|
|
1600
|
+
severity: "error",
|
|
1601
|
+
source: "previewUrl",
|
|
1602
|
+
path: issue.path,
|
|
1603
|
+
surface: issue.surface,
|
|
1604
|
+
message: issue.message,
|
|
1605
|
+
expression: issue.expression
|
|
1606
|
+
}));
|
|
1607
|
+
return [...hookIssues, ...accessIssues, ...adminConditionIssues, ...previewUrlIssues];
|
|
1608
|
+
}
|
|
1609
|
+
function formatConfigDiagnostics(source, diagnostics, options = {}) {
|
|
1610
|
+
const color = options.color ?? false;
|
|
1611
|
+
const header = `${colorize("Config validation failed", 31, color)} in ${colorize(source, 36, color)}`;
|
|
1612
|
+
const lines = diagnostics.flatMap((issue, index) => [
|
|
1613
|
+
`${colorize(`${index + 1}.`, 31, color)} ${colorize(issue.path, 1, color)}`,
|
|
1614
|
+
` ${issue.message}`,
|
|
1615
|
+
` ${colorize("surface:", 90, color)} ${issue.surface}`
|
|
1616
|
+
]);
|
|
1617
|
+
return `${header}
|
|
1618
|
+
${lines.join("\n")}`;
|
|
1619
|
+
}
|
|
1620
|
+
var ConfigValidationError = class extends Error {
|
|
1621
|
+
diagnostics;
|
|
1622
|
+
source;
|
|
1623
|
+
constructor(source, diagnostics) {
|
|
1624
|
+
super(formatConfigDiagnostics(source, diagnostics));
|
|
1625
|
+
this.name = "ConfigValidationError";
|
|
1626
|
+
this.source = source;
|
|
1627
|
+
this.diagnostics = diagnostics;
|
|
1628
|
+
}
|
|
1629
|
+
};
|
|
1630
|
+
function assertValidDeclarativeHooksInConfig(config, source = "config") {
|
|
1631
|
+
const diagnostics = collectDeclarativeHookValidationIssues(config).map((issue) => ({
|
|
1632
|
+
severity: "error",
|
|
1633
|
+
source: "declarativeHook",
|
|
1634
|
+
path: issue.path,
|
|
1635
|
+
surface: issue.surface,
|
|
1636
|
+
message: issue.message,
|
|
1637
|
+
expression: issue.expression
|
|
1638
|
+
}));
|
|
1639
|
+
if (diagnostics.length === 0) return;
|
|
1640
|
+
throw new ConfigValidationError(source, diagnostics);
|
|
1641
|
+
}
|
|
1642
|
+
function assertValidDeclarativeAccessInConfig(config, source = "config") {
|
|
1643
|
+
const diagnostics = collectDeclarativeAccessValidationIssues(config).map((issue) => ({
|
|
1644
|
+
severity: "error",
|
|
1645
|
+
source: "declarativeAccess",
|
|
1646
|
+
path: issue.path,
|
|
1647
|
+
surface: issue.surface,
|
|
1648
|
+
message: issue.message,
|
|
1649
|
+
expression: issue.expression
|
|
1650
|
+
}));
|
|
1651
|
+
if (diagnostics.length === 0) return;
|
|
1652
|
+
throw new ConfigValidationError(source, diagnostics);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1067
1655
|
// src/utils/hooks.ts
|
|
1068
1656
|
async function runCollectionHooks(hooks, args, options = {}) {
|
|
1069
1657
|
if (!hooks || hooks.length === 0) {
|
|
1070
|
-
return args.data ?? args.doc ?? void 0;
|
|
1658
|
+
return args.data ?? args.doc ?? args.query ?? void 0;
|
|
1071
1659
|
}
|
|
1072
|
-
|
|
1660
|
+
const payloadKey = args.data !== void 0 ? "data" : args.doc !== void 0 ? "doc" : "query" in args ? "query" : void 0;
|
|
1661
|
+
let currentPayload = payloadKey === void 0 ? void 0 : args[payloadKey];
|
|
1073
1662
|
for (const hook of hooks) {
|
|
1074
1663
|
try {
|
|
1075
|
-
const
|
|
1664
|
+
const hookArgs = {
|
|
1076
1665
|
...args,
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1666
|
+
...payloadKey !== void 0 ? { [payloadKey]: currentPayload } : {}
|
|
1667
|
+
};
|
|
1668
|
+
const result = typeof hook === "string" ? await (() => {
|
|
1669
|
+
if (options.surface) {
|
|
1670
|
+
assertValidDeclarativeHookExpression(
|
|
1671
|
+
hook,
|
|
1672
|
+
options.surface,
|
|
1673
|
+
options.path ?? options.surface
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
return import_jexl3.default.eval(hook, hookArgs);
|
|
1677
|
+
})() : await hook(hookArgs);
|
|
1080
1678
|
if (result !== void 0) {
|
|
1081
|
-
|
|
1679
|
+
if (typeof hook === "string" && options.surface) {
|
|
1680
|
+
assertValidDeclarativeHookResult(
|
|
1681
|
+
result,
|
|
1682
|
+
options.surface,
|
|
1683
|
+
options.path ?? options.surface
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
currentPayload = typeof hook === "string" && currentPayload && typeof currentPayload === "object" && !Array.isArray(currentPayload) && result && typeof result === "object" && !Array.isArray(result) ? {
|
|
1687
|
+
...currentPayload,
|
|
1688
|
+
...result
|
|
1689
|
+
} : result;
|
|
1082
1690
|
}
|
|
1083
1691
|
} catch (err) {
|
|
1084
1692
|
if (options.isolated) {
|
|
@@ -1093,17 +1701,17 @@ async function runCollectionHooks(hooks, args, options = {}) {
|
|
|
1093
1701
|
}
|
|
1094
1702
|
return currentPayload;
|
|
1095
1703
|
}
|
|
1096
|
-
async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
|
|
1704
|
+
async function executeFieldBeforeChange(fields, data, originalDoc, user, db, pathPrefix = "") {
|
|
1097
1705
|
if (!data || typeof data !== "object") return data;
|
|
1098
1706
|
const result = { ...data };
|
|
1099
1707
|
for (const field of fields) {
|
|
1100
1708
|
if (!field.name) continue;
|
|
1709
|
+
const fieldPath = pathPrefix ? `${pathPrefix}.${field.name}` : field.name;
|
|
1101
1710
|
const value = result[field.name];
|
|
1102
1711
|
const origValue = originalDoc?.[field.name];
|
|
1103
1712
|
let updatedValue = value;
|
|
1104
1713
|
if (field.hooks?.beforeChange) {
|
|
1105
1714
|
for (const hook of field.hooks.beforeChange) {
|
|
1106
|
-
const typedHook = hook;
|
|
1107
1715
|
const hookArgs = {
|
|
1108
1716
|
value: updatedValue,
|
|
1109
1717
|
originalDoc: originalDoc ?? void 0,
|
|
@@ -1111,7 +1719,14 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
|
|
|
1111
1719
|
user,
|
|
1112
1720
|
db
|
|
1113
1721
|
};
|
|
1114
|
-
updatedValue = await
|
|
1722
|
+
updatedValue = typeof hook === "string" ? await (() => {
|
|
1723
|
+
assertValidDeclarativeHookExpression(
|
|
1724
|
+
hook,
|
|
1725
|
+
"field.beforeChange",
|
|
1726
|
+
fieldPath
|
|
1727
|
+
);
|
|
1728
|
+
return import_jexl3.default.eval(hook, hookArgs);
|
|
1729
|
+
})() : await hook(hookArgs);
|
|
1115
1730
|
}
|
|
1116
1731
|
result[field.name] = updatedValue;
|
|
1117
1732
|
}
|
|
@@ -1122,7 +1737,8 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
|
|
|
1122
1737
|
updatedValue,
|
|
1123
1738
|
origValue,
|
|
1124
1739
|
user,
|
|
1125
|
-
db
|
|
1740
|
+
db,
|
|
1741
|
+
fieldPath
|
|
1126
1742
|
);
|
|
1127
1743
|
} else if (field.type === "array" && field.fields && Array.isArray(updatedValue)) {
|
|
1128
1744
|
const arrayResult = [];
|
|
@@ -1135,7 +1751,8 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
|
|
|
1135
1751
|
item,
|
|
1136
1752
|
origItem,
|
|
1137
1753
|
user,
|
|
1138
|
-
db
|
|
1754
|
+
db,
|
|
1755
|
+
fieldPath
|
|
1139
1756
|
)
|
|
1140
1757
|
);
|
|
1141
1758
|
}
|
|
@@ -1155,7 +1772,8 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
|
|
|
1155
1772
|
blockData,
|
|
1156
1773
|
origBlock,
|
|
1157
1774
|
user,
|
|
1158
|
-
db
|
|
1775
|
+
db,
|
|
1776
|
+
fieldPath
|
|
1159
1777
|
)
|
|
1160
1778
|
);
|
|
1161
1779
|
} else {
|
|
@@ -1285,12 +1903,12 @@ function validateUpload(file, config) {
|
|
|
1285
1903
|
}
|
|
1286
1904
|
|
|
1287
1905
|
// src/auth/jexl.ts
|
|
1288
|
-
var
|
|
1906
|
+
var import_jexl4 = __toESM(require("jexl"), 1);
|
|
1289
1907
|
async function evaluateAccess(expression, context) {
|
|
1290
1908
|
if (expression === void 0 || expression === null) return false;
|
|
1291
1909
|
if (typeof expression === "boolean") return expression;
|
|
1292
1910
|
try {
|
|
1293
|
-
const result = await
|
|
1911
|
+
const result = await import_jexl4.default.eval(expression, context);
|
|
1294
1912
|
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
1295
1913
|
return result;
|
|
1296
1914
|
}
|
|
@@ -2173,6 +2791,10 @@ var CollectionController = class {
|
|
|
2173
2791
|
query: where,
|
|
2174
2792
|
user,
|
|
2175
2793
|
db: readonlyDb
|
|
2794
|
+
},
|
|
2795
|
+
{
|
|
2796
|
+
surface: "collection.beforeRead",
|
|
2797
|
+
path: `collection:${this.collection.slug}.hooks.beforeRead`
|
|
2176
2798
|
}
|
|
2177
2799
|
);
|
|
2178
2800
|
if (beforeReadResult !== void 0) {
|
|
@@ -2455,6 +3077,9 @@ var CollectionController = class {
|
|
|
2455
3077
|
user,
|
|
2456
3078
|
operation: "create",
|
|
2457
3079
|
db: readonlyDb
|
|
3080
|
+
}, {
|
|
3081
|
+
surface: "collection.beforeChange",
|
|
3082
|
+
path: `collection:${this.collection.slug}.hooks.beforeChange`
|
|
2458
3083
|
});
|
|
2459
3084
|
const doc = this.collection.workflow ? (await createWorkflowDocument({
|
|
2460
3085
|
config,
|
|
@@ -2489,6 +3114,9 @@ var CollectionController = class {
|
|
|
2489
3114
|
req: c.req,
|
|
2490
3115
|
user,
|
|
2491
3116
|
db: readonlyDb
|
|
3117
|
+
}, {
|
|
3118
|
+
surface: "collection.afterRead",
|
|
3119
|
+
path: `collection:${this.collection.slug}.hooks.afterRead`
|
|
2492
3120
|
});
|
|
2493
3121
|
const finalDoc = await executeFieldAfterRead(
|
|
2494
3122
|
this.collection.fields,
|
|
@@ -2586,6 +3214,9 @@ var CollectionController = class {
|
|
|
2586
3214
|
user,
|
|
2587
3215
|
operation: "create",
|
|
2588
3216
|
db: readonlyDb
|
|
3217
|
+
}, {
|
|
3218
|
+
surface: "collection.beforeChange",
|
|
3219
|
+
path: `collection:${this.collection.slug}.hooks.beforeChange`
|
|
2589
3220
|
});
|
|
2590
3221
|
const doc = await db.create({
|
|
2591
3222
|
collection: this.collection.slug,
|
|
@@ -2608,6 +3239,9 @@ var CollectionController = class {
|
|
|
2608
3239
|
req: c.req,
|
|
2609
3240
|
user,
|
|
2610
3241
|
db: readonlyDb
|
|
3242
|
+
}, {
|
|
3243
|
+
surface: "collection.afterRead",
|
|
3244
|
+
path: `collection:${this.collection.slug}.hooks.afterRead`
|
|
2611
3245
|
});
|
|
2612
3246
|
const finalDoc = await executeFieldAfterRead(
|
|
2613
3247
|
this.collection.fields,
|
|
@@ -2716,6 +3350,9 @@ var CollectionController = class {
|
|
|
2716
3350
|
user,
|
|
2717
3351
|
operation: "update",
|
|
2718
3352
|
db: readonlyDb
|
|
3353
|
+
}, {
|
|
3354
|
+
surface: "collection.beforeChange",
|
|
3355
|
+
path: `collection:${this.collection.slug}.hooks.beforeChange`
|
|
2719
3356
|
});
|
|
2720
3357
|
const doc = this.collection.workflow ? (await saveWorkflowDraft({
|
|
2721
3358
|
config,
|
|
@@ -2753,6 +3390,9 @@ var CollectionController = class {
|
|
|
2753
3390
|
req: c.req,
|
|
2754
3391
|
user,
|
|
2755
3392
|
db: readonlyDb
|
|
3393
|
+
}, {
|
|
3394
|
+
surface: "collection.afterRead",
|
|
3395
|
+
path: `collection:${this.collection.slug}.hooks.afterRead`
|
|
2756
3396
|
});
|
|
2757
3397
|
const finalDoc = await executeFieldAfterRead(
|
|
2758
3398
|
this.collection.fields,
|
|
@@ -3147,6 +3787,9 @@ var GlobalController = class {
|
|
|
3147
3787
|
query: void 0,
|
|
3148
3788
|
user,
|
|
3149
3789
|
db: readonlyDb
|
|
3790
|
+
}, {
|
|
3791
|
+
surface: "global.beforeRead",
|
|
3792
|
+
path: `global:${this.global.slug}.hooks.beforeRead`
|
|
3150
3793
|
});
|
|
3151
3794
|
let data = await db.getGlobal({ slug: this.global.slug });
|
|
3152
3795
|
const isEmpty = !data || isFunctionallyEmpty(data);
|
|
@@ -3184,6 +3827,10 @@ var GlobalController = class {
|
|
|
3184
3827
|
req: c.req,
|
|
3185
3828
|
user,
|
|
3186
3829
|
db: readonlyDb
|
|
3830
|
+
},
|
|
3831
|
+
{
|
|
3832
|
+
surface: "global.afterRead",
|
|
3833
|
+
path: `global:${this.global.slug}.hooks.afterRead`
|
|
3187
3834
|
}
|
|
3188
3835
|
);
|
|
3189
3836
|
const docWithFieldHooks = await executeFieldAfterRead(
|
|
@@ -3266,6 +3913,9 @@ var GlobalController = class {
|
|
|
3266
3913
|
user,
|
|
3267
3914
|
operation: "update",
|
|
3268
3915
|
db: readonlyDb
|
|
3916
|
+
}, {
|
|
3917
|
+
surface: "global.beforeChange",
|
|
3918
|
+
path: `global:${this.global.slug}.hooks.beforeChange`
|
|
3269
3919
|
});
|
|
3270
3920
|
const updated = await db.updateGlobal({ slug: this.global.slug, data });
|
|
3271
3921
|
await runCollectionHooks(
|
|
@@ -3285,6 +3935,9 @@ var GlobalController = class {
|
|
|
3285
3935
|
req: c.req,
|
|
3286
3936
|
user,
|
|
3287
3937
|
db: readonlyDb
|
|
3938
|
+
}, {
|
|
3939
|
+
surface: "global.afterRead",
|
|
3940
|
+
path: `global:${this.global.slug}.hooks.afterRead`
|
|
3288
3941
|
});
|
|
3289
3942
|
const finalDoc = await executeFieldAfterRead(
|
|
3290
3943
|
this.global.fields,
|
|
@@ -3438,6 +4091,12 @@ function mergeDynamicConfig(baseConfig, dynamic) {
|
|
|
3438
4091
|
return {
|
|
3439
4092
|
...baseConfig,
|
|
3440
4093
|
...normalizedDynamic.blocks ? { blocks: normalizedDynamic.blocks } : {},
|
|
4094
|
+
...dynamic.accessPolicies ? {
|
|
4095
|
+
accessPolicies: {
|
|
4096
|
+
...baseConfig.accessPolicies ?? {},
|
|
4097
|
+
...dynamic.accessPolicies
|
|
4098
|
+
}
|
|
4099
|
+
} : {},
|
|
3441
4100
|
...dynamic.admin ? { admin: { ...baseConfig.admin, ...dynamic.admin } } : {},
|
|
3442
4101
|
...dynamic.adminAuth ? { adminAuth: { ...baseConfig.adminAuth, ...dynamic.adminAuth } } : {},
|
|
3443
4102
|
collections: normalizedDynamic.collections ? [...baseConfig.collections, ...normalizedDynamic.collections] : baseConfig.collections,
|
|
@@ -3611,6 +4270,9 @@ var MediaController = class {
|
|
|
3611
4270
|
}
|
|
3612
4271
|
};
|
|
3613
4272
|
|
|
4273
|
+
// src/controllers/auth.controller.ts
|
|
4274
|
+
var import_node_crypto2 = require("crypto");
|
|
4275
|
+
|
|
3614
4276
|
// src/auth/lockout.ts
|
|
3615
4277
|
var DEFAULT_MAX_LOGIN_ATTEMPTS = 5;
|
|
3616
4278
|
var DEFAULT_LOCK_TIME_MS = 10 * 60 * 1e3;
|
|
@@ -3655,6 +4317,7 @@ var emailTokens = {
|
|
|
3655
4317
|
border: "#dde0d7",
|
|
3656
4318
|
accent: "#b6ff2e",
|
|
3657
4319
|
code: "#f1f3ec",
|
|
4320
|
+
codeBorder: "#e2e5dc",
|
|
3658
4321
|
dangerSurface: "#fff2f0",
|
|
3659
4322
|
dangerBorder: "#ffc9c2",
|
|
3660
4323
|
dangerText: "#9f251b"
|
|
@@ -3704,7 +4367,13 @@ function table(content, style = "width:100%") {
|
|
|
3704
4367
|
}
|
|
3705
4368
|
function detailBox(content, monospace = false) {
|
|
3706
4369
|
const font = monospace ? emailTokens.mono : emailTokens.font;
|
|
3707
|
-
return table(
|
|
4370
|
+
return table(
|
|
4371
|
+
row(
|
|
4372
|
+
escapeHtml(content),
|
|
4373
|
+
`padding:14px 16px;font-family:${font};font-size:13px;line-height:1.5;font-weight:${monospace ? "400" : "700"};color:${emailTokens.colors.text};word-break:break-all`
|
|
4374
|
+
),
|
|
4375
|
+
`width:100%;background:${emailTokens.colors.code};border:1px solid ${emailTokens.colors.codeBorder};border-radius:${emailTokens.radius.control}`
|
|
4376
|
+
);
|
|
3708
4377
|
}
|
|
3709
4378
|
function ctaButton(label, href) {
|
|
3710
4379
|
const safeHref = safeHttpUrl(href);
|
|
@@ -3714,22 +4383,46 @@ function ctaButton(label, href) {
|
|
|
3714
4383
|
"padding:8px 0 24px"
|
|
3715
4384
|
), "width:auto");
|
|
3716
4385
|
}
|
|
4386
|
+
function safeLinkDetailBox(href) {
|
|
4387
|
+
const safeHref = safeHttpUrl(href);
|
|
4388
|
+
if (!safeHref) return "";
|
|
4389
|
+
return detailBox(safeHref, true);
|
|
4390
|
+
}
|
|
3717
4391
|
function alertBox(content) {
|
|
3718
4392
|
return table(row(escapeHtml(content), `padding:13px 16px;font-family:${emailTokens.font};font-size:13px;line-height:1.5;color:${emailTokens.colors.dangerText}`), `width:100%;background:${emailTokens.colors.dangerSurface};border:1px solid ${emailTokens.colors.dangerBorder};border-radius:${emailTokens.radius.control}`);
|
|
3719
4393
|
}
|
|
3720
4394
|
function layout({ preheader, title, content, footer }) {
|
|
3721
4395
|
return `<!doctype html>
|
|
3722
|
-
<html lang="en">
|
|
3723
|
-
<head
|
|
3724
|
-
|
|
3725
|
-
<
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
4396
|
+
<html lang="en" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
|
|
4397
|
+
<head>
|
|
4398
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
4399
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4400
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
4401
|
+
<meta name="x-apple-disable-message-reformatting">
|
|
4402
|
+
<meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no">
|
|
4403
|
+
<meta name="color-scheme" content="light">
|
|
4404
|
+
<meta name="supported-color-schemes" content="light">
|
|
4405
|
+
<title>${escapeHtml(title)}</title>
|
|
4406
|
+
</head>
|
|
4407
|
+
<body style="margin:0;padding:0;background-color:${emailTokens.colors.canvas};word-spacing:normal;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%">
|
|
4408
|
+
<div style="display:none;max-height:0;max-width:0;overflow:hidden;opacity:0;mso-hide:all;color:transparent;font-size:1px;line-height:1px">
|
|
4409
|
+
${escapeHtml(preheader)}​͏‌   ͏‌
|
|
4410
|
+
</div>
|
|
4411
|
+
<center lang="en" style="width:100%;background-color:${emailTokens.colors.canvas}">
|
|
4412
|
+
${table(
|
|
4413
|
+
row(
|
|
4414
|
+
table(
|
|
4415
|
+
row(" ", `height:5px;background:${emailTokens.colors.accent};font-size:0;line-height:0`) + row(`${sectionLabel("Dyrected")}${heading(title)}`, "padding:30px 32px 24px") + row(content, "padding:0 32px 32px") + row(
|
|
4416
|
+
`${divider()}${paragraph(footer, "20px 0 6px")}${paragraph("Privacy: this message contains account-related information; please avoid forwarding it.", "0")}`,
|
|
4417
|
+
"padding:0 32px 28px"
|
|
4418
|
+
),
|
|
4419
|
+
`width:100%;max-width:${emailTokens.width};background:${emailTokens.colors.surface};border:1px solid ${emailTokens.colors.border};border-radius:${emailTokens.radius.card};overflow:hidden`
|
|
4420
|
+
),
|
|
4421
|
+
"padding:32px 12px"
|
|
3730
4422
|
),
|
|
3731
|
-
|
|
3732
|
-
)
|
|
4423
|
+
`width:100%;background:${emailTokens.colors.canvas}`
|
|
4424
|
+
)}
|
|
4425
|
+
</center>
|
|
3733
4426
|
</body>
|
|
3734
4427
|
</html>`;
|
|
3735
4428
|
}
|
|
@@ -3815,12 +4508,14 @@ function buildWelcomeEmail(config, args) {
|
|
|
3815
4508
|
}
|
|
3816
4509
|
function buildInviteEmail(config, args) {
|
|
3817
4510
|
const custom = config.email?.templates?.invite?.(args);
|
|
4511
|
+
const inviteLink = args.url;
|
|
4512
|
+
const inviteLinkBlock = inviteLink ? safeLinkDetailBox(inviteLink) : "";
|
|
3818
4513
|
return {
|
|
3819
4514
|
subject: custom?.subject ?? "You've been invited",
|
|
3820
4515
|
html: custom?.html ?? layout({
|
|
3821
|
-
preheader: "You've been invited to join a Dyrected
|
|
4516
|
+
preheader: "You've been invited to join a Dyrected admin area.",
|
|
3822
4517
|
title: "You've been invited",
|
|
3823
|
-
content: `${args.invitedByEmail ? paragraph(`You were invited by ${args.invitedByEmail}.`) : ""}${paragraph("Use the token below to accept your invitation. It expires in 7 days.")}${sectionLabel("Invitation token")}${detailBox(args.token, true)}`,
|
|
4518
|
+
content: inviteLink ? `${args.invitedByEmail ? paragraph(`You were invited by ${args.invitedByEmail}.`) : ""}${paragraph("Use the invitation link below to create your account. The link expires in 7 days.")}${ctaButton("Accept invitation", inviteLink)}${sectionLabel("Invitation link")}${inviteLinkBlock}${paragraph("If the button does not work, copy and paste the link above into your browser.", "12px 0 0")}` : `${args.invitedByEmail ? paragraph(`You were invited by ${args.invitedByEmail}.`) : ""}${paragraph("Use the invitation token below to accept your invitation. It expires in 7 days.")}${sectionLabel("Invitation token")}${detailBox(args.token, true)}`,
|
|
3824
4519
|
footer: "If you weren't expecting this invitation, you can safely ignore this email."
|
|
3825
4520
|
})
|
|
3826
4521
|
};
|
|
@@ -3828,12 +4523,13 @@ function buildInviteEmail(config, args) {
|
|
|
3828
4523
|
function buildResetPasswordEmail(config, args) {
|
|
3829
4524
|
const custom = config.email?.templates?.resetPassword?.(args);
|
|
3830
4525
|
const resetLink = args.url;
|
|
4526
|
+
const resetLinkBlock = resetLink ? safeLinkDetailBox(resetLink) : "";
|
|
3831
4527
|
return {
|
|
3832
4528
|
subject: custom?.subject ?? "Reset your password",
|
|
3833
4529
|
html: custom?.html ?? layout({
|
|
3834
4530
|
preheader: "Reset your Dyrected password.",
|
|
3835
4531
|
title: "Reset your password",
|
|
3836
|
-
content: `${paragraph("We received a request to reset your password. The reset link
|
|
4532
|
+
content: resetLink ? `${paragraph("We received a request to reset your password. The reset link expires in 1 hour.")}${ctaButton("Reset password", resetLink)}${sectionLabel("Reset link")}${resetLinkBlock}${paragraph("If the button does not work, copy and paste the link above into your browser.", "12px 0 0")}` : `${paragraph("We received a request to reset your password. The reset token expires in 1 hour.")}${sectionLabel("Reset token")}${detailBox(args.token, true)}`,
|
|
3837
4533
|
footer: "If you didn't request a password reset, you can safely ignore this email."
|
|
3838
4534
|
})
|
|
3839
4535
|
};
|
|
@@ -3866,6 +4562,36 @@ var AuthController = class {
|
|
|
3866
4562
|
} = user;
|
|
3867
4563
|
return safeUser;
|
|
3868
4564
|
}
|
|
4565
|
+
hasField(name) {
|
|
4566
|
+
return (this.collection.fields || []).some((field) => field.name === name);
|
|
4567
|
+
}
|
|
4568
|
+
async buildPendingInviteData(email, extraFields = {}) {
|
|
4569
|
+
const {
|
|
4570
|
+
id: _id,
|
|
4571
|
+
password: _password,
|
|
4572
|
+
email: _email,
|
|
4573
|
+
...safeExtraFields
|
|
4574
|
+
} = extraFields;
|
|
4575
|
+
const data = {
|
|
4576
|
+
...safeExtraFields,
|
|
4577
|
+
email,
|
|
4578
|
+
password: await hashPassword((0, import_node_crypto2.randomBytes)(32).toString("hex"))
|
|
4579
|
+
};
|
|
4580
|
+
if (this.hasField("status")) {
|
|
4581
|
+
data.status = "pending";
|
|
4582
|
+
}
|
|
4583
|
+
return data;
|
|
4584
|
+
}
|
|
4585
|
+
buildAcceptedInviteData(hashedPassword, extraFields) {
|
|
4586
|
+
const data = {
|
|
4587
|
+
...extraFields,
|
|
4588
|
+
password: hashedPassword
|
|
4589
|
+
};
|
|
4590
|
+
if (this.hasField("status")) {
|
|
4591
|
+
data.status = "active";
|
|
4592
|
+
}
|
|
4593
|
+
return data;
|
|
4594
|
+
}
|
|
3869
4595
|
clearLockoutState(c, userId) {
|
|
3870
4596
|
const db = c.get("config").db;
|
|
3871
4597
|
if (!db) return Promise.resolve(null);
|
|
@@ -4000,6 +4726,12 @@ var AuthController = class {
|
|
|
4000
4726
|
401
|
|
4001
4727
|
);
|
|
4002
4728
|
}
|
|
4729
|
+
if (user.status === "pending") {
|
|
4730
|
+
return c.json(
|
|
4731
|
+
{ error: true, message: "This invitation has not been accepted yet." },
|
|
4732
|
+
403
|
|
4733
|
+
);
|
|
4734
|
+
}
|
|
4003
4735
|
const lockedUntilMs = getLockedUntilMs(user.lockedUntil);
|
|
4004
4736
|
if (lockedUntilMs !== null && lockedUntilMs > Date.now()) {
|
|
4005
4737
|
const retryAfterSeconds = Math.max(
|
|
@@ -4264,6 +4996,7 @@ var AuthController = class {
|
|
|
4264
4996
|
// ---------------------------------------------------------------------------
|
|
4265
4997
|
// POST /invite
|
|
4266
4998
|
// Requires auth. Issues a signed invite token and emails it to the invitee.
|
|
4999
|
+
// If inviteUrl is provided, the email uses a clickable acceptance URL.
|
|
4267
5000
|
// ---------------------------------------------------------------------------
|
|
4268
5001
|
async invite(c) {
|
|
4269
5002
|
const config = c.get("config");
|
|
@@ -4277,17 +5010,32 @@ var AuthController = class {
|
|
|
4277
5010
|
if (!body?.email) {
|
|
4278
5011
|
return c.json({ error: true, message: "email is required." }, 400);
|
|
4279
5012
|
}
|
|
5013
|
+
const inviteData = body?.data && typeof body.data === "object" && !Array.isArray(body.data) ? body.data : {};
|
|
4280
5014
|
const existing = await db.find({
|
|
4281
5015
|
collection: this.collection.slug,
|
|
4282
5016
|
where: { email: body.email },
|
|
4283
5017
|
limit: 1
|
|
4284
5018
|
});
|
|
4285
|
-
|
|
5019
|
+
const existingUser = existing.docs[0];
|
|
5020
|
+
const existingIsPending = existingUser?.status === "pending";
|
|
5021
|
+
if (existingUser && !existingIsPending) {
|
|
4286
5022
|
return c.json(
|
|
4287
5023
|
{ error: true, message: "An account with that email already exists." },
|
|
4288
5024
|
409
|
|
4289
5025
|
);
|
|
4290
5026
|
}
|
|
5027
|
+
if (!existingUser) {
|
|
5028
|
+
await db.create({
|
|
5029
|
+
collection: this.collection.slug,
|
|
5030
|
+
data: await this.buildPendingInviteData(body.email, inviteData)
|
|
5031
|
+
});
|
|
5032
|
+
} else if (Object.keys(inviteData).length > 0) {
|
|
5033
|
+
await db.update({
|
|
5034
|
+
collection: this.collection.slug,
|
|
5035
|
+
id: String(existingUser.id),
|
|
5036
|
+
data: await this.buildPendingInviteData(body.email, inviteData)
|
|
5037
|
+
});
|
|
5038
|
+
}
|
|
4291
5039
|
const inviteToken = await signCollectionToken(
|
|
4292
5040
|
{
|
|
4293
5041
|
sub: body.email,
|
|
@@ -4297,10 +5045,13 @@ var AuthController = class {
|
|
|
4297
5045
|
},
|
|
4298
5046
|
"7d"
|
|
4299
5047
|
);
|
|
5048
|
+
const inviteUrl = body?.inviteUrl;
|
|
5049
|
+
const url = inviteUrl ? `${inviteUrl}${inviteUrl.includes("?") ? "&" : "?"}inviteToken=${encodeURIComponent(inviteToken)}` : void 0;
|
|
4300
5050
|
try {
|
|
4301
5051
|
const { subject, html } = buildInviteEmail(config, {
|
|
4302
5052
|
token: inviteToken,
|
|
4303
|
-
invitedByEmail: requestUser.email
|
|
5053
|
+
invitedByEmail: requestUser.email,
|
|
5054
|
+
url
|
|
4304
5055
|
});
|
|
4305
5056
|
await sendEmail(config, { to: body.email, subject, html });
|
|
4306
5057
|
} catch (err) {
|
|
@@ -4310,7 +5061,12 @@ var AuthController = class {
|
|
|
4310
5061
|
email: body.email
|
|
4311
5062
|
});
|
|
4312
5063
|
}
|
|
4313
|
-
return c.json({
|
|
5064
|
+
return c.json({
|
|
5065
|
+
success: true,
|
|
5066
|
+
message: `Invite sent to ${body.email}.`,
|
|
5067
|
+
token: inviteToken,
|
|
5068
|
+
inviteUrl: url
|
|
5069
|
+
});
|
|
4314
5070
|
}
|
|
4315
5071
|
// ---------------------------------------------------------------------------
|
|
4316
5072
|
// POST /accept-invite
|
|
@@ -4349,7 +5105,9 @@ var AuthController = class {
|
|
|
4349
5105
|
where: { email: inviteeEmail },
|
|
4350
5106
|
limit: 1
|
|
4351
5107
|
});
|
|
4352
|
-
|
|
5108
|
+
const existingUser = existing.docs[0];
|
|
5109
|
+
const existingIsPending = existingUser?.status === "pending";
|
|
5110
|
+
if (existingUser && !existingIsPending) {
|
|
4353
5111
|
return c.json(
|
|
4354
5112
|
{ error: true, message: "An account with that email already exists." },
|
|
4355
5113
|
409
|
|
@@ -4357,9 +5115,17 @@ var AuthController = class {
|
|
|
4357
5115
|
}
|
|
4358
5116
|
const { token: _t, password: _p, ...extraFields } = body;
|
|
4359
5117
|
const hashedPassword = await hashPassword(body.password);
|
|
4360
|
-
const user = await db.
|
|
5118
|
+
const user = existingUser ? await db.update({
|
|
5119
|
+
collection: this.collection.slug,
|
|
5120
|
+
id: String(existingUser.id),
|
|
5121
|
+
data: this.buildAcceptedInviteData(hashedPassword, extraFields)
|
|
5122
|
+
}) : await db.create({
|
|
4361
5123
|
collection: this.collection.slug,
|
|
4362
|
-
data: {
|
|
5124
|
+
data: {
|
|
5125
|
+
...extraFields,
|
|
5126
|
+
email: inviteeEmail,
|
|
5127
|
+
...this.buildAcceptedInviteData(hashedPassword, {})
|
|
5128
|
+
}
|
|
4363
5129
|
});
|
|
4364
5130
|
const sessionToken = await issueAuthSessionToken({
|
|
4365
5131
|
config,
|
|
@@ -4385,7 +5151,7 @@ var AuthController = class {
|
|
|
4385
5151
|
};
|
|
4386
5152
|
|
|
4387
5153
|
// src/controllers/admin-auth.controller.ts
|
|
4388
|
-
var
|
|
5154
|
+
var import_node_crypto3 = require("crypto");
|
|
4389
5155
|
var import_node_util3 = require("util");
|
|
4390
5156
|
var import_jose2 = require("jose");
|
|
4391
5157
|
var AdminAuthController = class {
|
|
@@ -4594,7 +5360,7 @@ var AdminAuthController = class {
|
|
|
4594
5360
|
access.roles,
|
|
4595
5361
|
access.data
|
|
4596
5362
|
),
|
|
4597
|
-
password: await hashPassword((0,
|
|
5363
|
+
password: await hashPassword((0, import_node_crypto3.randomBytes)(32).toString("hex"))
|
|
4598
5364
|
}
|
|
4599
5365
|
});
|
|
4600
5366
|
} else if (user) {
|
|
@@ -4680,7 +5446,7 @@ var AdminAuthController = class {
|
|
|
4680
5446
|
}
|
|
4681
5447
|
async buildOidcStartUrl(provider, returnTo, siteId, origin) {
|
|
4682
5448
|
const discovery = await this.getOidcDiscovery(provider);
|
|
4683
|
-
const nonce = (0,
|
|
5449
|
+
const nonce = (0, import_node_crypto3.randomBytes)(16).toString("hex");
|
|
4684
5450
|
const state = await this.signState({
|
|
4685
5451
|
providerId: provider.id,
|
|
4686
5452
|
returnTo,
|
|
@@ -6247,6 +7013,7 @@ function getSwaggerHtml(specUrl) {
|
|
|
6247
7013
|
}
|
|
6248
7014
|
|
|
6249
7015
|
// src/router.ts
|
|
7016
|
+
var SERIALIZED_ADMIN_HOOK_PREFIX = "__dyrected_fn__:";
|
|
6250
7017
|
function accessGate(config, target, action) {
|
|
6251
7018
|
return async (c, next) => {
|
|
6252
7019
|
const user = c.get("user");
|
|
@@ -6273,7 +7040,7 @@ function serializeFieldForApi(f) {
|
|
|
6273
7040
|
if (serialized.admin?.hooks) {
|
|
6274
7041
|
const hooks = { ...serialized.admin.hooks };
|
|
6275
7042
|
if (typeof hooks.onChange === "function") {
|
|
6276
|
-
hooks.onChange = hooks.onChange.toString()
|
|
7043
|
+
hooks.onChange = `${SERIALIZED_ADMIN_HOOK_PREFIX}${hooks.onChange.toString()}`;
|
|
6277
7044
|
}
|
|
6278
7045
|
if (typeof hooks.options === "function") {
|
|
6279
7046
|
hooks.options = hooks.options.toString();
|
|
@@ -6307,6 +7074,10 @@ function registerRoutes(app, config) {
|
|
|
6307
7074
|
app.get("/api/schemas", optionalAuth(config), async (c) => {
|
|
6308
7075
|
const siteId = c.req.header("X-Site-Id");
|
|
6309
7076
|
const requestConfig = siteId && config.onSchemaFetch ? mergeDynamicConfig(config, await config.onSchemaFetch(siteId)) : config;
|
|
7077
|
+
assertValidDeclarativeHooksInConfig(requestConfig, "/api/schemas");
|
|
7078
|
+
assertValidDeclarativeAccessInConfig(requestConfig, "/api/schemas");
|
|
7079
|
+
assertValidAdminConditionsInConfig(requestConfig, "/api/schemas");
|
|
7080
|
+
assertValidPreviewUrlsInConfig(requestConfig, "/api/schemas");
|
|
6310
7081
|
const collections = [...requestConfig.collections];
|
|
6311
7082
|
const globals = [...requestConfig.globals];
|
|
6312
7083
|
const user = c.get("user");
|
|
@@ -6421,7 +7192,18 @@ function registerRoutes(app, config) {
|
|
|
6421
7192
|
globals: filteredGlobals,
|
|
6422
7193
|
admin: requestConfig.admin || {},
|
|
6423
7194
|
adminAuth: getPublicAdminAuthConfig(requestConfig.adminAuth, collections),
|
|
6424
|
-
hasStorage: !!requestConfig.storage
|
|
7195
|
+
hasStorage: !!requestConfig.storage,
|
|
7196
|
+
configDiagnostics: collectConfigDiagnostics(requestConfig),
|
|
7197
|
+
adminHealth: {
|
|
7198
|
+
emailConfigured: !!requestConfig.email,
|
|
7199
|
+
secureAuthSecretConfigured: !!process.env.DYRECTED_JWT_SECRET,
|
|
7200
|
+
authCollectionConfigured: requestConfig.collections.some(
|
|
7201
|
+
(collection) => !!collection.auth
|
|
7202
|
+
),
|
|
7203
|
+
uploadCollectionConfigured: requestConfig.collections.some(
|
|
7204
|
+
(collection) => !!collection.upload
|
|
7205
|
+
)
|
|
7206
|
+
}
|
|
6425
7207
|
});
|
|
6426
7208
|
});
|
|
6427
7209
|
app.get(
|
|
@@ -7116,6 +7898,10 @@ var AUTH_SESSIONS_COLLECTION_CONFIG = {
|
|
|
7116
7898
|
};
|
|
7117
7899
|
function normalizeConfig(config) {
|
|
7118
7900
|
const schemaAwareConfig = normalizeSchemaFragment(config);
|
|
7901
|
+
assertValidDeclarativeHooksInConfig(schemaAwareConfig, "config");
|
|
7902
|
+
assertValidDeclarativeAccessInConfig(schemaAwareConfig, "config");
|
|
7903
|
+
assertValidAdminConditionsInConfig(schemaAwareConfig, "config");
|
|
7904
|
+
assertValidPreviewUrlsInConfig(schemaAwareConfig, "config");
|
|
7119
7905
|
const collections = schemaAwareConfig?.collections || [];
|
|
7120
7906
|
const globals = schemaAwareConfig?.globals || [];
|
|
7121
7907
|
const needsAudit = collections.some((col) => col.audit);
|
|
@@ -7178,6 +7964,27 @@ function normalizeConfig(config) {
|
|
|
7178
7964
|
}
|
|
7179
7965
|
];
|
|
7180
7966
|
}
|
|
7967
|
+
if (!existingFieldNames.has("status")) {
|
|
7968
|
+
fields = [
|
|
7969
|
+
...fields,
|
|
7970
|
+
{
|
|
7971
|
+
name: "status",
|
|
7972
|
+
type: "select",
|
|
7973
|
+
label: "Status",
|
|
7974
|
+
defaultValue: "active",
|
|
7975
|
+
options: [
|
|
7976
|
+
{ value: "active", label: "Active" },
|
|
7977
|
+
{ value: "pending", label: "Pending" }
|
|
7978
|
+
],
|
|
7979
|
+
access: {
|
|
7980
|
+
update: "user.roles && 'admin' in user.roles && user.id != id"
|
|
7981
|
+
},
|
|
7982
|
+
admin: {
|
|
7983
|
+
condition: `!(data.roles && "admin" in data.roles)`
|
|
7984
|
+
}
|
|
7985
|
+
}
|
|
7986
|
+
];
|
|
7987
|
+
}
|
|
7181
7988
|
fields = fields.map((field) => {
|
|
7182
7989
|
if (field.name === "email") {
|
|
7183
7990
|
return {
|
|
@@ -7217,6 +8024,15 @@ function normalizeConfig(config) {
|
|
|
7217
8024
|
}
|
|
7218
8025
|
};
|
|
7219
8026
|
}
|
|
8027
|
+
if (field.name === "status") {
|
|
8028
|
+
return {
|
|
8029
|
+
...field,
|
|
8030
|
+
access: {
|
|
8031
|
+
...field.access || {},
|
|
8032
|
+
update: "user.roles && 'admin' in user.roles && user.id != id"
|
|
8033
|
+
}
|
|
8034
|
+
};
|
|
8035
|
+
}
|
|
7220
8036
|
return field;
|
|
7221
8037
|
});
|
|
7222
8038
|
}
|
|
@@ -7254,9 +8070,7 @@ function normalizeConfig(config) {
|
|
|
7254
8070
|
}
|
|
7255
8071
|
}
|
|
7256
8072
|
const updatedFieldNames = new Set(fields.map((f) => f.name));
|
|
7257
|
-
const fieldsToInject = SYSTEM_FIELDS.filter(
|
|
7258
|
-
(f) => !updatedFieldNames.has(f.name)
|
|
7259
|
-
);
|
|
8073
|
+
const fieldsToInject = SYSTEM_FIELDS.filter((f) => !updatedFieldNames.has(f.name));
|
|
7260
8074
|
const workflow = col.workflow || (col.drafts ? simplePublishingWorkflow() : void 0);
|
|
7261
8075
|
return {
|
|
7262
8076
|
...col,
|
|
@@ -7264,20 +8078,13 @@ function normalizeConfig(config) {
|
|
|
7264
8078
|
fields: [...fields, ...fieldsToInject]
|
|
7265
8079
|
};
|
|
7266
8080
|
});
|
|
7267
|
-
const hasAuditCollection = normalizedCollections.some(
|
|
7268
|
-
(col) => col.slug === AUDIT_COLLECTION_SLUG
|
|
7269
|
-
);
|
|
8081
|
+
const hasAuditCollection = normalizedCollections.some((col) => col.slug === AUDIT_COLLECTION_SLUG);
|
|
7270
8082
|
const systemCollections = [];
|
|
7271
|
-
if (needsAudit && !hasAuditCollection)
|
|
7272
|
-
|
|
7273
|
-
if (needsWorkflow && !normalizedCollections.some(
|
|
7274
|
-
(col) => col.slug === WORKFLOW_HISTORY_COLLECTION
|
|
7275
|
-
)) {
|
|
8083
|
+
if (needsAudit && !hasAuditCollection) systemCollections.push(AUDIT_COLLECTION);
|
|
8084
|
+
if (needsWorkflow && !normalizedCollections.some((col) => col.slug === WORKFLOW_HISTORY_COLLECTION)) {
|
|
7276
8085
|
systemCollections.push(WORKFLOW_HISTORY_COLLECTION_CONFIG);
|
|
7277
8086
|
}
|
|
7278
|
-
if (needsWorkflow && !normalizedCollections.some(
|
|
7279
|
-
(col) => col.slug === LIFECYCLE_EVENTS_COLLECTION
|
|
7280
|
-
)) {
|
|
8087
|
+
if (needsWorkflow && !normalizedCollections.some((col) => col.slug === LIFECYCLE_EVENTS_COLLECTION)) {
|
|
7281
8088
|
systemCollections.push(LIFECYCLE_EVENTS_COLLECTION_CONFIG);
|
|
7282
8089
|
}
|
|
7283
8090
|
if (needsAuthSessions && !normalizedCollections.some((col) => col.slug === AUTH_SESSIONS_COLLECTION)) {
|