Files
ABIANAPP_NODE_PRODUCCION/src/services/driverLicenseStorage.js
T

106 lines
3.0 KiB
JavaScript

const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const { decryptBuffer } = require('./driverLicenseCrypto');
const DEFAULT_STORAGE_DIR = path.resolve(__dirname, '..', '..', 'secure_storage', 'driver-license');
const getStorageDir = () =>
process.env.DRIVER_LICENSE_STORAGE_DIR || DEFAULT_STORAGE_DIR;
const assertSafeStorageKey = (storageKey) => {
const normalized = String(storageKey || '').trim();
if (!normalized || normalized.includes('..') || normalized.startsWith('/')) {
throw new Error('Invalid storage key');
}
return normalized;
};
const resolveStoragePath = (storageKey) => {
const safeStorageKey = assertSafeStorageKey(storageKey);
const baseDir = path.resolve(getStorageDir());
const candidatePath = path.resolve(baseDir, ...safeStorageKey.split('/'));
if (candidatePath !== baseDir && !candidatePath.startsWith(`${baseDir}${path.sep}`)) {
throw new Error('Invalid storage key path');
}
return candidatePath;
};
const ensureStorageDirExists = async (absolutePath) => {
await fs.promises.mkdir(path.dirname(absolutePath), {
recursive: true,
mode: 0o700
});
};
const generateStorageKey = () => {
const now = new Date();
const year = String(now.getUTCFullYear());
const month = String(now.getUTCMonth() + 1).padStart(2, '0');
const uuid = crypto.randomUUID();
const hash = crypto
.createHash('sha256')
.update(`${uuid}:${Date.now()}:${crypto.randomBytes(32).toString('hex')}`)
.digest('hex')
.slice(0, 24);
return path.posix.join(year, month, `${uuid}_${hash}.bin`);
};
const persistEncryptedBuffer = async (encryptedBuffer) => {
if (!Buffer.isBuffer(encryptedBuffer)) {
throw new Error('encryptedBuffer must be a Buffer');
}
const storageKey = generateStorageKey();
const absolutePath = resolveStoragePath(storageKey);
await ensureStorageDirExists(absolutePath);
await fs.promises.writeFile(absolutePath, encryptedBuffer, {
mode: 0o600,
flag: 'wx'
});
return {
storageKey
};
};
const readEncryptedBuffer = async (storageKey) => {
const absolutePath = resolveStoragePath(storageKey);
return fs.promises.readFile(absolutePath);
};
const readDecryptedBuffer = async (storageKey, encryptionMetadata) => {
const ciphertext = await readEncryptedBuffer(storageKey);
return decryptBuffer({
ciphertext,
ivHex: encryptionMetadata?.ivHex,
authTagHex: encryptionMetadata?.authTagHex,
algorithm: encryptionMetadata?.algorithm
});
};
const removeStoredFile = async (storageKey) => {
try {
await fs.promises.unlink(resolveStoragePath(storageKey));
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}
};
module.exports = {
getStorageDir,
persistEncryptedBuffer,
readEncryptedBuffer,
readDecryptedBuffer,
removeStoredFile
};