feat: add dual filesystem storage for FIEL credentials

Save encrypted .cer, .key, and metadata to FIEL_STORAGE_PATH alongside
the existing DB storage. Each file has separate .iv and .tag sidecar files.
Filesystem failure is non-blocking (logs warning, DB remains primary).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Consultoria AS
2026-03-15 23:32:42 +00:00
parent d8f9f92389
commit 12dda005af

View File

@@ -1,6 +1,9 @@
import { Credential } from '@nodecfdi/credentials/node';
import { writeFile, mkdir } from 'fs/promises';
import { join } from 'path';
import { prisma } from '../config/database.js';
import { encryptFielCredentials, decryptFielCredentials } from './sat/sat-crypto.service.js';
import { env } from '../config/env.js';
import { encryptFielCredentials, encrypt, decryptFielCredentials } from './sat/sat-crypto.service.js';
import type { FielStatus } from '@horux/shared';
/**
@@ -110,6 +113,37 @@ export async function uploadFiel(
},
});
// Save encrypted files to filesystem (dual storage)
try {
const fielDir = join(env.FIEL_STORAGE_PATH, rfc.toUpperCase());
await mkdir(fielDir, { recursive: true, mode: 0o700 });
// Re-encrypt for filesystem (independent keys from DB)
const fsEncrypted = encryptFielCredentials(cerData, keyData, password);
await writeFile(join(fielDir, 'certificate.cer.enc'), fsEncrypted.encryptedCer, { mode: 0o600 });
await writeFile(join(fielDir, 'certificate.cer.iv'), fsEncrypted.cerIv, { mode: 0o600 });
await writeFile(join(fielDir, 'certificate.cer.tag'), fsEncrypted.cerTag, { mode: 0o600 });
await writeFile(join(fielDir, 'private_key.key.enc'), fsEncrypted.encryptedKey, { mode: 0o600 });
await writeFile(join(fielDir, 'private_key.key.iv'), fsEncrypted.keyIv, { mode: 0o600 });
await writeFile(join(fielDir, 'private_key.key.tag'), fsEncrypted.keyTag, { mode: 0o600 });
// Encrypt and store metadata
const metadata = JSON.stringify({
serial: serialNumber,
validFrom: validFrom.toISOString(),
validUntil: validUntil.toISOString(),
uploadedAt: new Date().toISOString(),
rfc: rfc.toUpperCase(),
});
const metaEncrypted = encrypt(Buffer.from(metadata, 'utf-8'));
await writeFile(join(fielDir, 'metadata.json.enc'), metaEncrypted.encrypted, { mode: 0o600 });
await writeFile(join(fielDir, 'metadata.json.iv'), metaEncrypted.iv, { mode: 0o600 });
await writeFile(join(fielDir, 'metadata.json.tag'), metaEncrypted.tag, { mode: 0o600 });
} catch (fsError) {
console.error('[FIEL] Filesystem storage failed (DB storage OK):', fsError);
}
const daysUntilExpiration = Math.ceil(
(validUntil.getTime() - Date.now()) / (1000 * 60 * 60 * 24)
);