File size: 1,571 Bytes
4e23b01 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | import { randomBytes } from 'node:crypto';
import {
chmod,
mkdir,
open,
readFile,
rename,
rm,
stat,
} from 'node:fs/promises';
import { dirname } from 'node:path';
export class PrivateFileTooPermissiveError extends Error {
readonly code = 'EPRIVATE_FILE_TOO_PERMISSIVE';
constructor(
readonly filePath: string,
readonly mode: number,
) {
super(
`private file ${filePath} is too permissive (mode ${mode.toString(8).padStart(3, '0')}); expected 0600`,
);
this.name = 'PrivateFileTooPermissiveError';
}
}
export async function writePrivateFile(
filePath: string,
data: string | Buffer,
): Promise<void> {
const dir = dirname(filePath);
await mkdir(dir, { recursive: true, mode: 0o700 });
await chmod(dir, 0o700);
const tmp = `${filePath}.tmp.${randomBytes(8).toString('hex')}`;
let handle: Awaited<ReturnType<typeof open>> | undefined;
try {
handle = await open(tmp, 'w', 0o600);
await handle.chmod(0o600);
await handle.writeFile(data);
await handle.sync();
await handle.close();
handle = undefined;
await rename(tmp, filePath);
} catch (err) {
if (handle) {
await handle.close().catch(() => {});
}
await rm(tmp, { force: true }).catch(() => {});
throw err;
}
}
export async function readPrivateFile(filePath: string): Promise<Buffer> {
const info = await stat(filePath);
if (process.platform !== 'win32' && (info.mode & 0o077) !== 0) {
throw new PrivateFileTooPermissiveError(filePath, info.mode & 0o777);
}
return readFile(filePath);
}
|