openid-client 2.4.2 → 2.5.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/CHANGELOG.md CHANGED
@@ -2,6 +2,50 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ # [2.5.0](https://github.com/panva/node-openid-client/compare/v2.4.5...v2.5.0) (2019-04-29)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * key lookup cache is now working as intended ([90d2f2a](https://github.com/panva/node-openid-client/commit/90d2f2a)), closes [#162](https://github.com/panva/node-openid-client/issues/162)
11
+
12
+
13
+ ### Features
14
+
15
+ * add support for azure ad v2 multitenant apps ([24486dd](https://github.com/panva/node-openid-client/commit/24486dd)), closes [/github.com/panva/node-openid-client/pull/148#issuecomment-483348258](https://github.com//github.com/panva/node-openid-client/pull/148/issues/issuecomment-483348258) [#148](https://github.com/panva/node-openid-client/issues/148)
16
+
17
+
18
+
19
+ <a name="2.4.5"></a>
20
+ ## [2.4.5](https://github.com/panva/node-openid-client/compare/v2.4.4...v2.4.5) (2018-11-05)
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * upgrade min node-jose version to fix its performance in node ([e682dfc](https://github.com/panva/node-openid-client/commit/e682dfc))
26
+
27
+
28
+
29
+ <a name="2.4.4"></a>
30
+ ## [2.4.4](https://github.com/panva/node-openid-client/compare/v2.4.3...v2.4.4) (2018-10-18)
31
+
32
+
33
+ ### Bug Fixes
34
+
35
+ * strategy code_verifier length, removed uuid dependency ([60d0cb8...ea4a8fd](https://github.com/panva/node-openid-client/compare/60d0cb8...ea4a8fd)), closes [#131](https://github.com/panva/node-openid-client/issues/131)
36
+
37
+
38
+
39
+ <a name="2.4.3"></a>
40
+ ## [2.4.3](https://github.com/panva/node-openid-client/compare/v2.4.2...v2.4.3) (2018-10-10)
41
+
42
+
43
+ ### Bug Fixes
44
+
45
+ * assign Discovery 1.0 defaults when discovering with .well-known ([74b593e](https://github.com/panva/node-openid-client/commit/74b593e))
46
+
47
+
48
+
5
49
  <a name="2.4.2"></a>
6
50
  ## [2.4.2](https://github.com/panva/node-openid-client/compare/v2.4.1...v2.4.2) (2018-09-27)
7
51
 
package/README.md CHANGED
@@ -57,7 +57,7 @@ versions, if you utilize these consider using the tilde ~ operator in your packa
57
57
  breaking changes may be introduced as part of these specification updates.
58
58
 
59
59
  ## Certification
60
- [<img width="184" height="96" align="right" src="https://cdn.rawgit.com/panva/node-openid-client/38cf016b/OpenID_Certified.png" alt="OpenID Certification">][openid-certified-link]
60
+ [<img width="184" height="96" align="right" src="https://cdn.jsdelivr.net/gh/panva/node-openid-client@38cf016b0837e6d4116de3780b28d222d5780bc9/OpenID_Certified.png" alt="OpenID Certification">][openid-certified-link]
61
61
  Filip Skokan has [certified][openid-certified-link] that [openid-client][npm-url]
62
62
  conforms to the RP Basic, RP Implicit, RP Hybrid, RP Config, RP Dynamic and RP Form Post profiles
63
63
  of the OpenID Connect™ protocol.
@@ -69,6 +69,12 @@ of the OpenID Connect™ protocol.
69
69
 
70
70
  [<img width="65" height="65" align="left" src="https://avatars.githubusercontent.com/u/2824157?s=75&v=4" alt="auth0-logo">][sponsor-auth0] If you want to quickly add OpenID Connect authentication to Node.js apps, feel free to check out Auth0's Node.js SDK and free plan at [auth0.com/overview][sponsor-auth0].<br><br>
71
71
 
72
+ <h2>Support</h2>
73
+
74
+ [<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160" align="right">][support-patreon]
75
+ If you or your business use openid-client, please consider becoming a [Patron][support-patreon] so I can continue maintaining it and adding new features carefree. You may also donate one-time via [PayPal][support-paypal].
76
+ [<img src="https://cdn.jsdelivr.net/gh/gregoiresgt/payment-icons@183140a5ff8f39b5a19d59ebeb2c77f03c3a24d3/Assets/Payment/PayPal/Paypal@2x.png" width="100" align="right">][support-paypal]
77
+
72
78
 
73
79
  ## Get started
74
80
  On the off-chance you want to manage multiple clients for multiple issuers you need to first get
@@ -153,8 +159,8 @@ client.authorizationCallback('https://client.example.com/callback', request.quer
153
159
  });
154
160
  ```
155
161
 
156
- Aside from `state` and `response_type`, checks for `nonce` (implicit and hybrid responses) and
157
- `max_age` are implemented. `id_token` signature and claims validation does not need to be requested,
162
+ Aside from `state` and `response_type`, checks for `nonce` (implicit and hybrid responses),
163
+ `max_age`, and `code_verifier` (for use with PKCE) are implemented. `id_token` signature and claims validation does not need to be requested,
158
164
  it is done automatically.
159
165
 
160
166
  ### OP Errors - OpenIdConnectError
@@ -558,7 +564,8 @@ Issuer.useRequest();
558
564
  [request-library]: https://github.com/request/request
559
565
  [signed-userinfo]: https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
560
566
  [openid-certified-link]: https://openid.net/certification/
561
- [openid-certified-logo]: https://cdn.rawgit.com/panva/node-openid-client/master/OpenID_Certified.png
562
567
  [passport-url]: http://passportjs.org
563
568
  [npm-url]: https://www.npmjs.com/package/openid-client
564
569
  [sponsor-auth0]: https://auth0.com/overview?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=openid-client&utm_content=auth
570
+ [support-patreon]: https://www.patreon.com/panva
571
+ [support-paypal]: https://www.paypal.me/panva
package/lib/client.js CHANGED
@@ -6,7 +6,6 @@ const querystring = require('querystring');
6
6
  const url = require('url');
7
7
 
8
8
  const jose = require('node-jose');
9
- const uuid = require('uuid/v4');
10
9
  const base64url = require('base64url');
11
10
  const _ = require('lodash');
12
11
  const tokenHash = require('oidc-token-hash');
@@ -19,6 +18,7 @@ const now = require('./util/unix_timestamp');
19
18
  const { CALLBACK_PROPERTIES, CLIENT_DEFAULTS, JWT_CONTENT } = require('./helpers/consts');
20
19
  const issuerRegistry = require('./issuer_registry');
21
20
  const forEach = require('./util/for_each');
21
+ const random = require('./util/random');
22
22
 
23
23
  const errorHandler = errorHandlerFactory();
24
24
  const bearerErrorHandler = errorHandlerFactory({ bearerEndpoint: true });
@@ -200,12 +200,15 @@ function assertIssuerConfiguration(issuer, endpoint) {
200
200
  assert(issuer[endpoint], `${endpoint} must be configured on the issuer`);
201
201
  }
202
202
 
203
- class Client {
203
+ class BaseClient {}
204
+
205
+ module.exports = (issuer, aadIssValidation = false) => class Client extends BaseClient {
204
206
  /**
205
207
  * @name constructor
206
208
  * @api public
207
209
  */
208
210
  constructor(metadata = {}, keystore) {
211
+ super();
209
212
  const properties = Object.assign({}, CLIENT_DEFAULTS, metadata);
210
213
 
211
214
  if (!metadata.token_endpoint_auth_method) { // if no explicit value was provided
@@ -570,7 +573,12 @@ class Client {
570
573
  }
571
574
 
572
575
  if (payload.iss !== undefined) {
573
- assert.equal(payload.iss, this.issuer.issuer, 'unexpected iss value');
576
+ if (aadIssValidation) {
577
+ const azureADv2Issuer = this.issuer.issuer.replace('{tenantid}', payload.tid);
578
+ assert.equal(payload.iss, azureADv2Issuer, 'unexpected iss value');
579
+ } else {
580
+ assert.equal(payload.iss, this.issuer.issuer, 'unexpected iss value');
581
+ }
574
582
  }
575
583
 
576
584
  if (payload.iat !== undefined) {
@@ -715,8 +723,9 @@ class Client {
715
723
  }
716
724
  }
717
725
 
718
- const { issuer } = this;
719
- return this.httpClient[verb](issuer.userinfo_endpoint, issuer.httpOptions(httpOptions))
726
+ return this.httpClient[verb](
727
+ this.issuer.userinfo_endpoint, this.issuer.httpOptions(httpOptions)
728
+ )
720
729
  .then(expectResponseWithBody(200))
721
730
  .then((response) => {
722
731
  if (JWT_CONTENT.exec(response.headers['content-type'])) {
@@ -951,7 +960,7 @@ class Client {
951
960
  return this.createSign(endpoint).then(sign => sign.update(JSON.stringify({
952
961
  iat: timestamp,
953
962
  exp: timestamp + 60,
954
- jti: uuid(),
963
+ jti: random(),
955
964
  iss: this.client_id,
956
965
  sub: this.client_id,
957
966
  aud: this.issuer[`${endpoint}_endpoint`],
@@ -1109,6 +1118,23 @@ class Client {
1109
1118
  static get httpClient() {
1110
1119
  return this.issuer.httpClient;
1111
1120
  }
1112
- }
1113
1121
 
1114
- module.exports = Client;
1122
+ /**
1123
+ * @name issuer
1124
+ * @api public
1125
+ */
1126
+ static get issuer() {
1127
+ return issuer;
1128
+ }
1129
+
1130
+
1131
+ /**
1132
+ * @name issuer
1133
+ * @api public
1134
+ */
1135
+ get issuer() { // eslint-disable-line class-methods-use-this
1136
+ return issuer;
1137
+ }
1138
+ };
1139
+
1140
+ module.exports.BaseClient = BaseClient;
@@ -6,6 +6,7 @@ const OIDC_DISCOVERY = '/.well-known/openid-configuration';
6
6
  const OAUTH2_DISCOVERY = '/.well-known/oauth-authorization-server';
7
7
  const WEBFINGER = '/.well-known/webfinger';
8
8
  const REL = 'http://openid.net/specs/connect/1.0/issuer';
9
+ const AAD_MULTITENANT_DISCOVERY = `https://login.microsoftonline.com/common/v2.0${OIDC_DISCOVERY}`;
9
10
 
10
11
  const CLIENT_DEFAULTS = {
11
12
  application_type: 'web',
@@ -49,6 +50,7 @@ const DEFAULT_HTTP_OPTIONS = {
49
50
  const JWT_CONTENT = /^application\/jwt/;
50
51
 
51
52
  module.exports = {
53
+ AAD_MULTITENANT_DISCOVERY,
52
54
  CALLBACK_PROPERTIES,
53
55
  CLIENT_DEFAULTS,
54
56
  DEFAULT_HTTP_OPTIONS,
package/lib/issuer.js CHANGED
@@ -6,17 +6,19 @@ const jose = require('node-jose');
6
6
  const _ = require('lodash');
7
7
  const pAny = require('p-any');
8
8
  const LRU = require('lru-cache');
9
+ const objectHash = require('object-hash');
9
10
 
10
11
  const http = require('./helpers/http');
11
12
  const httpRequest = require('./helpers/http_request');
12
13
  const errorHandler = require('./helpers/error_handler')();
13
- const BaseClient = require('./client');
14
+ const getClient = require('./client');
14
15
  const registry = require('./issuer_registry');
15
16
  const expectResponseWithBody = require('./helpers/expect_response');
16
17
  const webfingerNormalize = require('./util/webfinger_normalize');
17
18
  const forEach = require('./util/for_each');
18
19
  const {
19
- DEFAULT_HTTP_OPTIONS, ISSUER_DEFAULTS, OIDC_DISCOVERY, OAUTH2_DISCOVERY, WEBFINGER, REL,
20
+ DEFAULT_HTTP_OPTIONS, ISSUER_DEFAULTS, OIDC_DISCOVERY,
21
+ OAUTH2_DISCOVERY, WEBFINGER, REL, AAD_MULTITENANT_DISCOVERY,
20
22
  } = require('./helpers/consts');
21
23
 
22
24
  const privateProps = new WeakMap();
@@ -29,12 +31,17 @@ function instance(ctx) {
29
31
  return privateProps.get(ctx);
30
32
  }
31
33
 
34
+ const AAD_MULTITENANT = Symbol('AAD_MULTITENANT');
35
+
32
36
  class Issuer {
33
37
  /**
34
38
  * @name constructor
35
39
  * @api public
36
40
  */
37
41
  constructor(meta = {}) {
42
+ const aadIssValidation = meta[AAD_MULTITENANT];
43
+ delete meta[AAD_MULTITENANT];
44
+
38
45
  ['introspection', 'revocation'].forEach((endpoint) => {
39
46
  // e.g. defaults introspection_endpoint to token_introspection_endpoint value
40
47
  if (
@@ -74,18 +81,8 @@ class Issuer {
74
81
 
75
82
  registry.set(this.issuer, this);
76
83
 
77
- const self = this;
78
-
79
84
  Object.defineProperty(this, 'Client', {
80
- value: class Client extends BaseClient {
81
- static get issuer() {
82
- return self;
83
- }
84
-
85
- get issuer() {
86
- return this.constructor.issuer;
87
- }
88
- },
85
+ value: getClient(this, aadIssValidation),
89
86
  });
90
87
  }
91
88
 
@@ -127,11 +124,24 @@ class Issuer {
127
124
  * @name key
128
125
  * @api private
129
126
  */
130
- key(def, allowMulti) {
127
+ key({
128
+ kid, kty, alg, use, key_ops: ops,
129
+ }, allowMulti = false) {
131
130
  const { cache } = instance(this);
132
131
 
132
+ const def = {
133
+ kid, kty, alg, use, key_ops: ops,
134
+ };
135
+
136
+ const defHash = objectHash(def, {
137
+ algorithm: 'sha256',
138
+ ignoreUnknown: true,
139
+ unorderedArrays: true,
140
+ unorderedSets: true,
141
+ });
142
+
133
143
  // refresh keystore on every unknown key but also only upto once every minute
134
- const freshJwksUri = cache.get(def) || cache.get('throttle');
144
+ const freshJwksUri = cache.get(defHash) || cache.get('throttle');
135
145
 
136
146
  return this.keystore(!freshJwksUri)
137
147
  .then(store => store.all(def))
@@ -139,7 +149,7 @@ class Issuer {
139
149
  assert(keys.length, 'no valid key found');
140
150
  if (!allowMulti) {
141
151
  assert.equal(keys.length, 1, 'multiple matching keys, kid must be provided');
142
- cache.set(def, true);
152
+ cache.set(defHash, true);
143
153
  }
144
154
  return keys[0];
145
155
  });
@@ -196,7 +206,12 @@ class Issuer {
196
206
  if (parsed.pathname.includes('/.well-known/')) {
197
207
  return this.httpClient.get(uri, this.httpOptions())
198
208
  .then(expectResponseWithBody(200))
199
- .then(response => new this(JSON.parse(response.body)))
209
+ .then(({ body }) => new Issuer(Object.assign(
210
+ {},
211
+ ISSUER_DEFAULTS,
212
+ JSON.parse(body),
213
+ { [AAD_MULTITENANT]: uri === AAD_MULTITENANT_DISCOVERY }
214
+ )))
200
215
  .catch(errorHandler.bind(this));
201
216
  }
202
217
 
@@ -216,7 +231,12 @@ class Issuer {
216
231
  const wellKnownUri = url.format(Object.assign({}, parsed, { pathname }));
217
232
  return this.httpClient.get(wellKnownUri, this.httpOptions())
218
233
  .then(expectResponseWithBody(200))
219
- .then(response => new this(Object.assign({}, ISSUER_DEFAULTS, JSON.parse(response.body))));
234
+ .then(({ body }) => new Issuer(Object.assign(
235
+ {},
236
+ ISSUER_DEFAULTS,
237
+ JSON.parse(body),
238
+ { [AAD_MULTITENANT]: wellKnownUri === AAD_MULTITENANT_DISCOVERY }
239
+ )));
220
240
  }))
221
241
  .catch((err) => {
222
242
  if (err instanceof pAny.AggregateError) {
@@ -6,11 +6,11 @@ const url = require('url');
6
6
  const assert = require('assert');
7
7
 
8
8
  const base64url = require('base64url');
9
- const uuid = require('uuid/v4');
10
9
  const _ = require('lodash');
11
10
 
12
11
  const OpenIdConnectError = require('./open_id_connect_error');
13
- const Client = require('./client');
12
+ const { BaseClient } = require('./client');
13
+ const random = require('./util/random');
14
14
 
15
15
  function verified(err, user, info = {}) {
16
16
  if (err) {
@@ -33,7 +33,7 @@ function OpenIDConnectStrategy({
33
33
  sessionKey,
34
34
  usePKCE = false,
35
35
  } = {}, verify) {
36
- assert(client instanceof Client, 'client must be an instance of openid-client Client');
36
+ assert(client instanceof BaseClient, 'client must be an instance of openid-client Client');
37
37
  assert.equal(typeof verify, 'function', 'verify must be a function');
38
38
 
39
39
  assert(client.issuer && client.issuer.issuer, 'client must have an issuer with an identifier');
@@ -81,17 +81,17 @@ OpenIDConnectStrategy.prototype.authenticate = function authenticate(req, option
81
81
  if (_.isEmpty(reqParams)) {
82
82
  // provide options object with extra authentication parameters
83
83
  const params = _.defaults({}, options, this._params, {
84
- state: uuid(),
84
+ state: random(),
85
85
  });
86
86
 
87
87
  if (!params.nonce && params.response_type.includes('id_token')) {
88
- params.nonce = uuid();
88
+ params.nonce = random();
89
89
  }
90
90
 
91
91
  req.session[sessionKey] = _.pick(params, 'nonce', 'state', 'max_age', 'response_type');
92
92
 
93
93
  if (this._usePKCE) {
94
- const verifier = uuid();
94
+ const verifier = random();
95
95
  req.session[sessionKey].code_verifier = verifier;
96
96
 
97
97
  switch (this._usePKCE) { // eslint-disable-line default-case
@@ -0,0 +1,5 @@
1
+ const { randomBytes } = require('crypto');
2
+
3
+ const base64url = require('base64url');
4
+
5
+ module.exports = (bytes = 32) => base64url(randomBytes(bytes));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openid-client",
3
- "version": "2.4.2",
3
+ "version": "2.5.0",
4
4
  "description": "OpenID Connect Relying Party (RP, Client) implementation for Node.js servers, supports passportjs",
5
5
  "keywords": [
6
6
  "auth",
@@ -42,11 +42,11 @@
42
42
  "base64url": "^3.0.0",
43
43
  "got": "^8.3.2",
44
44
  "lodash": "^4.17.11",
45
- "lru-cache": "^4.1.3",
46
- "node-jose": "^1.0.0",
45
+ "lru-cache": "^5.1.1",
46
+ "node-jose": "^1.1.0",
47
+ "object-hash": "^1.3.1",
47
48
  "oidc-token-hash": "^3.0.1",
48
- "p-any": "^1.1.0",
49
- "uuid": "^3.3.2"
49
+ "p-any": "^1.1.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@commitlint/cli": "^7.1.2",
@@ -55,18 +55,13 @@
55
55
  "eslint": "^5.6.0",
56
56
  "eslint-config-airbnb-base": "^13.1.0",
57
57
  "eslint-plugin-import": "^2.14.0",
58
- "husky": "^1.0.0",
59
- "koa": "^2.5.3",
60
- "koa-body": "^4.0.4",
61
- "koa-ejs": "^4.1.2",
62
- "koa-router": "^7.4.0",
63
- "koa-session": "^5.9.0",
64
- "mocha": "^5.2.0",
58
+ "husky": "^2.1.0",
59
+ "mocha": "^6.1.4",
65
60
  "nock": "^10.0.0",
66
- "nyc": "^13.0.1",
61
+ "nyc": "^14.0.0",
67
62
  "readable-mock-req": "^0.2.2",
68
63
  "request": "^2.88.0",
69
- "sinon": "^6.3.4",
64
+ "sinon": "^7.0.0",
70
65
  "timekeeper": "^2.1.2"
71
66
  },
72
67
  "engines": {