@open-mercato/shared 0.4.2-canary-c02407ff85
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/build.mjs +101 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +7 -0
- package/dist/lib/api/crud.js +47 -0
- package/dist/lib/api/crud.js.map +7 -0
- package/dist/lib/api/scoped.js +140 -0
- package/dist/lib/api/scoped.js.map +7 -0
- package/dist/lib/auth/jwt.js +34 -0
- package/dist/lib/auth/jwt.js.map +7 -0
- package/dist/lib/auth/server.js +157 -0
- package/dist/lib/auth/server.js.map +7 -0
- package/dist/lib/boolean.js +22 -0
- package/dist/lib/boolean.js.map +7 -0
- package/dist/lib/bootstrap/appResolver.js +43 -0
- package/dist/lib/bootstrap/appResolver.js.map +7 -0
- package/dist/lib/bootstrap/dynamicLoader.js +108 -0
- package/dist/lib/bootstrap/dynamicLoader.js.map +7 -0
- package/dist/lib/bootstrap/factory.js +59 -0
- package/dist/lib/bootstrap/factory.js.map +7 -0
- package/dist/lib/bootstrap/index.js +11 -0
- package/dist/lib/bootstrap/index.js.map +7 -0
- package/dist/lib/bootstrap/types.js +1 -0
- package/dist/lib/bootstrap/types.js.map +7 -0
- package/dist/lib/cache/segments.js +36 -0
- package/dist/lib/cache/segments.js.map +7 -0
- package/dist/lib/cli/progress.js +46 -0
- package/dist/lib/cli/progress.js.map +7 -0
- package/dist/lib/commands/command-bus.js +285 -0
- package/dist/lib/commands/command-bus.js.map +7 -0
- package/dist/lib/commands/customFieldSnapshots.js +66 -0
- package/dist/lib/commands/customFieldSnapshots.js.map +7 -0
- package/dist/lib/commands/helpers.js +98 -0
- package/dist/lib/commands/helpers.js.map +7 -0
- package/dist/lib/commands/index.js +8 -0
- package/dist/lib/commands/index.js.map +7 -0
- package/dist/lib/commands/operationMetadata.js +32 -0
- package/dist/lib/commands/operationMetadata.js.map +7 -0
- package/dist/lib/commands/registry.js +43 -0
- package/dist/lib/commands/registry.js.map +7 -0
- package/dist/lib/commands/scope.js +44 -0
- package/dist/lib/commands/scope.js.map +7 -0
- package/dist/lib/commands/types.js +8 -0
- package/dist/lib/commands/types.js.map +7 -0
- package/dist/lib/crud/cache-stats.js +98 -0
- package/dist/lib/crud/cache-stats.js.map +7 -0
- package/dist/lib/crud/cache.js +175 -0
- package/dist/lib/crud/cache.js.map +7 -0
- package/dist/lib/crud/custom-fields-client.js +52 -0
- package/dist/lib/crud/custom-fields-client.js.map +7 -0
- package/dist/lib/crud/custom-fields.js +467 -0
- package/dist/lib/crud/custom-fields.js.map +7 -0
- package/dist/lib/crud/errors.js +24 -0
- package/dist/lib/crud/errors.js.map +7 -0
- package/dist/lib/crud/exporters.js +154 -0
- package/dist/lib/crud/exporters.js.map +7 -0
- package/dist/lib/crud/factory.js +1311 -0
- package/dist/lib/crud/factory.js.map +7 -0
- package/dist/lib/crud/types.js +1 -0
- package/dist/lib/crud/types.js.map +7 -0
- package/dist/lib/custom-fields/normalize.js +36 -0
- package/dist/lib/custom-fields/normalize.js.map +7 -0
- package/dist/lib/data/engine.js +396 -0
- package/dist/lib/data/engine.js.map +7 -0
- package/dist/lib/db/escapeLikePattern.js +5 -0
- package/dist/lib/db/escapeLikePattern.js.map +7 -0
- package/dist/lib/db/mikro.js +82 -0
- package/dist/lib/db/mikro.js.map +7 -0
- package/dist/lib/di/container.js +94 -0
- package/dist/lib/di/container.js.map +7 -0
- package/dist/lib/email/send.js +12 -0
- package/dist/lib/email/send.js.map +7 -0
- package/dist/lib/encryption/aes.js +58 -0
- package/dist/lib/encryption/aes.js.map +7 -0
- package/dist/lib/encryption/customFieldValues.js +49 -0
- package/dist/lib/encryption/customFieldValues.js.map +7 -0
- package/dist/lib/encryption/entityFields.js +26 -0
- package/dist/lib/encryption/entityFields.js.map +7 -0
- package/dist/lib/encryption/entityIds.js +80 -0
- package/dist/lib/encryption/entityIds.js.map +7 -0
- package/dist/lib/encryption/find.js +45 -0
- package/dist/lib/encryption/find.js.map +7 -0
- package/dist/lib/encryption/indexDoc.js +69 -0
- package/dist/lib/encryption/indexDoc.js.map +7 -0
- package/dist/lib/encryption/kms.js +282 -0
- package/dist/lib/encryption/kms.js.map +7 -0
- package/dist/lib/encryption/subscriber.js +330 -0
- package/dist/lib/encryption/subscriber.js.map +7 -0
- package/dist/lib/encryption/tenantDataEncryptionService.js +252 -0
- package/dist/lib/encryption/tenantDataEncryptionService.js.map +7 -0
- package/dist/lib/encryption/toggles.js +18 -0
- package/dist/lib/encryption/toggles.js.map +7 -0
- package/dist/lib/entities/naming.js +9 -0
- package/dist/lib/entities/naming.js.map +7 -0
- package/dist/lib/entities/system-entities.js +43 -0
- package/dist/lib/entities/system-entities.js.map +7 -0
- package/dist/lib/frontend/organizationEvents.js +41 -0
- package/dist/lib/frontend/organizationEvents.js.map +7 -0
- package/dist/lib/frontend/useOrganizationScope.js +32 -0
- package/dist/lib/frontend/useOrganizationScope.js.map +7 -0
- package/dist/lib/hotkeys/index.js +128 -0
- package/dist/lib/hotkeys/index.js.map +7 -0
- package/dist/lib/i18n/app-dictionaries.js +17 -0
- package/dist/lib/i18n/app-dictionaries.js.map +7 -0
- package/dist/lib/i18n/config.js +7 -0
- package/dist/lib/i18n/config.js.map +7 -0
- package/dist/lib/i18n/context.js +50 -0
- package/dist/lib/i18n/context.js.map +7 -0
- package/dist/lib/i18n/server.js +68 -0
- package/dist/lib/i18n/server.js.map +7 -0
- package/dist/lib/i18n/translate.js +45 -0
- package/dist/lib/i18n/translate.js.map +7 -0
- package/dist/lib/indexers/error-log.js +82 -0
- package/dist/lib/indexers/error-log.js.map +7 -0
- package/dist/lib/indexers/status-log.js +80 -0
- package/dist/lib/indexers/status-log.js.map +7 -0
- package/dist/lib/lib/auth/jwt.js +34 -0
- package/dist/lib/lib/auth/jwt.js.map +7 -0
- package/dist/lib/lib/auth/server.js +77 -0
- package/dist/lib/lib/auth/server.js.map +7 -0
- package/dist/lib/lib/email/send.js +12 -0
- package/dist/lib/lib/email/send.js.map +7 -0
- package/dist/lib/lib/i18n/config.js +7 -0
- package/dist/lib/lib/i18n/config.js.map +7 -0
- package/dist/lib/lib/i18n/context.js +31 -0
- package/dist/lib/lib/i18n/context.js.map +7 -0
- package/dist/lib/lib/utils.js +9 -0
- package/dist/lib/lib/utils.js.map +7 -0
- package/dist/lib/location/countries.js +68 -0
- package/dist/lib/location/countries.js.map +7 -0
- package/dist/lib/modules/index.js +6 -0
- package/dist/lib/modules/index.js.map +7 -0
- package/dist/lib/modules/registry.js +18 -0
- package/dist/lib/modules/registry.js.map +7 -0
- package/dist/lib/openapi/crud.js +137 -0
- package/dist/lib/openapi/crud.js.map +7 -0
- package/dist/lib/openapi/generator.js +1131 -0
- package/dist/lib/openapi/generator.js.map +7 -0
- package/dist/lib/openapi/index.js +10 -0
- package/dist/lib/openapi/index.js.map +7 -0
- package/dist/lib/openapi/sanitize.js +110 -0
- package/dist/lib/openapi/sanitize.js.map +7 -0
- package/dist/lib/openapi/types.js +1 -0
- package/dist/lib/openapi/types.js.map +7 -0
- package/dist/lib/profiler/index.js +258 -0
- package/dist/lib/profiler/index.js.map +7 -0
- package/dist/lib/query/engine.js +729 -0
- package/dist/lib/query/engine.js.map +7 -0
- package/dist/lib/query/join-utils.js +195 -0
- package/dist/lib/query/join-utils.js.map +7 -0
- package/dist/lib/query/types.js +9 -0
- package/dist/lib/query/types.js.map +7 -0
- package/dist/lib/search/config.js +32 -0
- package/dist/lib/search/config.js.map +7 -0
- package/dist/lib/search/tokenize.js +34 -0
- package/dist/lib/search/tokenize.js.map +7 -0
- package/dist/lib/slugify.js +24 -0
- package/dist/lib/slugify.js.map +7 -0
- package/dist/lib/testing/bootstrap.js +51 -0
- package/dist/lib/testing/bootstrap.js.map +7 -0
- package/dist/lib/testing/index.js +17 -0
- package/dist/lib/testing/index.js.map +7 -0
- package/dist/lib/testing/renderWithProviders.js +15 -0
- package/dist/lib/testing/renderWithProviders.js.map +7 -0
- package/dist/lib/url.js +12 -0
- package/dist/lib/url.js.map +7 -0
- package/dist/lib/utils.js +13 -0
- package/dist/lib/utils.js.map +7 -0
- package/dist/lib/version.js +7 -0
- package/dist/lib/version.js.map +7 -0
- package/dist/modules/dashboard/widgets.js +1 -0
- package/dist/modules/dashboard/widgets.js.map +7 -0
- package/dist/modules/dsl.js +30 -0
- package/dist/modules/dsl.js.map +7 -0
- package/dist/modules/entities/kinds.js +22 -0
- package/dist/modules/entities/kinds.js.map +7 -0
- package/dist/modules/entities/options.js +26 -0
- package/dist/modules/entities/options.js.map +7 -0
- package/dist/modules/entities/validation.js +102 -0
- package/dist/modules/entities/validation.js.map +7 -0
- package/dist/modules/entities/validators.js +88 -0
- package/dist/modules/entities/validators.js.map +7 -0
- package/dist/modules/entities.js +1 -0
- package/dist/modules/entities.js.map +7 -0
- package/dist/modules/navigation/sidebarPreferences.js +50 -0
- package/dist/modules/navigation/sidebarPreferences.js.map +7 -0
- package/dist/modules/perspectives/types.js +1 -0
- package/dist/modules/perspectives/types.js.map +7 -0
- package/dist/modules/registry.js +96 -0
- package/dist/modules/registry.js.map +7 -0
- package/dist/modules/search.js +15 -0
- package/dist/modules/search.js.map +7 -0
- package/dist/modules/vector.js +1 -0
- package/dist/modules/vector.js.map +7 -0
- package/dist/modules/widgets/injection-loader.js +180 -0
- package/dist/modules/widgets/injection-loader.js.map +7 -0
- package/dist/modules/widgets/injection.js +1 -0
- package/dist/modules/widgets/injection.js.map +7 -0
- package/dist/security/features.js +23 -0
- package/dist/security/features.js.map +7 -0
- package/dist/types/pg.d.js +1 -0
- package/dist/types/pg.d.js.map +7 -0
- package/dist/types/react-email.d.js +1 -0
- package/dist/types/react-email.d.js.map +7 -0
- package/dist/types/resend.d.js +1 -0
- package/dist/types/resend.d.js.map +7 -0
- package/jest.config.cjs +22 -0
- package/package.json +88 -0
- package/src/index.ts +0 -0
- package/src/lib/api/__tests__/scoped.test.ts +38 -0
- package/src/lib/api/crud.ts +59 -0
- package/src/lib/api/scoped.ts +239 -0
- package/src/lib/auth/jwt.ts +39 -0
- package/src/lib/auth/server.ts +199 -0
- package/src/lib/boolean.ts +17 -0
- package/src/lib/bootstrap/appResolver.ts +85 -0
- package/src/lib/bootstrap/dynamicLoader.ts +177 -0
- package/src/lib/bootstrap/factory.ts +108 -0
- package/src/lib/bootstrap/index.ts +23 -0
- package/src/lib/bootstrap/types.ts +31 -0
- package/src/lib/cache/segments.ts +56 -0
- package/src/lib/cli/progress.ts +55 -0
- package/src/lib/commands/__tests__/command-bus.test.ts +84 -0
- package/src/lib/commands/__tests__/helpers.test.ts +42 -0
- package/src/lib/commands/command-bus.ts +349 -0
- package/src/lib/commands/customFieldSnapshots.ts +86 -0
- package/src/lib/commands/helpers.ts +143 -0
- package/src/lib/commands/index.ts +4 -0
- package/src/lib/commands/operationMetadata.ts +40 -0
- package/src/lib/commands/registry.ts +46 -0
- package/src/lib/commands/scope.ts +59 -0
- package/src/lib/commands/types.ts +63 -0
- package/src/lib/crud/__tests__/crud-factory.test.ts +333 -0
- package/src/lib/crud/__tests__/custom-fields.test.ts +150 -0
- package/src/lib/crud/cache-stats.ts +127 -0
- package/src/lib/crud/cache.ts +205 -0
- package/src/lib/crud/custom-fields-client.ts +54 -0
- package/src/lib/crud/custom-fields.ts +607 -0
- package/src/lib/crud/errors.ts +23 -0
- package/src/lib/crud/exporters.ts +188 -0
- package/src/lib/crud/factory.ts +1622 -0
- package/src/lib/crud/types.ts +29 -0
- package/src/lib/custom-fields/normalize.ts +45 -0
- package/src/lib/data/engine.ts +562 -0
- package/src/lib/db/escapeLikePattern.ts +2 -0
- package/src/lib/db/mikro.ts +100 -0
- package/src/lib/di/container.ts +105 -0
- package/src/lib/email/send.ts +18 -0
- package/src/lib/encryption/__tests__/customFieldValues.test.ts +63 -0
- package/src/lib/encryption/__tests__/indexDoc.test.ts +115 -0
- package/src/lib/encryption/aes.ts +64 -0
- package/src/lib/encryption/customFieldValues.ts +67 -0
- package/src/lib/encryption/entityFields.ts +39 -0
- package/src/lib/encryption/entityIds.ts +107 -0
- package/src/lib/encryption/find.ts +81 -0
- package/src/lib/encryption/indexDoc.ts +104 -0
- package/src/lib/encryption/kms.ts +337 -0
- package/src/lib/encryption/subscriber.ts +416 -0
- package/src/lib/encryption/tenantDataEncryptionService.ts +313 -0
- package/src/lib/encryption/toggles.ts +15 -0
- package/src/lib/entities/naming.ts +6 -0
- package/src/lib/entities/system-entities.ts +43 -0
- package/src/lib/frontend/organizationEvents.ts +55 -0
- package/src/lib/frontend/useOrganizationScope.ts +30 -0
- package/src/lib/hotkeys/index.ts +168 -0
- package/src/lib/i18n/app-dictionaries.ts +18 -0
- package/src/lib/i18n/config.ts +4 -0
- package/src/lib/i18n/context.tsx +66 -0
- package/src/lib/i18n/server.ts +74 -0
- package/src/lib/i18n/translate.ts +54 -0
- package/src/lib/indexers/error-log.ts +106 -0
- package/src/lib/indexers/status-log.ts +119 -0
- package/src/lib/lib/auth/jwt.ts +39 -0
- package/src/lib/lib/auth/server.ts +94 -0
- package/src/lib/lib/email/send.ts +18 -0
- package/src/lib/lib/i18n/config.ts +4 -0
- package/src/lib/lib/i18n/context.tsx +38 -0
- package/src/lib/lib/utils.ts +6 -0
- package/src/lib/location/countries.ts +97 -0
- package/src/lib/modules/index.ts +1 -0
- package/src/lib/modules/registry.ts +18 -0
- package/src/lib/openapi/crud.ts +218 -0
- package/src/lib/openapi/generator.ts +1311 -0
- package/src/lib/openapi/index.ts +4 -0
- package/src/lib/openapi/sanitize.ts +137 -0
- package/src/lib/openapi/types.ts +79 -0
- package/src/lib/profiler/index.ts +371 -0
- package/src/lib/query/__tests__/engine.test.ts +274 -0
- package/src/lib/query/engine.ts +837 -0
- package/src/lib/query/join-utils.ts +238 -0
- package/src/lib/query/types.ts +121 -0
- package/src/lib/search/config.ts +49 -0
- package/src/lib/search/tokenize.ts +45 -0
- package/src/lib/slugify.ts +28 -0
- package/src/lib/testing/bootstrap.ts +124 -0
- package/src/lib/testing/index.ts +15 -0
- package/src/lib/testing/renderWithProviders.tsx +31 -0
- package/src/lib/url.ts +12 -0
- package/src/lib/utils.ts +17 -0
- package/src/lib/version.ts +5 -0
- package/src/modules/__tests__/dsl.test.ts +35 -0
- package/src/modules/__tests__/registry.test.ts +300 -0
- package/src/modules/dashboard/widgets.ts +57 -0
- package/src/modules/dsl.ts +32 -0
- package/src/modules/entities/__tests__/validation.test.ts +52 -0
- package/src/modules/entities/kinds.ts +20 -0
- package/src/modules/entities/options.ts +36 -0
- package/src/modules/entities/validation.ts +118 -0
- package/src/modules/entities/validators.ts +93 -0
- package/src/modules/entities.ts +102 -0
- package/src/modules/navigation/sidebarPreferences.ts +62 -0
- package/src/modules/perspectives/types.ts +40 -0
- package/src/modules/registry.ts +249 -0
- package/src/modules/search.ts +325 -0
- package/src/modules/vector.ts +122 -0
- package/src/modules/widgets/__tests__/injection.test.ts +48 -0
- package/src/modules/widgets/injection-loader.ts +235 -0
- package/src/modules/widgets/injection.ts +120 -0
- package/src/security/features.ts +22 -0
- package/src/types/pg.d.ts +2 -0
- package/src/types/react-email.d.ts +2 -0
- package/src/types/resend.d.ts +2 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +9 -0
- package/watch.mjs +6 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import type { EntityMetadata, EventArgs, EventSubscriber } from '@mikro-orm/core'
|
|
2
|
+
import { ReferenceKind } from '@mikro-orm/core'
|
|
3
|
+
import { resolveEntityIdFromMetadata } from './entityIds'
|
|
4
|
+
import { TenantDataEncryptionService } from './tenantDataEncryptionService'
|
|
5
|
+
import { isTenantDataEncryptionEnabled } from './toggles'
|
|
6
|
+
import { isEncryptionDebugEnabled } from './toggles'
|
|
7
|
+
import { resolveTenantEncryptionService } from './customFieldValues'
|
|
8
|
+
|
|
9
|
+
type Scoped = {
|
|
10
|
+
tenantId?: string | null
|
|
11
|
+
tenant_id?: string | null
|
|
12
|
+
tenant?: { id?: string | null } | null
|
|
13
|
+
organizationId?: string | null
|
|
14
|
+
organization_id?: string | null
|
|
15
|
+
organization?: { id?: string | null } | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type Scope = { tenantId: string | null; organizationId: string | null }
|
|
19
|
+
|
|
20
|
+
function resolveScope(entity: Scoped): Scope {
|
|
21
|
+
const tenantId = entity.tenantId ?? entity.tenant_id ?? entity.tenant?.id ?? null
|
|
22
|
+
const organizationId = entity.organizationId ?? entity.organization_id ?? entity.organization?.id ?? null
|
|
23
|
+
return {
|
|
24
|
+
tenantId: tenantId ? String(tenantId) : null,
|
|
25
|
+
organizationId: organizationId ? String(organizationId) : null,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function debug(event: string, payload: Record<string, unknown>) {
|
|
30
|
+
if (!isEncryptionDebugEnabled()) return
|
|
31
|
+
try {
|
|
32
|
+
// eslint-disable-next-line no-console
|
|
33
|
+
console.debug(event, payload)
|
|
34
|
+
} catch {
|
|
35
|
+
// ignore
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const registeredEventManagers = new WeakSet<object>()
|
|
40
|
+
|
|
41
|
+
const toSnakeCase = (value: string): string =>
|
|
42
|
+
value.replace(/([A-Z])/g, '_$1').replace(/__/g, '_').toLowerCase()
|
|
43
|
+
|
|
44
|
+
export class TenantEncryptionSubscriber implements EventSubscriber<any> {
|
|
45
|
+
constructor(private readonly service: TenantDataEncryptionService) {}
|
|
46
|
+
|
|
47
|
+
getSubscribedEntities() {
|
|
48
|
+
return [] // listen to all entities
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private resolveMeta(
|
|
52
|
+
meta: EntityMetadata<any> | undefined,
|
|
53
|
+
entity: Record<string, unknown>,
|
|
54
|
+
em?: { getMetadata?: () => any },
|
|
55
|
+
): EntityMetadata<any> | undefined {
|
|
56
|
+
if (meta) return meta
|
|
57
|
+
const ctor = (entity as any)?.constructor
|
|
58
|
+
const name = ctor?.name
|
|
59
|
+
const registry = em?.getMetadata?.()
|
|
60
|
+
if (!registry || !name) return meta
|
|
61
|
+
try { return registry.find?.(name) } catch {}
|
|
62
|
+
try { return registry.find?.(ctor) } catch {}
|
|
63
|
+
try { return registry.get?.(name) } catch {}
|
|
64
|
+
try { return registry.get?.(ctor) } catch {}
|
|
65
|
+
const all =
|
|
66
|
+
(typeof registry.getAll === 'function' && registry.getAll()) ||
|
|
67
|
+
(Array.isArray((registry as any).metadata) ? (registry as any).metadata : undefined) ||
|
|
68
|
+
(registry as any).metadata ||
|
|
69
|
+
{}
|
|
70
|
+
try {
|
|
71
|
+
const entries = Array.isArray(all) ? all : Object.values<any>(all)
|
|
72
|
+
const match = entries.find(
|
|
73
|
+
(m: any) =>
|
|
74
|
+
m?.className === name ||
|
|
75
|
+
m?.name === name ||
|
|
76
|
+
m?.entityName === name ||
|
|
77
|
+
m?.collection === ctor?.prototype?.__meta?.tableName ||
|
|
78
|
+
m?.tableName === ctor?.prototype?.__meta?.tableName,
|
|
79
|
+
)
|
|
80
|
+
if (match) return match as EntityMetadata<any>
|
|
81
|
+
} catch {
|
|
82
|
+
// best-effort
|
|
83
|
+
}
|
|
84
|
+
return meta
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private resolveEntityId(meta: EntityMetadata<any> | undefined): string | null {
|
|
88
|
+
try {
|
|
89
|
+
return resolveEntityIdFromMetadata(meta)
|
|
90
|
+
} catch {
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private syncOriginalEntityData(
|
|
96
|
+
target: Record<string, unknown>,
|
|
97
|
+
meta: EntityMetadata<any> | undefined,
|
|
98
|
+
em?: { getComparator?: () => any },
|
|
99
|
+
) {
|
|
100
|
+
const helper = (target as any)?.__helper
|
|
101
|
+
if (!helper || typeof helper !== 'object') return
|
|
102
|
+
|
|
103
|
+
// Prefer MikroORM comparator snapshot so change detection uses the expected shape.
|
|
104
|
+
try {
|
|
105
|
+
const comparator = em?.getComparator?.()
|
|
106
|
+
if (comparator?.prepareEntity) {
|
|
107
|
+
helper.__originalEntityData = comparator.prepareEntity(target)
|
|
108
|
+
helper.__touched = false
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
} catch (err) {
|
|
112
|
+
debug('⚪️ subscriber.sync_original.comparator_failed', {
|
|
113
|
+
entity: meta?.className || meta?.name,
|
|
114
|
+
message: (err as Error)?.message ?? String(err),
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Fallback: shallow snapshot of scalar/owner props to keep entities clean without comparator.
|
|
119
|
+
const properties = meta?.properties ? Object.values(meta.properties) : []
|
|
120
|
+
if (properties.length === 0) return
|
|
121
|
+
const snapshot: Record<string, unknown> = { ...(helper.__originalEntityData ?? {}) }
|
|
122
|
+
for (const prop of properties) {
|
|
123
|
+
if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes((prop as any).kind)) continue
|
|
124
|
+
const name = (prop as any).name
|
|
125
|
+
if (typeof name !== 'string' || !name.length) continue
|
|
126
|
+
snapshot[name] = (target as Record<string, unknown>)[name]
|
|
127
|
+
}
|
|
128
|
+
helper.__originalEntityData = snapshot
|
|
129
|
+
helper.__touched = false
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private async encrypt(
|
|
133
|
+
target: Record<string, unknown>,
|
|
134
|
+
meta: EntityMetadata<any> | undefined,
|
|
135
|
+
em?: { getMetadata?: () => any; getComparator?: () => any },
|
|
136
|
+
changeSet?: { payload?: Record<string, unknown> },
|
|
137
|
+
) {
|
|
138
|
+
if (!isTenantDataEncryptionEnabled() || !this.service.isEnabled()) {
|
|
139
|
+
debug('⚪️ subscriber.skip', { reason: 'disabled', entity: meta?.className || meta?.name })
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
const resolvedMeta = this.resolveMeta(meta, target, em)
|
|
143
|
+
const entityId = this.resolveEntityId(resolvedMeta)
|
|
144
|
+
if (!entityId) {
|
|
145
|
+
debug('⚠️ subscriber.decrypt.skip.entity_id_missing', {
|
|
146
|
+
metaName: resolvedMeta?.className || resolvedMeta?.name,
|
|
147
|
+
table: (resolvedMeta as any)?.tableName,
|
|
148
|
+
})
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
const { tenantId, organizationId } = resolveScope(target)
|
|
152
|
+
if (!tenantId) {
|
|
153
|
+
debug('⚪️ subscriber.skip', { reason: 'no-tenant', entityId })
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
const encrypted = await this.service.encryptEntityPayload(entityId, target, tenantId, organizationId)
|
|
157
|
+
const metaProps: Record<string, unknown> = resolvedMeta?.properties && typeof resolvedMeta.properties === 'object'
|
|
158
|
+
? resolvedMeta.properties
|
|
159
|
+
: {}
|
|
160
|
+
const payloadObj: Record<string, unknown> | null =
|
|
161
|
+
changeSet && typeof changeSet === 'object'
|
|
162
|
+
? (typeof changeSet.payload === 'object' && changeSet.payload
|
|
163
|
+
? (changeSet.payload as Record<string, unknown>)
|
|
164
|
+
: ((changeSet.payload = {}) as Record<string, unknown>))
|
|
165
|
+
: null
|
|
166
|
+
const updates: Record<string, unknown> = {}
|
|
167
|
+
const columnNameFor = (propKey: string, prop: Record<string, unknown> | undefined): string => {
|
|
168
|
+
try {
|
|
169
|
+
if (prop && typeof prop === 'object') {
|
|
170
|
+
const explicit = (prop as any)?.fieldName
|
|
171
|
+
if (typeof explicit === 'string' && explicit.length) return explicit
|
|
172
|
+
const name = (prop as any)?.name
|
|
173
|
+
if (typeof name === 'string' && name.length) return name
|
|
174
|
+
}
|
|
175
|
+
} catch (err) {
|
|
176
|
+
debug('⚠️ subscriber.column_name.resolve', {
|
|
177
|
+
entityId,
|
|
178
|
+
propKey,
|
|
179
|
+
message: (err as Error)?.message ?? String(err),
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
return toSnakeCase(propKey)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const [key, value] of Object.entries(encrypted)) {
|
|
186
|
+
const prop = (metaProps as Record<string, any>)[key]
|
|
187
|
+
if (!prop || typeof prop !== 'object') continue
|
|
188
|
+
if ((target as Record<string, unknown>)[key] === value) continue
|
|
189
|
+
updates[key] = value
|
|
190
|
+
}
|
|
191
|
+
if (Object.keys(updates).length === 0) return
|
|
192
|
+
Object.assign(target, updates)
|
|
193
|
+
if (payloadObj) {
|
|
194
|
+
try {
|
|
195
|
+
const ensureColumnKey = (propKey: string, value: unknown) => {
|
|
196
|
+
const columnName = columnNameFor(propKey, (metaProps as Record<string, any>)[propKey])
|
|
197
|
+
const canonicalKey = columnName || toSnakeCase(propKey)
|
|
198
|
+
const aliases = new Set(
|
|
199
|
+
[propKey, toSnakeCase(propKey), columnName, columnName ? toSnakeCase(columnName) : undefined].filter(
|
|
200
|
+
(v): v is string => typeof v === 'string' && v.length > 0,
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
for (const alias of aliases) {
|
|
204
|
+
if (Object.prototype.hasOwnProperty.call(payloadObj, alias)) delete payloadObj[alias]
|
|
205
|
+
}
|
|
206
|
+
const finalKey = columnName || toSnakeCase(propKey)
|
|
207
|
+
payloadObj[finalKey] = value
|
|
208
|
+
}
|
|
209
|
+
for (const key of Object.keys(updates)) {
|
|
210
|
+
ensureColumnKey(key, updates[key])
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
debug('⚠️ subscriber.payload.normalize.error', {
|
|
214
|
+
entityId,
|
|
215
|
+
message: (err as Error)?.message ?? String(err),
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async decryptEntityGraph(
|
|
222
|
+
target: Record<string, unknown>,
|
|
223
|
+
meta: EntityMetadata<any> | undefined,
|
|
224
|
+
em?: { getMetadata?: () => any; getComparator?: () => any },
|
|
225
|
+
opts: { syncOriginal?: boolean; seen?: WeakSet<object>; fallbackScope?: Scope } = {},
|
|
226
|
+
) {
|
|
227
|
+
await this.decrypt(target, meta, em, opts)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private async decrypt(
|
|
231
|
+
target: Record<string, unknown>,
|
|
232
|
+
meta: EntityMetadata<any> | undefined,
|
|
233
|
+
em?: { getMetadata?: () => any; getComparator?: () => any },
|
|
234
|
+
{
|
|
235
|
+
syncOriginal = false,
|
|
236
|
+
seen,
|
|
237
|
+
fallbackScope,
|
|
238
|
+
}: { syncOriginal?: boolean; seen?: WeakSet<object>; fallbackScope?: Scope } = {},
|
|
239
|
+
) {
|
|
240
|
+
const visited = seen ?? new WeakSet<object>()
|
|
241
|
+
if (visited.has(target as object)) return
|
|
242
|
+
visited.add(target as object)
|
|
243
|
+
if (!isTenantDataEncryptionEnabled() || !this.service.isEnabled()) {
|
|
244
|
+
debug('⚪️ subscriber.skip', { reason: 'disabled', entity: meta?.className || meta?.name })
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
const resolvedMeta = this.resolveMeta(meta, target, em)
|
|
248
|
+
const entityId = this.resolveEntityId(resolvedMeta)
|
|
249
|
+
if (!entityId) return
|
|
250
|
+
const { tenantId, organizationId } = resolveScope(target)
|
|
251
|
+
const scopedTenantId = tenantId ?? fallbackScope?.tenantId ?? null
|
|
252
|
+
const scopedOrgId = organizationId ?? fallbackScope?.organizationId ?? null
|
|
253
|
+
if (!scopedTenantId) {
|
|
254
|
+
debug('⚪️ subscriber.skip', { reason: 'no-tenant', entityId })
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
const decrypted = await this.service.decryptEntityPayload(entityId, target, scopedTenantId, scopedOrgId)
|
|
258
|
+
Object.assign(target, decrypted)
|
|
259
|
+
if (syncOriginal) {
|
|
260
|
+
this.syncOriginalEntityData(target, resolvedMeta, em as any)
|
|
261
|
+
}
|
|
262
|
+
const nextFallback =
|
|
263
|
+
fallbackScope ??
|
|
264
|
+
(tenantId || organizationId
|
|
265
|
+
? { tenantId: tenantId ?? null, organizationId: organizationId ?? null }
|
|
266
|
+
: { tenantId: scopedTenantId, organizationId: scopedOrgId })
|
|
267
|
+
// Best-effort deep decrypt for loaded relations so populated graphs get cleaned too.
|
|
268
|
+
try {
|
|
269
|
+
const extractEntities = (value: any): any[] => {
|
|
270
|
+
if (!value) return []
|
|
271
|
+
// MikroORM Reference wrapper
|
|
272
|
+
if (typeof value === 'object' && typeof (value as any).isInitialized === 'function') {
|
|
273
|
+
try {
|
|
274
|
+
if ((value as any).isInitialized()) {
|
|
275
|
+
const unwrapped = typeof (value as any).unwrap === 'function' ? (value as any).unwrap() : (value as any).__entity ?? (value as any)
|
|
276
|
+
if (unwrapped && typeof unwrapped === 'object') return [unwrapped]
|
|
277
|
+
}
|
|
278
|
+
} catch {
|
|
279
|
+
// ignore
|
|
280
|
+
}
|
|
281
|
+
return []
|
|
282
|
+
}
|
|
283
|
+
// Collection wrapper
|
|
284
|
+
if (typeof value === 'object' && typeof (value as any).isInitialized === 'function' && typeof (value as any).getItems === 'function') {
|
|
285
|
+
try {
|
|
286
|
+
return (value as any).isInitialized() ? (value as any).getItems() ?? [] : []
|
|
287
|
+
} catch {
|
|
288
|
+
return []
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (Array.isArray(value)) return value
|
|
292
|
+
if (typeof value === 'object') return [value]
|
|
293
|
+
return []
|
|
294
|
+
}
|
|
295
|
+
const props = resolvedMeta?.properties ? Object.values(resolvedMeta.properties) : []
|
|
296
|
+
for (const prop of props) {
|
|
297
|
+
const kind = (prop as any)?.kind
|
|
298
|
+
const name = (prop as any)?.name
|
|
299
|
+
if (typeof name !== 'string' || !name.length) continue
|
|
300
|
+
const value = (target as any)[name]
|
|
301
|
+
if (!value) continue
|
|
302
|
+
// Single-valued relation
|
|
303
|
+
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(kind)) {
|
|
304
|
+
const nestedEntities = extractEntities(value)
|
|
305
|
+
for (const nested of nestedEntities) {
|
|
306
|
+
const nestedMeta = this.resolveMeta((nested as any).__meta ?? (nested as any).__helper?.__meta, nested, em)
|
|
307
|
+
await this.decrypt(nested as Record<string, unknown>, nestedMeta, em, {
|
|
308
|
+
syncOriginal: true,
|
|
309
|
+
seen: visited,
|
|
310
|
+
fallbackScope: nextFallback,
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
continue
|
|
314
|
+
}
|
|
315
|
+
// Collections
|
|
316
|
+
if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(kind)) {
|
|
317
|
+
const items = extractEntities(value)
|
|
318
|
+
for (const item of items) {
|
|
319
|
+
if (!item || typeof item !== 'object') continue
|
|
320
|
+
const nestedMeta = this.resolveMeta((item as any).__meta ?? (item as any).__helper?.__meta, item, em)
|
|
321
|
+
await this.decrypt(item as Record<string, unknown>, nestedMeta, em, {
|
|
322
|
+
syncOriginal: true,
|
|
323
|
+
seen: visited,
|
|
324
|
+
fallbackScope: nextFallback,
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
} catch (err) {
|
|
330
|
+
debug('⚠️ subscriber.deep_decrypt.error', {
|
|
331
|
+
entityId,
|
|
332
|
+
message: (err as Error)?.message ?? String(err),
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async beforeCreate(args: EventArgs<any>) {
|
|
338
|
+
await this.encrypt(args.entity as Record<string, unknown>, args.meta, args.em, args.changeSet as any)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async beforeUpdate(args: EventArgs<any>) {
|
|
342
|
+
await this.decrypt(args.entity as Record<string, unknown>, args.meta, args.em)
|
|
343
|
+
await this.encrypt(args.entity as Record<string, unknown>, args.meta, args.em, args.changeSet as any)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async afterCreate(args: EventArgs<any>) {
|
|
347
|
+
await this.decrypt(args.entity as Record<string, unknown>, args.meta, args.em, { syncOriginal: true })
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async afterUpdate(args: EventArgs<any>) {
|
|
351
|
+
await this.decrypt(args.entity as Record<string, unknown>, args.meta, args.em, { syncOriginal: true })
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async afterUpsert(args: EventArgs<any>) {
|
|
355
|
+
await this.decrypt(args.entity as Record<string, unknown>, args.meta, args.em, { syncOriginal: true })
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async onLoad(args: EventArgs<any>) {
|
|
359
|
+
await this.decrypt(args.entity as Record<string, unknown>, args.meta, args.em, { syncOriginal: true })
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async afterFind(args: EventArgs<any> & { entities?: unknown[] }) {
|
|
363
|
+
const entities = Array.isArray(args.entities) ? args.entities : []
|
|
364
|
+
for (const entity of entities) {
|
|
365
|
+
await this.decrypt(entity as Record<string, unknown>, args.meta, args.em, { syncOriginal: true })
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function registerTenantEncryptionSubscriber(
|
|
371
|
+
em: { getEventManager?: () => { registerSubscriber?: (subscriber: EventSubscriber<any>) => void } } | null | undefined,
|
|
372
|
+
service: TenantDataEncryptionService,
|
|
373
|
+
): void {
|
|
374
|
+
const eventManager = em?.getEventManager?.()
|
|
375
|
+
if (!eventManager || typeof eventManager.registerSubscriber !== 'function') return
|
|
376
|
+
if (registeredEventManagers.has(eventManager)) return
|
|
377
|
+
eventManager.registerSubscriber(new TenantEncryptionSubscriber(service))
|
|
378
|
+
registeredEventManagers.add(eventManager)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export async function decryptEntitiesWithFallbackScope(
|
|
382
|
+
targets: unknown | unknown[],
|
|
383
|
+
{
|
|
384
|
+
em,
|
|
385
|
+
tenantId,
|
|
386
|
+
organizationId,
|
|
387
|
+
encryptionService,
|
|
388
|
+
}: {
|
|
389
|
+
em: { getMetadata?: () => any; getComparator?: () => any }
|
|
390
|
+
tenantId?: string | null
|
|
391
|
+
organizationId?: string | null
|
|
392
|
+
encryptionService?: TenantDataEncryptionService | null
|
|
393
|
+
},
|
|
394
|
+
): Promise<void> {
|
|
395
|
+
if (!isTenantDataEncryptionEnabled()) return
|
|
396
|
+
const list = Array.isArray(targets) ? targets : [targets]
|
|
397
|
+
if (!list.length) return
|
|
398
|
+
const service = encryptionService ?? resolveTenantEncryptionService(em as any)
|
|
399
|
+
if (!service || !service.isEnabled()) return
|
|
400
|
+
const subscriber = new TenantEncryptionSubscriber(service)
|
|
401
|
+
const fallback: Scope | undefined =
|
|
402
|
+
tenantId || organizationId
|
|
403
|
+
? {
|
|
404
|
+
tenantId: tenantId ?? null,
|
|
405
|
+
organizationId: organizationId ?? null,
|
|
406
|
+
}
|
|
407
|
+
: undefined
|
|
408
|
+
for (const entity of list) {
|
|
409
|
+
if (!entity || typeof entity !== 'object') continue
|
|
410
|
+
const meta = (entity as any).__meta ?? (entity as any).__helper?.__meta
|
|
411
|
+
await subscriber.decryptEntityGraph(entity as Record<string, unknown>, meta, em as any, {
|
|
412
|
+
syncOriginal: true,
|
|
413
|
+
fallbackScope: fallback,
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
}
|