diff --git a/deno.json b/deno.json index e8f7dc6b..6218dcda 100644 --- a/deno.json +++ b/deno.json @@ -62,6 +62,7 @@ "@std/path": "jsr:@std/path@^1.0.8", "@std/semver": "jsr:@std/semver@^1.0.5", "@std/testing": "jsr:@std/testing@^1.0.4", + "@types/node": "npm:@types/node@^24.5.1", "jsdom": "npm:jsdom@^25.0.1", "rollup": "npm:rollup@^4.27.3", "rollup-plugin-version-injector": "npm:rollup-plugin-version-injector@^1.3.3", diff --git a/deno.lock b/deno.lock index 8144658e..34fe6161 100644 --- a/deno.lock +++ b/deno.lock @@ -43,6 +43,8 @@ "npm:@rollup/plugin-node-resolve@^15.3.0": "15.3.0_rollup@4.27.3", "npm:@rollup/plugin-replace@^6.0.1": "6.0.1_rollup@4.27.3", "npm:@rollup/plugin-terser@~0.4.4": "0.4.4_rollup@4.27.3", + "npm:@types/node@*": "24.5.1", + "npm:@types/node@^24.5.1": "24.5.1", "npm:jsdom@^25.0.1": "25.0.1", "npm:rollup-plugin-version-injector@^1.3.3": "1.3.3", "npm:rollup@^4.27.3": "4.27.3", @@ -1261,6 +1263,12 @@ "@types/estree@1.0.6": { "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, + "@types/node@24.5.1": { + "integrity": "sha512-/SQdmUP2xa+1rdx7VwB9yPq8PaKej8TD5cQ+XfKDPWWC+VDJU4rvVVagXqKUzhKjtFoNA8rXDJAkCxQPAe00+Q==", + "dependencies": [ + "undici-types" + ] + }, "@types/resolve@1.20.2": { "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==" }, @@ -1814,6 +1822,9 @@ "typescript@5.6.3": { "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==" }, + "undici-types@7.12.0": { + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==" + }, "unicode-canonical-property-names-ecmascript@2.0.1": { "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==" }, @@ -1889,6 +1900,7 @@ "npm:@rollup/plugin-node-resolve@^15.3.0", "npm:@rollup/plugin-replace@^6.0.1", "npm:@rollup/plugin-terser@~0.4.4", + "npm:@types/node@^24.5.1", "npm:jsdom@^25.0.1", "npm:rollup-plugin-version-injector@^1.3.3", "npm:rollup@^4.27.3", diff --git a/packages/server/src/helpers/iso/isoBase64URL.ts b/packages/server/src/helpers/iso/isoBase64URL.ts index 207f285a..2acd7439 100644 --- a/packages/server/src/helpers/iso/isoBase64URL.ts +++ b/packages/server/src/helpers/iso/isoBase64URL.ts @@ -33,7 +33,12 @@ export function fromBuffer( buffer: Uint8Array_, to: 'base64' | 'base64url' = 'base64url', ): string { - return base64.fromArrayBuffer(buffer.buffer, to === 'base64url'); + /** + * Gracefully handle Uint8Array subclass types, like Node's Buffer, that can have a large + * ArrayBuffer backing it. + */ + const _normalized = new Uint8Array(buffer); + return base64.fromArrayBuffer(_normalized.buffer, to === 'base64url'); } /** diff --git a/packages/server/src/registration/generateRegistrationOptions.test.ts b/packages/server/src/registration/generateRegistrationOptions.test.ts index ae533ee3..da8c22ad 100644 --- a/packages/server/src/registration/generateRegistrationOptions.test.ts +++ b/packages/server/src/registration/generateRegistrationOptions.test.ts @@ -1,5 +1,6 @@ import { assertEquals, assertRejects } from '@std/assert'; import { returnsNext, stub } from '@std/testing/mock'; +import { Buffer } from 'node:buffer'; import { generateRegistrationOptions } from './generateRegistrationOptions.ts'; import { _generateChallengeInternals } from '../helpers/generateChallenge.ts'; @@ -386,3 +387,16 @@ Deno.test('should map "remoteDevice" authenticator preference to hint and attach assertEquals(options.hints, ['hybrid']); assertEquals(options.authenticatorSelection?.authenticatorAttachment, 'cross-platform'); }); + +Deno.test('should generate a reasonable user.id when passed a Node Buffer', async () => { + const options = await generateRegistrationOptions({ + rpID: 'not.real', + rpName: 'SimpleWebAuthn', + userName: 'usernameHere', + // @ts-ignore: Intentionally using a Node Buffer (which is a Uint8Array subclass) + userID: Buffer.from('someUserID', 'utf-8'), + }); + + assertEquals(options.user.id, 'c29tZVVzZXJJRA'); + assertEquals(isoBase64URL.toUTF8String(options.user.id), 'someUserID'); +});