From 7819df73ebf6391377ef3e7623948d8329ac47f5 Mon Sep 17 00:00:00 2001 From: Filip Skokan <panva.ip@gmail.com> Date: Tue, 13 Apr 2021 22:00:58 +0200 Subject: [PATCH] fix: isObject helper in different vm contexts or jest re-assigned globals closes #178 --- src/jwks/remote.ts | 5 +++-- src/lib/is_object.ts | 16 +++++++++++++++- src/lib/jwt_claims_set.ts | 3 ++- test/jwk/jwk2key.test.mjs | 12 +++++++----- test/jwk/thumbprint.test.mjs | 10 ++++++---- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/jwks/remote.ts b/src/jwks/remote.ts index 6779ea6162..04335f3cda 100644 --- a/src/jwks/remote.ts +++ b/src/jwks/remote.ts @@ -11,6 +11,7 @@ import { JWKSMultipleMatchingKeys, } from '../util/errors.js' import fetchJson from '../runtime/fetch.js' +import isObject from '../lib/is_object.js' function getKtyFromAlg(alg: string) { switch (alg.substr(0, 2)) { @@ -55,8 +56,8 @@ export interface RemoteJWKSetOptions { agent?: https.Agent | http.Agent } -function isJWKLike(key: object): key is JWK { - return key && typeof key === 'object' +function isJWKLike(key: unknown): key is JWK { + return isObject(key) } class RemoteJWKSet { diff --git a/src/lib/is_object.ts b/src/lib/is_object.ts index 4b041f1cf0..f5afee9b47 100644 --- a/src/lib/is_object.ts +++ b/src/lib/is_object.ts @@ -1,3 +1,17 @@ +function isObjectLike(value: unknown) { + return typeof value === 'object' && value !== null +} + export default function isObject(input: unknown): boolean { - return typeof input === 'object' && !!input && input.constructor === Object + if (!isObjectLike(input) || Object.prototype.toString.call(input) !== '[object Object]') { + return false + } + if (Object.getPrototypeOf(input) === null) { + return true + } + let proto = input + while (Object.getPrototypeOf(proto) !== null) { + proto = Object.getPrototypeOf(proto) + } + return Object.getPrototypeOf(input) === proto } diff --git a/src/lib/jwt_claims_set.ts b/src/lib/jwt_claims_set.ts index d0f60ba9f2..efa6c8c1fe 100644 --- a/src/lib/jwt_claims_set.ts +++ b/src/lib/jwt_claims_set.ts @@ -8,6 +8,7 @@ import { JWTClaimValidationFailed, JWTExpired, JWTInvalid } from '../util/errors import { decoder } from './buffer_utils.js' import epoch from './epoch.js' import secs from './secs.js' +import isObject from './is_object.js' const normalizeTyp = (value: string) => value.toLowerCase().replace(/^application\//, '') @@ -46,7 +47,7 @@ export default ( // } - if (typeof payload !== 'object' || !payload || Array.isArray(payload)) { + if (!isObject(payload)) { throw new JWTInvalid('JWT Claims Set must be a top-level JSON object') } diff --git a/test/jwk/jwk2key.test.mjs b/test/jwk/jwk2key.test.mjs index dec376adf0..ce03a3c98f 100644 --- a/test/jwk/jwk2key.test.mjs +++ b/test/jwk/jwk2key.test.mjs @@ -35,10 +35,12 @@ Promise.all([import(`${keyRoot}/jwk/parse`), import(`${keyRoot}/jwk/from_key_lik instanceOf: TypeError, message: 'JWK must be an object', }); - await t.throwsAsync(parseJwk(Object.create(null)), { - instanceOf: TypeError, - message: 'JWK must be an object', - }); + const nullPrototype = Object.create(null); + nullPrototype.crv = 'P-256'; + nullPrototype.kty = 'EC'; + nullPrototype.x = 'q3zAwR_kUwtdLEwtB2oVfucXiLHmEhu9bJUFYjJxYGs'; + nullPrototype.y = '8h0D-ONoU-iZqrq28TyUxEULxuGwJZGMJYTMbeMshvI'; + await t.notThrowsAsync(parseJwk(nullPrototype, 'ES256')); }); test('JWK kty must be recognized', async (t) => { @@ -150,7 +152,7 @@ Promise.all([import(`${keyRoot}/jwk/parse`), import(`${keyRoot}/jwk/from_key_lik await t.notThrowsAsync(async () => { let key = await parseJwk({ ...jwk, ext: true }); t.is(key.type, 'private'); - const exportedJwk = await fromKeyLike(key) + const exportedJwk = await fromKeyLike(key); key = await parseJwk(exportedJwk, jwk.alg); t.is(key.type, 'private'); }); diff --git a/test/jwk/thumbprint.test.mjs b/test/jwk/thumbprint.test.mjs index 1d36f4a931..f1f7c058e3 100644 --- a/test/jwk/thumbprint.test.mjs +++ b/test/jwk/thumbprint.test.mjs @@ -48,10 +48,12 @@ import(`${keyRoot}/jwk/thumbprint`).then( instanceOf: TypeError, message: 'JWK must be an object', }); - await t.throwsAsync(thumbprint(Object.create(null)), { - instanceOf: TypeError, - message: 'JWK must be an object', - }); + const nullPrototype = Object.create(null); + nullPrototype.crv = 'P-256'; + nullPrototype.kty = 'EC'; + nullPrototype.x = 'q3zAwR_kUwtdLEwtB2oVfucXiLHmEhu9bJUFYjJxYGs'; + nullPrototype.y = '8h0D-ONoU-iZqrq28TyUxEULxuGwJZGMJYTMbeMshvI'; + await t.notThrowsAsync(thumbprint(nullPrototype)); }); test('JWK kty must be recognized', async (t) => {