In my NestJS project I use TypeORM with external schema definition. I want to use a ValueTransformer to encrypt sensitive data in some database columns, e.g. the email of the user should be encrypted. The ValueTransformer depends on an encryption service from another module. I can't figure out how to inject that service via DI.
export const UserSchema = new EntitySchema<User>(<EntitySchemaOptions<User>>{
name: 'user',
columns: {
email: {
type: 'varchar',
nullable: false,
transformer: new EncryptedValueTransformer()
} as EntitySchemaColumnOptions,
},
});
export class EncryptedValueTransformer implements ValueTransformer {
@Inject()
private cryptoService: CryptoService
public to(value: unknown): string | unknown {
// error: cryptoService undefined
return this.cryptoService.encrypt(value);
}
public from(value: unknown): unknown {
// error: cryptoService undefined
return this.cryptoService.decrypt(value);
}
}
@Injectable()
export class CryptoService {
public constructor(@Inject('CONFIG_OPTIONS') private options: CryptoModuleOptions) {
// options contain a secret key from process.env
}
public encrypt(data: string): string | undefined | null { ... }
public decrypt(data: string): string | undefined | null { ... }
}
@Module({})
export class CryptoModule {
public static register(options: CryptoModuleOptions): DynamicModule {
return {
module: CryptoModule,
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: options,
},
CryptoService,
],
exports: [CryptoService],
};
}
}
@Module({
imports: [
DomainModule,
CryptoModule.register({ secretKey: process.env.ENCRYPTION_KEY }),
TypeOrmModule.forFeature([
UserSchema,
UserTypeOrmRepository,
]),
],
providers: [
UserRepositoryProvider,
UserRepositoryTypeOrmAdapter,
],
exports: [
UserRepositoryProvider,
UserRepositoryTypeOrmAdapter,
],
})
export class PersistenceModule {}
With the above code the CryptoService instance in EncryptedValueTransformer is undefined. I searched related issues. According to this post https://stackoverflow.com/a/57593989/11964644 NestJS needs some context for DI to work. If that context is not given, you can manually provide the dependency.
My workaround now is this:
export class EncryptedValueTransformer implements ValueTransformer {
private cryptoService: CryptoService
public constructor() {
// tightly coupled now an process.env.ENCRYPTION_KEY still undefined at instantiation time
this.cryptoService = new CryptoService({ secretKey: process.env.ENCRYPTION_KEY });
}
}
But with this workaround the process.env variable is not yet resolvable at the point where the class is being instantiated, so I need to modify CryptoService in a way that it reads the env variable at runtime itself. And at this point the CryptoService is not reusable anymore.
How can I inject the CryptoService into the EncryptedValueTransformer or into UserSchema with this external schema setup? Or any better way to solve this?