cloudflare / workerd

The JavaScript / Wasm runtime that powers Cloudflare Workers
https://blog.cloudflare.com/workerd-open-source-workers-runtime/
Apache License 2.0
6.05k stars 287 forks source link

šŸ› Bug Report ā€” node:util#inherits implementation breaks with `jws`/`jswebtoken` npm packages #2257

Closed IgorMinar closed 3 months ago

IgorMinar commented 3 months ago

Repro:

Expected: it works

Actual:

 Uncaught TypeError: The "superCtor.prototype" property must be of type object. Received undefined
    at null.<anonymous> (node:util:106:15) in inherits
    at null.<anonymous>
  (.../node_modules/jws/lib/data-stream.js:39:6) in
  node_modules/jws/lib/data-stream.js
    at null.<anonymous> (index.js:12:50) in __require
    at null.<anonymous>
  (.../node_modules/jws/lib/sign-stream.js:3:18) in
  node_modules/jws/lib/sign-stream.js
    at null.<anonymous> (index.js:12:50) in __require
    at null.<anonymous>
  (.../node_modules/jws/index.js:2:18) in
  node_modules/jws/index.js
    at null.<anonymous> (index.js:12:50) in __require
    at null.<anonymous>
  (.../node_modules/jsonwebtoken/decode.js:1:11) in
  node_modules/jsonwebtoken/decode.js
    at null.<anonymous> (index.js:12:50) in __require
    at null.<anonymous>
  (.../node_modules/jsonwebtoken/index.js:2:11) in
  node_modules/jsonwebtoken/index.js
   [code: 10021]

more context: internal CF chat

jasnell commented 3 months ago

Any chance of getting an easily reproduceable test case? The "install as use..." does not really provide enough info.

Our implementation of util.inherits(...) is generally identical to Node.js' so it's unlikely the bug is there at all, so a reproduction will be necessary to help narrow it down.

First is our implementation, Second is Node.js' : image

irvinebroque commented 3 months ago

@jasnell https://github.com/irvinebroque/node-util-repro

WowVeryLogin commented 3 months ago

My worker code that reproduces the problem:

import JwkToPem from "jwk-to-pem";

async function handleRequest(
  request: Request,
  env: Env,
  ctx: ExecutionContext
) {
  JwkToPem("1234");
  return new Response("", {
    status: 200
  });
}

export default {
  fetch: handleRequest,
};

And used package.json:

{
  "name": "cloud-connector-api",
  "version": "0.0.0",
  "devDependencies": {
    "@cloudflare/workers-types": "^4.20231121.0",
    "eslint": "^8.31.0",
    "eslint-config-prettier": "^8.6.0",
    "eslint-config-typescript": "^3.0.0",
    "prettier": "^2.8.1",
    "vitest": "^1.0.1",
    "wrangler": "^3.18.0"
  },
  "private": true,
  "scripts": {
    "start": "wrangler dev",
    "deploy": "wrangler publish",
    "format": "prettier --write  '*.{json,js}' 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'",
    "lint": "eslint --max-warnings=0 && prettier --check '*.{json,js}' 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'",
    "test": "vitest"
  },
  "dependencies": {
    "@cloudflare/flame-common": "^3.1.0",
    "jsonwebtoken": "^9.0.2",
    "jwks-client": "^2.0.2"
  }
}

