@hapi/bell 13.0.2 → 13.2.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/lib/index.d.ts CHANGED
@@ -30,7 +30,6 @@ export type Provider =
30
30
  | 'live'
31
31
  | 'medium'
32
32
  | 'meetup'
33
- | 'mixer'
34
33
  | 'nest'
35
34
  | 'okta'
36
35
  | 'phabricator'
@@ -89,7 +88,7 @@ export interface OptionalOptions {
89
88
  * * Twitter supports `force_login`, `screen_name`.
90
89
  * * Linkedin supports `fields`.
91
90
  */
92
- providerParams?: StringLikeMap | ((request: Request) => StringLikeMap) | undefined;
91
+ providerParams?: StringLikeMap | ((request: Request) => StringLikeMap | Promise<StringLikeMap>) | undefined;
93
92
  /**
94
93
  * allows passing query parameters from a bell protected endpoint to the auth request.
95
94
  * It will merge the query params you pass along with the providerParams and any other predefined ones.
@@ -125,6 +124,12 @@ export interface OptionalOptions {
125
124
  | { extendedProfile?: boolean | undefined; getMethod?: string | undefined }
126
125
  | { uri?: string | undefined }
127
126
  | undefined;
127
+ /**
128
+ * provider-specific query parameters for the token endpoint.
129
+ * It may be passed either as an object to merge into the query string,
130
+ * or a function which takes the client's request and returns an object.
131
+ */
132
+ tokenParams?: StringLikeMap | ((request: Request) => StringLikeMap | Promise<StringLikeMap>) | undefined;
128
133
  /**
129
134
  * an object of key-value pairs that specify additional
130
135
  * URL query parameters to send with the profile request to the provider.
@@ -199,6 +204,12 @@ export interface Credentials2 extends Credentials {
199
204
  expiresIn?: number | undefined;
200
205
  }
201
206
 
207
+ /**
208
+ * an endpoint URI, or a function of the request returning one.
209
+ * @param request the hapi request object being authenticated.
210
+ */
211
+ export type ProviderUri = string | ((request: Request) => string | Promise<string>);
212
+
202
213
  export interface CustomProtocol {
203
214
  /**
204
215
  * The name of the protocol.
@@ -208,11 +219,11 @@ export interface CustomProtocol {
208
219
  /**
209
220
  * the authorization endpoint URI.
210
221
  */
211
- auth: string;
222
+ auth: ProviderUri;
212
223
  /**
213
224
  * the access token endpoint URI.
214
225
  */
215
- token: string;
226
+ token: ProviderUri;
216
227
  /**
217
228
  * a headers object with additional headers required by the provider
218
229
  * (e.g. GitHub required the 'User-Agent' header which is set by default).
@@ -228,12 +239,14 @@ export interface CustomProtocol {
228
239
  * Change the object directly within the function (profile information is typically stored under credentials.profile).
229
240
  * @param params the parsed information received from the provider (e.g. token, secret, and other custom fields).
230
241
  * @param get an OAuth helper function to make authenticated requests using the credentials received.
242
+ * @param request the hapi request object of the authorization callback.
231
243
  */
232
244
  export type ProfileGetter<C extends Credentials> = (
233
245
  this: CustomProviderOptions,
234
246
  credentials: C,
235
247
  params: { [key: string]: string },
236
248
  get: AuthedRequest,
249
+ request: Request,
237
250
  ) => Promise<void>;
238
251
 
239
252
  export interface CustomProtocol1 extends CustomProtocol {
@@ -251,7 +264,7 @@ export interface CustomProtocol1 extends CustomProtocol {
251
264
  /**
252
265
  * the temporary credentials (request token) endpoint).
253
266
  */
254
- temporary?: string | undefined;
267
+ temporary?: ProviderUri | undefined;
255
268
 
256
269
  profile: ProfileGetter<Credentials1>;
257
270
  }
@@ -302,4 +315,4 @@ export function simulate(credentialsFunc: RequestPassThrough): void;
302
315
  * [See docs](https://github.com/hapijs/bell/blob/master/API.md#simulated-authentication)
303
316
  * Disables simulation mode
304
317
  */
305
- export function simulate(state: false): void;
318
+ export function simulate(state: false): void;
package/lib/index.js CHANGED
@@ -9,6 +9,7 @@ const Providers = require('./providers');
9
9
 
10
10
  const internals = {
11
11
  simulate: false,
12
+ endpoint: Joi.alternatives(Joi.string(), Joi.func()),
12
13
  flexBoolean: Joi.boolean().truthy('yes', 1, '1').falsy('no', 0, '0')
13
14
  };
14
15
 
@@ -38,8 +39,8 @@ exports.plugin = {
38
39
  internals.provider = Joi.object({
39
40
  name: Joi.string().optional().default('custom'),
40
41
  protocol: ['oauth', 'oauth2'],
41
- auth: Joi.string().required(),
42
- token: Joi.string().required(),
42
+ auth: internals.endpoint.required(),
43
+ token: internals.endpoint.required(),
43
44
  headers: Joi.object(),
44
45
  profile: Joi.func(),
45
46
  profileMethod: Joi.valid('get', 'post').default('get')
@@ -47,7 +48,7 @@ internals.provider = Joi.object({
47
48
  .when('.protocol', {
48
49
  is: 'oauth',
49
50
  then: Joi.object({
50
- temporary: Joi.string().required(),
51
+ temporary: internals.endpoint.required(),
51
52
  signatureMethod: Joi.valid('HMAC-SHA1', 'RSA-SHA1').default('HMAC-SHA1')
52
53
  }),
53
54
  otherwise: Joi.object({
@@ -104,6 +105,8 @@ internals.schema = Joi.object({
104
105
 
105
106
  config: Joi.object(),
106
107
 
108
+ tokenParams: Joi.alternatives(Joi.object(), Joi.func()),
109
+
107
110
  profileParams: Joi.object(),
108
111
 
109
112
  skipProfile: internals.flexBoolean.optional().default(false),
package/lib/oauth.js CHANGED
@@ -19,10 +19,16 @@ const internals = {
19
19
 
20
20
  exports.v1 = function (settings) {
21
21
 
22
- const client = new internals.Client(settings);
22
+ // The client uses temporary and token, so we can share one unless either is a function
23
+
24
+ const shared = [settings.provider.temporary, settings.provider.token].some((endpoint) => typeof endpoint === 'function') ?
25
+ null :
26
+ new internals.Client(settings);
23
27
 
24
28
  return async function (request, h) {
25
29
 
30
+ const client = shared ?? await internals.client(request, settings);
31
+
26
32
  const cookie = settings.cookie;
27
33
  const name = settings.name;
28
34
  const protocol = internals.getProtocol(request, settings);
@@ -72,14 +78,14 @@ exports.v1 = function (settings) {
72
78
 
73
79
  h.state(cookie, state);
74
80
 
75
- const authQuery = internals.resolveProviderParams(request, settings.providerParams);
76
- authQuery.oauth_token = temp.oauth_token;
77
-
78
- if (settings.allowRuntimeProviderParams) {
79
- Hoek.merge(authQuery, request.query);
80
- }
81
+ const authQuery = {
82
+ ...await internals.resolveProviderParams(request, settings.providerParams),
83
+ oauth_token: temp.oauth_token,
84
+ ...(settings.allowRuntimeProviderParams && request.query)
85
+ };
81
86
 
82
- return h.redirect(settings.provider.auth + '?' + internals.queryString(authQuery)).takeover();
87
+ const authUri = await internals.resolveProviderUri(request, settings.provider.auth);
88
+ return h.redirect(authUri + '?' + internals.queryString(authQuery)).takeover();
83
89
  }
84
90
 
85
91
  // Authorization callback
@@ -123,7 +129,7 @@ exports.v1 = function (settings) {
123
129
  const get = async (uri, params = {}) => {
124
130
 
125
131
  if (settings.profileParams) {
126
- Hoek.merge(params, settings.profileParams);
132
+ Object.assign(params, settings.profileParams);
127
133
  }
128
134
 
129
135
  const { payload: resource } = await client.resource('get', uri, params, { token: token.oauth_token, secret: token.oauth_token_secret });
@@ -131,7 +137,7 @@ exports.v1 = function (settings) {
131
137
  };
132
138
 
133
139
  try {
134
- await settings.provider.profile.call(settings, credentials, token, get);
140
+ await settings.provider.profile.call(settings, credentials, token, get, request);
135
141
  }
136
142
  catch (err) {
137
143
  return h.unauthenticated(err, { credentials });
@@ -178,16 +184,14 @@ exports.v2 = function (settings) {
178
184
  credentials.query = request.query;
179
185
 
180
186
  const nonce = Cryptiles.randomAlphanumString(internals.nonceLength);
181
- const query = internals.resolveProviderParams(request, settings.providerParams);
182
-
183
- if (settings.allowRuntimeProviderParams) {
184
- Hoek.merge(query, request.query);
185
- }
186
-
187
- query.client_id = settings.clientId;
188
- query.response_type = 'code';
189
- query.redirect_uri = internals.location(request, protocol, settings.location);
190
- query.state = nonce;
187
+ const query = {
188
+ ...await internals.resolveProviderParams(request, settings.providerParams),
189
+ ...(settings.allowRuntimeProviderParams && request.query),
190
+ client_id: settings.clientId,
191
+ response_type: 'code',
192
+ redirect_uri: internals.location(request, protocol, settings.location),
193
+ state: nonce
194
+ };
191
195
 
192
196
  if (settings.runtimeStateCallback) {
193
197
  const runtimeState = settings.runtimeStateCallback(request);
@@ -230,7 +234,9 @@ exports.v2 = function (settings) {
230
234
  }
231
235
 
232
236
  h.state(cookie, state);
233
- return h.redirect(settings.provider.auth + '?' + internals.queryString(query)).takeover();
237
+
238
+ const authUri = await internals.resolveProviderUri(request, settings.provider.auth);
239
+ return h.redirect(authUri + '?' + internals.queryString(query)).takeover();
234
240
  }
235
241
 
236
242
  // Authorization callback
@@ -251,7 +257,8 @@ exports.v2 = function (settings) {
251
257
  const query = {
252
258
  grant_type: 'authorization_code',
253
259
  code: request.query.code,
254
- redirect_uri: internals.location(request, protocol, settings.location)
260
+ redirect_uri: internals.location(request, protocol, settings.location),
261
+ ...await internals.resolveProviderParams(request, settings.tokenParams)
255
262
  };
256
263
 
257
264
  if (settings.provider.pkce) {
@@ -289,8 +296,9 @@ exports.v2 = function (settings) {
289
296
 
290
297
  // Obtain token
291
298
 
299
+ const tokenUri = await internals.resolveProviderUri(request, settings.provider.token);
292
300
  try {
293
- var { res: tokenRes, payload } = await Wreck.post(settings.provider.token, requestOptions);
301
+ var { res: tokenRes, payload } = await Wreck.post(tokenUri, requestOptions);
294
302
  }
295
303
  catch (err) {
296
304
  return h.unauthenticated(Boom.internal('Failed obtaining ' + name + ' access token', err), { credentials });
@@ -360,7 +368,7 @@ exports.v2 = function (settings) {
360
368
  };
361
369
 
362
370
  try {
363
- await settings.provider.profile.call(settings, credentials, payload, get);
371
+ await settings.provider.profile.call(settings, credentials, payload, get, request);
364
372
  }
365
373
  catch (err) {
366
374
  return h.unauthenticated(err, { credentials });
@@ -729,8 +737,25 @@ internals.getProtocol = function (request, settings) {
729
737
  };
730
738
 
731
739
 
732
- internals.resolveProviderParams = function (request, params) {
740
+ internals.resolveProviderParams = async function (request, params) {
741
+
742
+ return (typeof params === 'function' ? await params(request) : params) ?? {};
743
+ };
744
+
745
+
746
+ internals.resolveProviderUri = async function (request, uri) {
747
+
748
+ return typeof uri === 'function' ? await uri(request) : uri;
749
+ };
750
+
751
+
752
+ internals.client = async function (request, settings) {
753
+
754
+ const provider = {
755
+ ...settings.provider,
756
+ temporary: await internals.resolveProviderUri(request, settings.provider.temporary),
757
+ token: await internals.resolveProviderUri(request, settings.provider.token)
758
+ };
733
759
 
734
- const obj = typeof params === 'function' ? params(request) : params;
735
- return obj ? Hoek.clone(obj) : {};
760
+ return new internals.Client({ ...settings, provider });
736
761
  };
@@ -25,7 +25,6 @@ exports = module.exports = {
25
25
  live: require('./live'),
26
26
  medium: require('./medium'),
27
27
  meetup: require('./meetup'),
28
- mixer: require('./mixer'),
29
28
  nest: require('./nest'),
30
29
  okta: require('./okta'),
31
30
  phabricator: require('./phabricator'),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hapi/bell",
3
3
  "description": "Third-party login plugin for hapi",
4
- "version": "13.0.2",
4
+ "version": "13.2.0",
5
5
  "repository": "git://github.com/hapijs/bell",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
@@ -51,21 +51,21 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@hapi/boom": "^10.0.1",
54
- "@hapi/bounce": "^3.0.1",
55
- "@hapi/cryptiles": "^6.0.1",
56
- "@hapi/hoek": "^11.0.2",
57
- "@hapi/wreck": "^18.0.1",
58
- "joi": "^17.7.1"
54
+ "@hapi/bounce": "^3.0.2",
55
+ "@hapi/cryptiles": "^6.0.3",
56
+ "@hapi/hoek": "^11.0.7",
57
+ "@hapi/wreck": "^18.1.2",
58
+ "joi": "^17.13.7"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@hapi/code": "^9.0.3",
62
- "@hapi/eslint-plugin": "*",
63
- "@hapi/hapi": "^21.2.1",
62
+ "@hapi/eslint-plugin": "^6.0.0",
63
+ "@hapi/hapi": "^21.4.10",
64
64
  "@hapi/hawk": "^8.0.0",
65
- "@hapi/lab": "^25.0.1",
66
- "@hapi/teamwork": "^6.0.0",
67
- "@types/node": "^20.11.30",
68
- "typescript": "^5.4.2"
65
+ "@hapi/lab": "^25.3.2",
66
+ "@hapi/teamwork": "^6.0.1",
67
+ "@types/node": "^20.19.43",
68
+ "typescript": "^6.0.3"
69
69
  },
70
70
  "scripts": {
71
71
  "test": "lab -a @hapi/code -t 100 -L -m 3000 -Y",
@@ -1,25 +0,0 @@
1
- 'use strict';
2
-
3
- const internals = {};
4
-
5
-
6
- exports = module.exports = function (options) {
7
-
8
- return {
9
- protocol: 'oauth2',
10
- useParamsAuth: true,
11
- auth: 'https://mixer.com/oauth/authorize',
12
- token: 'https://mixer.com/api/v1/oauth/token',
13
- scope: ['user:details:self'],
14
- scopeSeparator: ' ',
15
- profile: async function (credentials, params, get) {
16
-
17
- const queryOptions = {
18
- oauth_token: params.access_token
19
- };
20
-
21
- const profile = await get('https://mixer.com/api/v1/users/current', queryOptions);
22
- credentials.profile = profile;
23
- }
24
- };
25
- };