feat(api): add field encryption, health check specs, and KYC encryption script
- Add field-level encryption service for PII data with AES-256-GCM - Add health check specs for Prisma and Redis indicators - Add MCP controller specs - Add encrypt-existing-kyc migration script for existing KYC data Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
79
scripts/encrypt-existing-kyc.ts
Normal file
79
scripts/encrypt-existing-kyc.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* One-time migration: encrypt existing plaintext kycData records.
|
||||
*
|
||||
* Usage:
|
||||
* KYC_ENCRYPTION_KEY=<hex-key> npx tsx scripts/encrypt-existing-kyc.ts [--dry-run]
|
||||
*
|
||||
* This script:
|
||||
* 1. Reads all User rows where kycData is not null
|
||||
* 2. Skips rows that are already encrypted (have the `enc:` prefix)
|
||||
* 3. Encrypts plaintext kycData using AES-256-GCM
|
||||
* 4. Updates each row in a transaction
|
||||
*/
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import {
|
||||
encryptField,
|
||||
isEncrypted,
|
||||
type FieldEncryptionConfig,
|
||||
} from '../apps/api/src/modules/shared/infrastructure/field-encryption';
|
||||
|
||||
async function main() {
|
||||
const key = process.env['KYC_ENCRYPTION_KEY'];
|
||||
if (!key) {
|
||||
console.error('ERROR: KYC_ENCRYPTION_KEY env var is required.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const dryRun = process.argv.includes('--dry-run');
|
||||
const config: FieldEncryptionConfig = {
|
||||
key,
|
||||
keyVersion: parseInt(process.env['KYC_ENCRYPTION_KEY_VERSION'] ?? '1', 10),
|
||||
};
|
||||
|
||||
// Use raw PrismaClient without encryption middleware to read plaintext
|
||||
const prisma = new PrismaClient();
|
||||
await prisma.$connect();
|
||||
|
||||
try {
|
||||
const users = await prisma.user.findMany({
|
||||
where: { kycData: { not: null } },
|
||||
select: { id: true, kycData: true },
|
||||
});
|
||||
|
||||
console.log(`Found ${users.length} users with kycData.`);
|
||||
|
||||
let encrypted = 0;
|
||||
let skipped = 0;
|
||||
|
||||
for (const user of users) {
|
||||
if (isEncrypted(user.kycData)) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const encryptedValue = encryptField(user.kycData, config);
|
||||
|
||||
if (dryRun) {
|
||||
console.log(`[DRY RUN] Would encrypt kycData for user ${user.id}`);
|
||||
} else {
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { kycData: encryptedValue },
|
||||
});
|
||||
}
|
||||
encrypted++;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${dryRun ? '[DRY RUN] ' : ''}Done. Encrypted: ${encrypted}, Already encrypted: ${skipped}`,
|
||||
);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Migration failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user