From 88e5140349c699f8c8049f84da8a2f4be6c42b16 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Tue, 20 Feb 2024 13:36:45 +0100 Subject: [PATCH] WIP encrypted custom fields Exploring some ideas for #2648 --- .../admin-ui/src/lib/react/src/public_api.ts | 2 + packages/core/src/bootstrap.ts | 2 +- .../config/custom-field/custom-field-types.ts | 4 +- .../core/src/connection/connection.module.ts | 8 +++- .../src/{entity => connection}/subscribers.ts | 25 ++++++++++++- .../core/src/entity/base/base.entity.spec.ts | 2 +- .../entity/register-custom-entity-fields.ts | 37 ++++++++++++++++++- packages/dev-server/dev-config.ts | 10 ++++- 8 files changed, 82 insertions(+), 8 deletions(-) rename packages/core/src/{entity => connection}/subscribers.ts (73%) diff --git a/packages/admin-ui/src/lib/react/src/public_api.ts b/packages/admin-ui/src/lib/react/src/public_api.ts index 2ee8e6a5c8..fce1078ccf 100644 --- a/packages/admin-ui/src/lib/react/src/public_api.ts +++ b/packages/admin-ui/src/lib/react/src/public_api.ts @@ -11,11 +11,13 @@ export * from './react-components/FormField'; export * from './react-components/Link'; export * from './react-components/PageBlock'; export * from './react-components/PageDetailLayout'; +export * from './react-components/RichTextEditor'; export * from './react-hooks/use-detail-component-data'; export * from './react-hooks/use-form-control'; export * from './react-hooks/use-injector'; export * from './react-hooks/use-page-metadata'; export * from './react-hooks/use-query'; +export * from './react-hooks/use-rich-text-editor'; export * from './react-hooks/use-route-params'; export * from './register-react-custom-detail-component'; export * from './register-react-data-table-component'; diff --git a/packages/core/src/bootstrap.ts b/packages/core/src/bootstrap.ts index b1a8bc1365..3dbb594d4a 100644 --- a/packages/core/src/bootstrap.ts +++ b/packages/core/src/bootstrap.ts @@ -205,7 +205,7 @@ export async function preBootstrapConfig( } const entities = await getAllEntities(userConfig); - const { coreSubscribersMap } = await import('./entity/subscribers.js'); + const { coreSubscribersMap } = await import('./connection/subscribers.js'); await setConfig({ dbConnectionOptions: { entities, diff --git a/packages/core/src/config/custom-field/custom-field-types.ts b/packages/core/src/config/custom-field/custom-field-types.ts index 12cabea311..6ab0ba3087 100644 --- a/packages/core/src/config/custom-field/custom-field-types.ts +++ b/packages/core/src/config/custom-field/custom-field-types.ts @@ -91,7 +91,9 @@ export type TypedCustomFieldConfig< > = BaseTypedCustomFieldConfig & (TypedCustomSingleFieldConfig | TypedCustomListFieldConfig); -export type StringCustomFieldConfig = TypedCustomFieldConfig<'string', GraphQLStringCustomFieldConfig>; +export type StringCustomFieldConfig = TypedCustomFieldConfig<'string', GraphQLStringCustomFieldConfig> & { + encryptionSecret?: string; +}; export type LocaleStringCustomFieldConfig = TypedCustomFieldConfig< 'localeString', GraphQLLocaleStringCustomFieldConfig diff --git a/packages/core/src/connection/connection.module.ts b/packages/core/src/connection/connection.module.ts index 3d33d02244..b4122a9983 100644 --- a/packages/core/src/connection/connection.module.ts +++ b/packages/core/src/connection/connection.module.ts @@ -5,6 +5,7 @@ import { DataSourceOptions } from 'typeorm'; import { ConfigModule } from '../config/config.module'; import { ConfigService } from '../config/config.service'; import { TypeOrmLogger } from '../config/logger/typeorm-logger'; +import { EncryptedCustomFieldSubscriber } from './subscribers'; import { TransactionSubscriber } from './transaction-subscriber'; import { TransactionWrapper } from './transaction-wrapper'; @@ -14,7 +15,12 @@ let defaultTypeOrmModule: DynamicModule; @Module({ imports: [ConfigModule], - providers: [TransactionalConnection, TransactionSubscriber, TransactionWrapper], + providers: [ + TransactionalConnection, + TransactionSubscriber, + TransactionWrapper, + EncryptedCustomFieldSubscriber, + ], exports: [TransactionalConnection, TransactionSubscriber, TransactionWrapper], }) export class ConnectionModule { diff --git a/packages/core/src/entity/subscribers.ts b/packages/core/src/connection/subscribers.ts similarity index 73% rename from packages/core/src/entity/subscribers.ts rename to packages/core/src/connection/subscribers.ts index f7b01589fb..51be327db5 100644 --- a/packages/core/src/entity/subscribers.ts +++ b/packages/core/src/connection/subscribers.ts @@ -1,6 +1,8 @@ -import { EntitySubscriberInterface, EventSubscriber, InsertEvent } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { EntitySubscriberInterface, EventSubscriber, InsertEvent, LoadEvent, UpdateEvent } from 'typeorm'; import { CalculatedColumnDefinition, CALCULATED_PROPERTIES } from '../common/calculated-decorator'; +import { ConfigService } from '../config/index'; interface EntityPrototype { [CALCULATED_PROPERTIES]: CalculatedColumnDefinition[]; @@ -47,9 +49,30 @@ export class CalculatedPropertySubscriber implements EntitySubscriberInterface { } } +@Injectable() +@EventSubscriber() +export class EncryptedCustomFieldSubscriber implements EntitySubscriberInterface { + // constructor(private configService: ConfigService) {} + + listenTo(): Function | string {} + + afterLoad(entity: any, event?: LoadEvent): Promise | void { + // console.log(`afterLoad: ${entity}`); + } + + beforeInsert(event: InsertEvent): Promise | void { + // console.log(`beforeInsert: ${event}`); + } + + beforeUpdate(event: UpdateEvent): Promise | void { + // console.log(`beforeUpdate: ${event}`); + } +} + /** * A map of the core TypeORM Subscribers. */ export const coreSubscribersMap = { CalculatedPropertySubscriber, + // EncryptedCustomFieldSubscriber, }; diff --git a/packages/core/src/entity/base/base.entity.spec.ts b/packages/core/src/entity/base/base.entity.spec.ts index b386f3b3bb..c0931e1222 100644 --- a/packages/core/src/entity/base/base.entity.spec.ts +++ b/packages/core/src/entity/base/base.entity.spec.ts @@ -2,7 +2,7 @@ import { DeepPartial } from '@vendure/common/lib/shared-types'; import { describe, expect, it } from 'vitest'; import { Calculated } from '../../common/index'; -import { CalculatedPropertySubscriber } from '../subscribers'; +import { CalculatedPropertySubscriber } from '../../connection/subscribers'; import { VendureEntity } from './base.entity'; diff --git a/packages/core/src/entity/register-custom-entity-fields.ts b/packages/core/src/entity/register-custom-entity-fields.ts index 4d5e76a999..7cbfa365aa 100644 --- a/packages/core/src/entity/register-custom-entity-fields.ts +++ b/packages/core/src/entity/register-custom-entity-fields.ts @@ -2,14 +2,19 @@ import { CustomFieldType } from '@vendure/common/lib/shared-types'; import { assertNever } from '@vendure/common/lib/shared-utils'; import { + AfterLoad, + BeforeUpdate, Column, ColumnOptions, ColumnType, DataSourceOptions, + EntitySubscriberInterface, + EventSubscriber, getMetadataArgsStorage, Index, JoinColumn, JoinTable, + LoadEvent, ManyToMany, ManyToOne, } from 'typeorm'; @@ -19,6 +24,7 @@ import { DateUtils } from 'typeorm/util/DateUtils'; import { CustomFieldConfig, CustomFields } from '../config/custom-field/custom-field-types'; import { Logger } from '../config/logger/vendure-logger'; import { VendureConfig } from '../config/vendure-config'; +import { VendureEntity } from './base/base.entity'; /** * The maximum length of the "length" argument of a MySQL varchar column. @@ -33,7 +39,8 @@ function registerCustomFieldsForEntity( entityName: keyof CustomFields, // eslint-disable-next-line @typescript-eslint/prefer-function-type ctor: { new (): any }, - translation = false, + translation: boolean, + targetEntity: Function, ) { const customFields = config.customFields && config.customFields[entityName]; const dbEngine = config.dbConnectionOptions.type; @@ -102,6 +109,25 @@ function registerCustomFieldsForEntity( // sufficient to add the `unique: true` property to the column options. Index({ unique: true })(instance, name); } + if (customField.type === 'string' && customField.encryptionSecret) { + @EventSubscriber() + class DynamicSubscriber implements EntitySubscriberInterface { + listenTo() { + return targetEntity; + } + + afterLoad(entity: any, event?: LoadEvent): Promise | void {} + + beforeInsert(event: any) { + console.log(`beforeInsert: ${event}`); + } + beforeUpdate(event: any) { + console.log(`beforeUpdate: ${event}`); + } + } + + (config.dbConnectionOptions.subscribers as any[]).push(DynamicSubscriber); + } } }; @@ -245,7 +271,13 @@ export function registerCustomEntityFields(config: VendureConfig) { const customFieldsMetadata = getCustomFieldsMetadata(entityName); const customFieldsClass = customFieldsMetadata.type(); if (customFieldsClass && typeof customFieldsClass !== 'string') { - registerCustomFieldsForEntity(config, entityName, customFieldsClass as any); + registerCustomFieldsForEntity( + config, + entityName, + customFieldsClass as any, + false, + customFieldsMetadata.target as Function, + ); } const translationsMetadata = metadataArgsStorage .filterRelations(customFieldsMetadata.target) @@ -263,6 +295,7 @@ export function registerCustomEntityFields(config: VendureConfig) { entityName, customFieldsTranslationClass as any, true, + customFieldsMetadata.target as Function, ); } } else { diff --git a/packages/dev-server/dev-config.ts b/packages/dev-server/dev-config.ts index 7cb3fc7c12..01493c2b1c 100644 --- a/packages/dev-server/dev-config.ts +++ b/packages/dev-server/dev-config.ts @@ -62,7 +62,15 @@ export const devConfig: VendureConfig = { paymentMethodHandlers: [dummyPaymentHandler], }, - customFields: {}, + customFields: { + Product: [ + { + name: 'secret', + type: 'string', + encryptionSecret: 'foo', + }, + ], + }, logger: new DefaultLogger({ level: LogLevel.Verbose }), importExportOptions: { importAssetsDir: path.join(__dirname, 'import-assets'),