This gives me:

  Uncaught TypeError: inherits2 is not a function
    at null.<anonymous>
  (file:///Users/ddavydov/work/cloud-connector-api/node_modules/asn1.js/lib/asn1/base/reporter.js:107:1)
  in node_modules/asn1.js/lib/asn1/base/reporter.js
    at null.<anonymous> (index.js:12:50) in __require
    at null.<anonymous>
  (file:///Users/ddavydov/work/cloud-connector-api/node_modules/asn1.js/lib/asn1/base/node.js:3:18)
  in node_modules/asn1.js/lib/asn1/base/node.js
    at null.<anonymous> (index.js:12:50) in __require
    at null.<anonymous>
  (file:///Users/ddavydov/work/cloud-connector-api/node_modules/asn1.js/lib/asn1/encoders/der.js:5:14)
  in node_modules/asn1.js/lib/asn1/encoders/der.js
    at null.<anonymous> (index.js:12:50) in __require
    at null.<anonymous>
  (file:///Users/ddavydov/work/cloud-connector-api/node_modules/asn1.js/lib/asn1/encoders/index.js:5:16)
  in node_modules/asn1.js/lib/asn1/encoders/index.js
    at null.<anonymous> (index.js:12:50) in __require
    at null.<anonymous>
  (file:///Users/ddavydov/work/cloud-connector-api/node_modules/asn1.js/lib/asn1/api.js:3:18) in
  node_modules/asn1.js/lib/asn1/api.js
    at null.<anonymous> (index.js:12:50) in __require
   [code: 10021]

Or the superCtor error:

import jwt from "jsonwebtoken";

async function handleRequest(
  request: Request,
  env: Env,
  ctx: ExecutionContext
) {
  jwt.sign("1", "1", { algorithm: "RS256" });
  return new Response("", {
    status: 200
  });
}

export default {
  fetch: handleRequest,
};

This gives:

Uncaught TypeError: The "superCtor.prototype" property must be of type object. Received undefined
    at null.<anonymous> (node:util:106:15) in inherits
    at null.<anonymous>
  (file:///Users/ddavydov/work/cloud-connector-api/node_modules/jws/lib/data-stream.js:39:6) in
  node_modules/jws/lib/data-stream.js
    at null.<anonymous> (index.js:12:50) in __require
    at null.<anonymous>
  (file:///Users/ddavydov/work/cloud-connector-api/node_modules/jws/lib/sign-stream.js:3:18) in
  node_modules/jws/lib/sign-stream.js
    at null.<anonymous> (index.js:12:50) in __require
    at null.<anonymous>
  (file:///Users/ddavydov/work/cloud-connector-api/node_modules/jws/index.js:2:18) in
  node_modules/jws/index.js
    at null.<anonymous> (index.js:12:50) in __require
    at null.<anonymous>
  (file:///Users/ddavydov/work/cloud-connector-api/node_modules/jsonwebtoken/decode.js:1:11) in
  node_modules/jsonwebtoken/decode.js
    at null.<anonymous> (index.js:12:50) in __require
    at null.<anonymous>
  (file:///Users/ddavydov/work/cloud-connector-api/node_modules/jsonwebtoken/index.js:2:11) in
  node_modules/jsonwebtoken/index.js
   [code: 10021]
jasnell commented 3 months ago

Ok, quick investigation shows that the bug here is not in workerd or our Node.js built-ins. The issue is actually in the way wrangler is transforming the source internally. Specifically, the line that is blowing up is util.inherits(DataStream, Stream) in the data-stream.js file in the jws module. However, if you look at the way wrangler is transforming things, the line const Stream = require('stream'), which in Node.js and in Workerd returns the Stream class, the transpiled code is instead producing a copy of the module namespace object..

Specifically, in the transpile code, Stream ends up as....

{
  Duplex: [Getter],
  PassThrough: [Getter],
  Readable: [Getter],
  Stream: [Getter],
  Transform: [Getter],
  Writable: [Getter],
  _isUint8Array: [Getter],
  _uint8ArrayToBuffer: [Getter],
  addAbortSignal: [Getter],
  compose: [Getter],
  destroy: [Getter],
  finished: [Getter],
  isDisturbed: [Getter],
  isErrored: [Getter],
  isReadable: [Getter],
  pipeline: [Getter],
  promises: [Getter]
}

... which is a copy of the module namespace object rather than the default export.

I would suspect that this is a limitation of the the transpiler that is being used.

@IgorMinar ... likely should either close this issue and open one in the wrangler repo or transfer this issue. Up to you either way.

jasnell commented 3 months ago

Issue opened in workers-sdk. Closing this one.

IgorMinar commented 3 months ago

Thanks for looking into this @jasnell, let's continue the convo on the wrangler issue. Thanks for filing it!