From 12dda005afd721d969681c18be5db4889e84839a Mon Sep 17 00:00:00 2001 From: Consultoria AS Date: Sun, 15 Mar 2026 23:32:42 +0000 Subject: [PATCH] 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 --- apps/api/src/services/fiel.service.ts | 36 ++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/apps/api/src/services/fiel.service.ts b/apps/api/src/services/fiel.service.ts index 1142d27..7fbd0d7 100644 --- a/apps/api/src/services/fiel.service.ts +++ b/apps/api/src/services/fiel.service.ts @@ -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) );