Fix passenger startup and clean tracked generated files
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user