@villedemontreal/jwt-validator 5.8.0 → 5.9.0

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/README.md CHANGED
@@ -155,6 +155,30 @@ let jwt: string = '...';
155
155
  let jwtPayload: IJWTPayload = await jwtValidator.verifyToken(jwt);
156
156
  ```
157
157
 
158
+ ### Token transformation middleware
159
+
160
+ When working locally, you do not have all the infrastructure required to simulate the translation between an access token and a JWT (ex: Kong).
161
+
162
+ Prior to usage of the `jwtValidationMiddleware`, you can use the `token transformation middleware` which do an API call to exchange your access token for an extended jwt and update the authorization header.
163
+
164
+ ```typescript
165
+ export async function createApp(apiRoutes: IHandlerRoute[]): Promise<express.Express> {
166
+ // ...
167
+
168
+ if (configs.security.jwt.enable) {
169
+ if (configs.environment.isLocal) {
170
+ // Required prior to the jwtValidationMiddleware
171
+ app.use(tokenTransformationMiddleware(config));
172
+ }
173
+
174
+ // ...
175
+ app.use(jwtValidationMiddleware())
176
+ }
177
+
178
+ // ...
179
+ }
180
+ ```
181
+
158
182
  ## Usage For Tests
159
183
 
160
184
  This library provides a mock tool to generate your own JWT with a signature which it will be correcly validated.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Configuration for the token transformation
3
+ * middleware (UTTM).
4
+ */
5
+ export interface ITokenTtransformationMiddlewareConfig {
6
+ service: ITokenTtransformationMiddlewareServiceConfig;
7
+ extensions: {
8
+ jwt: {
9
+ customDataProvider: ITokenTtransformationMiddlewareCustomDataProviderConfig;
10
+ };
11
+ };
12
+ }
13
+ /**
14
+ * Configuration specific for the token transformation service.
15
+ */
16
+ export interface ITokenTtransformationMiddlewareServiceConfig {
17
+ uri: string;
18
+ }
19
+ /**
20
+ * Configuration specific for the custom data provider.
21
+ */
22
+ export interface ITokenTtransformationMiddlewareCustomDataProviderConfig {
23
+ uri: string;
24
+ method: string;
25
+ options: any;
26
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=tokenTransformationMiddlewareConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenTransformationMiddlewareConfig.js","sourceRoot":"","sources":["../../../src/config/tokenTransformationMiddlewareConfig.ts"],"names":[],"mappings":""}
@@ -2,6 +2,7 @@ export * from './config/constants';
2
2
  export * from './config/init';
3
3
  export * from './jwtValidator';
4
4
  export * from './middleware/jwtMiddleware';
5
+ export * from './middleware/tokenTransformationMiddleware';
5
6
  export * from './models/expressRequest';
6
7
  export * from './models/gluuUserType';
7
8
  export * from './models/jwtPayload';
package/dist/src/index.js CHANGED
@@ -23,6 +23,7 @@ __exportStar(require("./config/constants"), exports);
23
23
  __exportStar(require("./config/init"), exports);
24
24
  __exportStar(require("./jwtValidator"), exports);
25
25
  __exportStar(require("./middleware/jwtMiddleware"), exports);
26
+ __exportStar(require("./middleware/tokenTransformationMiddleware"), exports);
26
27
  __exportStar(require("./models/expressRequest"), exports);
27
28
  __exportStar(require("./models/gluuUserType"), exports);
28
29
  __exportStar(require("./models/jwtPayload"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,qDAAmC;AACnC,6CAA6C;AAC7C,gDAAgD;AAChD,6CAA6C;AAC7C,qCAAqC;AACrC,6CAA6C;AAC7C,gDAA8B;AAC9B,iDAA+B;AAC/B,6DAA2C;AAC3C,0DAAwC;AACxC,wDAAsC;AACtC,sDAAoC;AACpC,qDAAmC;AACnC,kDAAgC;AAChC,kDAAgC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,qDAAmC;AACnC,6CAA6C;AAC7C,gDAAgD;AAChD,6CAA6C;AAC7C,qCAAqC;AACrC,6CAA6C;AAC7C,gDAA8B;AAC9B,iDAA+B;AAC/B,6DAA2C;AAC3C,6EAA2D;AAC3D,0DAAwC;AACxC,wDAAsC;AACtC,sDAAoC;AACpC,qDAAmC;AACnC,kDAAgC;AAChC,kDAAgC"}
@@ -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
- return (req, res, next) => {
14
- try {
15
- const authHeader = req.get(http_header_fields_typed_1.default.AUTHORIZATION);
16
- if (mandatoryValidation || authHeader) {
17
- jwtValidator_1.jwtValidator
18
- .verifyAuthorizationHeader(authHeader)
19
- .then((jwt) => {
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
- catch (err) {
32
- next(err);
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,mDAAgD;AAChD,kDAA+C;AAE/C;;;;GAIG;AACI,MAAM,uBAAuB,GAEqD,CACvF,mBAAmB,GAAG,IAAI,EAC1B,EAAE;IACF,OAAO,CAAC,GAAoB,EAAE,GAAqB,EAAE,IAA0B,EAAQ,EAAE;QACvF,IAAI;YACF,MAAM,UAAU,GAAW,GAAG,CAAC,GAAG,CAAC,kCAAqB,CAAC,aAAa,CAAC,CAAC;YACxE,IAAI,mBAAmB,IAAI,UAAU,EAAE;gBACrC,2BAAY;qBACT,yBAAyB,CAAC,UAAU,CAAC;qBACrC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;oBACX,GAAW,CAAC,qBAAS,CAAC,qBAAqB,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;oBACxD,IAAI,EAAE,CAAC;gBACT,CAAC,CAAC;qBACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;oBACb,IAAI,CAAC,GAAG,CAAC,CAAC;gBACZ,CAAC,CAAC,CAAC;aACN;iBAAM;gBACL,IAAI,EAAE,CAAC;aACR;SACF;QAAC,OAAO,GAAG,EAAE;YACZ,IAAI,CAAC,GAAG,CAAC,CAAC;SACX;IACH,CAAC,CAAC;AACJ,CAAC,CAAC;AAzBW,QAAA,uBAAuB,2BAyBlC"}
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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"}
@@ -0,0 +1,9 @@
1
+ import * as express from 'express';
2
+ import { ITokenTtransformationMiddlewareConfig } from '../config/tokenTransformationMiddlewareConfig';
3
+ /**
4
+ * Token transformation Middleware. It will generate extended jwt
5
+ * in exchange for an access token.
6
+ *
7
+ * @param {boolean} config Configuration of the middleware.
8
+ */
9
+ export declare const tokenTransformationMiddleware: (config?: ITokenTtransformationMiddlewareConfig) => (req: express.Request, res: express.Response, next: express.NextFunction) => void;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tokenTransformationMiddleware = void 0;
4
+ const general_utils_1 = require("@villedemontreal/general-utils");
5
+ const http_header_fields_typed_1 = require("http-header-fields-typed");
6
+ const constants_1 = require("../config/constants");
7
+ const customError_1 = require("../models/customError");
8
+ const logger_1 = require("../utils/logger");
9
+ const superagent = require("superagent");
10
+ const _regexAccessToken = /([a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})/;
11
+ const logger = (0, logger_1.createLogger)('Token transformation middleware');
12
+ /**
13
+ * Token transformation Middleware. It will generate extended jwt
14
+ * in exchange for an access token.
15
+ *
16
+ * @param {boolean} config Configuration of the middleware.
17
+ */
18
+ const tokenTransformationMiddleware = (config) => {
19
+ return (req, res, next) => {
20
+ try {
21
+ // Validate the authorization header
22
+ const authHeader = req.get(http_header_fields_typed_1.default.AUTHORIZATION);
23
+ if (general_utils_1.utils.isBlank(authHeader)) {
24
+ throw (0, customError_1.createInvalidAuthHeaderError)({
25
+ code: constants_1.constants.errors.codes.INVALID_VALUE,
26
+ target: 'authorization_header',
27
+ message: 'authorization header is empty',
28
+ });
29
+ }
30
+ // Extract the access token value from the authorization header
31
+ const accessTokenRegExpArray = _regexAccessToken.exec(authHeader);
32
+ if (accessTokenRegExpArray.length <= 1) {
33
+ throw (0, customError_1.createInvalidAuthHeaderError)({
34
+ code: constants_1.constants.errors.codes.INVALID_VALUE,
35
+ target: 'access_token',
36
+ message: 'could not find a valid access token from the authorization header',
37
+ });
38
+ }
39
+ const accessToken = accessTokenRegExpArray[1];
40
+ // Call the service endpoint to exchange the access token for a extended jwt
41
+ superagent
42
+ .post(config.service.uri)
43
+ .send({ accessToken, extensions: config.extensions })
44
+ .then((response) => {
45
+ const extendedJwt = response.body.jwts?.extended;
46
+ logger.debug(extendedJwt, 'Extended jwt content.');
47
+ const basicJwt = response.body.jwts?.basic;
48
+ logger.debug(basicJwt, 'Basic jwt content.');
49
+ // Get the extended jwt. If not available, fallback to basic jwt.
50
+ const jwt = extendedJwt ?? basicJwt;
51
+ if (jwt) {
52
+ // Warning: Headers are all in lowercase. To be sure to replace
53
+ // the authorization header instead of duplicate it, must use lower case property name.
54
+ req.headers[http_header_fields_typed_1.default.AUTHORIZATION.toLowerCase()] = `Bearer ${jwt}`;
55
+ logger.debug(req.headers, 'Request headers');
56
+ next();
57
+ }
58
+ else {
59
+ const err = (0, customError_1.createInvalidJwtError)({
60
+ code: constants_1.constants.errors.codes.NULL_VALUE,
61
+ target: 'jwt',
62
+ message: 'could not get a valid jwt from token translation service',
63
+ });
64
+ next(err);
65
+ }
66
+ })
67
+ .catch((err) => next(err));
68
+ }
69
+ catch (err) {
70
+ next(err);
71
+ }
72
+ };
73
+ };
74
+ exports.tokenTransformationMiddleware = tokenTransformationMiddleware;
75
+ //# sourceMappingURL=tokenTransformationMiddleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenTransformationMiddleware.js","sourceRoot":"","sources":["../../../src/middleware/tokenTransformationMiddleware.ts"],"names":[],"mappings":";;;AAAA,kEAAuD;AAEvD,uEAA6D;AAC7D,mDAAgD;AAEhD,uDAA4F;AAC5F,4CAA+C;AAC/C,yCAA0C;AAE1C,MAAM,iBAAiB,GAAG,gEAAgE,CAAC;AAE3F,MAAM,MAAM,GAAG,IAAA,qBAAY,EAAC,iCAAiC,CAAC,CAAC;AAE/D;;;;;GAKG;AACI,MAAM,6BAA6B,GAE+C,CACvF,MAAM,EACN,EAAE;IACF,OAAO,CAAC,GAAoB,EAAE,GAAqB,EAAE,IAA0B,EAAQ,EAAE;QACvF,IAAI;YACF,oCAAoC;YACpC,MAAM,UAAU,GAAW,GAAG,CAAC,GAAG,CAAC,kCAAqB,CAAC,aAAa,CAAC,CAAC;YACxE,IAAI,qBAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE;gBAC7B,MAAM,IAAA,0CAA4B,EAAC;oBACjC,IAAI,EAAE,qBAAS,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa;oBAC1C,MAAM,EAAE,sBAAsB;oBAC9B,OAAO,EAAE,+BAA+B;iBACzC,CAAC,CAAC;aACJ;YAED,+DAA+D;YAC/D,MAAM,sBAAsB,GAAG,iBAAiB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAClE,IAAI,sBAAsB,CAAC,MAAM,IAAI,CAAC,EAAE;gBACtC,MAAM,IAAA,0CAA4B,EAAC;oBACjC,IAAI,EAAE,qBAAS,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa;oBAC1C,MAAM,EAAE,cAAc;oBACtB,OAAO,EAAE,mEAAmE;iBAC7E,CAAC,CAAC;aACJ;YACD,MAAM,WAAW,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAAC;YAE9C,4EAA4E;YAC5E,UAAU;iBACP,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;iBACxB,IAAI,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;iBACpD,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;gBACjB,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC;gBACjD,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,uBAAuB,CAAC,CAAC;gBAEnD,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC;gBAC3C,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC;gBAE7C,iEAAiE;gBACjE,MAAM,GAAG,GAAG,WAAW,IAAI,QAAQ,CAAC;gBAEpC,IAAI,GAAG,EAAE;oBACP,+DAA+D;oBAC/D,uFAAuF;oBACvF,GAAG,CAAC,OAAO,CAAC,kCAAqB,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC,GAAG,UAAU,GAAG,EAAE,CAAC;oBACjF,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;oBAC7C,IAAI,EAAE,CAAC;iBACR;qBAAM;oBACL,MAAM,GAAG,GAAG,IAAA,mCAAqB,EAAC;wBAChC,IAAI,EAAE,qBAAS,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU;wBACvC,MAAM,EAAE,KAAK;wBACb,OAAO,EAAE,0DAA0D;qBACpE,CAAC,CAAC;oBACH,IAAI,CAAC,GAAG,CAAC,CAAC;iBACX;YACH,CAAC,CAAC;iBACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;SAC9B;QAAC,OAAO,GAAG,EAAE;YACZ,IAAI,CAAC,GAAG,CAAC,CAAC;SACX;IACH,CAAC,CAAC;AACJ,CAAC,CAAC;AA9DW,QAAA,6BAA6B,iCA8DxC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@villedemontreal/jwt-validator",
3
- "version": "5.8.0",
3
+ "version": "5.9.0",
4
4
  "description": "Module to validate JWT (JSON Web Tokens)",
5
5
  "main": "dist/src/index.js",
6
6
  "typings": "dist/src",
@@ -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
66
  "@villedemontreal/scripting": "2.1.6",
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,28 @@
1
+ /**
2
+ * Configuration for the token transformation
3
+ * middleware (UTTM).
4
+ */
5
+ export interface ITokenTtransformationMiddlewareConfig {
6
+ service: ITokenTtransformationMiddlewareServiceConfig;
7
+ extensions: {
8
+ jwt: {
9
+ customDataProvider: ITokenTtransformationMiddlewareCustomDataProviderConfig;
10
+ };
11
+ };
12
+ }
13
+
14
+ /**
15
+ * Configuration specific for the token transformation service.
16
+ */
17
+ export interface ITokenTtransformationMiddlewareServiceConfig {
18
+ uri: string;
19
+ }
20
+
21
+ /**
22
+ * Configuration specific for the custom data provider.
23
+ */
24
+ export interface ITokenTtransformationMiddlewareCustomDataProviderConfig {
25
+ uri: string;
26
+ method: string;
27
+ options: any;
28
+ }
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ export * from './config/constants';
7
7
  export * from './config/init';
8
8
  export * from './jwtValidator';
9
9
  export * from './middleware/jwtMiddleware';
10
+ export * from './middleware/tokenTransformationMiddleware';
10
11
  export * from './models/expressRequest';
11
12
  export * from './models/gluuUserType';
12
13
  export * from './models/jwtPayload';
@@ -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?: boolean
13
- ) => (req: express.Request, res: express.Response, next: express.NextFunction) => void = (
14
- mandatoryValidation = true
15
- ) => {
16
- return (req: express.Request, res: express.Response, next: express.NextFunction): void => {
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 any)[constants.requestExtraVariables.JWT] = jwt;
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
- };
@@ -0,0 +1,82 @@
1
+ import { utils } from '@villedemontreal/general-utils';
2
+ import * as express from 'express';
3
+ import httpHeaderFieldsTyped from 'http-header-fields-typed';
4
+ import { constants } from '../config/constants';
5
+ import { ITokenTtransformationMiddlewareConfig } from '../config/tokenTransformationMiddlewareConfig';
6
+ import { createInvalidAuthHeaderError, createInvalidJwtError } from '../models/customError';
7
+ import { createLogger } from '../utils/logger';
8
+ import superagent = require('superagent');
9
+
10
+ const _regexAccessToken = /([a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})/;
11
+
12
+ const logger = createLogger('Token transformation middleware');
13
+
14
+ /**
15
+ * Token transformation Middleware. It will generate extended jwt
16
+ * in exchange for an access token.
17
+ *
18
+ * @param {boolean} config Configuration of the middleware.
19
+ */
20
+ export const tokenTransformationMiddleware: (
21
+ config?: ITokenTtransformationMiddlewareConfig
22
+ ) => (req: express.Request, res: express.Response, next: express.NextFunction) => void = (
23
+ config
24
+ ) => {
25
+ return (req: express.Request, res: express.Response, next: express.NextFunction): void => {
26
+ try {
27
+ // Validate the authorization header
28
+ const authHeader: string = req.get(httpHeaderFieldsTyped.AUTHORIZATION);
29
+ if (utils.isBlank(authHeader)) {
30
+ throw createInvalidAuthHeaderError({
31
+ code: constants.errors.codes.INVALID_VALUE,
32
+ target: 'authorization_header',
33
+ message: 'authorization header is empty',
34
+ });
35
+ }
36
+
37
+ // Extract the access token value from the authorization header
38
+ const accessTokenRegExpArray = _regexAccessToken.exec(authHeader);
39
+ if (accessTokenRegExpArray.length <= 1) {
40
+ throw createInvalidAuthHeaderError({
41
+ code: constants.errors.codes.INVALID_VALUE,
42
+ target: 'access_token',
43
+ message: 'could not find a valid access token from the authorization header',
44
+ });
45
+ }
46
+ const accessToken = accessTokenRegExpArray[1];
47
+
48
+ // Call the service endpoint to exchange the access token for a extended jwt
49
+ superagent
50
+ .post(config.service.uri)
51
+ .send({ accessToken, extensions: config.extensions })
52
+ .then((response) => {
53
+ const extendedJwt = response.body.jwts?.extended;
54
+ logger.debug(extendedJwt, 'Extended jwt content.');
55
+
56
+ const basicJwt = response.body.jwts?.basic;
57
+ logger.debug(basicJwt, 'Basic jwt content.');
58
+
59
+ // Get the extended jwt. If not available, fallback to basic jwt.
60
+ const jwt = extendedJwt ?? basicJwt;
61
+
62
+ if (jwt) {
63
+ // Warning: Headers are all in lowercase. To be sure to replace
64
+ // the authorization header instead of duplicate it, must use lower case property name.
65
+ req.headers[httpHeaderFieldsTyped.AUTHORIZATION.toLowerCase()] = `Bearer ${jwt}`;
66
+ logger.debug(req.headers, 'Request headers');
67
+ next();
68
+ } else {
69
+ const err = createInvalidJwtError({
70
+ code: constants.errors.codes.NULL_VALUE,
71
+ target: 'jwt',
72
+ message: 'could not get a valid jwt from token translation service',
73
+ });
74
+ next(err);
75
+ }
76
+ })
77
+ .catch((err) => next(err));
78
+ } catch (err) {
79
+ next(err);
80
+ }
81
+ };
82
+ };