@villedemontreal/jwt-validator 5.8.1 → 5.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/middleware/jwtMiddleware.d.ts +1 -1
- package/dist/src/middleware/jwtMiddleware.js +18 -21
- package/dist/src/middleware/jwtMiddleware.js.map +1 -1
- package/dist/src/middleware/jwtMiddleware.test.d.ts +1 -0
- package/dist/src/middleware/jwtMiddleware.test.js +112 -0
- package/dist/src/middleware/jwtMiddleware.test.js.map +1 -0
- package/package.json +11 -10
- package/src/middleware/jwtMiddleware.test.ts +138 -0
- package/src/middleware/jwtMiddleware.ts +10 -10
|
@@ -4,4 +4,4 @@ import * as express from 'express';
|
|
|
4
4
|
*
|
|
5
5
|
* @param {boolean} mandatoryValidation Defines if the JWT is mandatory. Defaults to true.
|
|
6
6
|
*/
|
|
7
|
-
export declare const jwtValidationMiddleware: (mandatoryValidation?: boolean) => (req: express.Request, res: express.Response, next: express.NextFunction) => void
|
|
7
|
+
export declare const jwtValidationMiddleware: (mandatoryValidation?: boolean) => (req: express.Request, res: express.Response, next: express.NextFunction) => Promise<void>;
|
|
@@ -2,36 +2,33 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.jwtValidationMiddleware = void 0;
|
|
4
4
|
const http_header_fields_typed_1 = require("http-header-fields-typed");
|
|
5
|
-
const constants_1 = require("../config/constants");
|
|
6
5
|
const jwtValidator_1 = require("../jwtValidator");
|
|
7
6
|
/**
|
|
8
7
|
* JWT validation Middleware
|
|
9
8
|
*
|
|
10
9
|
* @param {boolean} mandatoryValidation Defines if the JWT is mandatory. Defaults to true.
|
|
11
10
|
*/
|
|
12
|
-
const jwtValidationMiddleware = (mandatoryValidation = true) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
req[constants_1.constants.requestExtraVariables.JWT] = jwt;
|
|
21
|
-
next();
|
|
22
|
-
})
|
|
23
|
-
.catch((err) => {
|
|
24
|
-
next(err);
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
11
|
+
const jwtValidationMiddleware = (mandatoryValidation = true) => async (req, res, next) => {
|
|
12
|
+
try {
|
|
13
|
+
const authHeader = req.get(http_header_fields_typed_1.default.AUTHORIZATION);
|
|
14
|
+
if (mandatoryValidation || authHeader) {
|
|
15
|
+
return jwtValidator_1.jwtValidator
|
|
16
|
+
.verifyAuthorizationHeader(authHeader)
|
|
17
|
+
.then((jwt) => {
|
|
18
|
+
req.jwt = jwt;
|
|
28
19
|
next();
|
|
29
|
-
}
|
|
20
|
+
})
|
|
21
|
+
.catch((err) => {
|
|
22
|
+
next(err);
|
|
23
|
+
});
|
|
30
24
|
}
|
|
31
|
-
|
|
32
|
-
next(
|
|
25
|
+
else {
|
|
26
|
+
next();
|
|
33
27
|
}
|
|
34
|
-
}
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
next(err);
|
|
31
|
+
}
|
|
35
32
|
};
|
|
36
33
|
exports.jwtValidationMiddleware = jwtValidationMiddleware;
|
|
37
34
|
//# sourceMappingURL=jwtMiddleware.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwtMiddleware.js","sourceRoot":"","sources":["../../../src/middleware/jwtMiddleware.ts"],"names":[],"mappings":";;;AACA,uEAA6D;AAC7D,
|
|
1
|
+
{"version":3,"file":"jwtMiddleware.js","sourceRoot":"","sources":["../../../src/middleware/jwtMiddleware.ts"],"names":[],"mappings":";;;AACA,uEAA6D;AAC7D,kDAA+C;AAG/C;;;;GAIG;AACI,MAAM,uBAAuB,GAClC,CAAC,mBAAmB,GAAG,IAAI,EAAE,EAAE,CAC/B,KAAK,EACH,GAAoB,EACpB,GAAqB,EACrB,IAA0B,EACX,EAAE;IACjB,IAAI;QACF,MAAM,UAAU,GAAW,GAAG,CAAC,GAAG,CAAC,kCAAqB,CAAC,aAAa,CAAC,CAAC;QACxE,IAAI,mBAAmB,IAAI,UAAU,EAAE;YACrC,OAAO,2BAAY;iBAChB,yBAAyB,CAAC,UAAU,CAAC;iBACrC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;gBACX,GAAuB,CAAC,GAAG,GAAG,GAAG,CAAC;gBACnC,IAAI,EAAE,CAAC;YACT,CAAC,CAAC;iBACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACb,IAAI,CAAC,GAAG,CAAC,CAAC;YACZ,CAAC,CAAC,CAAC;SACN;aAAM;YACL,IAAI,EAAE,CAAC;SACR;KACF;IAAC,OAAO,GAAG,EAAE;QACZ,IAAI,CAAC,GAAG,CAAC,CAAC;KACX;AACH,CAAC,CAAC;AAzBS,QAAA,uBAAuB,2BAyBhC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai = require("chai");
|
|
4
|
+
const chai_1 = require("chai");
|
|
5
|
+
const chaiAsPromised = require("chai-as-promised");
|
|
6
|
+
const node_mocks_http_1 = require("node-mocks-http");
|
|
7
|
+
const sinon_1 = require("sinon");
|
|
8
|
+
const jwtValidator_1 = require("../jwtValidator");
|
|
9
|
+
const jwtMiddleware_1 = require("./jwtMiddleware");
|
|
10
|
+
chai.use(chaiAsPromised);
|
|
11
|
+
describe('#jwtValidationMiddleware', () => {
|
|
12
|
+
let middleware;
|
|
13
|
+
let nextFunction = (0, sinon_1.spy)();
|
|
14
|
+
let verifyMethod;
|
|
15
|
+
let jwt;
|
|
16
|
+
let authorizationHeader;
|
|
17
|
+
let request;
|
|
18
|
+
let response;
|
|
19
|
+
before(() => {
|
|
20
|
+
nextFunction = (0, sinon_1.spy)();
|
|
21
|
+
jwt = {
|
|
22
|
+
sub: '1234567890',
|
|
23
|
+
name: 'Peter Neighbor',
|
|
24
|
+
iat: 1694264949,
|
|
25
|
+
};
|
|
26
|
+
authorizationHeader = `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlBldGVyIE5laWdoYm9yIiwiaWF0IjoxNjk0MjY0OTQ5fQ.ncai230HG-KbDL2ximBZz29Smt-yOFBgZYrJTmQreqA`;
|
|
27
|
+
});
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
nextFunction.resetHistory();
|
|
30
|
+
request = (0, node_mocks_http_1.createRequest)({
|
|
31
|
+
method: 'GET',
|
|
32
|
+
url: '/',
|
|
33
|
+
headers: { Authorization: authorizationHeader },
|
|
34
|
+
});
|
|
35
|
+
response = {};
|
|
36
|
+
});
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
verifyMethod?.restore();
|
|
39
|
+
});
|
|
40
|
+
const DELAY = 50;
|
|
41
|
+
it('should invoke #verifyAuthorizationHeader with authorization header', async function () {
|
|
42
|
+
// Cf. https://mochajs.org/api/mocha#slow
|
|
43
|
+
this.slow(2 * DELAY + 10 /* budgeted test duration */);
|
|
44
|
+
// GIVEN
|
|
45
|
+
middleware = (0, jwtMiddleware_1.jwtValidationMiddleware)();
|
|
46
|
+
request = (0, node_mocks_http_1.createRequest)({
|
|
47
|
+
method: 'GET',
|
|
48
|
+
url: '/',
|
|
49
|
+
headers: { Authorization: authorizationHeader },
|
|
50
|
+
});
|
|
51
|
+
verifyMethod = (0, sinon_1.stub)(jwtValidator_1.jwtValidator, 'verifyAuthorizationHeader').returns(delay(DELAY).then(() => jwt));
|
|
52
|
+
// WHEN
|
|
53
|
+
await middleware(request, response, nextFunction);
|
|
54
|
+
// THEN
|
|
55
|
+
(0, chai_1.expect)(verifyMethod.callCount).to.equal(1);
|
|
56
|
+
(0, chai_1.expect)(verifyMethod.args[0]).to.deep.equal([authorizationHeader]);
|
|
57
|
+
(0, chai_1.expect)(nextFunction.callCount).to.equal(1);
|
|
58
|
+
(0, chai_1.expect)(nextFunction.args[0]).to.deep.equal([]);
|
|
59
|
+
(0, chai_1.expect)(request.jwt).to.equal(jwt);
|
|
60
|
+
});
|
|
61
|
+
it('should *NOT* invoke #verifyAuthorizationHeader *WITHOUT* authorization header and "mandatory" trigger being *OFF*', async () => {
|
|
62
|
+
// GIVEN
|
|
63
|
+
middleware = (0, jwtMiddleware_1.jwtValidationMiddleware)(false);
|
|
64
|
+
request.headers = {}; // ∅
|
|
65
|
+
verifyMethod = (0, sinon_1.stub)(jwtValidator_1.jwtValidator, 'verifyAuthorizationHeader').returns(delay(50).then(() => jwt));
|
|
66
|
+
// WHEN
|
|
67
|
+
await middleware(request, response, nextFunction);
|
|
68
|
+
// THEN
|
|
69
|
+
(0, chai_1.expect)(verifyMethod.callCount).to.equal(0);
|
|
70
|
+
(0, chai_1.expect)(nextFunction.callCount).to.equal(1);
|
|
71
|
+
(0, chai_1.expect)(nextFunction.args[0]).to.deep.equal([]);
|
|
72
|
+
(0, chai_1.expect)(request.jwt).to.be.undefined;
|
|
73
|
+
});
|
|
74
|
+
it('should fail synchronously if #verifyAuthorizationHeader does so', async () => {
|
|
75
|
+
// GIVEN
|
|
76
|
+
middleware = (0, jwtMiddleware_1.jwtValidationMiddleware)();
|
|
77
|
+
const error = new Error('💣');
|
|
78
|
+
verifyMethod = (0, sinon_1.stub)(jwtValidator_1.jwtValidator, 'verifyAuthorizationHeader').throws(error);
|
|
79
|
+
// WHEN
|
|
80
|
+
const operation = () => middleware(request, response, nextFunction);
|
|
81
|
+
// THEN
|
|
82
|
+
(0, chai_1.expect)(operation).not.to.throw();
|
|
83
|
+
(0, chai_1.expect)(verifyMethod.callCount).to.equal(1);
|
|
84
|
+
(0, chai_1.expect)(verifyMethod.args[0]).to.deep.equal([authorizationHeader]);
|
|
85
|
+
(0, chai_1.expect)(nextFunction.callCount).to.equal(1);
|
|
86
|
+
(0, chai_1.expect)(nextFunction.args[0]).to.deep.equal([error]);
|
|
87
|
+
(0, chai_1.expect)(request.jwt).to.be.undefined;
|
|
88
|
+
});
|
|
89
|
+
it('should fail *ASYNCHRONOUSLY* if #verifyAuthorizationHeader does so', async () => {
|
|
90
|
+
// GIVEN
|
|
91
|
+
middleware = (0, jwtMiddleware_1.jwtValidationMiddleware)();
|
|
92
|
+
const error = new Error('💣');
|
|
93
|
+
verifyMethod = (0, sinon_1.stub)(jwtValidator_1.jwtValidator, 'verifyAuthorizationHeader').returns(delay(50).then(() => {
|
|
94
|
+
throw error;
|
|
95
|
+
}));
|
|
96
|
+
// WHEN
|
|
97
|
+
const promise = middleware(request, response, nextFunction);
|
|
98
|
+
// THEN
|
|
99
|
+
await (0, chai_1.expect)(promise).not.to.be.eventually.rejected;
|
|
100
|
+
(0, chai_1.expect)(verifyMethod.callCount).to.equal(1);
|
|
101
|
+
(0, chai_1.expect)(verifyMethod.args[0]).to.deep.equal([authorizationHeader]);
|
|
102
|
+
(0, chai_1.expect)(nextFunction.callCount).to.equal(1);
|
|
103
|
+
(0, chai_1.expect)(nextFunction.args[0]).to.deep.equal([error]);
|
|
104
|
+
(0, chai_1.expect)(request.jwt).to.be.undefined;
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
async function delay(duration) {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
setTimeout(resolve, duration);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=jwtMiddleware.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwtMiddleware.test.js","sourceRoot":"","sources":["../../../src/middleware/jwtMiddleware.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@villedemontreal/jwt-validator",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.1",
|
|
4
4
|
"description": "Module to validate JWT (JSON Web Tokens)",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"typings": "dist/src",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@types/nock": "10.0.3",
|
|
36
|
-
"@villedemontreal/general-utils": "5.
|
|
37
|
-
"@villedemontreal/http-request": "7.4.
|
|
38
|
-
"@villedemontreal/logger": "6.
|
|
36
|
+
"@villedemontreal/general-utils": "5.17.3",
|
|
37
|
+
"@villedemontreal/http-request": "7.4.8",
|
|
38
|
+
"@villedemontreal/logger": "6.6.2",
|
|
39
39
|
"app-root-path": "3.1.0",
|
|
40
40
|
"http-header-fields-typed": "1.3.0",
|
|
41
41
|
"http-status-codes": "2.2.0",
|
|
@@ -47,23 +47,25 @@
|
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/app-root-path": "1.2.4",
|
|
50
|
+
"@types/chai": "4.3.4",
|
|
51
|
+
"@types/chai-as-promised": "^7.1.6",
|
|
50
52
|
"@types/express": "4.17.17",
|
|
53
|
+
"@types/fs-extra": "11.0.1",
|
|
51
54
|
"@types/http-status-codes": "1.2.0",
|
|
52
55
|
"@types/jsonwebtoken": "9.0.1",
|
|
53
56
|
"@types/lodash": "4.14.192",
|
|
57
|
+
"@types/mocha": "10.0.1",
|
|
54
58
|
"@types/node": "18.15.11",
|
|
55
59
|
"@types/request": "2.48.8",
|
|
56
60
|
"@types/request-promise-native": "1.0.18",
|
|
57
|
-
"@types/superagent": "4.1.16",
|
|
58
|
-
"@types/chai": "4.3.4",
|
|
59
|
-
"@types/fs-extra": "11.0.1",
|
|
60
|
-
"@types/mocha": "10.0.1",
|
|
61
61
|
"@types/sinon": "10.0.13",
|
|
62
|
+
"@types/superagent": "4.1.16",
|
|
62
63
|
"@types/validator": "13.7.14",
|
|
63
64
|
"@typescript-eslint/eslint-plugin": "5.57.1",
|
|
64
65
|
"@typescript-eslint/parser": "5.57.1",
|
|
65
|
-
"@villedemontreal/scripting": "2.1.
|
|
66
|
+
"@villedemontreal/scripting": "2.1.8",
|
|
66
67
|
"chai": "4.3.7",
|
|
68
|
+
"chai-as-promised": "^7.1.1",
|
|
67
69
|
"eslint": "8.38.0",
|
|
68
70
|
"eslint-config-prettier": "8.8.0",
|
|
69
71
|
"eslint-plugin-prettier": "4.2.1",
|
|
@@ -75,7 +77,6 @@
|
|
|
75
77
|
"node-notifier": "10.0.1",
|
|
76
78
|
"nyc": "15.1.0",
|
|
77
79
|
"sinon": "15.0.3",
|
|
78
|
-
"superagent-mocker": "0.5.2",
|
|
79
80
|
"typescript": "5.0.4",
|
|
80
81
|
"validator": "13.9.0"
|
|
81
82
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import * as chai from 'chai';
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
import * as chaiAsPromised from 'chai-as-promised';
|
|
4
|
+
import { Request, Response } from 'express';
|
|
5
|
+
import { createRequest as createRequestMock } from 'node-mocks-http';
|
|
6
|
+
import { SinonSpy, SinonStub, spy, stub } from 'sinon';
|
|
7
|
+
import { jwtValidator } from '../jwtValidator';
|
|
8
|
+
import { IRequestWithJwt } from '../models/expressRequest';
|
|
9
|
+
import { IJWTPayload } from '../models/jwtPayload';
|
|
10
|
+
import { jwtValidationMiddleware } from './jwtMiddleware';
|
|
11
|
+
|
|
12
|
+
chai.use(chaiAsPromised);
|
|
13
|
+
|
|
14
|
+
describe('#jwtValidationMiddleware', () => {
|
|
15
|
+
let middleware: ReturnType<typeof jwtValidationMiddleware>;
|
|
16
|
+
let nextFunction: SinonSpy = spy();
|
|
17
|
+
let verifyMethod: SinonStub;
|
|
18
|
+
let jwt: IJWTPayload;
|
|
19
|
+
let authorizationHeader: string;
|
|
20
|
+
let request: Request;
|
|
21
|
+
let response: Response;
|
|
22
|
+
|
|
23
|
+
before(() => {
|
|
24
|
+
nextFunction = spy();
|
|
25
|
+
jwt = {
|
|
26
|
+
sub: '1234567890',
|
|
27
|
+
name: 'Peter Neighbor',
|
|
28
|
+
iat: 1694264949,
|
|
29
|
+
} as IJWTPayload;
|
|
30
|
+
authorizationHeader = `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlBldGVyIE5laWdoYm9yIiwiaWF0IjoxNjk0MjY0OTQ5fQ.ncai230HG-KbDL2ximBZz29Smt-yOFBgZYrJTmQreqA`;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
nextFunction.resetHistory();
|
|
35
|
+
request = createRequestMock({
|
|
36
|
+
method: 'GET',
|
|
37
|
+
url: '/',
|
|
38
|
+
headers: { Authorization: authorizationHeader },
|
|
39
|
+
});
|
|
40
|
+
response = {} as Response;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
verifyMethod?.restore();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const DELAY = 50;
|
|
48
|
+
|
|
49
|
+
it('should invoke #verifyAuthorizationHeader with authorization header', async function () {
|
|
50
|
+
// Cf. https://mochajs.org/api/mocha#slow
|
|
51
|
+
this.slow(2 * DELAY + 10 /* budgeted test duration */);
|
|
52
|
+
|
|
53
|
+
// GIVEN
|
|
54
|
+
middleware = jwtValidationMiddleware();
|
|
55
|
+
request = createRequestMock({
|
|
56
|
+
method: 'GET',
|
|
57
|
+
url: '/',
|
|
58
|
+
headers: { Authorization: authorizationHeader },
|
|
59
|
+
});
|
|
60
|
+
verifyMethod = stub(jwtValidator, 'verifyAuthorizationHeader').returns(
|
|
61
|
+
delay(DELAY).then(() => jwt)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// WHEN
|
|
65
|
+
await middleware(request, response, nextFunction);
|
|
66
|
+
|
|
67
|
+
// THEN
|
|
68
|
+
expect(verifyMethod.callCount).to.equal(1);
|
|
69
|
+
expect(verifyMethod.args[0]).to.deep.equal([authorizationHeader]);
|
|
70
|
+
expect(nextFunction.callCount).to.equal(1);
|
|
71
|
+
expect(nextFunction.args[0]).to.deep.equal([]);
|
|
72
|
+
expect((request as IRequestWithJwt).jwt).to.equal(jwt);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should *NOT* invoke #verifyAuthorizationHeader *WITHOUT* authorization header and "mandatory" trigger being *OFF*', async () => {
|
|
76
|
+
// GIVEN
|
|
77
|
+
middleware = jwtValidationMiddleware(false);
|
|
78
|
+
request.headers = {}; // ∅
|
|
79
|
+
verifyMethod = stub(jwtValidator, 'verifyAuthorizationHeader').returns(
|
|
80
|
+
delay(50).then(() => jwt)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// WHEN
|
|
84
|
+
await middleware(request, response, nextFunction);
|
|
85
|
+
|
|
86
|
+
// THEN
|
|
87
|
+
expect(verifyMethod.callCount).to.equal(0);
|
|
88
|
+
expect(nextFunction.callCount).to.equal(1);
|
|
89
|
+
expect(nextFunction.args[0]).to.deep.equal([]);
|
|
90
|
+
expect((request as IRequestWithJwt).jwt).to.be.undefined;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should fail synchronously if #verifyAuthorizationHeader does so', async () => {
|
|
94
|
+
// GIVEN
|
|
95
|
+
middleware = jwtValidationMiddleware();
|
|
96
|
+
const error: Error = new Error('💣');
|
|
97
|
+
verifyMethod = stub(jwtValidator, 'verifyAuthorizationHeader').throws(error);
|
|
98
|
+
|
|
99
|
+
// WHEN
|
|
100
|
+
const operation = () => middleware(request, response, nextFunction);
|
|
101
|
+
|
|
102
|
+
// THEN
|
|
103
|
+
expect(operation).not.to.throw();
|
|
104
|
+
expect(verifyMethod.callCount).to.equal(1);
|
|
105
|
+
expect(verifyMethod.args[0]).to.deep.equal([authorizationHeader]);
|
|
106
|
+
expect(nextFunction.callCount).to.equal(1);
|
|
107
|
+
expect(nextFunction.args[0]).to.deep.equal([error]);
|
|
108
|
+
expect((request as IRequestWithJwt).jwt).to.be.undefined;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should fail *ASYNCHRONOUSLY* if #verifyAuthorizationHeader does so', async () => {
|
|
112
|
+
// GIVEN
|
|
113
|
+
middleware = jwtValidationMiddleware();
|
|
114
|
+
const error: Error = new Error('💣');
|
|
115
|
+
verifyMethod = stub(jwtValidator, 'verifyAuthorizationHeader').returns(
|
|
116
|
+
delay(50).then(() => {
|
|
117
|
+
throw error;
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// WHEN
|
|
122
|
+
const promise = middleware(request, response, nextFunction);
|
|
123
|
+
|
|
124
|
+
// THEN
|
|
125
|
+
await expect(promise).not.to.be.eventually.rejected;
|
|
126
|
+
expect(verifyMethod.callCount).to.equal(1);
|
|
127
|
+
expect(verifyMethod.args[0]).to.deep.equal([authorizationHeader]);
|
|
128
|
+
expect(nextFunction.callCount).to.equal(1);
|
|
129
|
+
expect(nextFunction.args[0]).to.deep.equal([error]);
|
|
130
|
+
expect((request as IRequestWithJwt).jwt).to.be.undefined;
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
async function delay(duration: number): Promise<void> {
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
setTimeout(resolve, duration);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import * as express from 'express';
|
|
2
2
|
import httpHeaderFieldsTyped from 'http-header-fields-typed';
|
|
3
|
-
import { constants } from '../config/constants';
|
|
4
3
|
import { jwtValidator } from '../jwtValidator';
|
|
4
|
+
import { IRequestWithJwt } from '../models/expressRequest';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* JWT validation Middleware
|
|
8
8
|
*
|
|
9
9
|
* @param {boolean} mandatoryValidation Defines if the JWT is mandatory. Defaults to true.
|
|
10
10
|
*/
|
|
11
|
-
export const jwtValidationMiddleware
|
|
12
|
-
mandatoryValidation
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
export const jwtValidationMiddleware =
|
|
12
|
+
(mandatoryValidation = true) =>
|
|
13
|
+
async (
|
|
14
|
+
req: express.Request,
|
|
15
|
+
res: express.Response,
|
|
16
|
+
next: express.NextFunction
|
|
17
|
+
): Promise<void> => {
|
|
17
18
|
try {
|
|
18
19
|
const authHeader: string = req.get(httpHeaderFieldsTyped.AUTHORIZATION);
|
|
19
20
|
if (mandatoryValidation || authHeader) {
|
|
20
|
-
jwtValidator
|
|
21
|
+
return jwtValidator
|
|
21
22
|
.verifyAuthorizationHeader(authHeader)
|
|
22
23
|
.then((jwt) => {
|
|
23
|
-
(req as
|
|
24
|
+
(req as IRequestWithJwt).jwt = jwt;
|
|
24
25
|
next();
|
|
25
26
|
})
|
|
26
27
|
.catch((err) => {
|
|
@@ -33,4 +34,3 @@ export const jwtValidationMiddleware: (
|
|
|
33
34
|
next(err);
|
|
34
35
|
}
|
|
35
36
|
};
|
|
36
|
-
};
|