Skip to content
This repository has been archived by the owner on Jul 15, 2024. It is now read-only.

Commit

Permalink
Release 3.0.0. Accept JWTs with date strings instead of timestamps. R…
Browse files Browse the repository at this point in the history
…emove `jwtOptions` from the constructor because using it is almost certainly going to cause more harm than good.
  • Loading branch information
jeff committed Jul 6, 2017
1 parent 331afa0 commit 6d776ea
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 8 deletions.
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "giftbit-cassava-routes",
"version": "2.0.0",
"version": "3.0.0",
"description": "Private Giftbit routes for use with Cassava.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -31,14 +31,14 @@
"@types/cookie": "^0.3.0",
"@types/jsonwebtoken": "^7.2.1",
"@types/mocha": "^2.2.41",
"@types/node": "^8.0.6",
"aws-sdk": "^2.80.0",
"@types/node": "^8.0.8",
"aws-sdk": "^2.81.0",
"chai": "^4.0.2",
"mocha": "^3.4.2",
"rimraf": "^2.6.1",
"ts-node": "^3.1.0",
"tslint": "^5.4.3",
"typescript": "2.3.4"
"tslint": "^5.5.0",
"typescript": "^2.4.1"
},
"dependencies": {
"jsonwebtoken": "^7.4.1"
Expand Down
70 changes: 70 additions & 0 deletions src/jwtauth/JwtAuthorizationRoute.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,60 @@ describe("JwtAuthorizationRoute", () => {
chai.assert.isTrue(secondHandlerCalled);
});

it("verifies a JWT with date strings", async() => {
// The spec calls for timestamps but we were issuing JWTs with date strings for a while.
// We'll still accept these technically-wrong JWTs.

const router = new cassava.Router();
const jwtAuthorizationRoute = new JwtAuthorizationRoute(Promise.resolve({secretkey:"secret"}));
jwtAuthorizationRoute.logErrors = false;
router.route(jwtAuthorizationRoute);
router.route({
matches: () => true,
handle: async evt => ({body: {}})
});

const resp = await cassava.testing.testRouter(router, cassava.testing.createTestProxyEvent("/foo/bar", "GET", {
headers: {
Authorization: "Bearer eyJ2ZXIiOjMsInZhdiI6MSwiYWxnIjoiSFMyNTYiLCJ0eXAiOiJKV1QifQ.eyJnIjp7Imd1aSI6InVzZXItZjJmZTU3ZTg2ZjQyNDc5ZTg5YzYwMzRmZTg0NGJmM2UtVEVTVCIsImdtaSI6InVzZXItZjJmZTU3ZTg2ZjQyNDc5ZTg5YzYwMzRmZTg0NGJmM2UtVEVTVCJ9LCJpYXQiOiIyMDE3LTA3LTA1VDIyOjQ5OjU5LjcxMiswMDAwIiwiZXhwIjoiMjIxNy0wNy0wNVQyMzo0OTo1OS43MTIrMDAwMCIsImp0aSI6ImJhZGdlLTMzZTQ1N2E2NDQ0ZTQ2YjY5MzU3YjkyMDMyM2ZjYWY2IiwicGFyZW50SnRpIjoiYmFkZ2UtYmMyM2IyYmQxMmIwNDJiYTk1ZTQyMzJiODBhZTNhYWIiLCJzY29wZXMiOlsiQyIsIkNFQyIsIkNFUiIsImxpZ2h0cmFpbFYxOmNhcmRTZWFyY2giXSwicm9sZXMiOltdfQ.ydBnIZXP_i7dhsIAQ-ajWltPX1uweLcMixkfaBEDCK4"
}
}));

chai.assert.equal(resp.statusCode, 200, JSON.stringify(resp));
});

it("rejects an Authorization header missing 'Bearer '", async() => {
const router = new cassava.Router();
const jwtAuthorizationRoute = new JwtAuthorizationRoute(Promise.resolve({secretkey:"secret"}));
jwtAuthorizationRoute.logErrors = false;
router.route(jwtAuthorizationRoute);

const resp = await cassava.testing.testRouter(router, cassava.testing.createTestProxyEvent("/foo/bar", "GET", {
headers: {
Authorization: "eyJ2ZXIiOjEsInZhdiI6MSwiYWxnIjoiSFMyNTYiLCJ0eXAiOiJKV1QifQ.eyJnIjp7Imd1aSI6InVzZXItNzA1MjIxMGJjYjk0NDQ4YjgyNWZmYTY4NTA4ZDI5YWQtVEVTVCIsImdtaSI6InVzZXItNzA1MjIxMGJjYjk0NDQ4YjgyNWZmYTY4NTA4ZDI5YWQifSwiaWF0IjoiMjAxNi0xMi0xMlQyMDoxMTo0MC45OTcrMDAwMCIsInNjb3BlcyI6WyJDIiwiVCIsIlIiLCJDRUMiLCJDRVIiLCJVQSIsIkYiXX0.uZxYrUPqwJk5oTTtDWaPOYzhRSt5dzRS4OZGYP8u2Po"
}
}));

chai.assert.isObject(resp);
chai.assert.equal(resp.statusCode, 401, JSON.stringify(resp));
});

it("rejects a JWT that is not base64", async() => {
const router = new cassava.Router();
const jwtAuthorizationRoute = new JwtAuthorizationRoute(Promise.resolve({secretkey:"secret"}));
jwtAuthorizationRoute.logErrors = false;
router.route(jwtAuthorizationRoute);

const resp = await cassava.testing.testRouter(router, cassava.testing.createTestProxyEvent("/foo/bar", "GET", {
headers: {
Authorization: "Bearer lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
}
}));

chai.assert.isObject(resp);
chai.assert.equal(resp.statusCode, 401, JSON.stringify(resp));
});

it("rejects an expired JWT", async() => {
const router = new cassava.Router();
const jwtAuthorizationRoute = new JwtAuthorizationRoute(Promise.resolve({secretkey:"secret"}));
Expand Down Expand Up @@ -214,4 +268,20 @@ describe("JwtAuthorizationRoute", () => {
chai.assert.isObject(resp);
chai.assert.equal(resp.statusCode, 401, JSON.stringify(resp));
});

it("rejects a JWT with alg:none", async() => {
const router = new cassava.Router();
const jwtAuthorizationRoute = new JwtAuthorizationRoute(Promise.resolve({secretkey:"secret"}));
jwtAuthorizationRoute.logErrors = false;
router.route(jwtAuthorizationRoute);

const resp = await cassava.testing.testRouter(router, cassava.testing.createTestProxyEvent("/foo/bar", "GET", {
headers: {
Authorization: "Bearer eyJ2ZXIiOjMsInZhdiI6MSwiYWxnIjoibm9uZSIsInR5cCI6IkpXVCJ9.eyJnIjp7Imd1aSI6InVzZXItZjJmZTU3ZTg2ZjQyNDc5ZTg5YzYwMzRmZTg0NGJmM2UtVEVTVCIsImdtaSI6InVzZXItZjJmZTU3ZTg2ZjQyNDc5ZTg5YzYwMzRmZTg0NGJmM2UtVEVTVCJ9LCJqdGkiOiJiYWRnZS0zM2U0NTdhNjQ0NGU0NmI2OTM1N2I5MjAzMjNmY2FmNiIsInNjb3BlcyI6WyJDIiwiQ0VDIiwiQ0VSIl0sInJvbGVzIjpbXX0.tFWA2jK8E0QVaG45h1BeARQQZxNJHVhIDh4-fs2qxhg"
}
}));

chai.assert.isObject(resp);
chai.assert.equal(resp.statusCode, 401, JSON.stringify(resp));
});
});
7 changes: 4 additions & 3 deletions src/jwtauth/JwtAuthorizationRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ export class JwtAuthorizationRoute implements cassava.routes.Route {

constructor(
private readonly authConfigPromise: Promise<AuthenticationConfig>,
private readonly rolesConfigPromise?: Promise<RolesConfig>,
private readonly jwtOptions?: jwt.VerifyOptions) {}
private readonly rolesConfigPromise?: Promise<RolesConfig>) {}

async handle(evt: cassava.RouterEvent): Promise<cassava.RouterResponse> {
try {
Expand All @@ -24,8 +23,10 @@ export class JwtAuthorizationRoute implements cassava.routes.Route {
throw new Error("Secret is null. Check that the source of the secret can be accessed.");
}

// Expiration time is checked manually because we issued JWTs with date string expirations,
// which is against the spec and the library rightly rejects those.
const token = this.getToken(evt);
const payload = jwt.verify(token, secret.secretkey, this.jwtOptions);
const payload = jwt.verify(token, secret.secretkey, {ignoreExpiration: true, algorithms: ["HS256"]});
const auth = new AuthorizationBadge(payload, this.rolesConfigPromise ? await this.rolesConfigPromise : null);
if (auth.expirationTime && auth.expirationTime.getTime() < Date.now()) {
throw new Error(`jwt expired at ${auth.expirationTime} (and it is currently ${new Date()})`);
Expand Down

0 comments on commit 6d776ea

Please sign in to comment.