From 7d12d1425995c626d93935c03e42c8340e6d622e Mon Sep 17 00:00:00 2001 From: 4gray Date: Fri, 25 Sep 2026 22:31:39 +0200 Subject: [PATCH] fix(settings): compile the PIN hashing helper in the Node backend build The web backend compiles the shared interfaces library without DOM typings, so the DOM-only `SubtleCrypto` / `BufferSource` names broke its Docker build. The helper now describes the WebCrypto surface it needs structurally and reaches it through `globalThis`. Co-Authored-By: Claude Fable 5.1 --- .../src/lib/parental-lock-pin.util.ts | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/libs/shared/interfaces/src/lib/parental-lock-pin.util.ts b/libs/shared/interfaces/src/lib/parental-lock-pin.util.ts index 284fb2662..45bb3beeb 100644 --- a/libs/shared/interfaces/src/lib/parental-lock-pin.util.ts +++ b/libs/shared/interfaces/src/lib/parental-lock-pin.util.ts @@ -26,12 +26,42 @@ export function isValidParentalLockPin(pin: unknown): pin is string { ); } -function getSubtleCrypto(): SubtleCrypto { - const cryptoApi = globalThis.crypto; - if (!cryptoApi?.subtle) { +/** + * Structural view of the WebCrypto surface this file needs. Named DOM types + * (`SubtleCrypto`, `BufferSource`) are unavailable to the Node build of the + * web backend, which compiles this shared library as well. + */ +interface WebCryptoSubtleLike { + importKey( + format: 'raw', + keyData: Uint8Array, + algorithm: 'PBKDF2', + extractable: boolean, + keyUsages: ['deriveBits'] + ): Promise; + deriveBits( + algorithm: { + name: 'PBKDF2'; + hash: 'SHA-256'; + salt: Uint8Array; + iterations: number; + }, + baseKey: unknown, + length: number + ): Promise; +} + +interface WebCryptoLike { + subtle?: WebCryptoSubtleLike; + getRandomValues?(array: T): T; +} + +function getWebCrypto(): Required { + const cryptoApi = (globalThis as { crypto?: WebCryptoLike }).crypto; + if (!cryptoApi?.subtle || !cryptoApi.getRandomValues) { throw new Error('WebCrypto is unavailable in this runtime.'); } - return cryptoApi.subtle; + return cryptoApi as Required; } function bytesToBase64(bytes: Uint8Array): string { @@ -56,7 +86,7 @@ async function derivePinHash( salt: Uint8Array, iterations: number ): Promise { - const subtle = getSubtleCrypto(); + const subtle = getWebCrypto().subtle; const keyMaterial = await subtle.importKey( 'raw', new TextEncoder().encode(pin), @@ -68,7 +98,7 @@ async function derivePinHash( { name: 'PBKDF2', hash: 'SHA-256', - salt: salt as BufferSource, + salt, iterations, }, keyMaterial, @@ -82,7 +112,7 @@ export async function hashParentalLockPin(pin: string): Promise { throw new Error('PIN must be 4 to 8 digits.'); } const salt = new Uint8Array(PIN_HASH_SALT_BYTES); - globalThis.crypto.getRandomValues(salt); + getWebCrypto().getRandomValues(salt); const hash = await derivePinHash(pin, salt, PIN_HASH_ITERATIONS); return [ PIN_HASH_VERSION,