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) => {