diff options
Diffstat (limited to 'test/js/third_party')
52 files changed, 3981 insertions, 0 deletions
diff --git a/test/js/third_party/jsonwebtoken/async_sign.test.js b/test/js/third_party/jsonwebtoken/async_sign.test.js new file mode 100644 index 000000000..6efb838d0 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/async_sign.test.js @@ -0,0 +1,159 @@ +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; +import jws from "jws"; +import { generateKeyPairSync } from "crypto"; +var PS_SUPPORTED = true; + +describe("signing a token asynchronously", function () { + describe("when signing a token", function () { + var secret = "shhhhhh"; + + it("should return the same result as singing synchronously", function (done) { + jwt.sign({ foo: "bar" }, secret, { algorithm: "HS256" }, function (err, asyncToken) { + if (err) return done(err); + var syncToken = jwt.sign({ foo: "bar" }, secret, { algorithm: "HS256" }); + expect(typeof asyncToken).toBe("string"); + expect(asyncToken.split(".")).toHaveLength(3); + expect(asyncToken).toEqual(syncToken); + done(); + }); + }); + + it("should work with empty options", function (done) { + jwt.sign({ abc: 1 }, "secret", {}, function (err) { + expect(err).toBeNull(); + done(); + }); + }); + + it("should work without options object at all", function (done) { + jwt.sign({ abc: 1 }, "secret", function (err) { + expect(err).toBeNull(); + done(); + }); + }); + + it("should work with none algorithm where secret is set", function (done) { + jwt.sign({ foo: "bar" }, "secret", { algorithm: "none" }, function (err, token) { + expect(typeof token).toBe("string"); + expect(token.split(".")).toHaveLength(3); + done(); + }); + }); + + //Known bug: https://github.com/brianloveswords/node-jws/issues/62 + //If you need this use case, you need to go for the non-callback-ish code style. + it.skip("should work with none algorithm where secret is falsy", function (done) { + jwt.sign({ foo: "bar" }, undefined, { algorithm: "none" }, function (err, token) { + expect(typeof token).toBe("string"); + expect(token.split(".")).toHaveLength(3); + done(); + }); + }); + + it("should return error when secret is not a cert for RS256", function (done) { + //this throw an error because the secret is not a cert and RS256 requires a cert. + jwt.sign({ foo: "bar" }, secret, { algorithm: "RS256" }, function (err) { + expect(err).toBeTruthy(); + done(); + }); + }); + + it("should not work for RS algorithms when modulus length is less than 2048 when allowInsecureKeySizes is false or not set", function (done) { + const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 1024 }); + + jwt.sign({ foo: "bar" }, privateKey, { algorithm: "RS256" }, function (err) { + expect(err).toBeTruthy(); + done(); + }); + }); + + it("should work for RS algorithms when modulus length is less than 2048 when allowInsecureKeySizes is true", function (done) { + const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 1024 }); + + jwt.sign({ foo: "bar" }, privateKey, { algorithm: "RS256", allowInsecureKeySizes: true }, done); + }); + + if (PS_SUPPORTED) { + it("should return error when secret is not a cert for PS256", function (done) { + //this throw an error because the secret is not a cert and PS256 requires a cert. + jwt.sign({ foo: "bar" }, secret, { algorithm: "PS256" }, function (err) { + expect(err).toBeTruthy(); + done(); + }); + }); + } + + it("should return error on wrong arguments", function (done) { + //this throw an error because the secret is not a cert and RS256 requires a cert. + jwt.sign({ foo: "bar" }, secret, { notBefore: {} }, function (err) { + expect(err).toBeTruthy(); + done(); + }); + }); + + it("should return error on wrong arguments (2)", function (done) { + jwt.sign("string", "secret", { noTimestamp: true }, function (err) { + expect(err).toBeTruthy(); + expect(err).toBeInstanceOf(Error); + done(); + }); + }); + + it("should not stringify the payload", function (done) { + jwt.sign("string", "secret", {}, function (err, token) { + if (err) { + return done(err); + } + expect(jws.decode(token).payload).toEqual("string"); + done(); + }); + }); + + describe("when mutatePayload is not set", function () { + it("should not apply claims to the original payload object (mutatePayload defaults to false)", function (done) { + var originalPayload = { foo: "bar" }; + jwt.sign(originalPayload, "secret", { notBefore: 60, expiresIn: 600 }, function (err) { + if (err) { + return done(err); + } + expect(originalPayload).not.toHaveProperty("nbf"); + expect(originalPayload).not.toHaveProperty("exp"); + done(); + }); + }); + }); + + describe("when mutatePayload is set to true", function () { + it("should apply claims directly to the original payload object", function (done) { + var originalPayload = { foo: "bar" }; + jwt.sign(originalPayload, "secret", { notBefore: 60, expiresIn: 600, mutatePayload: true }, function (err) { + if (err) { + return done(err); + } + expect(originalPayload).toHaveProperty("nbf"); + expect(originalPayload).toHaveProperty("exp"); + done(); + }); + }); + }); + + describe("secret must have a value", function () { + [undefined, "", 0].forEach(function (secret) { + it( + "should return an error if the secret is falsy and algorithm is not set to none: " + + (typeof secret === "string" ? "(empty string)" : secret), + function (done) { + // This is needed since jws will not answer for falsy secrets + jwt.sign("string", secret, {}, function (err, token) { + expect(err).toBeTruthy(); + expect(err.message).toEqual("secretOrPrivateKey must have a value"); + expect(token).toBeFalsy(); + done(); + }); + }, + ); + }); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/buffer.test.js b/test/js/third_party/jsonwebtoken/buffer.test.js new file mode 100644 index 000000000..28d310221 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/buffer.test.js @@ -0,0 +1,10 @@ +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; + +describe("buffer payload", function () { + it("should work", function () { + var payload = new Buffer("TkJyotZe8NFpgdfnmgINqg==", "base64"); + var token = jwt.sign(payload, "signing key"); + expect(jwt.decode(token)).toBe(payload.toString()); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/claim-aud.test.js b/test/js/third_party/jsonwebtoken/claim-aud.test.js new file mode 100644 index 000000000..b850b265b --- /dev/null +++ b/test/js/third_party/jsonwebtoken/claim-aud.test.js @@ -0,0 +1,423 @@ +"use strict"; + +import jwt from "jsonwebtoken"; +import { expect, describe, it, beforeEach } from "bun:test"; +import util from "util"; +import testUtils from "./test-utils"; + +function signWithAudience(audience, payload, callback) { + const options = { algorithm: "HS256" }; + if (audience !== undefined) { + options.audience = audience; + } + + testUtils.signJWTHelper(payload, "secret", options, callback); +} + +function verifyWithAudience(token, audience, callback) { + testUtils.verifyJWTHelper(token, "secret", { audience }, callback); +} + +describe("audience", function () { + describe('`jwt.sign` "audience" option validation', function () { + [true, false, null, -1, 1, 0, -1.1, 1.1, -Infinity, Infinity, NaN, {}, { foo: "bar" }].forEach(audience => { + it(`should error with with value ${util.inspect(audience)}`, function (done) { + signWithAudience(audience, {}, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", '"audience" must be a string or array'); + }); + }); + }); + }); + + // undefined needs special treatment because {} is not the same as {aud: undefined} + it("should error with with value undefined", function (done) { + testUtils.signJWTHelper({}, "secret", { audience: undefined, algorithm: "HS256" }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", '"audience" must be a string or array'); + }); + }); + }); + + it('should error when "aud" is in payload', function (done) { + signWithAudience("my_aud", { aud: "" }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty( + "message", + 'Bad "options.audience" option. The payload already has an "aud" property.', + ); + }); + }); + }); + + it("should error with a string payload", function (done) { + signWithAudience("my_aud", "a string payload", err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", "invalid audience option for string payload"); + }); + }); + }); + + it("should error with a Buffer payload", function (done) { + signWithAudience("my_aud", new Buffer("a Buffer payload"), err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", "invalid audience option for object payload"); + }); + }); + }); + }); + + describe('when signing and verifying a token with "audience" option', function () { + describe('with a "aud" of "urn:foo" in payload', function () { + let token; + + beforeEach(function (done) { + signWithAudience("urn:foo", {}, (err, t) => { + token = t; + done(err); + }); + }); + + [ + undefined, + "urn:foo", + /^urn:f[o]{2}$/, + ["urn:no_match", "urn:foo"], + ["urn:no_match", /^urn:f[o]{2}$/], + [/^urn:no_match$/, /^urn:f[o]{2}$/], + [/^urn:no_match$/, "urn:foo"], + ].forEach(audience => { + it(`should verify and decode with verify "audience" option of ${util.inspect(audience)}`, function (done) { + verifyWithAudience(token, audience, (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", "urn:foo"); + }); + }); + }); + }); + + it(`should error on no match with a string verify "audience" option`, function (done) { + verifyWithAudience(token, "urn:no-match", err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", `jwt audience invalid. expected: urn:no-match`); + }); + }); + }); + + it('should error on no match with an array of string verify "audience" option', function (done) { + verifyWithAudience(token, ["urn:no-match-1", "urn:no-match-2"], err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", `jwt audience invalid. expected: urn:no-match-1 or urn:no-match-2`); + }); + }); + }); + + it('should error on no match with a Regex verify "audience" option', function (done) { + verifyWithAudience(token, /^urn:no-match$/, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", `jwt audience invalid. expected: /^urn:no-match$/`); + }); + }); + }); + + it('should error on no match with an array of Regex verify "audience" option', function (done) { + verifyWithAudience(token, [/^urn:no-match-1$/, /^urn:no-match-2$/], err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty( + "message", + `jwt audience invalid. expected: /^urn:no-match-1$/ or /^urn:no-match-2$/`, + ); + }); + }); + }); + + it('should error on no match with an array of a Regex and a string in verify "audience" option', function (done) { + verifyWithAudience(token, [/^urn:no-match$/, "urn:no-match"], err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", `jwt audience invalid. expected: /^urn:no-match$/ or urn:no-match`); + }); + }); + }); + }); + + describe('with an array of ["urn:foo", "urn:bar"] for "aud" value in payload', function () { + let token; + + beforeEach(function (done) { + signWithAudience(["urn:foo", "urn:bar"], {}, (err, t) => { + token = t; + done(err); + }); + }); + + [ + undefined, + "urn:foo", + /^urn:f[o]{2}$/, + ["urn:no_match", "urn:foo"], + ["urn:no_match", /^urn:f[o]{2}$/], + [/^urn:no_match$/, /^urn:f[o]{2}$/], + [/^urn:no_match$/, "urn:foo"], + ].forEach(audience => { + it(`should verify and decode with verify "audience" option of ${util.inspect(audience)}`, function (done) { + verifyWithAudience(token, audience, (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]); + }); + }); + }); + }); + + it(`should error on no match with a string verify "audience" option`, function (done) { + verifyWithAudience(token, "urn:no-match", err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", `jwt audience invalid. expected: urn:no-match`); + }); + }); + }); + + it('should error on no match with an array of string verify "audience" option', function (done) { + verifyWithAudience(token, ["urn:no-match-1", "urn:no-match-2"], err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", `jwt audience invalid. expected: urn:no-match-1 or urn:no-match-2`); + }); + }); + }); + + it('should error on no match with a Regex verify "audience" option', function (done) { + verifyWithAudience(token, /^urn:no-match$/, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", `jwt audience invalid. expected: /^urn:no-match$/`); + }); + }); + }); + + it('should error on no match with an array of Regex verify "audience" option', function (done) { + verifyWithAudience(token, [/^urn:no-match-1$/, /^urn:no-match-2$/], err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty( + "message", + `jwt audience invalid. expected: /^urn:no-match-1$/ or /^urn:no-match-2$/`, + ); + }); + }); + }); + + it('should error on no match with an array of a Regex and a string in verify "audience" option', function (done) { + verifyWithAudience(token, [/^urn:no-match$/, "urn:no-match"], err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", `jwt audience invalid. expected: /^urn:no-match$/ or urn:no-match`); + }); + }); + }); + + describe('when checking for a matching on both "urn:foo" and "urn:bar"', function () { + it('should verify with an array of stings verify "audience" option', function (done) { + verifyWithAudience(token, ["urn:foo", "urn:bar"], (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]); + }); + }); + }); + + it('should verify with a Regex verify "audience" option', function (done) { + verifyWithAudience(token, /^urn:[a-z]{3}$/, (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]); + }); + }); + }); + + it('should verify with an array of Regex verify "audience" option', function (done) { + verifyWithAudience(token, [/^urn:f[o]{2}$/, /^urn:b[ar]{2}$/], (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]); + }); + }); + }); + }); + + describe('when checking for a matching for "urn:foo"', function () { + it('should verify with a string verify "audience"', function (done) { + verifyWithAudience(token, "urn:foo", (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]); + }); + }); + }); + + it('should verify with a Regex verify "audience" option', function (done) { + verifyWithAudience(token, /^urn:f[o]{2}$/, (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]); + }); + }); + }); + + it('should verify with an array of Regex verify "audience"', function (done) { + verifyWithAudience(token, [/^urn:no-match$/, /^urn:f[o]{2}$/], (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]); + }); + }); + }); + + it('should verify with an array containing a string and a Regex verify "audience" option', function (done) { + verifyWithAudience(token, ["urn:no_match", /^urn:f[o]{2}$/], (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]); + }); + }); + }); + + it('should verify with an array containing a Regex and a string verify "audience" option', function (done) { + verifyWithAudience(token, [/^urn:no-match$/, "urn:foo"], (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]); + }); + }); + }); + }); + + describe('when checking matching for "urn:bar"', function () { + it('should verify with a string verify "audience"', function (done) { + verifyWithAudience(token, "urn:bar", (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]); + }); + }); + }); + + it('should verify with a Regex verify "audience" option', function (done) { + verifyWithAudience(token, /^urn:b[ar]{2}$/, (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]); + }); + }); + }); + + it('should verify with an array of Regex verify "audience" option', function (done) { + verifyWithAudience(token, [/^urn:no-match$/, /^urn:b[ar]{2}$/], (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]); + }); + }); + }); + + it('should verify with an array containing a string and a Regex verify "audience" option', function (done) { + verifyWithAudience(token, ["urn:no_match", /^urn:b[ar]{2}$/], (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]); + }); + }); + }); + + it('should verify with an array containing a Regex and a string verify "audience" option', function (done) { + verifyWithAudience(token, [/^urn:no-match$/, "urn:bar"], (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]); + }); + }); + }); + }); + }); + + describe('without a "aud" value in payload', function () { + let token; + + beforeEach(function (done) { + signWithAudience(undefined, {}, (err, t) => { + token = t; + done(err); + }); + }); + + it('should verify and decode without verify "audience" option', function (done) { + verifyWithAudience(token, undefined, (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).not.toHaveProperty("aud"); + }); + }); + }); + + it('should error on no match with a string verify "audience" option', function (done) { + verifyWithAudience(token, "urn:no-match", err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", "jwt audience invalid. expected: urn:no-match"); + }); + }); + }); + + it('should error on no match with an array of string verify "audience" option', function (done) { + verifyWithAudience(token, ["urn:no-match-1", "urn:no-match-2"], err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", "jwt audience invalid. expected: urn:no-match-1 or urn:no-match-2"); + }); + }); + }); + + it('should error on no match with a Regex verify "audience" option', function (done) { + verifyWithAudience(token, /^urn:no-match$/, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", "jwt audience invalid. expected: /^urn:no-match$/"); + }); + }); + }); + + it('should error on no match with an array of Regex verify "audience" option', function (done) { + verifyWithAudience(token, [/^urn:no-match-1$/, /^urn:no-match-2$/], err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty( + "message", + "jwt audience invalid. expected: /^urn:no-match-1$/ or /^urn:no-match-2$/", + ); + }); + }); + }); + + it('should error on no match with an array of a Regex and a string in verify "audience" option', function (done) { + verifyWithAudience(token, [/^urn:no-match$/, "urn:no-match"], err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", "jwt audience invalid. expected: /^urn:no-match$/ or urn:no-match"); + }); + }); + }); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/claim-exp.test.js b/test/js/third_party/jsonwebtoken/claim-exp.test.js new file mode 100644 index 000000000..ee836a755 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/claim-exp.test.js @@ -0,0 +1,316 @@ +"use strict"; + +import jwt from "jsonwebtoken"; +import { expect, describe, it, beforeEach } from "bun:test"; +import util from "util"; +import testUtils from "./test-utils"; +import jws from "jws"; +import sinon from "sinon"; + +function signWithExpiresIn(expiresIn, payload, callback) { + const options = { algorithm: "HS256" }; + if (expiresIn !== undefined) { + options.expiresIn = expiresIn; + } + testUtils.signJWTHelper(payload, "secret", options, callback); +} + +describe("expires", function () { + describe('`jwt.sign` "expiresIn" option validation', function () { + [ + true, + false, + null, + -1.1, + 1.1, + -Infinity, + Infinity, + NaN, + " ", + "", + "invalid", + [], + ["foo"], + {}, + { foo: "bar" }, + ].forEach(expiresIn => { + it(`should error with with value ${util.inspect(expiresIn)}`, function (done) { + signWithExpiresIn(expiresIn, {}, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message"); + }); + }); + }); + }); + + // undefined needs special treatment because {} is not the same as {expiresIn: undefined} + it("should error with with value undefined", function (done) { + testUtils.signJWTHelper({}, "secret", { expiresIn: undefined, algorithm: "HS256" }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty( + "message", + '"expiresIn" should be a number of seconds or string representing a timespan', + ); + }); + }); + }); + + it('should error when "exp" is in payload', function (done) { + signWithExpiresIn(100, { exp: 100 }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty( + "message", + 'Bad "options.expiresIn" option the payload already has an "exp" property.', + ); + }); + }); + }); + + it("should error with a string payload", function (done) { + signWithExpiresIn(100, "a string payload", err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", "invalid expiresIn option for string payload"); + }); + }); + }); + + it("should error with a Buffer payload", function (done) { + signWithExpiresIn(100, Buffer.from("a Buffer payload"), err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", "invalid expiresIn option for object payload"); + }); + }); + }); + }); + + describe('`jwt.sign` "exp" claim validation', function () { + [true, false, null, undefined, "", " ", "invalid", [], ["foo"], {}, { foo: "bar" }].forEach(exp => { + it(`should error with with value ${util.inspect(exp)}`, function (done) { + signWithExpiresIn(undefined, { exp }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", '"exp" should be a number of seconds'); + }); + }); + }); + }); + }); + + describe('"exp" in payload validation', function () { + [true, false, null, -Infinity, Infinity, NaN, "", " ", "invalid", [], ["foo"], {}, { foo: "bar" }].forEach(exp => { + it(`should error with with value ${util.inspect(exp)}`, function (done) { + const header = { alg: "HS256" }; + const payload = { exp }; + const token = jws.sign({ header, payload, secret: "secret", encoding: "utf8" }); + testUtils.verifyJWTHelper(token, "secret", { exp }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", "invalid exp value"); + }); + }); + }); + }); + }); + + describe("when signing and verifying a token with expires option", function () { + let fakeClock; + beforeEach(function () { + fakeClock = sinon.useFakeTimers({ now: 60000 }); + }); + + afterEach(function () { + fakeClock.uninstall(); + }); + + it('should set correct "exp" with negative number of seconds', function (done) { + signWithExpiresIn(-10, {}, (e1, token) => { + fakeClock.tick(-10001); + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("exp", 50); + }); + }); + }); + }); + + it('should set correct "exp" with positive number of seconds', function (done) { + signWithExpiresIn(10, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("exp", 70); + }); + }); + }); + }); + + it('should set correct "exp" with zero seconds', function (done) { + signWithExpiresIn(0, {}, (e1, token) => { + fakeClock.tick(-1); + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("exp", 60); + }); + }); + }); + }); + + it('should set correct "exp" with negative string timespan', function (done) { + signWithExpiresIn("-10 s", {}, (e1, token) => { + fakeClock.tick(-10001); + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("exp", 50); + }); + }); + }); + }); + + it('should set correct "exp" with positive string timespan', function (done) { + signWithExpiresIn("10 s", {}, (e1, token) => { + fakeClock.tick(-10001); + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("exp", 70); + }); + }); + }); + }); + + it('should set correct "exp" with zero string timespan', function (done) { + signWithExpiresIn("0 s", {}, (e1, token) => { + fakeClock.tick(-1); + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("exp", 60); + }); + }); + }); + }); + + // TODO an exp of -Infinity should fail validation + it('should set null "exp" when given -Infinity', function (done) { + signWithExpiresIn(undefined, { exp: -Infinity }, (err, token) => { + const decoded = jwt.decode(token); + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("exp", null); + }); + }); + }); + + // TODO an exp of Infinity should fail validation + it('should set null "exp" when given value Infinity', function (done) { + signWithExpiresIn(undefined, { exp: Infinity }, (err, token) => { + const decoded = jwt.decode(token); + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("exp", null); + }); + }); + }); + + // TODO an exp of NaN should fail validation + it('should set null "exp" when given value NaN', function (done) { + signWithExpiresIn(undefined, { exp: NaN }, (err, token) => { + const decoded = jwt.decode(token); + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("exp", null); + }); + }); + }); + + it('should set correct "exp" when "iat" is passed', function (done) { + signWithExpiresIn(-10, { iat: 80 }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("exp", 70); + }); + }); + }); + }); + + it('should verify "exp" using "clockTimestamp"', function (done) { + signWithExpiresIn(10, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { clockTimestamp: 69 }, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("iat", 60); + expect(decoded).toHaveProperty("exp", 70); + }); + }); + }); + }); + + it('should verify "exp" using "clockTolerance"', function (done) { + signWithExpiresIn(5, {}, (e1, token) => { + fakeClock.tick(10000); + testUtils.verifyJWTHelper(token, "secret", { clockTimestamp: 6 }, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("iat", 60); + expect(decoded).toHaveProperty("exp", 65); + }); + }); + }); + }); + + it('should ignore a expired token when "ignoreExpiration" is true', function (done) { + signWithExpiresIn("-10 s", {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { ignoreExpiration: true }, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("iat", 60); + expect(decoded).toHaveProperty("exp", 50); + }); + }); + }); + }); + + it('should error on verify if "exp" is at current time', function (done) { + signWithExpiresIn(undefined, { exp: 60 }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, e2 => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeInstanceOf(jwt.TokenExpiredError); + expect(e2).toHaveProperty("message", "jwt expired"); + }); + }); + }); + }); + + it('should error on verify if "exp" is before current time using clockTolerance', function (done) { + signWithExpiresIn(-5, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { clockTolerance: 5 }, e2 => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeInstanceOf(jwt.TokenExpiredError); + expect(e2).toHaveProperty("message", "jwt expired"); + }); + }); + }); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/claim-iat.test.js b/test/js/third_party/jsonwebtoken/claim-iat.test.js new file mode 100644 index 000000000..6d72a58f6 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/claim-iat.test.js @@ -0,0 +1,254 @@ +"use strict"; + +import jwt from "jsonwebtoken"; +import { expect, describe, it, beforeEach } from "bun:test"; +import util from "util"; +import testUtils from "./test-utils"; +import jws from "jws"; +import sinon from "sinon"; + +function signWithIssueAt(issueAt, options, callback) { + const payload = {}; + if (issueAt !== undefined) { + payload.iat = issueAt; + } + const opts = Object.assign({ algorithm: "HS256" }, options); + // async calls require a truthy secret + // see: https://github.com/brianloveswords/node-jws/issues/62 + testUtils.signJWTHelper(payload, "secret", opts, callback); +} + +function verifyWithIssueAt(token, maxAge, options, secret, callback) { + const opts = Object.assign({ maxAge }, options); + testUtils.verifyJWTHelper(token, secret, opts, callback); +} + +describe("issue at", function () { + describe('`jwt.sign` "iat" claim validation', function () { + [true, false, null, "", "invalid", [], ["foo"], {}, { foo: "bar" }].forEach(iat => { + it(`should error with iat of ${util.inspect(iat)}`, function (done) { + signWithIssueAt(iat, {}, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err.message).toEqual('"iat" should be a number of seconds'); + }); + }); + }); + }); + + // undefined needs special treatment because {} is not the same as {iat: undefined} + it("should error with iat of undefined", function (done) { + testUtils.signJWTHelper({ iat: undefined }, "secret", { algorithm: "HS256" }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err.message).toEqual('"iat" should be a number of seconds'); + }); + }); + }); + }); + + describe('"iat" in payload with "maxAge" option validation', function () { + [true, false, null, undefined, -Infinity, Infinity, NaN, "", "invalid", [], ["foo"], {}, { foo: "bar" }].forEach( + iat => { + it(`should error with iat of ${util.inspect(iat)}`, function (done) { + const header = { alg: "HS256" }; + const payload = { iat }; + const token = jws.sign({ header, payload, secret: "secret", encoding: "utf8" }); + verifyWithIssueAt(token, "1 min", {}, "secret", err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err.message).toEqual("iat required when maxAge is specified"); + }); + }); + }); + }, + ); + }); + + describe("when signing a token", function () { + let fakeClock; + beforeEach(function () { + fakeClock = sinon.useFakeTimers({ now: 60000 }); + }); + + afterEach(function () { + fakeClock.uninstall(); + }); + + [ + { + description: 'should default to current time for "iat"', + iat: undefined, + expectedIssueAt: 60, + options: {}, + }, + { + description: 'should sign with provided time for "iat"', + iat: 100, + expectedIssueAt: 100, + options: {}, + }, + // TODO an iat of -Infinity should fail validation + { + description: 'should set null "iat" when given -Infinity', + iat: -Infinity, + expectedIssueAt: null, + options: {}, + }, + // TODO an iat of Infinity should fail validation + { + description: 'should set null "iat" when given Infinity', + iat: Infinity, + expectedIssueAt: null, + options: {}, + }, + // TODO an iat of NaN should fail validation + { + description: 'should set to current time for "iat" when given value NaN', + iat: NaN, + expectedIssueAt: 60, + options: {}, + }, + { + description: 'should remove default "iat" with "noTimestamp" option', + iat: undefined, + expectedIssueAt: undefined, + options: { noTimestamp: true }, + }, + { + description: 'should remove provided "iat" with "noTimestamp" option', + iat: 10, + expectedIssueAt: undefined, + options: { noTimestamp: true }, + }, + ].forEach(testCase => { + it(testCase.description, function (done) { + signWithIssueAt(testCase.iat, testCase.options, (err, token) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(jwt.decode(token).iat).toEqual(testCase.expectedIssueAt); + }); + }); + }); + }); + }); + + describe("when verifying a token", function () { + let fakeClock; + + beforeEach(function () { + fakeClock = sinon.useFakeTimers({ now: 60000 }); + }); + + afterEach(function () { + fakeClock.uninstall(); + }); + + [ + { + description: 'should verify using "iat" before the "maxAge"', + clockAdvance: 10000, + maxAge: 11, + options: {}, + }, + { + description: 'should verify using "iat" before the "maxAge" with a provided "clockTimestamp', + clockAdvance: 60000, + maxAge: 11, + options: { clockTimestamp: 70 }, + }, + { + description: 'should verify using "iat" after the "maxAge" but within "clockTolerance"', + clockAdvance: 10000, + maxAge: 9, + options: { clockTimestamp: 2 }, + }, + ].forEach(testCase => { + it(testCase.description, function (done) { + const token = jwt.sign({}, "secret", { algorithm: "HS256" }); + fakeClock.tick(testCase.clockAdvance); + verifyWithIssueAt(token, testCase.maxAge, testCase.options, "secret", (err, token) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(typeof token).toBe("object"); + }); + }); + }); + }); + + [ + { + description: 'should throw using "iat" equal to the "maxAge"', + clockAdvance: 10000, + maxAge: 10, + options: {}, + expectedError: "maxAge exceeded", + expectedExpiresAt: 70000, + }, + { + description: 'should throw using "iat" after the "maxAge"', + clockAdvance: 10000, + maxAge: 9, + options: {}, + expectedError: "maxAge exceeded", + expectedExpiresAt: 69000, + }, + { + description: 'should throw using "iat" after the "maxAge" with a provided "clockTimestamp', + clockAdvance: 60000, + maxAge: 10, + options: { clockTimestamp: 70 }, + expectedError: "maxAge exceeded", + expectedExpiresAt: 70000, + }, + { + description: 'should throw using "iat" after the "maxAge" and "clockTolerance', + clockAdvance: 10000, + maxAge: 8, + options: { clockTolerance: 2 }, + expectedError: "maxAge exceeded", + expectedExpiresAt: 68000, + }, + ].forEach(testCase => { + it(testCase.description, function (done) { + const expectedExpiresAtDate = new Date(testCase.expectedExpiresAt); + const token = jwt.sign({}, "secret", { algorithm: "HS256" }); + fakeClock.tick(testCase.clockAdvance); + + verifyWithIssueAt(token, testCase.maxAge, testCase.options, "secret", err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err.message).toEqual(testCase.expectedError); + expect(err.expiredAt).toStrictEqual(expectedExpiresAtDate); + }); + }); + }); + }); + }); + + describe("with string payload", function () { + it("should not add iat to string", function (done) { + const payload = "string payload"; + const options = { algorithm: "HS256" }; + testUtils.signJWTHelper(payload, "secret", options, (err, token) => { + const decoded = jwt.decode(token); + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toEqual(payload); + }); + }); + }); + + it("should not add iat to stringified object", function (done) { + const payload = "{}"; + const options = { algorithm: "HS256", header: { typ: "JWT" } }; + testUtils.signJWTHelper(payload, "secret", options, (err, token) => { + const decoded = jwt.decode(token); + testUtils.asyncCheck(done, () => { + expect(err).toEqual(null); + expect(JSON.stringify(decoded)).toEqual(payload); + }); + }); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/claim-iss.test.js b/test/js/third_party/jsonwebtoken/claim-iss.test.js new file mode 100644 index 000000000..3b2e9dacf --- /dev/null +++ b/test/js/third_party/jsonwebtoken/claim-iss.test.js @@ -0,0 +1,185 @@ +"use strict"; + +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; +import util from "util"; +import testUtils from "./test-utils"; + +function signWithIssuer(issuer, payload, callback) { + const options = { algorithm: "HS256" }; + if (issuer !== undefined) { + options.issuer = issuer; + } + testUtils.signJWTHelper(payload, "secret", options, callback); +} + +describe("issuer", function () { + describe('`jwt.sign` "issuer" option validation', function () { + [true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, [], ["foo"], {}, { foo: "bar" }].forEach( + issuer => { + it(`should error with with value ${util.inspect(issuer)}`, function (done) { + signWithIssuer(issuer, {}, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", '"issuer" must be a string'); + }); + }); + }); + }, + ); + + // undefined needs special treatment because {} is not the same as {issuer: undefined} + it("should error with with value undefined", function (done) { + testUtils.signJWTHelper({}, "secret", { issuer: undefined, algorithm: "HS256" }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", '"issuer" must be a string'); + }); + }); + }); + + it('should error when "iss" is in payload', function (done) { + signWithIssuer("foo", { iss: "bar" }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty( + "message", + 'Bad "options.issuer" option. The payload already has an "iss" property.', + ); + }); + }); + }); + + it("should error with a string payload", function (done) { + signWithIssuer("foo", "a string payload", err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", "invalid issuer option for string payload"); + }); + }); + }); + + it("should error with a Buffer payload", function (done) { + signWithIssuer("foo", new Buffer("a Buffer payload"), err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", "invalid issuer option for object payload"); + }); + }); + }); + }); + + describe("when signing and verifying a token", function () { + it('should not verify "iss" if verify "issuer" option not provided', function (done) { + signWithIssuer(undefined, { iss: "foo" }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("iss", "foo"); + }); + }); + }); + }); + + describe('with string "issuer" option', function () { + it('should verify with a string "issuer"', function (done) { + signWithIssuer("foo", {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { issuer: "foo" }, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("iss", "foo"); + }); + }); + }); + }); + + it('should verify with a string "iss"', function (done) { + signWithIssuer(undefined, { iss: "foo" }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { issuer: "foo" }, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("iss", "foo"); + }); + }); + }); + }); + + it('should error if "iss" does not match verify "issuer" option', function (done) { + signWithIssuer(undefined, { iss: "foobar" }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { issuer: "foo" }, e2 => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeInstanceOf(jwt.JsonWebTokenError); + expect(e2).toHaveProperty("message", "jwt issuer invalid. expected: foo"); + }); + }); + }); + }); + + it('should error without "iss" and with verify "issuer" option', function (done) { + signWithIssuer(undefined, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { issuer: "foo" }, e2 => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeInstanceOf(jwt.JsonWebTokenError); + expect(e2).toHaveProperty("message", "jwt issuer invalid. expected: foo"); + }); + }); + }); + }); + }); + + describe('with array "issuer" option', function () { + it('should verify with a string "issuer"', function (done) { + signWithIssuer("bar", {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { issuer: ["foo", "bar"] }, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("iss", "bar"); + }); + }); + }); + }); + + it('should verify with a string "iss"', function (done) { + signWithIssuer(undefined, { iss: "foo" }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { issuer: ["foo", "bar"] }, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("iss", "foo"); + }); + }); + }); + }); + + it('should error if "iss" does not match verify "issuer" option', function (done) { + signWithIssuer(undefined, { iss: "foobar" }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { issuer: ["foo", "bar"] }, e2 => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeInstanceOf(jwt.JsonWebTokenError); + expect(e2).toHaveProperty("message", "jwt issuer invalid. expected: foo,bar"); + }); + }); + }); + }); + + it('should error without "iss" and with verify "issuer" option', function (done) { + signWithIssuer(undefined, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { issuer: ["foo", "bar"] }, e2 => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeInstanceOf(jwt.JsonWebTokenError); + expect(e2).toHaveProperty("message", "jwt issuer invalid. expected: foo,bar"); + }); + }); + }); + }); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/claim-jti.test.js b/test/js/third_party/jsonwebtoken/claim-jti.test.js new file mode 100644 index 000000000..18aa15df8 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/claim-jti.test.js @@ -0,0 +1,135 @@ +"use strict"; + +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; +import util from "util"; +import testUtils from "./test-utils"; + +function signWithJWTId(jwtid, payload, callback) { + const options = { algorithm: "HS256" }; + if (jwtid !== undefined) { + options.jwtid = jwtid; + } + testUtils.signJWTHelper(payload, "secret", options, callback); +} + +describe("jwtid", function () { + describe('`jwt.sign` "jwtid" option validation', function () { + [true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, [], ["foo"], {}, { foo: "bar" }].forEach( + jwtid => { + it(`should error with with value ${util.inspect(jwtid)}`, function (done) { + signWithJWTId(jwtid, {}, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", '"jwtid" must be a string'); + }); + }); + }); + }, + ); + + // undefined needs special treatment because {} is not the same as {jwtid: undefined} + it("should error with with value undefined", function (done) { + testUtils.signJWTHelper({}, "secret", { jwtid: undefined, algorithm: "HS256" }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", '"jwtid" must be a string'); + }); + }); + }); + + it('should error when "jti" is in payload', function (done) { + signWithJWTId("foo", { jti: "bar" }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty( + "message", + 'Bad "options.jwtid" option. The payload already has an "jti" property.', + ); + }); + }); + }); + + it("should error with a string payload", function (done) { + signWithJWTId("foo", "a string payload", err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", "invalid jwtid option for string payload"); + }); + }); + }); + + it("should error with a Buffer payload", function (done) { + signWithJWTId("foo", new Buffer("a Buffer payload"), err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", "invalid jwtid option for object payload"); + }); + }); + }); + }); + + describe("when signing and verifying a token", function () { + it('should not verify "jti" if verify "jwtid" option not provided', function (done) { + signWithJWTId(undefined, { jti: "foo" }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("jti", "foo"); + }); + }); + }); + }); + + describe('with "jwtid" option', function () { + it('should verify with "jwtid" option', function (done) { + signWithJWTId("foo", {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { jwtid: "foo" }, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("jti", "foo"); + }); + }); + }); + }); + + it('should verify with "jti" in payload', function (done) { + signWithJWTId(undefined, { jti: "foo" }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { jetid: "foo" }, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("jti", "foo"); + }); + }); + }); + }); + + it('should error if "jti" does not match verify "jwtid" option', function (done) { + signWithJWTId(undefined, { jti: "bar" }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { jwtid: "foo" }, e2 => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeInstanceOf(jwt.JsonWebTokenError); + expect(e2).toHaveProperty("message", "jwt jwtid invalid. expected: foo"); + }); + }); + }); + }); + + it('should error without "jti" and with verify "jwtid" option', function (done) { + signWithJWTId(undefined, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { jwtid: "foo" }, e2 => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeInstanceOf(jwt.JsonWebTokenError); + expect(e2).toHaveProperty("message", "jwt jwtid invalid. expected: foo"); + }); + }); + }); + }); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/claim-nbf.test.js b/test/js/third_party/jsonwebtoken/claim-nbf.test.js new file mode 100644 index 000000000..9c2e54c5c --- /dev/null +++ b/test/js/third_party/jsonwebtoken/claim-nbf.test.js @@ -0,0 +1,312 @@ +"use strict"; + +import jwt from "jsonwebtoken"; +import { expect, describe, it, beforeEach } from "bun:test"; +import util from "util"; +import testUtils from "./test-utils"; +import jws from "jws"; +import sinon from "sinon"; + +function signWithNotBefore(notBefore, payload, callback) { + const options = { algorithm: "HS256" }; + if (notBefore !== undefined) { + options.notBefore = notBefore; + } + testUtils.signJWTHelper(payload, "secret", options, callback); +} + +describe("not before", function () { + describe('`jwt.sign` "notBefore" option validation', function () { + [ + true, + false, + null, + -1.1, + 1.1, + -Infinity, + Infinity, + NaN, + "", + " ", + "invalid", + [], + ["foo"], + {}, + { foo: "bar" }, + ].forEach(notBefore => { + it(`should error with with value ${util.inspect(notBefore)}`, function (done) { + signWithNotBefore(notBefore, {}, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message"); + }); + }); + }); + }); + + // undefined needs special treatment because {} is not the same as {notBefore: undefined} + it("should error with with value undefined", function (done) { + testUtils.signJWTHelper({}, "secret", { notBefore: undefined, algorithm: "HS256" }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty( + "message", + '"notBefore" should be a number of seconds or string representing a timespan', + ); + }); + }); + }); + + it('should error when "nbf" is in payload', function (done) { + signWithNotBefore(100, { nbf: 100 }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty( + "message", + 'Bad "options.notBefore" option the payload already has an "nbf" property.', + ); + }); + }); + }); + + it("should error with a string payload", function (done) { + signWithNotBefore(100, "a string payload", err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", "invalid notBefore option for string payload"); + }); + }); + }); + + it("should error with a Buffer payload", function (done) { + signWithNotBefore(100, new Buffer("a Buffer payload"), err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", "invalid notBefore option for object payload"); + }); + }); + }); + }); + + describe('`jwt.sign` "nbf" claim validation', function () { + [true, false, null, undefined, "", " ", "invalid", [], ["foo"], {}, { foo: "bar" }].forEach(nbf => { + it(`should error with with value ${util.inspect(nbf)}`, function (done) { + signWithNotBefore(undefined, { nbf }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", '"nbf" should be a number of seconds'); + }); + }); + }); + }); + }); + + describe('"nbf" in payload validation', function () { + [true, false, null, -Infinity, Infinity, NaN, "", " ", "invalid", [], ["foo"], {}, { foo: "bar" }].forEach(nbf => { + it(`should error with with value ${util.inspect(nbf)}`, function (done) { + const header = { alg: "HS256" }; + const payload = { nbf }; + const token = jws.sign({ header, payload, secret: "secret", encoding: "utf8" }); + testUtils.verifyJWTHelper(token, "secret", { nbf }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", "invalid nbf value"); + }); + }); + }); + }); + }); + + describe('when signing and verifying a token with "notBefore" option', function () { + let fakeClock; + beforeEach(function () { + fakeClock = sinon.useFakeTimers({ now: 60000 }); + }); + + afterEach(function () { + fakeClock.uninstall(); + }); + + it('should set correct "nbf" with negative number of seconds', function (done) { + signWithNotBefore(-10, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("nbf", 50); + }); + }); + }); + }); + + it('should set correct "nbf" with positive number of seconds', function (done) { + signWithNotBefore(10, {}, (e1, token) => { + fakeClock.tick(10000); + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("nbf", 70); + }); + }); + }); + }); + + it('should set correct "nbf" with zero seconds', function (done) { + signWithNotBefore(0, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("nbf", 60); + }); + }); + }); + }); + + it('should set correct "nbf" with negative string timespan', function (done) { + signWithNotBefore("-10 s", {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("nbf", 50); + }); + }); + }); + }); + + it('should set correct "nbf" with positive string timespan', function (done) { + signWithNotBefore("10 s", {}, (e1, token) => { + fakeClock.tick(10000); + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("nbf", 70); + }); + }); + }); + }); + + it('should set correct "nbf" with zero string timespan', function (done) { + signWithNotBefore("0 s", {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("nbf", 60); + }); + }); + }); + }); + + // TODO an nbf of -Infinity should fail validation + it('should set null "nbf" when given -Infinity', function (done) { + signWithNotBefore(undefined, { nbf: -Infinity }, (err, token) => { + const decoded = jwt.decode(token); + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("nbf", null); + }); + }); + }); + + // TODO an nbf of Infinity should fail validation + it('should set null "nbf" when given value Infinity', function (done) { + signWithNotBefore(undefined, { nbf: Infinity }, (err, token) => { + const decoded = jwt.decode(token); + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("nbf", null); + }); + }); + }); + + // TODO an nbf of NaN should fail validation + it('should set null "nbf" when given value NaN', function (done) { + signWithNotBefore(undefined, { nbf: NaN }, (err, token) => { + const decoded = jwt.decode(token); + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("nbf", null); + }); + }); + }); + + it('should set correct "nbf" when "iat" is passed', function (done) { + signWithNotBefore(-10, { iat: 40 }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("nbf", 30); + }); + }); + }); + }); + + it('should verify "nbf" using "clockTimestamp"', function (done) { + signWithNotBefore(10, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { clockTimestamp: 70 }, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("iat", 60); + expect(decoded).toHaveProperty("nbf", 70); + }); + }); + }); + }); + + it('should verify "nbf" using "clockTolerance"', function (done) { + signWithNotBefore(5, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { clockTolerance: 6 }, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("iat", 60); + expect(decoded).toHaveProperty("nbf", 65); + }); + }); + }); + }); + + it('should ignore a not active token when "ignoreNotBefore" is true', function (done) { + signWithNotBefore("10 s", {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { ignoreNotBefore: true }, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("iat", 60); + expect(decoded).toHaveProperty("nbf", 70); + }); + }); + }); + }); + + it('should error on verify if "nbf" is after current time', function (done) { + signWithNotBefore(undefined, { nbf: 61 }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, e2 => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeInstanceOf(jwt.NotBeforeError); + expect(e2).toHaveProperty("message", "jwt not active"); + }); + }); + }); + }); + + it('should error on verify if "nbf" is after current time using clockTolerance', function (done) { + signWithNotBefore(5, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { clockTolerance: 4 }, e2 => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeInstanceOf(jwt.NotBeforeError); + expect(e2).toHaveProperty("message", "jwt not active"); + }); + }); + }); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/claim-private.test.js b/test/js/third_party/jsonwebtoken/claim-private.test.js new file mode 100644 index 000000000..51c56edb2 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/claim-private.test.js @@ -0,0 +1,55 @@ +"use strict"; + +import { expect, describe, it } from "bun:test"; +import util from "util"; +import testUtils from "./test-utils"; + +function signWithPayload(payload, callback) { + testUtils.signJWTHelper(payload, "secret", { algorithm: "HS256" }, callback); +} + +describe("with a private claim", function () { + [true, false, null, -1, 0, 1, -1.1, 1.1, "", "private claim", "UTF8 - José", [], ["foo"], {}, { foo: "bar" }].forEach( + privateClaim => { + it(`should sign and verify with claim of ${util.inspect(privateClaim)}`, function (done) { + signWithPayload({ privateClaim }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("privateClaim", privateClaim); + }); + }); + }); + }); + }, + ); + + // these values JSON.stringify to null + [-Infinity, Infinity, NaN].forEach(privateClaim => { + it(`should sign and verify with claim of ${util.inspect(privateClaim)}`, function (done) { + signWithPayload({ privateClaim }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("privateClaim", null); + }); + }); + }); + }); + }); + + // private claims with value undefined are not added to the payload + it(`should sign and verify with claim of undefined`, function (done) { + signWithPayload({ privateClaim: undefined }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).not.toHaveProperty("privateClaim"); + }); + }); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/claim-sub.test.js b/test/js/third_party/jsonwebtoken/claim-sub.test.js new file mode 100644 index 000000000..6846a688d --- /dev/null +++ b/test/js/third_party/jsonwebtoken/claim-sub.test.js @@ -0,0 +1,133 @@ +"use strict"; + +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; +import util from "util"; +import testUtils from "./test-utils"; + +function signWithSubject(subject, payload, callback) { + const options = { algorithm: "HS256" }; + if (subject !== undefined) { + options.subject = subject; + } + testUtils.signJWTHelper(payload, "secret", options, callback); +} + +describe("subject", function () { + describe('`jwt.sign` "subject" option validation', function () { + [true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, [], ["foo"], {}, { foo: "bar" }].forEach( + subject => { + it(`should error with with value ${util.inspect(subject)}`, function (done) { + signWithSubject(subject, {}, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", '"subject" must be a string'); + }); + }); + }); + }, + ); + + // undefined needs special treatment because {} is not the same as {subject: undefined} + it("should error with with value undefined", function (done) { + testUtils.signJWTHelper({}, "secret", { subject: undefined, algorithm: "HS256" }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", '"subject" must be a string'); + }); + }); + }); + + it('should error when "sub" is in payload', function (done) { + signWithSubject("foo", { sub: "bar" }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty( + "message", + 'Bad "options.subject" option. The payload already has an "sub" property.', + ); + }); + }); + }); + + it("should error with a string payload", function (done) { + signWithSubject("foo", "a string payload", err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", "invalid subject option for string payload"); + }); + }); + }); + + it("should error with a Buffer payload", function (done) { + signWithSubject("foo", new Buffer("a Buffer payload"), err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", "invalid subject option for object payload"); + }); + }); + }); + }); + + describe('when signing and verifying a token with "subject" option', function () { + it('should verify with a string "subject"', function (done) { + signWithSubject("foo", {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { subject: "foo" }, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("sub", "foo"); + }); + }); + }); + }); + + it('should verify with a string "sub"', function (done) { + signWithSubject(undefined, { sub: "foo" }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { subject: "foo" }, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("sub", "foo"); + }); + }); + }); + }); + + it('should not verify "sub" if verify "subject" option not provided', function (done) { + signWithSubject(undefined, { sub: "foo" }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeNull(); + expect(decoded).toHaveProperty("sub", "foo"); + }); + }); + }); + }); + + it('should error if "sub" does not match verify "subject" option', function (done) { + signWithSubject(undefined, { sub: "foo" }, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { subject: "bar" }, e2 => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeInstanceOf(jwt.JsonWebTokenError); + expect(e2).toHaveProperty("message", "jwt subject invalid. expected: bar"); + }); + }); + }); + }); + + it('should error without "sub" and with verify "subject" option', function (done) { + signWithSubject(undefined, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, "secret", { subject: "foo" }, e2 => { + testUtils.asyncCheck(done, () => { + expect(e1).toBeNull(); + expect(e2).toBeInstanceOf(jwt.JsonWebTokenError); + expect(e2).toHaveProperty("message", "jwt subject invalid. expected: foo"); + }); + }); + }); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/decoding.test.js b/test/js/third_party/jsonwebtoken/decoding.test.js new file mode 100644 index 000000000..617c7f295 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/decoding.test.js @@ -0,0 +1,9 @@ +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; + +describe("decoding", function () { + it("should not crash when decoding a null token", function () { + var decoded = jwt.decode("null"); + expect(decoded).toEqual(null); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/dsa-private.pem b/test/js/third_party/jsonwebtoken/dsa-private.pem new file mode 100644 index 000000000..e73003a12 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/dsa-private.pem @@ -0,0 +1,36 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIGWAIBAAKCAgEArzbPbt//BQpsYsnoZR4R9nXgcuvcXoH8WZjRsb4ZPfVJGchG +7CfRMlG0HR34vcUpehNj5pAavErhfNnk1CEal0TyDsOkBY/+JG239zXgRzMYjSE6 +ptX5kj5pGv0uXVoozSP/JZblI8/Spd6TZkblLNAYOl3ssfcUGN4NFDXlzmiWvP+q +6ZUgE8tD7CSryicICKmXcVQIa6AG8ultYa6mBAaewzMbiIt2TUo9smglpEqGeHoL +CuLb3e7zLf0AhWDZOgTTfe1KFEiK6TXMe9HWYeP3MPuyKhS20GmT/Zcu5VN4wbr0 +bP+mTWk700oLJ0OPQ6YgGkyqBmh/Bsi/TqnpJWS/mjRbJEe3E2NmNMwmP4jwJ79V +JClp5Gg9kbM6hPkmGNnhbbFzn3kwY3pi9/AiqpGyr3GUPhXvP7fYwAu/A5ISKw8r +87j/EJntyIzm51fcm8Q0mq1IDt4tNkIOwJEIc45h9r7ZC1VAKkzlCa7XT04GguFo +JMaJBYESYcOAmbKRojo8P/cN4fPuemuhQFQplkFIM6FtG9cJMo2ayp6ukH9Up8tn +8j7YgE/m9BL9SnUIbNlti9j0cNgeKVn24WC38hw9D8M0/sR5gYyclWh/OotCttoQ +I8ySZzSvB4GARZHbexagvg1EdV93ctYyAWGLkpJYAzuiXbt7FayG7e2ifYkCIQDp +IldsAFGVaiJRQdiKsWdReOSjzH6h8cw6Co3OCISiOQKCAgEAnSU29U65jK3W2BiA +fKTlTBx2yDUCDFeqnla5arZ2njGsUKiP2nocArAPLQggwk9rfqufybQltM8+zjmE +zeb4mUCVhSbTH7BvP903U0YEabZJCHLx80nTywq2RgQs0Qmn43vs2U5EidYR0xj8 +CCNAH5gdzd9/CL1RYACHAf7zj4n68ZaNkAy9Jz1JjYXjP6IAxJh1W/Y0vsdFdIJ/ +dnuxsyMCUCSwDvSNApSfATO/tw+DCVpGgKo4qE8b8lsfXKeihuMzyXuSe/D98YN2 +UFWRTQ6gFxGrntg3LOn41RXSkXxzixgl7quacIJzm8jrFkDJSx4AZ8rgt/9JbThA +XF9PVlCVv7GL1NztUs4cDK+zsJld4O1rlI3QOz5DWq9oA+Hj1MN3L9IW3Iv2Offo +AaubXJhuv0xPWYmtCo06mPgSwkWPjDnGCbp1vuI8zPTsfyhsahuKeW0h8JttW4GB +6CTtC1AVWA1pJug5pBo36S5G24ihRsdG3Q5/aTlnke7t7H1Tkh2KuvV9hD5a5Xtw +cnuiEcKjyR0FWR81RdsAKh+7QNI3Lx75c95i22Aupon5R/Qkb05VzHdd299bb78c +x5mW8Dsg4tKLF7kpDAcWmx7JpkPHQ+5V9N766sfZ+z/PiVWfNAK8gzJRn/ceLQcK +C6uOhcZgN0o4UYrmYEy9icxJ44wCggIBAIu+yagyVMS+C5OqOprmtteh/+MyaYI+ +Q3oPXFR8eHLJftsBWev1kRfje1fdxzzx/k4SQMRbxxbMtGV74KNwRUzEWOkoyAHP +AAjhMio1mxknPwAxRjWDOSE0drGJPyGpI9ZfpMUtvekQO7MCGqa45vPldY10RwZC +VN66AIpxSF0MG1OEmgD+noHMI7moclw/nw+ZUPaIFxvPstlD4EsPDkdE0I6x3k3b +UXlWAYAJFR6fNf8+Ki3xnjLjW9da3cU/p2H7+LrFDP+kPUGJpqr4bG606GUcV3Cl +dznoqlgaudWgcQCQx0NPzi7k5O7PXr7C3UU0cg+5+GkviIzogaioxidvvchnG+UU +0y5nVuji6G69j5sUhlcFXte31Nte2VUb6P8umo+mbDT0UkZZZzoOsCpw+cJ8OHOV +emFIhVphNHqQt20Tq6WVRBx+p4+YNWiThvmLtmLh0QghdnUrJZxyXx7/p8K5SE9/ ++qU11t5dUvYS+53U1gJ2kgIFO4Zt6gaoOyexTt5f4Ganh9IcJ01wegl5WT58aDtf +hmw0HnOrgbWt4lRkxOra281hL74xcgtgMZQ32PTOy8wTEVTk03mmqlIq/dV4jgBc +Nh1FGQwGEeGlfbuNSB4nqgMN6zn1PmI7oCWLD9XLR6VZTebF7pGfpHtYczyivuxf +e1YOro6e0mUqAiEAx4K3cPG3dxH91uU3L+sS2vzqXEVn2BmSMmkGczSOgn4= +-----END DSA PRIVATE KEY----- diff --git a/test/js/third_party/jsonwebtoken/dsa-public.pem b/test/js/third_party/jsonwebtoken/dsa-public.pem new file mode 100644 index 000000000..659d96b79 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/dsa-public.pem @@ -0,0 +1,36 @@ +-----BEGIN PUBLIC KEY----- +MIIGSDCCBDoGByqGSM44BAEwggQtAoICAQCvNs9u3/8FCmxiyehlHhH2deBy69xe +gfxZmNGxvhk99UkZyEbsJ9EyUbQdHfi9xSl6E2PmkBq8SuF82eTUIRqXRPIOw6QF +j/4kbbf3NeBHMxiNITqm1fmSPmka/S5dWijNI/8lluUjz9Kl3pNmRuUs0Bg6Xeyx +9xQY3g0UNeXOaJa8/6rplSATy0PsJKvKJwgIqZdxVAhroAby6W1hrqYEBp7DMxuI +i3ZNSj2yaCWkSoZ4egsK4tvd7vMt/QCFYNk6BNN97UoUSIrpNcx70dZh4/cw+7Iq +FLbQaZP9ly7lU3jBuvRs/6ZNaTvTSgsnQ49DpiAaTKoGaH8GyL9OqeklZL+aNFsk +R7cTY2Y0zCY/iPAnv1UkKWnkaD2RszqE+SYY2eFtsXOfeTBjemL38CKqkbKvcZQ+ +Fe8/t9jAC78DkhIrDyvzuP8Qme3IjObnV9ybxDSarUgO3i02Qg7AkQhzjmH2vtkL +VUAqTOUJrtdPTgaC4WgkxokFgRJhw4CZspGiOjw/9w3h8+56a6FAVCmWQUgzoW0b +1wkyjZrKnq6Qf1Sny2fyPtiAT+b0Ev1KdQhs2W2L2PRw2B4pWfbhYLfyHD0PwzT+ +xHmBjJyVaH86i0K22hAjzJJnNK8HgYBFkdt7FqC+DUR1X3dy1jIBYYuSklgDO6Jd +u3sVrIbt7aJ9iQIhAOkiV2wAUZVqIlFB2IqxZ1F45KPMfqHxzDoKjc4IhKI5AoIC +AQCdJTb1TrmMrdbYGIB8pOVMHHbINQIMV6qeVrlqtnaeMaxQqI/aehwCsA8tCCDC +T2t+q5/JtCW0zz7OOYTN5viZQJWFJtMfsG8/3TdTRgRptkkIcvHzSdPLCrZGBCzR +Cafje+zZTkSJ1hHTGPwII0AfmB3N338IvVFgAIcB/vOPifrxlo2QDL0nPUmNheM/ +ogDEmHVb9jS+x0V0gn92e7GzIwJQJLAO9I0ClJ8BM7+3D4MJWkaAqjioTxvyWx9c +p6KG4zPJe5J78P3xg3ZQVZFNDqAXEaue2Dcs6fjVFdKRfHOLGCXuq5pwgnObyOsW +QMlLHgBnyuC3/0ltOEBcX09WUJW/sYvU3O1SzhwMr7OwmV3g7WuUjdA7PkNar2gD +4ePUw3cv0hbci/Y59+gBq5tcmG6/TE9Zia0KjTqY+BLCRY+MOcYJunW+4jzM9Ox/ +KGxqG4p5bSHwm21bgYHoJO0LUBVYDWkm6DmkGjfpLkbbiKFGx0bdDn9pOWeR7u3s +fVOSHYq69X2EPlrle3Bye6IRwqPJHQVZHzVF2wAqH7tA0jcvHvlz3mLbYC6miflH +9CRvTlXMd13b31tvvxzHmZbwOyDi0osXuSkMBxabHsmmQ8dD7lX03vrqx9n7P8+J +VZ80AryDMlGf9x4tBwoLq46FxmA3SjhRiuZgTL2JzEnjjAOCAgYAAoICAQCLvsmo +MlTEvguTqjqa5rbXof/jMmmCPkN6D1xUfHhyyX7bAVnr9ZEX43tX3cc88f5OEkDE +W8cWzLRle+CjcEVMxFjpKMgBzwAI4TIqNZsZJz8AMUY1gzkhNHaxiT8hqSPWX6TF +Lb3pEDuzAhqmuObz5XWNdEcGQlTeugCKcUhdDBtThJoA/p6BzCO5qHJcP58PmVD2 +iBcbz7LZQ+BLDw5HRNCOsd5N21F5VgGACRUenzX/Piot8Z4y41vXWt3FP6dh+/i6 +xQz/pD1Biaaq+GxutOhlHFdwpXc56KpYGrnVoHEAkMdDT84u5OTuz16+wt1FNHIP +ufhpL4iM6IGoqMYnb73IZxvlFNMuZ1bo4uhuvY+bFIZXBV7Xt9TbXtlVG+j/LpqP +pmw09FJGWWc6DrAqcPnCfDhzlXphSIVaYTR6kLdtE6ullUQcfqePmDVok4b5i7Zi +4dEIIXZ1KyWccl8e/6fCuUhPf/qlNdbeXVL2Evud1NYCdpICBTuGbeoGqDsnsU7e +X+Bmp4fSHCdNcHoJeVk+fGg7X4ZsNB5zq4G1reJUZMTq2tvNYS++MXILYDGUN9j0 +zsvMExFU5NN5pqpSKv3VeI4AXDYdRRkMBhHhpX27jUgeJ6oDDes59T5iO6Aliw/V +y0elWU3mxe6Rn6R7WHM8or7sX3tWDq6OntJlKg== +-----END PUBLIC KEY----- diff --git a/test/js/third_party/jsonwebtoken/ecdsa-private.pem b/test/js/third_party/jsonwebtoken/ecdsa-private.pem new file mode 100644 index 000000000..aad4c4d93 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/ecdsa-private.pem @@ -0,0 +1,18 @@ +-----BEGIN EC PARAMETERS----- +MIH3AgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP////////// +/////zBbBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY12Ko6 +k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsDFQDEnTYIhucEk2pmeOETnSa3gZ9+ +kARBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLeszoPShOUXYmMKWT+NC4v4af5uO5+tK +fA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////AAAAAP//////////vOb6racXnoTz +ucrC/GMlUQIBAQ== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MIIBaAIBAQQgeg2m9tJJsnURyjTUihohiJahj9ETy3csUIt4EYrV+J2ggfowgfcC +AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA//////////////// +MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr +vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE +axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W +K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8 +YyVRAgEBoUQDQgAEEWluurrkZECnq27UpNauq16f9+5DDMFJZ3HV43Ujc3tcXQ++ +N1T/0CAA8ve286f32s7rkqX/pPokI/HBpP5p3g== +-----END EC PRIVATE KEY----- diff --git a/test/js/third_party/jsonwebtoken/ecdsa-public-invalid.pem b/test/js/third_party/jsonwebtoken/ecdsa-public-invalid.pem new file mode 100644 index 000000000..016d86d5f --- /dev/null +++ b/test/js/third_party/jsonwebtoken/ecdsa-public-invalid.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA +AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA//// +///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd +NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5 +RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA +//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABEfZiYJDbghTGQ+KGnHGSl6K +yUqK/BL2uJIg7Z0bx48v6+L7Ve8MCS17eptkMT2e4l5B/ZGDVUHb6uZ5xFROLBw= +-----END PUBLIC KEY----- diff --git a/test/js/third_party/jsonwebtoken/ecdsa-public-x509.pem b/test/js/third_party/jsonwebtoken/ecdsa-public-x509.pem new file mode 100644 index 000000000..ef9fe22c3 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/ecdsa-public-x509.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGjCCAsKgAwIBAgIJANuPNBWwp6wzMAkGByqGSM49BAEwRTELMAkGA1UEBhMC +QVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdp +dHMgUHR5IEx0ZDAeFw0xNzA2MTAxMTAzMjJaFw0yNzA2MDgxMTAzMjJaMEUxCzAJ +BgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l +dCBXaWRnaXRzIFB0eSBMdGQwggFLMIIBAwYHKoZIzj0CATCB9wIBATAsBgcqhkjO +PQEBAiEA/////wAAAAEAAAAAAAAAAAAAAAD///////////////8wWwQg/////wAA +AAEAAAAAAAAAAAAAAAD///////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQaw +zFOw9jvOPD4n0mBLAxUAxJ02CIbnBJNqZnjhE50mt4GffpAEQQRrF9Hy4SxCR/i8 +5uVjpEDydwN9gS3rM6D0oTlF2JjClk/jQuL+Gn+bjufrSnwPnhYrzjNXazFezsu2 +QGg3v1H1AiEA/////wAAAAD//////////7zm+q2nF56E87nKwvxjJVECAQEDQgAE +EWluurrkZECnq27UpNauq16f9+5DDMFJZ3HV43Ujc3tcXQ++N1T/0CAA8ve286f3 +2s7rkqX/pPokI/HBpP5p3qOBpzCBpDAdBgNVHQ4EFgQUAF43lnAvCztZZGaGMoxs +cp6tpz8wdQYDVR0jBG4wbIAUAF43lnAvCztZZGaGMoxscp6tpz+hSaRHMEUxCzAJ +BgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l +dCBXaWRnaXRzIFB0eSBMdGSCCQDbjzQVsKesMzAMBgNVHRMEBTADAQH/MAkGByqG +SM49BAEDRwAwRAIgV039oh2RtcSwywQ/0dWAwc20NHxrgmKoQ5A3AS5A9d0CIBCV +2AlKDFjmDC7zjldNhWbMcIlSSj71ghhhxeS0F8v1 +-----END CERTIFICATE----- diff --git a/test/js/third_party/jsonwebtoken/ecdsa-public.pem b/test/js/third_party/jsonwebtoken/ecdsa-public.pem new file mode 100644 index 000000000..6cfee2f8f --- /dev/null +++ b/test/js/third_party/jsonwebtoken/ecdsa-public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA +AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA//// +///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd +NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5 +RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA +//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABBFpbrq65GRAp6tu1KTWrqte +n/fuQwzBSWdx1eN1I3N7XF0PvjdU/9AgAPL3tvOn99rO65Kl/6T6JCPxwaT+ad4= +-----END PUBLIC KEY----- diff --git a/test/js/third_party/jsonwebtoken/encoding.test.js b/test/js/third_party/jsonwebtoken/encoding.test.js new file mode 100644 index 000000000..c8ad38dc0 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/encoding.test.js @@ -0,0 +1,37 @@ +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; + +describe("encoding", function () { + function b64_to_utf8(str) { + return decodeURIComponent(escape(atob(str))); + } + + it("should properly encode the token (utf8)", function () { + var expected = "José"; + var token = jwt.sign({ name: expected }, "shhhhh"); + var decoded_name = JSON.parse(b64_to_utf8(token.split(".")[1])).name; + expect(decoded_name).toEqual(expected); + }); + + it("should properly encode the token (binary)", function () { + var expected = "José"; + var token = jwt.sign({ name: expected }, "shhhhh", { encoding: "binary" }); + var decoded_name = JSON.parse(atob(token.split(".")[1])).name; + expect(decoded_name).toEqual(expected); + }); + + it("should return the same result when decoding", function () { + var username = "測試"; + + var token = jwt.sign( + { + username: username, + }, + "test", + ); + + var payload = jwt.verify(token, "test"); + + expect(payload.username).toEqual(username); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/expires_format.test.js b/test/js/third_party/jsonwebtoken/expires_format.test.js new file mode 100644 index 000000000..4d44243ab --- /dev/null +++ b/test/js/third_party/jsonwebtoken/expires_format.test.js @@ -0,0 +1,10 @@ +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; + +describe("expires option", function () { + it("should throw on deprecated expiresInSeconds option", function () { + expect(function () { + jwt.sign({ foo: 123 }, "123", { expiresInSeconds: 5 }); + }).toThrow('"expiresInSeconds" is not allowed'); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/header-kid.test.js b/test/js/third_party/jsonwebtoken/header-kid.test.js new file mode 100644 index 000000000..0eeea3b08 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/header-kid.test.js @@ -0,0 +1,83 @@ +"use strict"; + +import jwt from "jsonwebtoken"; +import { expect, describe, it, beforeEach } from "bun:test"; +import util from "util"; +import testUtils from "./test-utils"; + +function signWithKeyId(keyid, payload, callback) { + const options = { algorithm: "HS256" }; + if (keyid !== undefined) { + options.keyid = keyid; + } + testUtils.signJWTHelper(payload, "secret", options, callback); +} + +describe("keyid", function () { + describe('`jwt.sign` "keyid" option validation', function () { + [true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, [], ["foo"], {}, { foo: "bar" }].forEach( + keyid => { + it(`should error with with value ${util.inspect(keyid)}`, function (done) { + signWithKeyId(keyid, {}, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", '"keyid" must be a string'); + }); + }); + }); + }, + ); + + // undefined needs special treatment because {} is not the same as {keyid: undefined} + it("should error with with value undefined", function (done) { + testUtils.signJWTHelper({}, "secret", { keyid: undefined, algorithm: "HS256" }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(Error); + expect(err).toHaveProperty("message", '"keyid" must be a string'); + }); + }); + }); + }); + + describe("when signing a token", function () { + it('should not add "kid" header when "keyid" option not provided', function (done) { + signWithKeyId(undefined, {}, (err, token) => { + testUtils.asyncCheck(done, () => { + const decoded = jwt.decode(token, { complete: true }); + expect(err).toBeNull(); + expect(decoded.header).not.toHaveProperty("kid"); + }); + }); + }); + + it('should add "kid" header when "keyid" option is provided and an object payload', function (done) { + signWithKeyId("foo", {}, (err, token) => { + testUtils.asyncCheck(done, () => { + const decoded = jwt.decode(token, { complete: true }); + expect(err).toBeNull(); + expect(decoded.header).toHaveProperty("kid", "foo"); + }); + }); + }); + + it('should add "kid" header when "keyid" option is provided and a Buffer payload', function (done) { + signWithKeyId("foo", new Buffer("a Buffer payload"), (err, token) => { + testUtils.asyncCheck(done, () => { + const decoded = jwt.decode(token, { complete: true }); + expect(err).toBeNull(); + expect(decoded.header).toHaveProperty("kid", "foo"); + }); + }); + }); + + it('should add "kid" header when "keyid" option is provided and a string payload', function (done) { + signWithKeyId("foo", "a string payload", (err, token) => { + testUtils.asyncCheck(done, () => { + const decoded = jwt.decode(token, { complete: true }); + expect(err).toBeNull(); + expect(decoded.header).toHaveProperty("kid", "foo"); + }); + }); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/invalid_exp.test.js b/test/js/third_party/jsonwebtoken/invalid_exp.test.js new file mode 100644 index 000000000..b5310c3bc --- /dev/null +++ b/test/js/third_party/jsonwebtoken/invalid_exp.test.js @@ -0,0 +1,54 @@ +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; + +describe("invalid expiration", function () { + it("should fail with string", function (done) { + var broken_token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxMjMiLCJmb28iOiJhZGFzIn0.cDa81le-pnwJMcJi3o3PBwB7cTJMiXCkizIhxbXAKRg"; + + jwt.verify(broken_token, "123", function (err) { + expect(err.name).toEqual("JsonWebTokenError"); + done(); + }); + }); + + it("should fail with 0", function (done) { + var broken_token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjAsImZvbyI6ImFkYXMifQ.UKxix5T79WwfqAA0fLZr6UrhU-jMES2unwCOFa4grEA"; + + jwt.verify(broken_token, "123", function (err) { + expect(err.name).toEqual("TokenExpiredError"); + done(); + }); + }); + + it("should fail with false", function (done) { + var broken_token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOmZhbHNlLCJmb28iOiJhZGFzIn0.iBn33Plwhp-ZFXqppCd8YtED77dwWU0h68QS_nEQL8I"; + + jwt.verify(broken_token, "123", function (err) { + expect(err.name).toEqual("JsonWebTokenError"); + done(); + }); + }); + + it("should fail with true", function (done) { + var broken_token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOnRydWUsImZvbyI6ImFkYXMifQ.eOWfZCTM5CNYHAKSdFzzk2tDkPQmRT17yqllO-ItIMM"; + + jwt.verify(broken_token, "123", function (err) { + expect(err.name).toEqual("JsonWebTokenError"); + done(); + }); + }); + + it("should fail with object", function (done) { + var broken_token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOnt9LCJmb28iOiJhZGFzIn0.1JjCTsWLJ2DF-CfESjLdLfKutUt3Ji9cC7ESlcoBHSY"; + + jwt.verify(broken_token, "123", function (err) { + expect(err.name).toEqual("JsonWebTokenError"); + done(); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/invalid_pub.pem b/test/js/third_party/jsonwebtoken/invalid_pub.pem new file mode 100644 index 000000000..2482abbde --- /dev/null +++ b/test/js/third_party/jsonwebtoken/invalid_pub.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJjCCAg6gAwIBAgIJAMyz3mSPlaW4MA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV +BAMUCyouYXV0aDAuY29tMB4XDTEzMDQxODE3MDE1MFoXDTI2MTIyNjE3MDE1MFow +FjEUMBIGA1UEAxQLKi5hdXRoMC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDZq1Ua0/BGm+TaBFoftKWeYMWrQG9Fx3g7ikErxljmyOvlwqkiat3q +ixX+Dxw9TFb5gbBjNJ+L3nt4YefJgLsYvsHqkOUxWsB+HM/ulJRVnVrZm1tI3Nbg +xO1BQ7DrGfBpq2KCxtQCaQFRlQJw1+qS5LwrdIvihB7Kc142VElCFFHJ6+09eMUy +jy00Z5pfQr4Am6W6eEOS9ObDbNs4XgKOcWe5khWXj3UStou+VgbAg40XcYht2IbY +gMfKF+VUZOy3+e+aRTqPOBU3MAeb0tvCCPUQJbNAUHgSKVhAvNf8mRwttVsOLT70 +anjjeCOd7RKS8fVKBwc2KtgNkghYdPY9AgMBAAGjdzB1MB0GA1UdDgQWBBSi4+X0 ++MvCKDdd375mDhx/ZBbJ4DBGBgNVHSMEPzA9gBSi4+X0+MvCKDdd375mDhx/ZBbJ +4KEapBgwFjEUMBIGA1UEAxQLKi5hdXRoMC5jb22CCQDMs95kj5WluDAMBgNVHRME +BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBi0qPe0DzlPSufq+Gdk2Fwf1pGEtjA +D34IxxJ9SX6r1DS/NIP7IOLUnNU8cP8BQWl7i413v29jJsNV457pjdmqf8J7OE9O +eF5Yz1x91gY/27561Iga/TQeIVOlFQAgx66eLfUFFoAig3hz2srZo5TzYBixMJsS +fYMXHPiU7KoLUqYXvpSXIllstQCu51KCC6t9H7wZ92lTES1v76hFY4edQ30sftPo +kjAYWGEhMjPo/r4THcdSMqKXoRtCGEun4pTXid7MJcTgdGDrAJddLWi6SxKecEVB +MhMu4XfUCdxCwqQPjHeJ+zE49A1CUdBB2FN3BNLbmTTwEBgmuwyGRzhj +-----END CERTIFICATE----- diff --git a/test/js/third_party/jsonwebtoken/issue_147.test.js b/test/js/third_party/jsonwebtoken/issue_147.test.js new file mode 100644 index 000000000..294e8528a --- /dev/null +++ b/test/js/third_party/jsonwebtoken/issue_147.test.js @@ -0,0 +1,10 @@ +import jwt from "jsonwebtoken"; +import { describe, it, expect } from "bun:test"; + +describe("issue 147 - signing with a sealed payload", function () { + it("should put the expiration claim", function () { + var token = jwt.sign(Object.seal({ foo: 123 }), "123", { expiresIn: 10 }); + var result = jwt.verify(token, "123"); + expect(result.exp).toBeCloseTo(Math.floor(Date.now() / 1000) + 10, 0.2); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/issue_304.test.js b/test/js/third_party/jsonwebtoken/issue_304.test.js new file mode 100644 index 000000000..257bcc09d --- /dev/null +++ b/test/js/third_party/jsonwebtoken/issue_304.test.js @@ -0,0 +1,43 @@ +import jwt from "jsonwebtoken"; +import { describe, it, expect } from "bun:test"; + +describe("issue 304 - verifying values other than strings", function () { + it("should fail with numbers", function (done) { + jwt.verify(123, "foo", function (err) { + expect(err.name).toEqual("JsonWebTokenError"); + done(); + }); + }); + + it("should fail with objects", function (done) { + jwt.verify({ foo: "bar" }, "biz", function (err) { + expect(err.name).toEqual("JsonWebTokenError"); + done(); + }); + }); + + it("should fail with arrays", function (done) { + jwt.verify(["foo"], "bar", function (err) { + expect(err.name).toEqual("JsonWebTokenError"); + done(); + }); + }); + + it("should fail with functions", function (done) { + jwt.verify( + function () {}, + "foo", + function (err) { + expect(err.name).toEqual("JsonWebTokenError"); + done(); + }, + ); + }); + + it("should fail with booleans", function (done) { + jwt.verify(true, "foo", function (err) { + expect(err.name).toEqual("JsonWebTokenError"); + done(); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/issue_70.test.js b/test/js/third_party/jsonwebtoken/issue_70.test.js new file mode 100644 index 000000000..2dfeabe54 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/issue_70.test.js @@ -0,0 +1,14 @@ +import jwt from "jsonwebtoken"; +import { describe, it } from "bun:test"; + +describe("issue 70 - public key start with BEING PUBLIC KEY", function () { + it("should work", function (done) { + var fs = require("fs"); + var cert_pub = fs.readFileSync(__dirname + "/rsa-public.pem"); + var cert_priv = fs.readFileSync(__dirname + "/rsa-private.pem"); + + var token = jwt.sign({ foo: "bar" }, cert_priv, { algorithm: "RS256" }); + + jwt.verify(token, cert_pub, done); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/jwt.asymmetric_signing.test.js b/test/js/third_party/jsonwebtoken/jwt.asymmetric_signing.test.js new file mode 100644 index 000000000..848dadb1a --- /dev/null +++ b/test/js/third_party/jsonwebtoken/jwt.asymmetric_signing.test.js @@ -0,0 +1,208 @@ +const PS_SUPPORTED = true; +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; +import fs from "fs"; +import path from "path"; + +function loadKey(filename) { + return fs.readFileSync(path.join(__dirname, filename)); +} + +const algorithms = { + RS256: { + pub_key: loadKey("pub.pem"), + priv_key: loadKey("priv.pem"), + invalid_pub_key: loadKey("invalid_pub.pem"), + }, + ES256: { + // openssl ecparam -name secp256r1 -genkey -param_enc explicit -out ecdsa-private.pem + priv_key: loadKey("ecdsa-private.pem"), + // openssl ec -in ecdsa-private.pem -pubout -out ecdsa-public.pem + pub_key: loadKey("ecdsa-public.pem"), + invalid_pub_key: loadKey("ecdsa-public-invalid.pem"), + }, +}; + +if (PS_SUPPORTED) { + algorithms.PS256 = { + pub_key: loadKey("pub.pem"), + priv_key: loadKey("priv.pem"), + invalid_pub_key: loadKey("invalid_pub.pem"), + }; +} + +describe("Asymmetric Algorithms", function () { + Object.keys(algorithms).forEach(function (algorithm) { + describe(algorithm, function () { + const pub = algorithms[algorithm].pub_key; + const priv = algorithms[algorithm].priv_key; + + // "invalid" means it is not the public key for the loaded "priv" key + const invalid_pub = algorithms[algorithm].invalid_pub_key; + + describe("when signing a token", function () { + const token = jwt.sign({ foo: "bar" }, priv, { algorithm: algorithm }); + + it("should be syntactically valid", function () { + expect(typeof token).toBe("string"); + expect(token.split(".")).toHaveLength(3); + }); + + describe("asynchronous", function () { + (algorithm === "ES256" ? it.todo : it)("should validate with public key", function (done) { + jwt.verify(token, pub, function (err, decoded) { + if (err) return done(err); + expect(decoded).toBeDefined(); + expect(decoded.foo).toBeTruthy(); + expect("bar").toBe(decoded.foo); + done(); + }); + }); + + it("should throw with invalid public key", function (done) { + jwt.verify(token, invalid_pub, function (err, decoded) { + expect(decoded).toBeUndefined(); + expect(err).toBeTruthy(); + done(); + }); + }); + }); + + describe("synchronous", function () { + (algorithm === "ES256" ? it.todo : it)("should validate with public key", function () { + const decoded = jwt.verify(token, pub); + expect(decoded).toBeDefined(); + expect(decoded.foo).toBeTruthy(); + expect("bar").toBe(decoded.foo); + }); + + it("should throw with invalid public key", function () { + const jwtVerify = jwt.verify.bind(null, token, invalid_pub); + expect(jwtVerify).toThrow(); + }); + }); + }); + + describe("when signing a token with expiration", function () { + (algorithm === "ES256" ? it.todo : it)("should be valid expiration", function (done) { + const token = jwt.sign({ foo: "bar" }, priv, { algorithm: algorithm, expiresIn: "10m" }); + jwt.verify(token, pub, function (err, decoded) { + if (err) return done(err); + expect(decoded).toBeTruthy(); + expect(err).toBeNull(); + done(); + }); + }); + + (algorithm === "ES256" ? it.todo : it)("should be invalid", function (done) { + // expired token + const token = jwt.sign({ foo: "bar" }, priv, { algorithm: algorithm, expiresIn: -1 * (10 * 60 * 1000) }); + jwt.verify(token, pub, function (err, decoded) { + expect(decoded).toBeUndefined(); + expect(err).toBeDefined(); + expect(err.name).toBe("TokenExpiredError"); + expect(err.expiredAt).toBeInstanceOf(Date); + expect(err).toBeInstanceOf(jwt.TokenExpiredError); + done(); + }); + }); + + (algorithm === "ES256" ? it.todo : it)("should NOT be invalid", function (done) { + // expired token + const token = jwt.sign({ foo: "bar" }, priv, { algorithm: algorithm, expiresIn: -1 * (10 * 60 * 1000) }); + + jwt.verify(token, pub, { ignoreExpiration: true }, function (err, decoded) { + expect(decoded).toBeDefined(); + expect(decoded.foo).toBeDefined(); + expect("bar").toBe(decoded.foo); + done(); + }); + }); + }); + + describe("when verifying a malformed token", function () { + it("should throw", function (done) { + jwt.verify("fruit.fruit.fruit", pub, function (err, decoded) { + expect(decoded).toBeUndefined(); + expect(err).toBeDefined(); + expect(err.name).toBe("JsonWebTokenError"); + done(); + }); + }); + }); + + describe("when decoding a jwt token with additional parts", function () { + const token = jwt.sign({ foo: "bar" }, priv, { algorithm: algorithm }); + + it("should throw", function (done) { + jwt.verify(token + ".foo", pub, function (err, decoded) { + expect(decoded).toBeUndefined(); + expect(err).toBeDefined(); + done(); + }); + }); + }); + + describe("when decoding a invalid jwt token", function () { + it("should return null", function (done) { + const payload = jwt.decode("whatever.token"); + expect(payload).toBeNull(); + done(); + }); + }); + + describe("when decoding a valid jwt token", function () { + it("should return the payload", function (done) { + const obj = { foo: "bar" }; + const token = jwt.sign(obj, priv, { algorithm: algorithm }); + const payload = jwt.decode(token); + expect(payload.foo).toEqual(obj.foo); + done(); + }); + it("should return the header and payload and signature if complete option is set", function (done) { + const obj = { foo: "bar" }; + const token = jwt.sign(obj, priv, { algorithm: algorithm }); + const decoded = jwt.decode(token, { complete: true }); + expect(decoded.payload.foo).toEqual(obj.foo); + expect(decoded.header).toStrictEqual({ typ: "JWT", alg: algorithm }); + expect(typeof decoded.signature).toBe("string"); + done(); + }); + }); + }); + }); + + describe("when signing a token with an unsupported private key type", function () { + it.todo("should throw an error", function () { + const obj = { foo: "bar" }; + const key = loadKey("dsa-private.pem"); + const algorithm = "RS256"; + + expect(function () { + jwt.sign(obj, key, { algorithm }); + }).toThrow('Unknown key type "dsa".'); + }); + }); + + describe("when signing a token with an incorrect private key type", function () { + it("should throw a validation error if key validation is enabled", function () { + const obj = { foo: "bar" }; + const key = loadKey("rsa-private.pem"); + const algorithm = "ES256"; + + expect(function () { + jwt.sign(obj, key, { algorithm }); + }).toThrow(/"alg" parameter for "rsa" key type must be one of:/); + }); + + it("should throw an unknown error if key validation is disabled", function () { + const obj = { foo: "bar" }; + const key = loadKey("rsa-private.pem"); + const algorithm = "ES256"; + + expect(function () { + jwt.sign(obj, key, { algorithm, allowInvalidAsymmetricKeyTypes: true }); + }).not.toThrow(/"alg" parameter for "rsa" key type must be one of:/); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/jwt.hs.test.js b/test/js/third_party/jsonwebtoken/jwt.hs.test.js new file mode 100644 index 000000000..65424f66a --- /dev/null +++ b/test/js/third_party/jsonwebtoken/jwt.hs.test.js @@ -0,0 +1,140 @@ +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; +import jws from "jws"; +import { generateKeyPairSync } from "crypto"; + +describe("HS256", function () { + describe("when signing using HS256", function () { + it("should throw if the secret is an asymmetric key", function () { + const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 }); + + expect(function () { + jwt.sign({ foo: "bar" }, privateKey, { algorithm: "HS256" }); + }).toThrow("must be a symmetric key"); + }); + + it("should throw if the payload is undefined", function () { + expect(function () { + jwt.sign(undefined, "secret", { algorithm: "HS256" }); + }).toThrow("payload is required"); + }); + + it("should throw if options is not a plain object", function () { + expect(function () { + jwt.sign({ foo: "bar" }, "secret", ["HS256"]); + }).toThrow('Expected "options" to be a plain object'); + }); + }); + + describe("with a token signed using HS256", function () { + var secret = "shhhhhh"; + + var token = jwt.sign({ foo: "bar" }, secret, { algorithm: "HS256" }); + + it("should be syntactically valid", function () { + expect(typeof token).toBe("string"); + expect(token.split(".")).toHaveLength(3); + }); + + it("should be able to validate without options", function (done) { + var callback = function (err, decoded) { + if (err) return done(err); + expect(decoded).toBeDefined(); + expect(decoded.foo).toBeDefined(); + expect("bar").toBe(decoded.foo); + done(); + }; + callback.issuer = "shouldn't affect"; + jwt.verify(token, secret, callback); + }); + + it("should validate with secret", function (done) { + jwt.verify(token, secret, function (err, decoded) { + if (err) return done(err); + expect(decoded).toBeDefined(); + expect(decoded.foo).toBeDefined(); + done(); + }); + }); + + it("should throw with invalid secret", function (done) { + jwt.verify(token, "invalid secret", function (err, decoded) { + expect(decoded).toBeUndefined(); + expect(err).toBeTruthy(); + done(); + }); + }); + + it("should throw with secret and token not signed", function (done) { + const header = { alg: "none" }; + const payload = { foo: "bar" }; + const token = jws.sign({ header, payload, secret: "secret", encoding: "utf8" }); + jwt.verify(token, "secret", function (err, decoded) { + expect(decoded).toBeUndefined(); + expect(err).toBeTruthy(); + done(); + }); + }); + + it("should throw with falsy secret and token not signed", function (done) { + const header = { alg: "none" }; + const payload = { foo: "bar" }; + const token = jws.sign({ header, payload, secret: null, encoding: "utf8" }); + jwt.verify(token, "secret", function (err, decoded) { + expect(decoded).toBeUndefined(); + expect(err).toBeTruthy(); + done(); + }); + }); + + it("should throw when verifying null", function (done) { + jwt.verify(null, "secret", function (err, decoded) { + expect(decoded).toBeUndefined(); + expect(err).toBeTruthy(); + done(); + }); + }); + + it("should return an error when the token is expired", function (done) { + var token = jwt.sign({ exp: 1 }, secret, { algorithm: "HS256" }); + jwt.verify(token, secret, { algorithm: "HS256" }, function (err, decoded) { + expect(decoded).toBeUndefined(); + expect(err).toBeTruthy(); + done(); + }); + }); + + it('should NOT return an error when the token is expired with "ignoreExpiration"', function (done) { + var token = jwt.sign({ exp: 1, foo: "bar" }, secret, { algorithm: "HS256" }); + jwt.verify(token, secret, { algorithm: "HS256", ignoreExpiration: true }, function (err, decoded) { + if (err) return done(err); + expect(decoded).toBeDefined(); + expect("bar").toBe(decoded.foo); + expect(decoded.foo).toBeDefined(); + done(); + }); + }); + + it("should default to HS256 algorithm when no options are passed", function () { + var token = jwt.sign({ foo: "bar" }, secret); + var verifiedToken = jwt.verify(token, secret); + expect(verifiedToken).toBeDefined(); + expect("bar").toBe(verifiedToken.foo); + }); + }); + + describe("should fail verification gracefully with trailing space in the jwt", function () { + var secret = "shhhhhh"; + var token = jwt.sign({ foo: "bar" }, secret, { algorithm: "HS256" }); + + it('should return the "invalid token" error', function (done) { + var malformedToken = token + " "; // corrupt the token by adding a space + jwt.verify(malformedToken, secret, { algorithm: "HS256", ignoreExpiration: true }, function (err) { + expect(err).not.toBeNull(); + expect("JsonWebTokenError").toBe(err.name); + expect("invalid token").toBe(err.message); + done(); + }); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/jwt.malicious.test.js b/test/js/third_party/jsonwebtoken/jwt.malicious.test.js new file mode 100644 index 000000000..8e31859cb --- /dev/null +++ b/test/js/third_party/jsonwebtoken/jwt.malicious.test.js @@ -0,0 +1,44 @@ +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; +import crypto from "crypto"; + +describe("when verifying a malicious token", function () { + // attacker has access to the public rsa key, but crafts the token as HS256 + // with kid set to the id of the rsa key, instead of the id of the hmac secret. + // const maliciousToken = jwt.sign( + // {foo: 'bar'}, + // pubRsaKey, + // {algorithm: 'HS256', keyid: 'rsaKeyId'} + // ); + // consumer accepts self signed tokens (HS256) and third party tokens (RS256) + const options = { algorithms: ["RS256", "HS256"] }; + + const { publicKey: pubRsaKey } = crypto.generateKeyPairSync("rsa", { modulusLength: 2048 }); + + it("should not allow HMAC verification with an RSA key in KeyObject format", function () { + const maliciousToken = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InJzYUtleUlkIn0.eyJmb28iOiJiYXIiLCJpYXQiOjE2NTk1MTA2MDh9.cOcHI1TXPbxTMlyVTfjArSWskrmezbrG8iR7uJHwtrQ"; + + expect(() => jwt.verify(maliciousToken, pubRsaKey, options)).toThrow("must be a symmetric key"); + }); + + it("should not allow HMAC verification with an RSA key in PEM format", function () { + const maliciousToken = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InJzYUtleUlkIn0.eyJmb28iOiJiYXIiLCJpYXQiOjE2NTk1MTA2MDh9.cOcHI1TXPbxTMlyVTfjArSWskrmezbrG8iR7uJHwtrQ"; + + expect(() => jwt.verify(maliciousToken, pubRsaKey.export({ type: "spki", format: "pem" }), options)).toThrow( + "must be a symmetric key", + ); + }); + + it("should not allow arbitrary execution from malicious Buffers containing objects with overridden toString functions", function () { + const token = jwt.sign({ "foo": "bar" }, "secret"); + const maliciousBuffer = { + toString: () => { + throw new Error("Arbitrary Code Execution"); + }, + }; + + expect(() => jwt.verify(token, maliciousBuffer)).toThrow("not valid key material"); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/noTimestamp.test.js b/test/js/third_party/jsonwebtoken/noTimestamp.test.js new file mode 100644 index 000000000..22a61b3eb --- /dev/null +++ b/test/js/third_party/jsonwebtoken/noTimestamp.test.js @@ -0,0 +1,10 @@ +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; + +describe("noTimestamp", function () { + it("should work with string", function () { + var token = jwt.sign({ foo: 123 }, "123", { expiresIn: "5m", noTimestamp: true }); + var result = jwt.verify(token, "123"); + expect(result.exp).toBeCloseTo(Math.floor(Date.now() / 1000) + 5 * 60, 0.5); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/non_object_values.test.js b/test/js/third_party/jsonwebtoken/non_object_values.test.js new file mode 100644 index 000000000..55b5d59ae --- /dev/null +++ b/test/js/third_party/jsonwebtoken/non_object_values.test.js @@ -0,0 +1,16 @@ +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; + +describe("non_object_values values", function () { + it("should work with string", function () { + var token = jwt.sign("hello", "123"); + var result = jwt.verify(token, "123"); + expect(result).toEqual("hello"); + }); + + it("should work with number", function () { + var token = jwt.sign(123, "123"); + var result = jwt.verify(token, "123"); + expect(result).toEqual("123"); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/option-complete.test.js b/test/js/third_party/jsonwebtoken/option-complete.test.js new file mode 100644 index 000000000..2b446e4e2 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/option-complete.test.js @@ -0,0 +1,53 @@ +"use strict"; + +import { expect, describe, it } from "bun:test"; +import testUtils from "./test-utils"; +import jws from "jws"; +import fs from "fs"; +import path from "path"; + +describe("complete option", function () { + const secret = fs.readFileSync(path.join(__dirname, "priv.pem")); + const pub = fs.readFileSync(path.join(__dirname, "pub.pem")); + + const header = { alg: "RS256" }; + const payload = { iat: Math.floor(Date.now() / 1000) }; + const signed = jws.sign({ header, payload, secret, encoding: "utf8" }); + const signature = jws.decode(signed).signature; + + [ + { + description: "should return header, payload and signature", + complete: true, + }, + ].forEach(testCase => { + it(testCase.description, function (done) { + testUtils.verifyJWTHelper(signed, pub, { typ: "JWT", complete: testCase.complete }, (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded.header).toHaveProperty("alg", header.alg); + expect(decoded.payload).toHaveProperty("iat", payload.iat); + expect(decoded).toHaveProperty("signature", signature); + }); + }); + }); + }); + [ + { + description: "should return payload", + complete: false, + }, + ].forEach(testCase => { + it(testCase.description, function (done) { + testUtils.verifyJWTHelper(signed, pub, { typ: "JWT", complete: testCase.complete }, (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded.header).toBeUndefined(); + expect(decoded.payload).toBeUndefined(); + expect(decoded.signature).toBeUndefined(); + expect(decoded).toHaveProperty("iat", payload.iat); + }); + }); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/option-maxAge.test.js b/test/js/third_party/jsonwebtoken/option-maxAge.test.js new file mode 100644 index 000000000..e48525344 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/option-maxAge.test.js @@ -0,0 +1,62 @@ +"use strict"; + +import jwt from "jsonwebtoken"; +import { expect, describe, it, beforeEach, afterEach } from "bun:test"; +import util from "util"; +import sinon from "sinon"; + +describe("maxAge option", function () { + let token; + + let fakeClock; + beforeEach(function () { + fakeClock = sinon.useFakeTimers({ now: 60000 }); + token = jwt.sign({ iat: 70 }, "secret", { algorithm: "HS256" }); + }); + + afterEach(function () { + fakeClock.uninstall(); + }); + + [ + { + description: "should work with a positive string value", + maxAge: "3s", + }, + { + description: "should work with a negative string value", + maxAge: "-3s", + }, + { + description: "should work with a positive numeric value", + maxAge: 3, + }, + { + description: "should work with a negative numeric value", + maxAge: -3, + }, + ].forEach(testCase => { + it(testCase.description, function (done) { + expect(() => jwt.verify(token, "secret", { maxAge: "3s", algorithm: "HS256" })).not.toThrow(); + jwt.verify(token, "secret", { maxAge: testCase.maxAge, algorithm: "HS256" }, err => { + expect(err).toBeNull(); + done(); + }); + }); + }); + + [true, "invalid", [], ["foo"], {}, { foo: "bar" }].forEach(maxAge => { + it(`should error with value ${util.inspect(maxAge)}`, function (done) { + expect(() => jwt.verify(token, "secret", { maxAge, algorithm: "HS256" })).toThrow( + '"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60', + ); + jwt.verify(token, "secret", { maxAge, algorithm: "HS256" }, err => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err.message).toEqual( + '"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60', + ); + done(); + }); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/option-nonce.test.js b/test/js/third_party/jsonwebtoken/option-nonce.test.js new file mode 100644 index 000000000..abe918d42 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/option-nonce.test.js @@ -0,0 +1,41 @@ +"use strict"; + +import jwt from "jsonwebtoken"; +import { expect, describe, it, beforeEach } from "bun:test"; +import util from "util"; +import testUtils from "./test-utils"; + +describe("nonce option", function () { + let token; + + beforeEach(function () { + token = jwt.sign({ nonce: "abcde" }, "secret", { algorithm: "HS256" }); + }); + [ + { + description: "should work with a string", + nonce: "abcde", + }, + ].forEach(testCase => { + it(testCase.description, function (done) { + testUtils.verifyJWTHelper(token, "secret", { nonce: testCase.nonce }, (err, decoded) => { + testUtils.asyncCheck(done, () => { + expect(err).toBeNull(); + expect(decoded).toHaveProperty("nonce", "abcde"); + }); + }); + }); + }); + [true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, "", " ", [], ["foo"], {}, { foo: "bar" }].forEach( + nonce => { + it(`should error with value ${util.inspect(nonce)}`, function (done) { + testUtils.verifyJWTHelper(token, "secret", { nonce }, err => { + testUtils.asyncCheck(done, () => { + expect(err).toBeInstanceOf(jwt.JsonWebTokenError); + expect(err).toHaveProperty("message", "nonce must be a non-empty string"); + }); + }); + }); + }, + ); +}); diff --git a/test/js/third_party/jsonwebtoken/package.json b/test/js/third_party/jsonwebtoken/package.json new file mode 100644 index 000000000..95228db02 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/package.json @@ -0,0 +1,8 @@ +{ + "name": "jsonwebtoken", + "dependencies": { + "jsonwebtoken": "9.0.2", + "jws": "4.0.0", + "sinon": "16.1.0" + } +} diff --git a/test/js/third_party/jsonwebtoken/prime256v1-private.pem b/test/js/third_party/jsonwebtoken/prime256v1-private.pem new file mode 100644 index 000000000..317366570 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/prime256v1-private.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMP1Xt/ic2jAHJva2Pll866d1jYL+dk3VdLytEU1+LFmoAoGCCqGSM49 +AwEHoUQDQgAEvIywoA1H1a2XpPPTqsRxSk6YnNRVsu4E+wTvb7uV6Yttvko9zWar +jmtM3LHDXk/nHn+Pva0KD+lby8gb2daHGg== +-----END EC PRIVATE KEY----- diff --git a/test/js/third_party/jsonwebtoken/priv.pem b/test/js/third_party/jsonwebtoken/priv.pem new file mode 100644 index 000000000..7be6d5abc --- /dev/null +++ b/test/js/third_party/jsonwebtoken/priv.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAvtH4wKLYlIXZlfYQFJtXZVC3fD8XMarzwvb/fHUyJ6NvNStN ++H7GHp3/QhZbSaRyqK5hu5xXtFLgnI0QG8oE1NlXbczjH45LeHWhPIdc2uHSpzXi +c78kOugMY1vng4J10PF6+T2FNaiv0iXeIQq9xbwwPYpflViQyJnzGCIZ7VGan6Gb +RKzyTKcB58yx24pJq+CviLXEY52TIW1l5imcjGvLtlCp1za9qBZa4XGoVqHi1kRX +kdDSHty6lZWj3KxoRvTbiaBCH+75U7rifS6fR9lqjWE57bCGoz7+BBu9YmPKtI1K +kyHFqWpxaJc/AKf9xgg+UumeqVcirUmAsHJrMwIDAQABAoIBAQCYKw05YSNhXVPk +eHLeW/pXuwR3OkCexPrakOmwMC0s2vIF7mChN0d6hvhVlUp68X7V8SnS2JxAGo8v +iHY+Et3DdwZ3cxnzwh+BEhzgDfoIOmkoGppZPyX/K6klWtbGUrTtSISOWXbvEXQU +G0qGAvDOzIGTsdMDX7slnU70Ac23JybPY5qBSiE+ky8U4dm2fUHMroWub4QP5vA/ +nqyWqX2FB/MEAbcujaknDQrFCtbmtUYlBbJCKGd9V3cGEqp6H7oH+ah2ofMc91gJ +mCHk3YyWZB/bcVXH3CA+s1ywvCOVDBZ3Nw7Pt9zIcv6Rl9UKIy+Nx0QjXxR90Hla +Tr0GHIShAoGBAPsD7uXm+0ksnGyKRYgvlVad8Z8FUFT6bf4B+vboDbx40FO8O/5V +PraBPC5z8YRSBOQ/WfccPQzakkA28F2pXlRpXu5JcErVWnyyUiKpX5sw6iPenQR2 +JO9hY/GFbKiwUhVHpvWMcXFqFLSQu2A86jPnFFEfG48ZT4IhTzINKJVZAoGBAMKc +B3YGfVfY9qiRFXzYRdSRLg5c8p/HzuWwXc9vfJ4kQTDkPXe/+nqD67rzeT54uVec +jKoIrsCu4BfEaoyvOT+1KmUfdEpBgYZuuEC4CZf7dgKbXOpPVvZDMyJ/e7HyqTpw +mvIYJLPm2fNAcAsnbrNX5mhLwwzEIltbplUUeRdrAoGBAKhZgPYsLkhrZRXevreR +wkTvdUfD1pbHxtFfHqROCjhnhsFCM7JmFcNtdaFqHYczQxiZ7IqxI7jlNsVek2Md +3qgaa5LBKlDmOuP67N9WXUrGSaJ5ATIm0qrB1Lf9VlzktIiVH8L7yHHaRby8fQ8U +i7b3ukaV6HPW895A3M6iyJ8xAoGAInp4S+3MaTL0SFsj/nFmtcle6oaHKc3BlyoP +BMBQyMfNkPbu+PdXTjtvGTknouzKkX4X4cwWAec5ppxS8EffEa1sLGxNMxa19vZI +yJaShI21k7Ko3I5f7tNrDNKfPKCsYMEwgnHKluDwfktNTnyW/Uk2dgXuMaXSHHN5 +XZt59K8CgYArGVOWK7LUmf3dkTIs3tXBm4/IMtUZmWmcP9C8Xe/Dg/IdQhK5CIx4 +VXl8rgZNeX/5/4nJ8Q3LrdLau1Iz620trNRGU6sGMs3x4WQbSq93RRbFzfG1oK74 +IOo5yIBxImQOSk5jz31gF9RJb15SDBIxonuWv8qAERyUfvrmEwR0kg== +-----END RSA PRIVATE KEY----- diff --git a/test/js/third_party/jsonwebtoken/pub.pem b/test/js/third_party/jsonwebtoken/pub.pem new file mode 100644 index 000000000..dd95d341e --- /dev/null +++ b/test/js/third_party/jsonwebtoken/pub.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIJAMKR/NsyfcazMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTIxMTEyMjM0MzQxWhcNMTYxMjIxMjM0MzQxWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAvtH4wKLYlIXZlfYQFJtXZVC3fD8XMarzwvb/fHUyJ6NvNStN+H7GHp3/ +QhZbSaRyqK5hu5xXtFLgnI0QG8oE1NlXbczjH45LeHWhPIdc2uHSpzXic78kOugM +Y1vng4J10PF6+T2FNaiv0iXeIQq9xbwwPYpflViQyJnzGCIZ7VGan6GbRKzyTKcB +58yx24pJq+CviLXEY52TIW1l5imcjGvLtlCp1za9qBZa4XGoVqHi1kRXkdDSHty6 +lZWj3KxoRvTbiaBCH+75U7rifS6fR9lqjWE57bCGoz7+BBu9YmPKtI1KkyHFqWpx +aJc/AKf9xgg+UumeqVcirUmAsHJrMwIDAQABo4GnMIGkMB0GA1UdDgQWBBTs83nk +LtoXFlmBUts3EIxcVvkvcjB1BgNVHSMEbjBsgBTs83nkLtoXFlmBUts3EIxcVvkv +cqFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV +BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAMKR/NsyfcazMAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBABw7w/5k4d5dVDgd/OOOmXdaaCIKvt7d +3ntlv1SSvAoKT8d8lt97Dm5RrmefBI13I2yivZg5bfTge4+vAV6VdLFdWeFp1b/F +OZkYUv6A8o5HW0OWQYVX26zIqBcG2Qrm3reiSl5BLvpj1WSpCsYvs5kaO4vFpMak +/ICgdZD+rxwxf8Vb/6fntKywWSLgwKH3mJ+Z0kRlpq1g1oieiOm1/gpZ35s0Yuor +XZba9ptfLCYSggg/qc3d3d0tbHplKYkwFm7f5ORGHDSD5SJm+gI7RPE+4bO8q79R +PAfbG1UGuJ0b/oigagciHhJp851SQRYf3JuNSc17BnK2L5IEtzjqr+Q= +-----END CERTIFICATE----- diff --git a/test/js/third_party/jsonwebtoken/rsa-private.pem b/test/js/third_party/jsonwebtoken/rsa-private.pem new file mode 100644 index 000000000..746366b5b --- /dev/null +++ b/test/js/third_party/jsonwebtoken/rsa-private.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAvzoCEC2rpSpJQaWZbUmlsDNwp83Jr4fi6KmBWIwnj1MZ6CUQ +7rBasuLI8AcfX5/10scSfQNCsTLV2tMKQaHuvyrVfwY0dINk+nkqB74QcT2oCCH9 +XduJjDuwWA4xLqAKuF96FsIes52opEM50W7/W7DZCKXkC8fFPFj6QF5ZzApDw2Qs +u3yMRmr7/W9uWeaTwfPx24YdY7Ah+fdLy3KN40vXv9c4xiSafVvnx9BwYL7H1Q8N +iK9LGEN6+JSWfgckQCs6UUBOXSZdreNN9zbQCwyzee7bOJqXUDAuLcFARzPw1EsZ +AyjVtGCKIQ0/btqK+jFunT2NBC8RItanDZpptQIDAQABAoIBAQCsssO4Pra8hFMC +gX7tr0x+tAYy1ewmpW8stiDFilYT33YPLKJ9HjHbSms0MwqHftwwTm8JDc/GXmW6 +qUui+I64gQOtIzpuW1fvyUtHEMSisI83QRMkF6fCSQm6jJ6oQAtOdZO6R/gYOPNb +3gayeS8PbMilQcSRSwp6tNTVGyC33p43uUUKAKHnpvAwUSc61aVOtw2wkD062XzM +hJjYpHm65i4V31AzXo8HF42NrAtZ8K/AuQZne5F/6F4QFVlMKzUoHkSUnTp60XZx +X77GuyDeDmCgSc2J7xvR5o6VpjsHMo3ek0gJk5ZBnTgkHvnpbULCRxTmDfjeVPue +v3NN2TBFAoGBAPxbqNEsXPOckGTvG3tUOAAkrK1hfW3TwvrW/7YXg1/6aNV4sklc +vqn/40kCK0v9xJIv9FM/l0Nq+CMWcrb4sjLeGwHAa8ASfk6hKHbeiTFamA6FBkvQ +//7GP5khD+y62RlWi9PmwJY21lEkn2mP99THxqvZjQiAVNiqlYdwiIc7AoGBAMH8 +f2Ay7Egc2KYRYU2qwa5E/Cljn/9sdvUnWM+gOzUXpc5sBi+/SUUQT8y/rY4AUVW6 +YaK7chG9YokZQq7ZwTCsYxTfxHK2pnG/tXjOxLFQKBwppQfJcFSRLbw0lMbQoZBk +S+zb0ufZzxc2fJfXE+XeJxmKs0TS9ltQuJiSqCPPAoGBALEc84K7DBG+FGmCl1sb +ZKJVGwwknA90zCeYtadrIT0/VkxchWSPvxE5Ep+u8gxHcqrXFTdILjWW4chefOyF +5ytkTrgQAI+xawxsdyXWUZtd5dJq8lxLtx9srD4gwjh3et8ZqtFx5kCHBCu29Fr2 +PA4OmBUMfrs0tlfKgV+pT2j5AoGBAKnA0Z5XMZlxVM0OTH3wvYhI6fk2Kx8TxY2G +nxsh9m3hgcD/mvJRjEaZnZto6PFoqcRBU4taSNnpRr7+kfH8sCht0k7D+l8AIutL +ffx3xHv9zvvGHZqQ1nHKkaEuyjqo+5kli6N8QjWNzsFbdvBQ0CLJoqGhVHsXuWnz +W3Z4cBbVAoGAEtnwY1OJM7+R2u1CW0tTjqDlYU2hUNa9t1AbhyGdI2arYp+p+umA +b5VoYLNsdvZhqjVFTrYNEuhTJFYCF7jAiZLYvYm0C99BqcJnJPl7JjWynoNHNKw3 +9f6PIOE1rAmPE8Cfz/GFF5115ZKVlq+2BY8EKNxbCIy2d/vMEvisnXI= +-----END RSA PRIVATE KEY----- diff --git a/test/js/third_party/jsonwebtoken/rsa-pss-invalid-salt-length-private.pem b/test/js/third_party/jsonwebtoken/rsa-pss-invalid-salt-length-private.pem new file mode 100644 index 000000000..cbafa662d --- /dev/null +++ b/test/js/third_party/jsonwebtoken/rsa-pss-invalid-salt-length-private.pem @@ -0,0 +1,29 @@ +-----BEGIN PRIVATE KEY----- +MIIE8gIBADBCBgkqhkiG9w0BAQowNaAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZI +hvcNAQEIMA0GCWCGSAFlAwQCAQUAogQCAgQABIIEpzCCBKMCAQACggEBAJy3FuDR +1qKXsC8o+0xDJbuJCnysT71EFDGQY2/b3cZmxW3rzDYLyE65t2Go1jeK5Kxs+kwS +1VxfefD8DifeDZN66wjRse4iWLcxmQB5FfishXOdozciimgXNvXJNS8X//feSofl +vDQaTUI0NJnw1qQ2CB0pgGInwajsRKpWnDOhfk3NA/cmGlmfhTtDSTxq0ReytUie +TjY7gy+S9YYm4bAgBcMeoup0GEPzYccK4+1yCmWzQZGFcrY1cuB9bL+vT7ajQFhe +WVKlp6z35GyBF2zI7gJSkHpUHaWV5+Z9aTr6+YP6U7xuCRvXQ/l6BEOUjt4Es2YG +3frgxeVbOs1gAakCAwEAAQKCAQAMvFxhnOwCfq1Ux9HUWsigOvzdMOuyB+xUMtXB +625Uh1mYG0eXRNHcg/9BMoVmMiVvVdPphsZMIX45dWJ5HvSffafIKbJ6FdR73s3+ +WdjNQsf9o1v2SRpSZ0CSLO3ji+HDdQ89iBAJc/G/ZZq4v/fRlIqIRC0ozO5SGhFi +fnNnRqH78d2KeJMX/g9jBZM8rJQCi+pb0keHmFmLJ5gZa4HokE8rWQJQY46PVYUH +W2BwEJToMl3MPC7D95soWVuFt3KHnIWhuma/tnCmd2AUvcMrdWq0CwStH3vuX4LB +vJug0toWkobt1tzZgzzCASb2EpzJj8UNxP1CzTQWsvl8OephAoGBAMVnmZeLHoh2 +kxn/+rXetZ4Msjgu19MHNQAtlMvqzwZLan0K/BhnHprJLy4SDOuQYIs+PYJuXdT7 +Yv2mp9kwTPz8glP9LAto4MDeDfCu0cyXmZb2VQcT/lqVyrwfx3Psqxm/Yxg62YKr +aQE8WqgZGUdOvU9dYU+7EmPlYpdGpPVlAoGBAMs7ks+12oE6kci3WApdnt0kk5+f +8fbQ0lp2vR3tEw8DURa5FnHWA4o46XvcMcuXwZBrpxANPNAxJJjMBs1hSkc8h4hd +4vjtRNYJpj+uBdDIRmdqTzbpWv+hv8Xpiol5EVgnMVs2UZWDjoxQ+mYa1R8tAUfj +ojzV2KBMWGCoHgj1AoGALki6JGQEBq72kpQILnhHUQVdC/s/s0TvUlldl+o4HBu2 +nhbjQL182YHuQ/kLenfhiwRO27QQ4A0JCrv2gt/mTTLPQ+4KU6qFd/MYhaQXoMay +xkh/aydu7cJNRIqW80E8ZM8Q5u91bEPQXO/PubYYzTVTAba9SDpud2mjEiEIMFkC +gYEAxINEQEgtkkuZ76UpIkzIcjkN7YlxJCFjZUnvL+KvTRL986TgyQ4RujOxwKx4 +Ec8ZwZX2opTKOt1p771IzorGkf87ZmayM9TpfLUz5dtVkD43pYOsOQKHlStIDgz2 +gltoo/6xwOrTFGlzCsa6eMR1U4Hm/SZlF8IHh2iLBFtLP4kCgYBqTi1XeWeVQVSA +y9Wolv9kMoRh/Xh6F2D8bTTybGshDVO+P4YLM4lLxh5UDZAd/VOkdf3ZIcUGv022 +lxrYbLbIEGckMCpkdHeZH/1/iuJUeiCrXeyNlQsXBrmJKr/0lENniJHGpiSEyvY5 +D8Oafyjd7ZjUmyBFvS4heQEC6Pjo3Q== +-----END PRIVATE KEY----- diff --git a/test/js/third_party/jsonwebtoken/rsa-pss-private.pem b/test/js/third_party/jsonwebtoken/rsa-pss-private.pem new file mode 100644 index 000000000..52b1c08e9 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/rsa-pss-private.pem @@ -0,0 +1,29 @@ +-----BEGIN PRIVATE KEY----- +MIIE8QIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZI +hvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAEggSnMIIEowIBAAKCAQEA00tEqqyF +VnyvcVA2ewVoSicCMdQXmWyYM82sBWX0wcnn0WUuZp1zjux4xTvQ71Lhx95OJCQZ +7r7b2192Im5ca37wNRbI6DhyXNdNVFXLFYlNAvgP+V0gIwlr6NgopdJqHCjYVv/g +GOoesRZaDdtV1A3O9CXdJ34x2HZh7nhwYK5hqZDhUW4rd+5GzIIzwCJfwgTQpkIc +18UeMMEoKJ6A0ixdpf43HqJ5fAB5nsbYFhyHpfiX1UO2EFJtSdbKEIbRmqcbNjG1 +tu1tjt6u8LI2coetLh/IYMbMfkyQz+eAUHLQCUb2R8BqLOL3hRqEsVTBo93UJlOs +VWC1fKaq+HOEWQIDAQABAoIBAAet23PagPQTjwAZcAlzjlvs5AMHQsj5gznqwSmR +ut3/e7SGrrOIXbv1iIQejZQ3w8CS/0MH/ttIRiRIaWTh9EDsjvKsU9FAxUNDiJTG +k3LCbTFCQ7kGiJWiu4XDCWMmwmLTRzLjlMjtr/+JS5eSVPcNKMGDI3D9K0xDLSxQ +u0DVigYgWOCWlejHCEU4yi6vBO0HlumWjVPelWb9GmihBDwCLUJtG0JA6H6rw+KS +i6SNXcMGVKfjEghChRp+HaMvLvMgU44Ptnj8jhlfBctXInBY1is1FfDSWxXdVbUM +1HdKXfV4A50GXSvJLiWP9ZZsaZ7NiBJK8IiJBXD72EFOzwECgYEA3RjnTJn9emzG +84eIHZQujWWt4Tk/wjeLJYOYtAZpF7R3/fYLVypX9Bsw1IbwZodq/jChTjMaUkYt +//FgUjF/t0uakEg1i+THPZvktNB8Q1E9NwHerB8HF/AD/jMALD+ejdLQ11Z4VScw +zyNmSvD9I84/sgpms5YVKSH9sqww2RkCgYEA9KYws3sTfRLc1hlsS25V6+Zg3ZCk +iGcp+zrxGC1gb2/PpRvEDBucZO21KbSRuQDavWIOZYl4fGu7s8wo2oF8RxOsHQsM +LJyjklruvtjnvuoft/bGAv2zLQkNaj+f7IgK6965gIxcLYL66UPCZZkTfL5CoJis +V0v2hBh1ES5bLUECgYEAuONeaLxNL9dO989akAGefDePFExfePYhshk91S2XLG+J ++CGMkjOioUsrpk3BMrwDSNU5zr8FP8/YH7OlrJYgCxN6CTWZMYb65hY7RskhYNnK +qvkxUBYSRH49mJDlkBsTZ93nLmvs7Kh9NHqRzBGCXjLXKPdxsrPKtj7qfENqBeEC +gYAC9dPXCCE3PTgw2wPlccNWZGY9qBdlkyH96TurmDj3gDnZ/JkFsHvW+M1dYNL2 +kx0Sd5JHBj/P+Zm+1jSUWEbBsWo+u7h8/bQ4/CKxanx7YefaWQESXjGB1P81jumH +einvqrVB6fDfmBsjIW/DvPNwafjyaoaDU+b6uDUKbS4rQQKBgCe0pvDl5lO8FM81 +NP7GoCIu1gKBS+us1sgYE65ZFmVXJ6b5DckvobXSjM60G2N5w2xaXEXJsnwMApf1 +SClQUsgNWcSXRwL+w0pIdyFKS25BSfwUNQ9n7QLJcYgmflbARTfB3He/10vbFzTp +G6ZAiKUp9bKFPzviII40AEPL2hPX +-----END PRIVATE KEY----- diff --git a/test/js/third_party/jsonwebtoken/rsa-public-key.pem b/test/js/third_party/jsonwebtoken/rsa-public-key.pem new file mode 100644 index 000000000..eb9a29bad --- /dev/null +++ b/test/js/third_party/jsonwebtoken/rsa-public-key.pem @@ -0,0 +1,8 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAvzoCEC2rpSpJQaWZbUmlsDNwp83Jr4fi6KmBWIwnj1MZ6CUQ7rBa +suLI8AcfX5/10scSfQNCsTLV2tMKQaHuvyrVfwY0dINk+nkqB74QcT2oCCH9XduJ +jDuwWA4xLqAKuF96FsIes52opEM50W7/W7DZCKXkC8fFPFj6QF5ZzApDw2Qsu3yM +Rmr7/W9uWeaTwfPx24YdY7Ah+fdLy3KN40vXv9c4xiSafVvnx9BwYL7H1Q8NiK9L +GEN6+JSWfgckQCs6UUBOXSZdreNN9zbQCwyzee7bOJqXUDAuLcFARzPw1EsZAyjV +tGCKIQ0/btqK+jFunT2NBC8RItanDZpptQIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/test/js/third_party/jsonwebtoken/rsa-public-key.test.js b/test/js/third_party/jsonwebtoken/rsa-public-key.test.js new file mode 100644 index 000000000..c343cb0a9 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/rsa-public-key.test.js @@ -0,0 +1,44 @@ +const PS_SUPPORTED = true; +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; +import { generateKeyPairSync } from "crypto"; + +describe("public key start with BEGIN RSA PUBLIC KEY", function () { + it("should work for RS family of algorithms", function (done) { + var fs = require("fs"); + var cert_pub = fs.readFileSync(__dirname + "/rsa-public-key.pem"); + var cert_priv = fs.readFileSync(__dirname + "/rsa-private.pem"); + + var token = jwt.sign({ foo: "bar" }, cert_priv, { algorithm: "RS256" }); + + jwt.verify(token, cert_pub, done); + }); + + it("should not work for RS algorithms when modulus length is less than 2048 when allowInsecureKeySizes is false or not set", function (done) { + const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 1024 }); + + expect(function () { + jwt.sign({ foo: "bar" }, privateKey, { algorithm: "RS256" }); + }).toThrow("minimum key size"); + + done(); + }); + + it("should work for RS algorithms when modulus length is less than 2048 when allowInsecureKeySizes is true", function (done) { + const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 1024 }); + + jwt.sign({ foo: "bar" }, privateKey, { algorithm: "RS256", allowInsecureKeySizes: true }, done); + }); + + if (PS_SUPPORTED) { + it("should work for PS family of algorithms", function (done) { + var fs = require("fs"); + var cert_pub = fs.readFileSync(__dirname + "/rsa-public-key.pem"); + var cert_priv = fs.readFileSync(__dirname + "/rsa-private.pem"); + + var token = jwt.sign({ foo: "bar" }, cert_priv, { algorithm: "PS256" }); + + jwt.verify(token, cert_pub, done); + }); + } +}); diff --git a/test/js/third_party/jsonwebtoken/rsa-public.pem b/test/js/third_party/jsonwebtoken/rsa-public.pem new file mode 100644 index 000000000..9307812ab --- /dev/null +++ b/test/js/third_party/jsonwebtoken/rsa-public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvzoCEC2rpSpJQaWZbUml +sDNwp83Jr4fi6KmBWIwnj1MZ6CUQ7rBasuLI8AcfX5/10scSfQNCsTLV2tMKQaHu +vyrVfwY0dINk+nkqB74QcT2oCCH9XduJjDuwWA4xLqAKuF96FsIes52opEM50W7/ +W7DZCKXkC8fFPFj6QF5ZzApDw2Qsu3yMRmr7/W9uWeaTwfPx24YdY7Ah+fdLy3KN +40vXv9c4xiSafVvnx9BwYL7H1Q8NiK9LGEN6+JSWfgckQCs6UUBOXSZdreNN9zbQ +Cwyzee7bOJqXUDAuLcFARzPw1EsZAyjVtGCKIQ0/btqK+jFunT2NBC8RItanDZpp +tQIDAQAB +-----END PUBLIC KEY----- diff --git a/test/js/third_party/jsonwebtoken/schema.test.js b/test/js/third_party/jsonwebtoken/schema.test.js new file mode 100644 index 000000000..5d3845d46 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/schema.test.js @@ -0,0 +1,72 @@ +var PS_SUPPORTED = true; +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; +import fs from "fs"; + +describe("schema", function () { + describe("sign options", function () { + var cert_rsa_priv = fs.readFileSync(__dirname + "/rsa-private.pem"); + var cert_ecdsa_priv = fs.readFileSync(__dirname + "/ecdsa-private.pem"); + var cert_secp384r1_priv = fs.readFileSync(__dirname + "/secp384r1-private.pem"); + var cert_secp521r1_priv = fs.readFileSync(__dirname + "/secp521r1-private.pem"); + + function sign(options, secretOrPrivateKey) { + jwt.sign({ foo: 123 }, secretOrPrivateKey, options); + } + + it("should validate algorithm", function () { + expect(function () { + sign({ algorithm: "foo" }, cert_rsa_priv); + }).toThrow(/"algorithm" must be a valid string enum value/); + sign({ algorithm: "none" }, null); + sign({ algorithm: "RS256" }, cert_rsa_priv); + sign({ algorithm: "RS384" }, cert_rsa_priv); + sign({ algorithm: "RS512" }, cert_rsa_priv); + if (PS_SUPPORTED) { + sign({ algorithm: "PS256" }, cert_rsa_priv); + sign({ algorithm: "PS384" }, cert_rsa_priv); + sign({ algorithm: "PS512" }, cert_rsa_priv); + } + sign({ algorithm: "ES256" }, cert_ecdsa_priv); + sign({ algorithm: "ES384" }, cert_secp384r1_priv); + sign({ algorithm: "ES512" }, cert_secp521r1_priv); + sign({ algorithm: "HS256" }, "superSecret"); + sign({ algorithm: "HS384" }, "superSecret"); + sign({ algorithm: "HS512" }, "superSecret"); + }); + + it("should validate header", function () { + expect(function () { + sign({ header: "foo" }, "superSecret"); + }).toThrow(/"header" must be an object/); + sign({ header: {} }, "superSecret"); + }); + + it("should validate encoding", function () { + expect(function () { + sign({ encoding: 10 }, "superSecret"); + }).toThrow(/"encoding" must be a string/); + sign({ encoding: "utf8" }, "superSecret"); + }); + + it("should validate noTimestamp", function () { + expect(function () { + sign({ noTimestamp: 10 }, "superSecret"); + }).toThrow(/"noTimestamp" must be a boolean/); + sign({ noTimestamp: true }, "superSecret"); + }); + }); + + describe("sign payload registered claims", function () { + function sign(payload) { + jwt.sign(payload, "foo123"); + } + + it("should validate exp", function () { + expect(function () { + sign({ exp: "1 monkey" }); + }).toThrow(/"exp" should be a number of seconds/); + sign({ exp: 10.1 }); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/secp384r1-private.pem b/test/js/third_party/jsonwebtoken/secp384r1-private.pem new file mode 100644 index 000000000..82336b6a2 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/secp384r1-private.pem @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCez58vZHVp+ArI7/fe835GAtRzE0AtrxGgQAY1U/uk2SQOaSw1ph61 +3Unr0ygS172gBwYFK4EEACKhZANiAARtwlnIqYqZxfiWR+/EM35nKHuLpOjUHiX1 +kEpSS03C9XlrBLNwLQfgjpYx9Qvqh26XAzTe74DYjcc748R+zZD2YAd3lV+OcdRE +U+DWm4j5E6dlOXzvmw/3qxUcg3rRgR4= +-----END EC PRIVATE KEY----- diff --git a/test/js/third_party/jsonwebtoken/secp521r1-private.pem b/test/js/third_party/jsonwebtoken/secp521r1-private.pem new file mode 100644 index 000000000..397a3df09 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/secp521r1-private.pem @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIBlWXKBKKCgTgf7+NS09TMv7/NO3RtMBn9xTe+46oNNNK405lrZ9mz +WYtlsYvkdsc2Cx3v5V8JegaCOM+XtAZ0MNKgBwYFK4EEACOhgYkDgYYABAFNzaM7 +Zb9ug0p5KaZb5mjHrIshoVJSHaOXGtcjLVUakYVk0v9VsE+FKqyuLYcORUuAZdxl +ITAlC5e5JZ0o8NEKbAE+8oOrePrItR3IFBtWO15p7qiRa2dBB8oQklFrmQaJYn4K +fDV0hYpfu6ahpRNu2akR7aMXL/vXrptCH/n64q9KjA== +-----END EC PRIVATE KEY----- diff --git a/test/js/third_party/jsonwebtoken/set_headers.test.js b/test/js/third_party/jsonwebtoken/set_headers.test.js new file mode 100644 index 000000000..2ed9831a5 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/set_headers.test.js @@ -0,0 +1,16 @@ +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; + +describe("set header", function () { + it("should add the header", function () { + var token = jwt.sign({ foo: 123 }, "123", { header: { foo: "bar" } }); + var decoded = jwt.decode(token, { complete: true }); + expect(decoded.header.foo).toEqual("bar"); + }); + + it("should allow overriding header", function () { + var token = jwt.sign({ foo: 123 }, "123", { header: { alg: "HS512" } }); + var decoded = jwt.decode(token, { complete: true }); + expect(decoded.header.alg).toEqual("HS512"); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/test-utils.js b/test/js/third_party/jsonwebtoken/test-utils.js new file mode 100644 index 000000000..94a7e43e9 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/test-utils.js @@ -0,0 +1,116 @@ +"use strict"; + +import jwt from "jsonwebtoken"; +function expect(value) { + return { + toEqual: expected => { + if (typeof value === "object") { + if (typeof expected === "object") { + for (const propertyName in expected) { + expect(value[propertyName]).toEqual(expected[propertyName]); + } + return; + } + throw new Error(`Expected ${value} to strictly equal ${expected}`); + } + if (value !== expected) { + throw new Error(`Expected ${value} to equal ${expected}`); + } + }, + toStrictEqual: expected => { + if (typeof value === "object") { + if (typeof expected === "object") { + for (const propertyName in expected) { + expect(value[propertyName]).toStrictEqual(expected[propertyName]); + } + return; + } + throw new Error(`Expected ${value} to strictly equal ${expected}`); + } + if (value !== expected) { + throw new Error(`Expected ${value} to strictly equal ${expected}`); + } + }, + }; +} +/** + * Correctly report errors that occur in an asynchronous callback + * @param {function(err): void} done The mocha callback + * @param {function(): void} testFunction The assertions function + */ +function asyncCheck(done, testFunction) { + try { + testFunction(); + done(); + } catch (err) { + done(err); + } +} + +/** + * Base64-url encode a string + * @param str {string} The string to encode + * @returns {string} The encoded string + */ +function base64UrlEncode(str) { + return Buffer.from(str).toString("base64").replace(/[=]/g, "").replace(/\+/g, "-").replace(/\//g, "_"); +} + +/** + * Verify a JWT, ensuring that the asynchronous and synchronous calls to `verify` have the same result + * @param {string} jwtString The JWT as a string + * @param {string} secretOrPrivateKey The shared secret or private key + * @param {object} options Verify options + * @param {function(err, token):void} callback + */ +function verifyJWTHelper(jwtString, secretOrPrivateKey, options, callback) { + let error; + let syncVerified; + try { + syncVerified = jwt.verify(jwtString, secretOrPrivateKey, options); + } catch (err) { + error = err; + } + jwt.verify(jwtString, secretOrPrivateKey, options, (err, asyncVerifiedToken) => { + if (error) { + callback(err); + } else { + expect(syncVerified).toStrictEqual(asyncVerifiedToken); + callback(null, syncVerified); + } + }); +} + +/** + * Sign a payload to create a JWT, ensuring that the asynchronous and synchronous calls to `sign` have the same result + * @param {object} payload The JWT payload + * @param {string} secretOrPrivateKey The shared secret or private key + * @param {object} options Sign options + * @param {function(err, token):void} callback + */ +function signJWTHelper(payload, secretOrPrivateKey, options, callback) { + let error; + let syncSigned; + try { + syncSigned = jwt.sign(payload, secretOrPrivateKey, options); + } catch (err) { + error = err; + } + jwt.sign(payload, secretOrPrivateKey, options, (err, asyncSigned) => { + if (error) { + callback(err); + } else { + expect(syncSigned).toEqual(asyncSigned); + callback(null, syncSigned); + } + }); +} + +export { asyncCheck, base64UrlEncode, signJWTHelper, verifyJWTHelper }; + +export default { + asyncCheck, + base64UrlEncode, + signJWTHelper, + verifyJWTHelper, +}; diff --git a/test/js/third_party/jsonwebtoken/undefined_secretOrPublickey.test.js b/test/js/third_party/jsonwebtoken/undefined_secretOrPublickey.test.js new file mode 100644 index 000000000..fb3f3b8d3 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/undefined_secretOrPublickey.test.js @@ -0,0 +1,18 @@ +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; + +var TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-IDcSemACt8x4iTMCda8Yhe3iZaWbvV5XKSTbuAn0M"; + +describe("verifying without specified secret or public key", function () { + it("should not verify null", function () { + expect(function () { + jwt.verify(TOKEN, null); + }).toThrow(/secret or public key must be provided/); + }); + + it("should not verify undefined", function () { + expect(function () { + jwt.verify(TOKEN); + }).toThrow(/secret or public key must be provided/); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/validateAsymmetricKey.test.js b/test/js/third_party/jsonwebtoken/validateAsymmetricKey.test.js new file mode 100644 index 000000000..4bcf13cb0 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/validateAsymmetricKey.test.js @@ -0,0 +1,209 @@ +import { expect, describe, it } from "bun:test"; +import { createPrivateKey } from "crypto"; +import fs from "fs"; +import path from "path"; +const PS_SUPPORTED = true; +const ASYMMETRIC_KEY_DETAILS_SUPPORTED = true; +const RSA_PSS_KEY_DETAILS_SUPPORTED = true; +const allowedAlgorithmsForKeys = { + "ec": ["ES256", "ES384", "ES512"], + "rsa": ["RS256", "PS256", "RS384", "PS384", "RS512", "PS512"], + "rsa-pss": ["PS256", "PS384", "PS512"], +}; + +const allowedCurves = { + ES256: "prime256v1", + ES384: "secp384r1", + ES512: "secp521r1", +}; + +function validateAsymmetricKey(algorithm, key) { + if (!algorithm || !key) return; + + const keyType = key.asymmetricKeyType; + if (!keyType) return; + + const allowedAlgorithms = allowedAlgorithmsForKeys[keyType]; + + if (!allowedAlgorithms) { + throw new Error(`Unknown key type "${keyType}".`); + } + + if (!allowedAlgorithms.includes(algorithm)) { + throw new Error(`"alg" parameter for "${keyType}" key type must be one of: ${allowedAlgorithms.join(", ")}.`); + } + + /* + * Ignore the next block from test coverage because it gets executed + * conditionally depending on the Node version. Not ignoring it would + * prevent us from reaching the target % of coverage for versions of + * Node under 15.7.0. + */ + /* istanbul ignore next */ + if (ASYMMETRIC_KEY_DETAILS_SUPPORTED) { + switch (keyType) { + case "ec": + const keyCurve = key.asymmetricKeyDetails.namedCurve; + const allowedCurve = allowedCurves[algorithm]; + + if (keyCurve !== allowedCurve) { + throw new Error(`"alg" parameter "${algorithm}" requires curve "${allowedCurve}".`); + } + break; + + case "rsa-pss": + if (RSA_PSS_KEY_DETAILS_SUPPORTED) { + const length = parseInt(algorithm.slice(-3), 10); + const { hashAlgorithm, mgf1HashAlgorithm, saltLength } = key.asymmetricKeyDetails; + + if (hashAlgorithm !== `sha${length}` || mgf1HashAlgorithm !== hashAlgorithm) { + throw new Error( + `Invalid key for this operation, its RSA-PSS parameters do not meet the requirements of "alg" ${algorithm}.`, + ); + } + + if (saltLength !== undefined && saltLength > length >> 3) { + throw new Error( + `Invalid key for this operation, its RSA-PSS parameter saltLength does not meet the requirements of "alg" ${algorithm}.`, + ); + } + } + break; + } + } +} + +function loadKey(filename) { + return createPrivateKey(fs.readFileSync(path.join(__dirname, filename))); +} + +const algorithmParams = { + RS256: { + invalidPrivateKey: loadKey("secp384r1-private.pem"), + }, + ES256: { + invalidPrivateKey: loadKey("priv.pem"), + }, +}; + +if (PS_SUPPORTED) { + algorithmParams.PS256 = { + invalidPrivateKey: loadKey("secp384r1-private.pem"), + }; +} + +describe("Asymmetric key validation", function () { + Object.keys(algorithmParams).forEach(function (algorithm) { + describe(algorithm, function () { + const keys = algorithmParams[algorithm]; + + describe("when validating a key with an invalid private key type", function () { + it("should throw an error", function () { + const expectedErrorMessage = /"alg" parameter for "[\w\d-]+" key type must be one of:/; + + expect(function () { + validateAsymmetricKey(algorithm, keys.invalidPrivateKey); + }).toThrow(expectedErrorMessage); + }); + }); + }); + }); + + describe("when the function has missing parameters", function () { + it("should pass the validation if no key has been provided", function () { + const algorithm = "ES256"; + validateAsymmetricKey(algorithm); + }); + + it.todo("should pass the validation if no algorithm has been provided", function () { + const key = loadKey("dsa-private.pem"); + validateAsymmetricKey(null, key); + }); + }); + + describe("when validating a key with an unsupported type", function () { + it.todo("should throw an error", function () { + const algorithm = "RS256"; + const key = loadKey("dsa-private.pem"); + const expectedErrorMessage = 'Unknown key type "dsa".'; + + expect(function () { + validateAsymmetricKey(algorithm, key); + }).toThrow(expectedErrorMessage); + }); + }); + + describe("Elliptic curve algorithms", function () { + const curvesAlgorithms = [ + { algorithm: "ES256", curve: "prime256v1" }, + { algorithm: "ES384", curve: "secp384r1" }, + { algorithm: "ES512", curve: "secp521r1" }, + ]; + + const curvesKeys = [ + { curve: "prime256v1", key: loadKey("prime256v1-private.pem") }, + { curve: "secp384r1", key: loadKey("secp384r1-private.pem") }, + { curve: "secp521r1", key: loadKey("secp521r1-private.pem") }, + ]; + + describe("when validating keys generated using Elliptic Curves", function () { + curvesAlgorithms.forEach(function (curveAlgorithm) { + curvesKeys.forEach(curveKeys => { + if (curveKeys.curve !== curveAlgorithm.curve) { + if (ASYMMETRIC_KEY_DETAILS_SUPPORTED) { + it(`should throw an error when validating an ${curveAlgorithm.algorithm} token for key with curve ${curveKeys.curve}`, function () { + expect(() => { + validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key); + }).toThrow(`"alg" parameter "${curveAlgorithm.algorithm}" requires curve "${curveAlgorithm.curve}".`); + }); + } else { + it(`should pass the validation for incorrect keys if the Node version does not support checking the key's curve name`, function () { + expect(() => { + validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key); + }).not.toThrow(); + }); + } + } else { + it(`should accept an ${curveAlgorithm.algorithm} token for key with curve ${curveKeys.curve}`, function () { + expect(() => { + validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key); + }).not.toThrow(); + }); + } + }); + }); + }); + }); + + if (RSA_PSS_KEY_DETAILS_SUPPORTED) { + describe.todo("RSA-PSS algorithms", function () { + // const key = loadKey('rsa-pss-private.pem'); + + it(`it should throw an error when validating a key with wrong RSA-RSS parameters`, function () { + const algorithm = "PS512"; + expect(function () { + validateAsymmetricKey(algorithm, key); + }).toThrow( + 'Invalid key for this operation, its RSA-PSS parameters do not meet the requirements of "alg" PS512', + ); + }); + + it(`it should throw an error when validating a key with invalid salt length`, function () { + const algorithm = "PS256"; + const shortSaltKey = loadKey("rsa-pss-invalid-salt-length-private.pem"); + expect(function () { + validateAsymmetricKey(algorithm, shortSaltKey); + }).toThrow( + 'Invalid key for this operation, its RSA-PSS parameter saltLength does not meet the requirements of "alg" PS256.', + ); + }); + + it(`it should pass the validation when the key matches all the requirements for the algorithm`, function () { + expect(function () { + const algorithm = "PS256"; + validateAsymmetricKey(algorithm, key); + }).not.toThrow(); + }); + }); + } +}); diff --git a/test/js/third_party/jsonwebtoken/verify.test.js b/test/js/third_party/jsonwebtoken/verify.test.js new file mode 100644 index 000000000..c7583892f --- /dev/null +++ b/test/js/third_party/jsonwebtoken/verify.test.js @@ -0,0 +1,318 @@ +import jwt from "jsonwebtoken"; +import { expect, describe, it, afterEach } from "bun:test"; +import jws from "jws"; +import sinon from "sinon"; +import fs from "fs"; +import path from "path"; + +describe("verify", function () { + const pub = fs.readFileSync(path.join(__dirname, "pub.pem")); + const priv = fs.readFileSync(path.join(__dirname, "priv.pem")); + + it("should first assume JSON claim set", function (done) { + const header = { alg: "RS256" }; + const payload = { iat: Math.floor(Date.now() / 1000) }; + + const signed = jws.sign({ + header: header, + payload: payload, + secret: priv, + encoding: "utf8", + }); + + jwt.verify(signed, pub, { typ: "JWT" }, function (err, p) { + if (err) return done(err); + expect(err).toBeNull(); + expect(p).toEqual(payload); + done(); + }); + }); + + it("should not be able to verify unsigned token", function () { + const header = { alg: "none" }; + const payload = { iat: Math.floor(Date.now() / 1000) }; + + const signed = jws.sign({ + header: header, + payload: payload, + secret: "secret", + encoding: "utf8", + }); + + expect(function () { + jwt.verify(signed, "secret", { typ: "JWT" }); + }).toThrow(/jwt signature is required/); + }); + + it("should not be able to verify unsigned token", function () { + const header = { alg: "none" }; + const payload = { iat: Math.floor(Date.now() / 1000) }; + + const signed = jws.sign({ + header: header, + payload: payload, + secret: "secret", + encoding: "utf8", + }); + + expect(function () { + jwt.verify(signed, undefined, { typ: "JWT" }); + }).toThrow(/please specify "none" in "algorithms" to verify unsigned tokens/); + }); + + it("should be able to verify unsigned token when none is specified", function (done) { + const header = { alg: "none" }; + const payload = { iat: Math.floor(Date.now() / 1000) }; + + const signed = jws.sign({ + header: header, + payload: payload, + secret: "secret", + encoding: "utf8", + }); + + jwt.verify(signed, null, { typ: "JWT", algorithms: ["none"] }, function (err, p) { + if (err) return done(err); + expect(err).toBeNull(); + expect(p).toEqual(payload); + done(); + }); + }); + + it("should not mutate options", function (done) { + const header = { alg: "HS256" }; + const payload = { iat: Math.floor(Date.now() / 1000) }; + const options = { typ: "JWT" }; + const signed = jws.sign({ + header: header, + payload: payload, + secret: "secret", + encoding: "utf8", + }); + + jwt.verify(signed, "secret", options, function (err) { + if (err) return done(err); + expect(err).toBeNull(); + expect(Object.keys(options).length).toEqual(1); + done(); + }); + }); + + describe("secret or token as callback", function () { + const token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU5Mn0.3aR3vocmgRpG05rsI9MpR6z2T_BGtMQaPq2YR6QaroU"; + const key = "key"; + + const payload = { foo: "bar", iat: 1437018582, exp: 1437018592 }; + const options = { algorithms: ["HS256"], ignoreExpiration: true }; + + it("without callback", function (done) { + jwt.verify(token, key, options, function (err, p) { + if (err) return done(err); + expect(err).toBeNull(); + expect(p).toEqual(payload); + done(); + }); + }); + + it("simple callback", function (done) { + const keyFunc = function (header, callback) { + expect(header).toEqual({ alg: "HS256", typ: "JWT" }); + + callback(undefined, key); + }; + + jwt.verify(token, keyFunc, options, function (err, p) { + if (err) return done(err); + expect(err).toBeNull(); + expect(p).toEqual(payload); + done(); + }); + }); + + it("should error if called synchronously", function (done) { + const keyFunc = function (header, callback) { + callback(undefined, key); + }; + + expect(function () { + jwt.verify(token, keyFunc, options); + }).toThrow(/verify must be called asynchronous if secret or public key is provided as a callback/); + + done(); + }); + + it("simple error", function (done) { + const keyFunc = function (header, callback) { + callback(new Error("key not found")); + }; + + jwt.verify(token, keyFunc, options, function (err, p) { + expect(err).toBeDefined(); + expect(err.name).toBe("JsonWebTokenError"); + expect(err.message).toMatch(/error in secret or public key callback/); + expect(p).toBeUndefined(); + done(); + }); + }); + + it("delayed callback", function (done) { + const keyFunc = function (header, callback) { + setTimeout(function () { + callback(undefined, key); + }, 25); + }; + + jwt.verify(token, keyFunc, options, function (err, p) { + if (err) return done(err); + expect(err).toBeNull(); + expect(p).toEqual(payload); + done(); + }); + }); + + it("delayed error", function (done) { + const keyFunc = function (header, callback) { + setTimeout(function () { + callback(new Error("key not found")); + }, 25); + }; + + jwt.verify(token, keyFunc, options, function (err, p) { + expect(err).toBeDefined(); + expect(err.name).toBe("JsonWebTokenError"); + expect(err.message).toMatch(/error in secret or public key callback/); + expect(p).toBeUndefined(); + done(); + }); + }); + }); + + describe("expiration", function () { + // { foo: 'bar', iat: 1437018582, exp: 1437018592 } + const token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU5Mn0.3aR3vocmgRpG05rsI9MpR6z2T_BGtMQaPq2YR6QaroU"; + const key = "key"; + + let clock; + afterEach(function () { + try { + clock.restore(); + } catch (e) {} + }); + + it("should error on expired token", function (done) { + clock = sinon.useFakeTimers(1437018650000); // iat + 58s, exp + 48s + const options = { algorithms: ["HS256"] }; + + jwt.verify(token, key, options, function (err, p) { + expect(err).toBeDefined(); + expect(err.name).toBe("TokenExpiredError"); + expect(err.message).toBe("jwt expired"); + expect(err?.expiredAt?.constructor?.name).toBe("Date"); + expect(Number(err.expiredAt)).toBe(1437018592000); + expect(p).toBeUndefined(); + done(); + }); + }); + + it("should not error on expired token within clockTolerance interval", function (done) { + clock = sinon.useFakeTimers(1437018594000); // iat + 12s, exp + 2s + const options = { algorithms: ["HS256"], clockTolerance: 5 }; + + jwt.verify(token, key, options, function (err, p) { + expect(err).toBeNull(); + expect(p.foo).toBe("bar"); + done(); + }); + }); + + describe("option: clockTimestamp", function () { + const clockTimestamp = 1000000000; + it("should verify unexpired token relative to user-provided clockTimestamp", function (done) { + const token = jwt.sign({ foo: "bar", iat: clockTimestamp, exp: clockTimestamp + 1 }, key); + jwt.verify(token, key, { clockTimestamp: clockTimestamp }, function (err) { + expect(err).toBeNull(); + done(); + }); + }); + it("should error on expired token relative to user-provided clockTimestamp", function (done) { + const token = jwt.sign({ foo: "bar", iat: clockTimestamp, exp: clockTimestamp + 1 }, key); + jwt.verify(token, key, { clockTimestamp: clockTimestamp + 1 }, function (err, p) { + expect(err).toBeDefined(); + expect(err.name).toBe("TokenExpiredError"); + expect(err.message).toBe("jwt expired"); + expect(err?.expiredAt?.constructor?.name).toBe("Date"); + expect(Number(err.expiredAt)).toBe((clockTimestamp + 1) * 1000); + expect(p).toBeUndefined(); + done(); + }); + }); + it("should verify clockTimestamp is a number", function (done) { + const token = jwt.sign({ foo: "bar", iat: clockTimestamp, exp: clockTimestamp + 1 }, key); + jwt.verify(token, key, { clockTimestamp: "notANumber" }, function (err, p) { + expect(err).toBeDefined(); + expect(err.name).toBe("JsonWebTokenError"); + expect(err.message).toBe("clockTimestamp must be a number"); + expect(p).toBeUndefined(); + done(); + }); + }); + }); + + describe("option: maxAge and clockTimestamp", function () { + // { foo: 'bar', iat: 1437018582, exp: 1437018800 } exp = iat + 218s + const token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODgwMH0.AVOsNC7TiT-XVSpCpkwB1240izzCIJ33Lp07gjnXVpA"; + it("cannot be more permissive than expiration", function (done) { + const clockTimestamp = 1437018900; // iat + 318s (exp: iat + 218s) + const options = { algorithms: ["HS256"], clockTimestamp: clockTimestamp, maxAge: "1000y" }; + + jwt.verify(token, key, options, function (err, p) { + // maxAge not exceded, but still expired + expect(err).toBeDefined(); + expect(err.name).toBe("TokenExpiredError"); + expect(err.message).toBe("jwt expired"); + expect(err?.expiredAt?.constructor?.name).toBe("Date"); + expect(Number(err.expiredAt)).toBe(1437018800000); + expect(p).toBeUndefined(); + done(); + }); + }); + }); + }); + + describe.todo("when verifying a token with an unsupported public key type", function () { + it("should throw an error", function () { + const token = + "eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2Njk5OTAwMDN9.YdjFWJtPg_9nccMnTfQyesWQ0UX-GsWrfCGit_HqjeIkNjoV6dkAJ8AtbnVEhA4oxwqSXx6ilMOfHEjmMlPtyyyVKkWKQHcIWYnqPbNSEv8a7Men8KhJTIWb4sf5YbhgSCpNvU_VIZjLO1Z0PzzgmEikp0vYbxZFAbCAlZCvUlcIc-kdjIRCnDJe0BBrYRxNLEJtYsf7D1yFIFIqw8-VP87yZdExA4eHsTaE84SgnL24ZK5h5UooDx-IRNd_rrMyio8kNy63grVxCWOtkXZ26iZk6v-HMsnBqxvUwR6-8wfaWrcpADkyUO1q3SNsoTdwtflbvfwgjo3uve0IvIzHMw"; + const key = fs.readFileSync(path.join(__dirname, "dsa-public.pem")); + + expect(function () { + jwt.verify(token, key); + }).toThrow('Unknown key type "dsa".'); + }); + }); + + describe("when verifying a token with an incorrect public key type", function () { + it("should throw a validation error if key validation is enabled", function () { + const token = + "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXkiOiJsb2FkIiwiaWF0IjoxNjcwMjMwNDE2fQ.7TYP8SB_9Tw1fNIfuG60b4tvoLPpDAVBQpV1oepnuKwjUz8GOw4fRLzclo0Q2YAXisJ3zIYMEFsHpYrflfoZJQ"; + const key = fs.readFileSync(path.join(__dirname, "rsa-public.pem")); + + expect(function () { + jwt.verify(token, key, { algorithms: ["ES256"] }); + }).toThrow('"alg" parameter for "rsa" key type must be one of: RS256, PS256, RS384, PS384, RS512, PS512.'); + }); + + it("should throw an unknown error if key validation is disabled", function () { + const token = + "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXkiOiJsb2FkIiwiaWF0IjoxNjcwMjMwNDE2fQ.7TYP8SB_9Tw1fNIfuG60b4tvoLPpDAVBQpV1oepnuKwjUz8GOw4fRLzclo0Q2YAXisJ3zIYMEFsHpYrflfoZJQ"; + const key = fs.readFileSync(path.join(__dirname, "rsa-public.pem")); + + expect(function () { + jwt.verify(token, key, { algorithms: ["ES256"], allowInvalidAsymmetricKeyTypes: true }); + }).not.toThrow('"alg" parameter for "rsa" key type must be one of: RS256, PS256, RS384, PS384, RS512, PS512.'); + }); + }); +}); diff --git a/test/js/third_party/jsonwebtoken/wrong_alg.test.js b/test/js/third_party/jsonwebtoken/wrong_alg.test.js new file mode 100644 index 000000000..948e467f9 --- /dev/null +++ b/test/js/third_party/jsonwebtoken/wrong_alg.test.js @@ -0,0 +1,49 @@ +var PS_SUPPORTED = true; +import jwt from "jsonwebtoken"; +import { expect, describe, it } from "bun:test"; +import path from "path"; +import fs from "fs"; + +var pub = fs.readFileSync(path.join(__dirname, "pub.pem"), "utf8"); +// priv is never used +// var priv = fs.readFileSync(path.join(__dirname, 'priv.pem')); + +var TOKEN = + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MjY1NDY5MTl9.ETgkTn8BaxIX4YqvUWVFPmum3moNZ7oARZtSBXb_vP4"; + +describe("when setting a wrong `header.alg`", function () { + describe("signing with pub key as symmetric", function () { + it("should not verify", function () { + expect(function () { + jwt.verify(TOKEN, pub); + }).toThrow(/invalid algorithm/); + }); + }); + + describe("signing with pub key as HS256 and whitelisting only RS256", function () { + it("should not verify", function () { + expect(function () { + jwt.verify(TOKEN, pub, { algorithms: ["RS256"] }); + }).toThrow(/invalid algorithm/); + }); + }); + + if (PS_SUPPORTED) { + describe("signing with pub key as HS256 and whitelisting only PS256", function () { + it("should not verify", function () { + expect(function () { + jwt.verify(TOKEN, pub, { algorithms: ["PS256"] }); + }).toThrow(/invalid algorithm/); + }); + }); + } + + describe("signing with HS256 and checking with HS384", function () { + it("should not verify", function () { + expect(function () { + var token = jwt.sign({ foo: "bar" }, "secret", { algorithm: "HS256" }); + jwt.verify(token, "some secret", { algorithms: ["HS384"] }); + }).toThrow(/invalid algorithm/); + }); + }); +}); |