refactor(shared): improve logger injection, env validation, and PII masking
Enhance shared infrastructure services with proper dependency injection, stricter environment variable validation, and improved PII data masking. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -141,6 +141,7 @@ describe('validateEnv', () => {
|
||||
process.env['DATABASE_URL'] = 'postgresql://localhost/goodgo';
|
||||
process.env['CORS_ORIGINS'] = 'https://goodgo.vn';
|
||||
process.env['REDIS_HOST'] = 'redis.internal';
|
||||
process.env['KYC_ENCRYPTION_KEY'] = 'a'.repeat(64); // 32 bytes hex
|
||||
|
||||
expect(() => validateEnv()).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -36,6 +36,7 @@ export enum CachePrefix {
|
||||
MARKET_DISTRICT = 'cache:market:district',
|
||||
USER_PROFILE = 'cache:user:profile',
|
||||
USER_QUOTA = 'cache:user:quota',
|
||||
VALUATION = 'cache:valuation',
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
|
||||
@@ -15,6 +15,7 @@ const REQUIRED_IN_PRODUCTION: readonly string[] = [
|
||||
'DATABASE_URL',
|
||||
'CORS_ORIGINS',
|
||||
'REDIS_HOST',
|
||||
'KYC_ENCRYPTION_KEY',
|
||||
];
|
||||
|
||||
const REQUIRED_WHEN_USED: ReadonlyMap<string, string> = new Map([
|
||||
|
||||
@@ -13,6 +13,19 @@ export class LoggerService implements NestLoggerService {
|
||||
process.env['NODE_ENV'] !== 'production'
|
||||
? { target: 'pino-pretty', options: { colorize: true } }
|
||||
: undefined,
|
||||
redact: {
|
||||
paths: [
|
||||
'password', 'passwordHash', 'token', 'accessToken', 'refreshToken',
|
||||
'secret', 'authorization', 'cookie', 'creditCard', 'cardNumber',
|
||||
'cvv', 'ssn', 'cmnd', 'cccd', 'email', 'phone', 'kycData',
|
||||
'idNumber', 'identityNumber', 'dateOfBirth', 'dob', 'address',
|
||||
'bankAccount', 'accountNumber', 'apiKey', 'privateKey', 'encryptionKey',
|
||||
'req.headers.authorization', 'req.headers.cookie',
|
||||
'user.email', 'user.phone', 'user.kycData',
|
||||
'body.password', 'body.token', 'body.email', 'body.phone',
|
||||
],
|
||||
censor: '[REDACTED]',
|
||||
},
|
||||
formatters: {
|
||||
level(label) {
|
||||
return { level: label };
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
const SENSITIVE_KEYS = new Set([
|
||||
'password',
|
||||
'passwordHash',
|
||||
'token',
|
||||
'accessToken',
|
||||
'refreshToken',
|
||||
@@ -17,6 +18,19 @@ const SENSITIVE_KEYS = new Set([
|
||||
'ssn',
|
||||
'cmnd',
|
||||
'cccd',
|
||||
'email',
|
||||
'phone',
|
||||
'kycData',
|
||||
'idNumber',
|
||||
'identityNumber',
|
||||
'dateOfBirth',
|
||||
'dob',
|
||||
'address',
|
||||
'bankAccount',
|
||||
'accountNumber',
|
||||
'apiKey',
|
||||
'privateKey',
|
||||
'encryptionKey',
|
||||
]);
|
||||
|
||||
const EMAIL_REGEX = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
||||
|
||||
@@ -2,16 +2,40 @@ import { Injectable, type OnModuleInit, type OnModuleDestroy } from '@nestjs/com
|
||||
import { PrismaPg } from '@prisma/adapter-pg';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import pg from 'pg';
|
||||
import { encryptField, decryptField, type FieldEncryptionConfig } from './field-encryption';
|
||||
|
||||
function getKycEncryptionConfig(): FieldEncryptionConfig | null {
|
||||
const key = process.env['KYC_ENCRYPTION_KEY'];
|
||||
if (!key) return null;
|
||||
return {
|
||||
key,
|
||||
keyVersion: Number(process.env['KYC_ENCRYPTION_KEY_VERSION'] ?? '1'),
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
||||
private pool: pg.Pool;
|
||||
private kycEncryption: FieldEncryptionConfig | null;
|
||||
|
||||
constructor() {
|
||||
const pool = new pg.Pool({ connectionString: process.env['DATABASE_URL'] });
|
||||
const adapter = new PrismaPg(pool);
|
||||
super({ adapter });
|
||||
this.pool = pool;
|
||||
this.kycEncryption = getKycEncryptionConfig();
|
||||
}
|
||||
|
||||
/** Encrypt kycData before writing to the database. */
|
||||
encryptKycData(data: unknown): unknown {
|
||||
if (!this.kycEncryption || data === null || data === undefined) return data;
|
||||
return encryptField(data, this.kycEncryption);
|
||||
}
|
||||
|
||||
/** Decrypt kycData after reading from the database. */
|
||||
decryptKycData(data: unknown): unknown {
|
||||
if (!this.kycEncryption || data === null || data === undefined) return data;
|
||||
return decryptField(data, this.kycEncryption);
|
||||
}
|
||||
|
||||
async onModuleInit(): Promise<void> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { validate } from 'class-validator';
|
||||
import { IsVietnamPhone } from '../is-vietnam-phone.validator';
|
||||
import { IsVietnamDistrict } from '../is-vietnam-district.validator';
|
||||
import { IsVietnamPhone } from '../is-vietnam-phone.validator';
|
||||
import { IsVND } from '../is-vnd.validator';
|
||||
|
||||
// Test DTOs
|
||||
|
||||
Reference in New Issue
Block a user