Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/utils/integrations/hrFieldCrypto.server.ts
T
2026-07-08 16:15:37 +02:00

72 lines
3.0 KiB
TypeScript

// P17 HR-Hub (AK-P17a) — field-level crypto SEAM for sensitive HR values.
//
// AD-8 mandates a PER-TENANT DEK / KMS envelope for the password-safe secrets and
// the employee emergency contact. This module is the seam every HR service goes
// through, so the underlying envelope provider can be swapped without touching the
// services.
//
// The per-tenant envelope landed with R-P5 (`getTenantFieldCrypto` in
// @hivegrid/db, AD-8). `getHrFieldCrypto()` now binds to it, which lights up the
// previously deferred sensitive-field paths (vault entry secrets + emergency
// contacts). `deferredHrFieldCrypto` / `HrCryptoNotProvisionedError` are retained
// as a fail-closed scaffold: a future binding may reject with a Klartext (M002)
// error rather than silently store plaintext if the master key is unconfigured.
// Service functions still accept an explicit `HrFieldCrypto` override so unit
// tests can inject a working in-memory provider.
//
// IMPORTANT: this is a server-only module (node:crypto-adjacent). Never import it
// from packages/ui-app or view-models.
import { getTenantFieldCrypto } from "@hivegrid/db/crypto/tenant-envelope.server";
/**
* Field-level crypto contract: encrypts/decrypts a single sensitive string for a
* given tenant. Implementations MUST scope the key material per tenant (AD-8) so
* a tenant deletion can crypto-shred only that tenant's data (M165).
*/
export interface HrFieldCrypto {
/** Encrypt `plaintext` for `tenantId`; returns a self-describing envelope string. */
encrypt(tenantId: string, plaintext: string): Promise<string>;
/** Decrypt an envelope string produced by {@link encrypt} for `tenantId`. */
decrypt(tenantId: string, ciphertext: string): Promise<string>;
}
/**
* Domain error raised when a sensitive HR value is read/written before the AD-8
* per-tenant envelope layer (separate ticket) is wired. Carries an i18n message
* key (M002 Klartext) so routes surface "this needs the encryption module" — not
* a bare 500.
*/
export class HrCryptoNotProvisionedError extends Error {
readonly messageKey = "hr.error.cryptoNotProvisioned";
constructor() {
super("hr.error.cryptoNotProvisioned");
this.name = "HrCryptoNotProvisionedError";
}
}
/**
* Production binding until the AD-8 envelope ticket lands: every call defers
* (throws {@link HrCryptoNotProvisionedError}). Swapping this for the real
* per-tenant envelope is the ONLY change that follow-up ticket makes here.
*/
export const deferredHrFieldCrypto: HrFieldCrypto = {
encrypt() {
return Promise.reject(new HrCryptoNotProvisionedError());
},
decrypt() {
return Promise.reject(new HrCryptoNotProvisionedError());
},
};
/**
* The crypto binding HR services use by default: the R-P5 per-tenant envelope
* provider (AD-8). `TenantFieldCrypto` is structurally identical to
* `HrFieldCrypto`, so it satisfies this contract directly. Service functions
* accept an explicit `HrFieldCrypto` override so unit tests can inject a working
* in-memory provider.
*/
export function getHrFieldCrypto(): HrFieldCrypto {
return getTenantFieldCrypto();
}