@beechcms/core 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/antivirus/noop-antivirus.provider.d.ts.map +1 -1
- package/dist/antivirus/virustotal-antivirus.provider.d.ts.map +1 -1
- package/dist/automations-grammar-words.d.ts +2 -0
- package/dist/automations-grammar-words.d.ts.map +1 -0
- package/dist/automations-grammar-words.js +8 -0
- package/dist/automations.repository.interface.d.ts +19 -0
- package/dist/automations.repository.interface.d.ts.map +1 -0
- package/dist/automations.repository.interface.js +3 -0
- package/dist/automations.runner.interface.d.ts +10 -0
- package/dist/automations.runner.interface.d.ts.map +1 -0
- package/dist/automations.runner.interface.js +3 -0
- package/dist/automations.runner.stub.d.ts +5 -0
- package/dist/automations.runner.stub.d.ts.map +1 -0
- package/dist/automations.runner.stub.js +7 -0
- package/dist/automations.types.d.ts +75 -0
- package/dist/automations.types.d.ts.map +1 -0
- package/dist/automations.types.js +3 -0
- package/dist/clock.d.ts +25 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +11 -0
- package/dist/common/canonical-json.d.ts +30 -0
- package/dist/common/canonical-json.d.ts.map +1 -0
- package/dist/common/canonical-json.js +89 -0
- package/dist/content/content.repository.d.ts +2 -2
- package/dist/content/content.repository.d.ts.map +1 -1
- package/dist/content/deletion-ledger.d.ts +39 -0
- package/dist/content/deletion-ledger.d.ts.map +1 -0
- package/dist/content/deletion-ledger.js +3 -0
- package/dist/content-scan.repository.d.ts +9 -0
- package/dist/content-scan.repository.d.ts.map +1 -0
- package/dist/content-scan.repository.js +3 -0
- package/dist/content.repository.d.ts +247 -0
- package/dist/content.repository.d.ts.map +1 -0
- package/dist/content.repository.js +59 -0
- package/dist/dashboard-layout.d.ts +154 -0
- package/dist/dashboard-layout.d.ts.map +1 -0
- package/dist/dashboard-layout.js +186 -0
- package/dist/dashboard-layout.repository.d.ts +18 -0
- package/dist/dashboard-layout.repository.d.ts.map +1 -0
- package/dist/dashboard-layout.repository.js +3 -0
- package/dist/dashboard-permissions.d.ts +3 -0
- package/dist/dashboard-permissions.d.ts.map +1 -0
- package/dist/dashboard-permissions.js +11 -0
- package/dist/dashboard-scopes.d.ts +12 -0
- package/dist/dashboard-scopes.d.ts.map +1 -0
- package/dist/dashboard-scopes.js +30 -0
- package/dist/define-seed.d.ts +3 -0
- package/dist/define-seed.d.ts.map +1 -0
- package/dist/define-seed.js +5 -0
- package/dist/demo-data.repository.d.ts +5 -0
- package/dist/demo-data.repository.d.ts.map +1 -0
- package/dist/demo-data.repository.js +3 -0
- package/dist/email.provider.d.ts +32 -0
- package/dist/email.provider.d.ts.map +1 -0
- package/dist/email.provider.js +4 -0
- package/dist/engine/ddl/ddl.d.ts +216 -0
- package/dist/engine/ddl/ddl.d.ts.map +1 -0
- package/dist/engine/ddl/ddl.js +574 -0
- package/dist/engine/ddl/schema-mutator.d.ts +26 -0
- package/dist/engine/ddl/schema-mutator.d.ts.map +1 -0
- package/dist/engine/ddl/schema-mutator.js +3 -0
- package/dist/engine/ddl/seed-ddl-destructive.d.ts +40 -0
- package/dist/engine/ddl/seed-ddl-destructive.d.ts.map +1 -0
- package/dist/engine/ddl/seed-ddl-destructive.js +149 -0
- package/dist/engine/ddl/seed-ddl.d.ts +43 -0
- package/dist/engine/ddl/seed-ddl.d.ts.map +1 -0
- package/dist/engine/ddl/seed-ddl.js +115 -0
- package/dist/engine/introspection/introspection.d.ts +101 -0
- package/dist/engine/introspection/introspection.d.ts.map +1 -0
- package/dist/engine/introspection/introspection.js +127 -0
- package/dist/engine/introspection/schema-fingerprint.d.ts +73 -0
- package/dist/engine/introspection/schema-fingerprint.d.ts.map +1 -0
- package/dist/engine/introspection/schema-fingerprint.js +97 -0
- package/dist/engine/introspection.d.ts +101 -0
- package/dist/engine/introspection.d.ts.map +1 -0
- package/dist/engine/introspection.js +127 -0
- package/dist/engine/localization/localization.d.ts +127 -0
- package/dist/engine/localization/localization.d.ts.map +1 -0
- package/dist/engine/localization/localization.js +212 -0
- package/dist/engine/localization.d.ts +127 -0
- package/dist/engine/localization.d.ts.map +1 -0
- package/dist/engine/localization.js +212 -0
- package/dist/engine/privacy/policies.d.ts +58 -0
- package/dist/engine/privacy/policies.d.ts.map +1 -0
- package/dist/engine/privacy/policies.js +185 -0
- package/dist/engine/privacy/privacy.service.d.ts +65 -0
- package/dist/engine/privacy/privacy.service.d.ts.map +1 -0
- package/dist/engine/privacy/privacy.service.js +114 -0
- package/dist/engine/privacy.service.d.ts.map +1 -1
- package/dist/engine/query/query.d.ts +23 -0
- package/dist/engine/query/query.d.ts.map +1 -0
- package/dist/engine/query/query.js +301 -0
- package/dist/engine/query/relations.d.ts +28 -0
- package/dist/engine/query/relations.d.ts.map +1 -0
- package/dist/engine/query/relations.js +25 -0
- package/dist/engine/query/serialize.d.ts +22 -0
- package/dist/engine/query/serialize.d.ts.map +1 -0
- package/dist/engine/query/serialize.js +175 -0
- package/dist/engine/schema-fingerprint.d.ts +73 -0
- package/dist/engine/schema-fingerprint.d.ts.map +1 -0
- package/dist/engine/schema-fingerprint.js +97 -0
- package/dist/engine/seed-registry.d.ts.map +1 -1
- package/dist/engine/seeds/define-seed.d.ts +3 -0
- package/dist/engine/seeds/define-seed.d.ts.map +1 -0
- package/dist/engine/seeds/define-seed.js +5 -0
- package/dist/engine/seeds/seed-registry.d.ts +71 -0
- package/dist/engine/seeds/seed-registry.d.ts.map +1 -0
- package/dist/engine/seeds/seed-registry.js +167 -0
- package/dist/engine/seeds/seed-types-generator.d.ts +25 -0
- package/dist/engine/seeds/seed-types-generator.d.ts.map +1 -0
- package/dist/engine/seeds/seed-types-generator.js +117 -0
- package/dist/engine/seeds/seed-validation.d.ts +30 -0
- package/dist/engine/seeds/seed-validation.d.ts.map +1 -0
- package/dist/engine/seeds/seed-validation.js +364 -0
- package/dist/engine/seeds/seeds.d.ts +20 -0
- package/dist/engine/seeds/seeds.d.ts.map +1 -0
- package/dist/engine/seeds/seeds.js +21 -0
- package/dist/engine/validation/emptiness.d.ts +12 -0
- package/dist/engine/validation/emptiness.d.ts.map +1 -0
- package/dist/engine/validation/emptiness.js +33 -0
- package/dist/engine/validation/index.d.ts +1 -1
- package/dist/engine/validation/index.d.ts.map +1 -1
- package/dist/engine.d.ts +144 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +741 -0
- package/dist/file-types.d.ts +13 -0
- package/dist/file-types.d.ts.map +1 -0
- package/dist/file-types.js +85 -0
- package/dist/hooks.d.ts +29 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +3 -0
- package/dist/id-generator.d.ts +26 -0
- package/dist/id-generator.d.ts.map +1 -0
- package/dist/id-generator.js +12 -0
- package/dist/idempotency.repository.d.ts +13 -0
- package/dist/idempotency.repository.d.ts.map +1 -0
- package/dist/idempotency.repository.js +3 -0
- package/dist/kanban-position.repository.d.ts +19 -0
- package/dist/kanban-position.repository.d.ts.map +1 -0
- package/dist/kanban-position.repository.js +3 -0
- package/dist/kanban.d.ts +75 -0
- package/dist/kanban.d.ts.map +1 -0
- package/dist/kanban.js +82 -0
- package/dist/layout-permissions.d.ts +3 -0
- package/dist/layout-permissions.d.ts.map +1 -0
- package/dist/layout-permissions.js +11 -0
- package/dist/media/image-transformer.d.ts +34 -0
- package/dist/media/image-transformer.d.ts.map +1 -0
- package/dist/media/image-transformer.js +3 -0
- package/dist/media/media-transform.d.ts +88 -0
- package/dist/media/media-transform.d.ts.map +1 -0
- package/dist/media/media-transform.js +193 -0
- package/dist/media.repository.d.ts +61 -0
- package/dist/media.repository.d.ts.map +1 -0
- package/dist/media.repository.js +3 -0
- package/dist/notifications/notification-service.d.ts +1 -1
- package/dist/oauth/pkce.d.ts +1 -1
- package/dist/oauth/role-guard.d.ts.map +1 -1
- package/dist/oauth/scopes.d.ts +1 -1
- package/dist/oauth/scopes.d.ts.map +1 -1
- package/dist/policies.d.ts +10 -0
- package/dist/policies.d.ts.map +1 -0
- package/dist/policies.js +33 -0
- package/dist/queue/queue.stub.d.ts.map +1 -1
- package/dist/queue/scheduler.stub.d.ts.map +1 -1
- package/dist/queue.interface.d.ts +39 -0
- package/dist/queue.interface.d.ts.map +1 -0
- package/dist/queue.interface.js +3 -0
- package/dist/queue.stub.d.ts +6 -0
- package/dist/queue.stub.d.ts.map +1 -0
- package/dist/queue.stub.js +8 -0
- package/dist/rate-limit/token-bucket-rate-limiter.d.ts.map +1 -1
- package/dist/rbac/evaluate.d.ts +57 -0
- package/dist/rbac/evaluate.d.ts.map +1 -0
- package/dist/rbac/evaluate.js +113 -0
- package/dist/rbac/invitation.repository.d.ts +66 -0
- package/dist/rbac/invitation.repository.d.ts.map +1 -0
- package/dist/rbac/invitation.repository.js +3 -0
- package/dist/rbac/permissions.d.ts +39 -0
- package/dist/rbac/permissions.d.ts.map +1 -0
- package/dist/rbac/permissions.js +43 -0
- package/dist/rbac/types.d.ts +118 -0
- package/dist/rbac/types.d.ts.map +1 -0
- package/dist/rbac/types.js +3 -0
- package/dist/relations.d.ts +28 -0
- package/dist/relations.d.ts.map +1 -0
- package/dist/relations.js +25 -0
- package/dist/richtext-render.d.ts +11 -0
- package/dist/richtext-render.d.ts.map +1 -0
- package/dist/richtext-render.js +87 -0
- package/dist/richtext.d.ts +11 -0
- package/dist/richtext.d.ts.map +1 -0
- package/dist/richtext.js +13 -0
- package/dist/scheduler.interface.d.ts +4 -0
- package/dist/scheduler.interface.d.ts.map +1 -0
- package/dist/scheduler.interface.js +3 -0
- package/dist/scheduler.stub.d.ts +5 -0
- package/dist/scheduler.stub.d.ts.map +1 -0
- package/dist/scheduler.stub.js +7 -0
- package/dist/schema/canonical.d.ts +15 -0
- package/dist/schema/canonical.d.ts.map +1 -0
- package/dist/schema/canonical.js +37 -0
- package/dist/schema/define.d.ts +42 -0
- package/dist/schema/define.d.ts.map +1 -0
- package/dist/schema/define.js +56 -0
- package/dist/schema/emit.d.ts +14 -0
- package/dist/schema/emit.d.ts.map +1 -0
- package/dist/schema/emit.js +43 -0
- package/dist/schema/index.d.ts +12 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +13 -0
- package/dist/schema/manifest-seeds.d.ts +12 -0
- package/dist/schema/manifest-seeds.d.ts.map +1 -0
- package/dist/schema/manifest-seeds.js +48 -0
- package/dist/schema/manifest-validation.d.ts +13 -0
- package/dist/schema/manifest-validation.d.ts.map +1 -0
- package/dist/schema/manifest-validation.js +41 -0
- package/dist/schema/manifest.types.d.ts +54 -0
- package/dist/schema/manifest.types.d.ts.map +1 -0
- package/dist/schema/manifest.types.js +7 -0
- package/dist/schema-mutator.d.ts +26 -0
- package/dist/schema-mutator.d.ts.map +1 -0
- package/dist/schema-mutator.js +3 -0
- package/dist/seed-ddl-destructive.d.ts +40 -0
- package/dist/seed-ddl-destructive.d.ts.map +1 -0
- package/dist/seed-ddl-destructive.js +149 -0
- package/dist/seed-ddl.d.ts +40 -0
- package/dist/seed-ddl.d.ts.map +1 -0
- package/dist/seed-ddl.js +100 -0
- package/dist/seed-layout.d.ts +145 -0
- package/dist/seed-layout.d.ts.map +1 -0
- package/dist/seed-layout.js +192 -0
- package/dist/seed-layout.repository.d.ts +22 -0
- package/dist/seed-layout.repository.d.ts.map +1 -0
- package/dist/seed-layout.repository.js +3 -0
- package/dist/seed-registry.d.ts +71 -0
- package/dist/seed-registry.d.ts.map +1 -0
- package/dist/seed-registry.js +143 -0
- package/dist/seed-types-generator.d.ts +1 -1
- package/dist/seed-types-generator.d.ts.map +1 -1
- package/dist/seed-validation.d.ts +23 -0
- package/dist/seed-validation.d.ts.map +1 -0
- package/dist/seed-validation.js +224 -0
- package/dist/seed.repository.d.ts +37 -0
- package/dist/seed.repository.d.ts.map +1 -0
- package/dist/seed.repository.js +3 -0
- package/dist/seeds.d.ts +20 -0
- package/dist/seeds.d.ts.map +1 -0
- package/dist/seeds.js +21 -0
- package/dist/site-settings.repository.d.ts +16 -0
- package/dist/site-settings.repository.d.ts.map +1 -0
- package/dist/site-settings.repository.js +3 -0
- package/dist/slug-utils.d.ts +18 -0
- package/dist/slug-utils.d.ts.map +1 -0
- package/dist/slug-utils.js +32 -0
- package/dist/storage.d.ts +46 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +3 -0
- package/dist/transfer/csv.d.ts +34 -0
- package/dist/transfer/csv.d.ts.map +1 -0
- package/dist/transfer/csv.js +135 -0
- package/dist/transfer/flat-seed.d.ts +13 -0
- package/dist/transfer/flat-seed.d.ts.map +1 -0
- package/dist/transfer/flat-seed.js +45 -0
- package/dist/transfer/index.d.ts +7 -0
- package/dist/transfer/index.d.ts.map +1 -0
- package/dist/transfer/index.js +8 -0
- package/dist/transfer/ndjson.d.ts +25 -0
- package/dist/transfer/ndjson.d.ts.map +1 -0
- package/dist/transfer/ndjson.js +62 -0
- package/dist/transfer/row-mapping.d.ts +36 -0
- package/dist/transfer/row-mapping.d.ts.map +1 -0
- package/dist/transfer/row-mapping.js +90 -0
- package/dist/transfer/transfer.constants.d.ts +20 -0
- package/dist/transfer/transfer.constants.d.ts.map +1 -0
- package/dist/transfer/transfer.constants.js +21 -0
- package/dist/transfer/transfer.types.d.ts +46 -0
- package/dist/transfer/transfer.types.d.ts.map +1 -0
- package/dist/transfer/transfer.types.js +6 -0
- package/dist/types.d.ts +243 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/validation.d.ts +50 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +660 -0
- package/dist/webhook-validation.d.ts +3 -0
- package/dist/webhook-validation.d.ts.map +1 -0
- package/dist/webhook-validation.js +6 -0
- package/package.json +4 -4
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
import { isLocaleDictionary, isLocalizedBranch } from '../localization/localization.js';
|
|
4
|
+
/**
|
|
5
|
+
* Computes an un-salted SHA-256 hex digest of a string value.
|
|
6
|
+
* Used for basic digest operations.
|
|
7
|
+
* @param value - The raw string value to hash.
|
|
8
|
+
* @returns A Promise resolving to a 64-character hex string.
|
|
9
|
+
*/
|
|
10
|
+
export async function sha256hex(value) {
|
|
11
|
+
const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value));
|
|
12
|
+
return Array.from(new Uint8Array(buf))
|
|
13
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
14
|
+
.join('');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Verifies if a candidate string matches a stored SHA-256 hash digest.
|
|
18
|
+
* @param stored - The expected 64-character SHA-256 hex digest.
|
|
19
|
+
* @param candidate - The raw candidate string to verify.
|
|
20
|
+
* @returns A Promise resolving to true if candidate matches stored digest, false otherwise.
|
|
21
|
+
*/
|
|
22
|
+
export async function verifyHashField(stored, candidate) {
|
|
23
|
+
return stored === (await sha256hex(candidate));
|
|
24
|
+
}
|
|
25
|
+
/** Normalizes raw policy strings to canonical DataClassification enum. */
|
|
26
|
+
export function normalizeClassification(raw) {
|
|
27
|
+
if (!raw)
|
|
28
|
+
return 'public';
|
|
29
|
+
const lower = raw.toLowerCase();
|
|
30
|
+
if (lower === 'confidential')
|
|
31
|
+
return 'confidential';
|
|
32
|
+
if (lower === 'restricted' || lower === 'hash')
|
|
33
|
+
return 'restricted';
|
|
34
|
+
if (lower === 'encrypt')
|
|
35
|
+
return 'confidential';
|
|
36
|
+
if (lower === 'internal')
|
|
37
|
+
return 'internal';
|
|
38
|
+
return 'public';
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolves the 4-tier DataClassification for a branch, bundling its storage
|
|
42
|
+
* strategy at rest and its API visibility rules (public vs auth context).
|
|
43
|
+
* @param branch - The seed branch definition to inspect.
|
|
44
|
+
* @returns The resolved classification details and visibility rules.
|
|
45
|
+
*/
|
|
46
|
+
export function resolveClassification(branch) {
|
|
47
|
+
const rawClassification = branch.policies?.classification ?? branch.policies?.privacy;
|
|
48
|
+
const classification = normalizeClassification(rawClassification);
|
|
49
|
+
switch (classification) {
|
|
50
|
+
case 'confidential':
|
|
51
|
+
return {
|
|
52
|
+
classification: 'confidential',
|
|
53
|
+
storage: 'encrypt',
|
|
54
|
+
publicVisibility: 'hidden',
|
|
55
|
+
authVisibility: 'full',
|
|
56
|
+
};
|
|
57
|
+
case 'restricted':
|
|
58
|
+
return {
|
|
59
|
+
classification: 'restricted',
|
|
60
|
+
storage: 'hash',
|
|
61
|
+
publicVisibility: 'hidden',
|
|
62
|
+
authVisibility: 'hidden',
|
|
63
|
+
};
|
|
64
|
+
case 'internal':
|
|
65
|
+
return {
|
|
66
|
+
classification: 'internal',
|
|
67
|
+
storage: 'plain',
|
|
68
|
+
publicVisibility: 'hidden',
|
|
69
|
+
authVisibility: 'full',
|
|
70
|
+
};
|
|
71
|
+
case 'public':
|
|
72
|
+
default:
|
|
73
|
+
return {
|
|
74
|
+
classification: 'public',
|
|
75
|
+
storage: 'plain',
|
|
76
|
+
publicVisibility: 'full',
|
|
77
|
+
authVisibility: 'full',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Resolves branch policies applying all defaults and classification rules.
|
|
83
|
+
* @param branch - The seed branch definition to inspect.
|
|
84
|
+
* @returns Complete required policies object for the branch.
|
|
85
|
+
*/
|
|
86
|
+
export function resolvePolicies(branch) {
|
|
87
|
+
const resolved = resolveClassification(branch);
|
|
88
|
+
const isRepeater = branch.type === 'repeater';
|
|
89
|
+
const isEncryptedOrHashed = resolved.storage !== 'plain';
|
|
90
|
+
const defaultVisibility = resolved.authVisibility;
|
|
91
|
+
const isPublicAllowed = branch.policies?.public ?? (resolved.publicVisibility === 'full');
|
|
92
|
+
const defaultFilter = isRepeater || resolved.storage === 'hash' ? false : branch.policies?.filter ?? true;
|
|
93
|
+
const defaultSort = isRepeater || isEncryptedOrHashed ? false : branch.policies?.sort ?? true;
|
|
94
|
+
const defaultSearch = isRepeater || isEncryptedOrHashed ? false : branch.policies?.search ?? true;
|
|
95
|
+
// publicEdit default: true for public fields (unless public: false), false for confidential/internal/restricted
|
|
96
|
+
const defaultPublicEdit = resolved.classification === 'public'
|
|
97
|
+
? (branch.policies?.publicEdit ?? isPublicAllowed)
|
|
98
|
+
: (branch.policies?.publicEdit ?? false);
|
|
99
|
+
return {
|
|
100
|
+
classification: resolved.classification,
|
|
101
|
+
privacy: resolved.storage,
|
|
102
|
+
visibility: branch.policies?.visibility ?? defaultVisibility,
|
|
103
|
+
search: defaultSearch,
|
|
104
|
+
filter: defaultFilter,
|
|
105
|
+
sort: defaultSort,
|
|
106
|
+
public: isPublicAllowed,
|
|
107
|
+
publicEdit: defaultPublicEdit,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const MASK = '••••••••';
|
|
111
|
+
/**
|
|
112
|
+
* The masked form of a value. A localized dictionary (a `?lang=all` read) masks each translation, exactly as
|
|
113
|
+
* a single-language read masks the one translation it resolves to; before this, any object became `null`.
|
|
114
|
+
*/
|
|
115
|
+
function maskValue(branch, value) {
|
|
116
|
+
if (typeof value === 'string')
|
|
117
|
+
return value.length > 0 ? MASK : null;
|
|
118
|
+
if (isLocalizedBranch(branch) && isLocaleDictionary(value)) {
|
|
119
|
+
return Object.fromEntries(Object.entries(value).map(([locale, translation]) => [
|
|
120
|
+
locale,
|
|
121
|
+
typeof translation === 'string' && translation.length > 0 ? MASK : null,
|
|
122
|
+
]));
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const SYSTEM_FIELDS = new Set(['id', 'slug', 'status', 'created_at', 'updated_at', 'version', 'has_pending_draft']);
|
|
127
|
+
/**
|
|
128
|
+
* Filters entry payload fields based on the caller's {@link ActorContext} and branch classification tier.
|
|
129
|
+
*
|
|
130
|
+
* Rules:
|
|
131
|
+
* - Public actor: receives `public` fields; `internal`, `confidential`, and `restricted` fields are omitted.
|
|
132
|
+
* - Authenticated actor: receives `public`, `internal`, and `confidential` fields; `restricted` fields are ALWAYS omitted.
|
|
133
|
+
* - System actor: receives all fields including `restricted` (used for internal worker/automation orchestration).
|
|
134
|
+
*
|
|
135
|
+
* @param data - Raw record fields object.
|
|
136
|
+
* @param seed - Content type seed definition containing branch definitions.
|
|
137
|
+
* @param actor - Context of the caller requesting the entry payload (defaults to authenticated).
|
|
138
|
+
* @returns Filtered data record containing only authorized fields for the given actor context.
|
|
139
|
+
*/
|
|
140
|
+
export function filterEntryForActor(data, seed, actor = { type: 'authenticated' }) {
|
|
141
|
+
const result = {};
|
|
142
|
+
for (const [key, value] of Object.entries(data)) {
|
|
143
|
+
if (SYSTEM_FIELDS.has(key)) {
|
|
144
|
+
result[key] = value;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const branch = seed.branches.find((b) => b.alias === key);
|
|
148
|
+
if (!branch) {
|
|
149
|
+
// Pass through unknown/extra fields unless actor is public
|
|
150
|
+
if (actor.type !== 'public') {
|
|
151
|
+
result[key] = value;
|
|
152
|
+
}
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const resolved = resolveClassification(branch);
|
|
156
|
+
const isPublicActor = actor.type === 'public';
|
|
157
|
+
const isSystemActor = actor.type === 'system';
|
|
158
|
+
// System actor sees all fields including restricted
|
|
159
|
+
if (isSystemActor) {
|
|
160
|
+
result[key] = value;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
// Public actor check
|
|
164
|
+
if (isPublicActor) {
|
|
165
|
+
if (resolved.publicVisibility === 'hidden')
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
// Authenticated actor check
|
|
170
|
+
if (resolved.authVisibility === 'hidden')
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
// Explicit visibility override check
|
|
174
|
+
if (branch.policies?.visibility === 'hidden')
|
|
175
|
+
continue;
|
|
176
|
+
// Masking rule check
|
|
177
|
+
if (branch.policies?.visibility === 'masked') {
|
|
178
|
+
result[key] = maskValue(branch, value);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
result[key] = value;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract for the Application-Level Encryption (ALE) and hashing privacy service.
|
|
3
|
+
* Used by the Botanical Engine to secure fields classified as `confidential` or `restricted`.
|
|
4
|
+
*/
|
|
5
|
+
export interface IPrivacyService {
|
|
6
|
+
/**
|
|
7
|
+
* Symmetrically encrypts a plaintext string using AES-256-GCM.
|
|
8
|
+
* @param plaintext - The raw string value to encrypt.
|
|
9
|
+
* @returns A Promise resolving to formatted ciphertext (`v1:<iv_base64>:<ciphertext_base64>`).
|
|
10
|
+
*/
|
|
11
|
+
encrypt(plaintext: string): Promise<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Decrypts a formatted AES-256-GCM ciphertext payload.
|
|
14
|
+
* @param ciphertext - The formatted ciphertext string (`v1:<iv_base64>:<ciphertext_base64>`).
|
|
15
|
+
* @returns A Promise resolving to the original plaintext string.
|
|
16
|
+
* @throws Error if the ciphertext format is invalid or decryption fails (authentication tag mismatch).
|
|
17
|
+
*/
|
|
18
|
+
decrypt(ciphertext: string): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Computes a deterministic HMAC SHA-256 digest of a plaintext string.
|
|
21
|
+
* Used for blind-indexing and one-way field digests (`restricted` classification).
|
|
22
|
+
* @param plaintext - The raw string value to hash.
|
|
23
|
+
* @returns A Promise resolving to a 64-character hexadecimal digest string.
|
|
24
|
+
*/
|
|
25
|
+
hash(plaintext: string): Promise<string>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Concrete Edge-native implementation of {@link IPrivacyService}.
|
|
29
|
+
* Employs Web Crypto API (`crypto.subtle`) for zero-dependency execution in Cloudflare Workers.
|
|
30
|
+
*/
|
|
31
|
+
export declare class PrivacyService implements IPrivacyService {
|
|
32
|
+
private readonly masterKey;
|
|
33
|
+
private aesKeyPromise?;
|
|
34
|
+
private hmacKeyPromise?;
|
|
35
|
+
/**
|
|
36
|
+
* Initializes the PrivacyService with a Master Encryption Key.
|
|
37
|
+
* @param masterKey - Secret key used to derive cryptographic keys for AES-GCM and HMAC.
|
|
38
|
+
* @throws Error if `masterKey` is empty or undefined.
|
|
39
|
+
*/
|
|
40
|
+
constructor(masterKey: string);
|
|
41
|
+
/**
|
|
42
|
+
* Lazy-loads and caches the derived AES-256-GCM {@link CryptoKey}.
|
|
43
|
+
* Derives a 256-bit key from `masterKey` via SHA-256 digest.
|
|
44
|
+
*/
|
|
45
|
+
private getAesKey;
|
|
46
|
+
/**
|
|
47
|
+
* Lazy-loads and caches the derived HMAC SHA-256 {@link CryptoKey}.
|
|
48
|
+
*/
|
|
49
|
+
private getHmacKey;
|
|
50
|
+
/**
|
|
51
|
+
* Encrypts a plaintext string with AES-256-GCM and a fresh 96-bit (12-byte) IV.
|
|
52
|
+
* Output is formatted as `v1:<iv_base64>:<ciphertext_base64>`.
|
|
53
|
+
*/
|
|
54
|
+
encrypt(plaintext: string): Promise<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Decrypts a `v1:<iv_base64>:<ciphertext_base64>` string back to its plaintext value.
|
|
57
|
+
* If the input string is not prefixed with `v1:`, returns the raw string as unencrypted legacy data.
|
|
58
|
+
*/
|
|
59
|
+
decrypt(ciphertext: string): Promise<string>;
|
|
60
|
+
/**
|
|
61
|
+
* Computes a deterministic HMAC SHA-256 hex digest for blind index generation.
|
|
62
|
+
*/
|
|
63
|
+
hash(plaintext: string): Promise<string>;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=privacy.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"privacy.service.d.ts","sourceRoot":"","sources":["../../../src/engine/privacy/privacy.service.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAE3C;;;;;OAKG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAE5C;;;;;OAKG;IACH,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACzC;AA4BD;;;GAGG;AACH,qBAAa,cAAe,YAAW,eAAe;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,aAAa,CAAC,CAAoB;IAC1C,OAAO,CAAC,cAAc,CAAC,CAAoB;IAE3C;;;;OAIG;gBACS,SAAS,EAAE,MAAM;IAO7B;;;OAGG;YACW,SAAS;IAmBvB;;OAEG;YACW,UAAU;IAexB;;;OAGG;IACG,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAcjD;;;OAGG;IACG,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAoBlD;;OAEG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAa/C"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
/**
|
|
4
|
+
* Converts a Uint8Array byte sequence to a Base64-encoded string.
|
|
5
|
+
* Native Edge-compatible implementation without Node.js Buffer dependencies.
|
|
6
|
+
*/
|
|
7
|
+
function bytesToBase64(bytes) {
|
|
8
|
+
let binary = '';
|
|
9
|
+
const len = bytes.byteLength;
|
|
10
|
+
for (let i = 0; i < len; i++) {
|
|
11
|
+
binary += String.fromCharCode(bytes[i]);
|
|
12
|
+
}
|
|
13
|
+
return btoa(binary);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Converts a Base64-encoded string back to a Uint8Array byte sequence.
|
|
17
|
+
* Native Edge-compatible implementation without Node.js Buffer dependencies.
|
|
18
|
+
*/
|
|
19
|
+
function base64ToBytes(base64) {
|
|
20
|
+
const binary = atob(base64);
|
|
21
|
+
const bytes = new Uint8Array(binary.length);
|
|
22
|
+
for (let i = 0; i < binary.length; i++) {
|
|
23
|
+
bytes[i] = binary.charCodeAt(i);
|
|
24
|
+
}
|
|
25
|
+
return bytes;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Concrete Edge-native implementation of {@link IPrivacyService}.
|
|
29
|
+
* Employs Web Crypto API (`crypto.subtle`) for zero-dependency execution in Cloudflare Workers.
|
|
30
|
+
*/
|
|
31
|
+
export class PrivacyService {
|
|
32
|
+
masterKey;
|
|
33
|
+
aesKeyPromise;
|
|
34
|
+
hmacKeyPromise;
|
|
35
|
+
/**
|
|
36
|
+
* Initializes the PrivacyService with a Master Encryption Key.
|
|
37
|
+
* @param masterKey - Secret key used to derive cryptographic keys for AES-GCM and HMAC.
|
|
38
|
+
* @throws Error if `masterKey` is empty or undefined.
|
|
39
|
+
*/
|
|
40
|
+
constructor(masterKey) {
|
|
41
|
+
if (!masterKey) {
|
|
42
|
+
throw new Error('PrivacyService requires a non-empty masterKey');
|
|
43
|
+
}
|
|
44
|
+
this.masterKey = masterKey;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Lazy-loads and caches the derived AES-256-GCM {@link CryptoKey}.
|
|
48
|
+
* Derives a 256-bit key from `masterKey` via SHA-256 digest.
|
|
49
|
+
*/
|
|
50
|
+
async getAesKey() {
|
|
51
|
+
if (!this.aesKeyPromise) {
|
|
52
|
+
this.aesKeyPromise = (async () => {
|
|
53
|
+
const keyData = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(this.masterKey));
|
|
54
|
+
return crypto.subtle.importKey('raw', keyData, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']);
|
|
55
|
+
})();
|
|
56
|
+
}
|
|
57
|
+
return this.aesKeyPromise;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Lazy-loads and caches the derived HMAC SHA-256 {@link CryptoKey}.
|
|
61
|
+
*/
|
|
62
|
+
async getHmacKey() {
|
|
63
|
+
if (!this.hmacKeyPromise) {
|
|
64
|
+
this.hmacKeyPromise = (async () => {
|
|
65
|
+
return crypto.subtle.importKey('raw', new TextEncoder().encode(this.masterKey), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
66
|
+
})();
|
|
67
|
+
}
|
|
68
|
+
return this.hmacKeyPromise;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Encrypts a plaintext string with AES-256-GCM and a fresh 96-bit (12-byte) IV.
|
|
72
|
+
* Output is formatted as `v1:<iv_base64>:<ciphertext_base64>`.
|
|
73
|
+
*/
|
|
74
|
+
async encrypt(plaintext) {
|
|
75
|
+
const key = await this.getAesKey();
|
|
76
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
77
|
+
const encodedText = new TextEncoder().encode(plaintext);
|
|
78
|
+
const encryptedBuffer = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, encodedText);
|
|
79
|
+
const ivBase64 = bytesToBase64(iv);
|
|
80
|
+
const ciphertextBase64 = bytesToBase64(new Uint8Array(encryptedBuffer));
|
|
81
|
+
return `v1:${ivBase64}:${ciphertextBase64}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Decrypts a `v1:<iv_base64>:<ciphertext_base64>` string back to its plaintext value.
|
|
85
|
+
* If the input string is not prefixed with `v1:`, returns the raw string as unencrypted legacy data.
|
|
86
|
+
*/
|
|
87
|
+
async decrypt(ciphertext) {
|
|
88
|
+
if (!ciphertext || !ciphertext.startsWith('v1:')) {
|
|
89
|
+
return ciphertext;
|
|
90
|
+
}
|
|
91
|
+
const parts = ciphertext.split(':');
|
|
92
|
+
if (parts.length !== 3) {
|
|
93
|
+
throw new Error('Invalid ciphertext format');
|
|
94
|
+
}
|
|
95
|
+
const [, ivBase64, cipherBase64] = parts;
|
|
96
|
+
const iv = base64ToBytes(ivBase64);
|
|
97
|
+
const cipherBytes = base64ToBytes(cipherBase64);
|
|
98
|
+
const key = await this.getAesKey();
|
|
99
|
+
const decryptedBuffer = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv }, key, cipherBytes);
|
|
100
|
+
return new TextDecoder().decode(decryptedBuffer);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Computes a deterministic HMAC SHA-256 hex digest for blind index generation.
|
|
104
|
+
*/
|
|
105
|
+
async hash(plaintext) {
|
|
106
|
+
const key = await this.getHmacKey();
|
|
107
|
+
const encodedText = new TextEncoder().encode(plaintext);
|
|
108
|
+
const signatureBuffer = await crypto.subtle.sign('HMAC', key, encodedText);
|
|
109
|
+
const hashBytes = new Uint8Array(signatureBuffer);
|
|
110
|
+
return Array.from(hashBytes)
|
|
111
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
112
|
+
.join('');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"privacy.service.d.ts","sourceRoot":"","sources":["../../src/engine/privacy.service.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAE3C;;;;;OAKG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAE5C;;;;;OAKG;IACH,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACzC;AA4BD;;;GAGG;AACH,qBAAa,cAAe,YAAW,eAAe;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,aAAa,CAAC,CAAoB;IAC1C,OAAO,CAAC,cAAc,CAAC,CAAoB;IAE3C;;;;OAIG;
|
|
1
|
+
{"version":3,"file":"privacy.service.d.ts","sourceRoot":"","sources":["../../src/engine/privacy.service.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAE3C;;;;;OAKG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAE5C;;;;;OAKG;IACH,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACzC;AA4BD;;;GAGG;AACH,qBAAa,cAAe,YAAW,eAAe;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,aAAa,CAAC,CAAoB;IAC1C,OAAO,CAAC,cAAc,CAAC,CAAoB;IAE3C;;;;OAIG;IACH,YAAY,SAAS,EAAE,MAAM,EAK5B;IAED;;;OAGG;YACW,SAAS;IAmBvB;;OAEG;YACW,UAAU;IAexB;;;OAGG;IACG,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYhD;IAED;;;OAGG;IACG,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBjD;IAED;;OAEG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAY7C;CACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Seed, SelectOptions, ParameterizedQuery } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a parameterized SQL SELECT query and bindings for a given Seed based on search, filtering, status, and ordering options.
|
|
4
|
+
*
|
|
5
|
+
* If `options.isCount` is set to `true`, it generates a counting query (`COUNT(*) as total`) rather than returning database rows.
|
|
6
|
+
* In count mode, column projections (`fields`), ordering (`orderBy` / `kanbanOrder`), and pagination (`LIMIT` / `OFFSET`) clauses and
|
|
7
|
+
* bindings are omitted, while join and filtering clauses are preserved.
|
|
8
|
+
*
|
|
9
|
+
* @param seed The seed definition.
|
|
10
|
+
* @param options Query configuration options.
|
|
11
|
+
* @returns The SQL query string and bindings.
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const { sql, bindings } = buildSelectQuery(postSeed, {
|
|
15
|
+
* status: 'published',
|
|
16
|
+
* filters: [{ column: 'title', type: 'text', conditions: [{ op: 'contains', value: 'cms' }] }],
|
|
17
|
+
* orderBy: { column: 'created_at', dir: 'DESC' },
|
|
18
|
+
* pagination: { limit: 20, offset: 0 },
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildSelectQuery(seed: Seed, options?: SelectOptions): ParameterizedQuery;
|
|
23
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../src/engine/query/query.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,IAAI,EAMJ,aAAa,EAEb,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAgErB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,aAAkB,GAAG,kBAAkB,CA4G5F"}
|