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.');
});
});
});
move-templates'>move-templates
Unnamed repository; edit this file 'description' to name the repository. | |
Age | Commit message (Collapse) | Author | Files | Lines |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `new Response(stream).arrayBuffer()`
- `new Response(stream).json()`
- `new Response(stream).text()`
- `new Response(stream).blob()`
|
|
|
|
|
|
|
|
|
|
|