diff options
Diffstat (limited to 'test/js/third_party/jsonwebtoken/verify.test.js')
-rw-r--r-- | test/js/third_party/jsonwebtoken/verify.test.js | 318 |
1 files changed, 318 insertions, 0 deletions
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.'); + }); + }); +}); |