54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
import type { Db } from "@wrnexus/db";
|
|
import { decrypt, encrypt } from "./index.ts";
|
|
|
|
export interface SecretResourceOptions<T extends Record<string, unknown>> {
|
|
db: () => Db;
|
|
table: string;
|
|
key: string | (() => string);
|
|
encrypted: readonly (keyof T & string)[];
|
|
hidden?: readonly (keyof T & string)[];
|
|
id?: string;
|
|
}
|
|
|
|
const SAFE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
|
/** Encrypt selected database fields and guarantee hidden fields never leave `safe()`. */
|
|
export function defineSecretResource<T extends Record<string, unknown>>(
|
|
options: SecretResourceOptions<T>,
|
|
) {
|
|
const checked = (value: string) => {
|
|
if (!SAFE.test(value)) throw new TypeError(`Unsafe secret resource identifier: ${value}`);
|
|
return value;
|
|
};
|
|
const table = checked(options.table);
|
|
const id = checked(options.id ?? "id");
|
|
const encrypted = options.encrypted.map(checked);
|
|
const hidden = new Set((options.hidden ?? options.encrypted).map(checked));
|
|
const key = () => (typeof options.key === "function" ? options.key() : options.key);
|
|
return {
|
|
async seal(input: T): Promise<T> {
|
|
const output = { ...input };
|
|
const writable = output as Record<string, unknown>;
|
|
for (const field of encrypted)
|
|
if (writable[field] !== undefined)
|
|
writable[field] = await encrypt(String(writable[field]), key());
|
|
return output;
|
|
},
|
|
async open(input: T): Promise<T> {
|
|
const output = { ...input };
|
|
const writable = output as Record<string, unknown>;
|
|
for (const field of encrypted)
|
|
if (writable[field]) writable[field] = await decrypt(String(writable[field]), key());
|
|
return output;
|
|
},
|
|
safe(input: T): Partial<T> {
|
|
return Object.fromEntries(
|
|
Object.entries(input).filter(([field]) => !hidden.has(field)),
|
|
) as Partial<T>;
|
|
},
|
|
async find(value: string | number): Promise<T | null> {
|
|
return options.db().one<T>(`SELECT * FROM ${table} WHERE ${id} = ?`, [value]);
|
|
},
|
|
};
|
|
}
|