Skip to content

Commit

Permalink
Support for multiple distinct instances, as in Passport itself (#186)
Browse files Browse the repository at this point in the history
  • Loading branch information
boutell authored Apr 15, 2023
1 parent 8e07bfb commit b8e5e06
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 191 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased

### Added

- Added a new named export `AuthTokenRefresh`. This is a
constructor that can be invoked to create a distinct instance, for
applications that require more than one Passport instance.

## [2.1.0] - 2021-06-29

### Added
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,23 @@ const extraParams = { some: 'extra_param' };
refresh.requestNewAccessToken('gmail', 'some_refresh_token', extraParams, done);
```

### Multiple instances

Projects that need multiple instances of Passport can construct them using the `Passport`
constructor available on the `passport` module. Similarly, this module provides
an `AuthTokenRefresh` constructor that can be used instead of the single instance provided
by default.

```javascript
const { Passport } = require('passport');
const { AuthTokenRefresh } = require('passport-oauth2-refresh');

const passport = new Passport();
const refresh = new AuthTokenRefresh();

// Additional, distinct instances of these modules can also be created
```

## Examples

- See [issue #1](https://github.com/fiznool/passport-oauth2-refresh/issues/1) for an example of how to refresh a token when requesting data from the Google APIs.
Expand Down
262 changes: 131 additions & 131 deletions lib/refresh.js
Original file line number Diff line number Diff line change
@@ -1,142 +1,142 @@
'use strict';

const AuthTokenRefresh = {};

AuthTokenRefresh._strategies = {};

/**
* Register a passport strategy so it can refresh an access token,
* with optional `name`, overridding the strategy's default name.
*
* A third optional options parameter is available, which can be
* used to create a custom OAuth2 adapter, or modify the one
* which is automatically created. This is useful if the
* strategy does not expose its internal OAuth2 adapter, or
* customizes the adapter in some way that needs to be replicated.
*
* Examples:
*
* refresh.use(strategy);
* refresh.use('facebook', strategy);
* refresh.use('activedirectory', strategy, {
* setRefreshOAuth2() {
* return new OAuth2(...);
* }
* });
*
* @param {String|Strategy} name
* @param {Strategy} passport strategy
* @param {Object} options
* @param {OAuth2} options.setRefreshOAuth2 a callback to modify the oauth2 adapter. Should return the oauth2 adapter to use when refreshing the token.
*/
AuthTokenRefresh.use = function (name, strategy, options) {
if (typeof name !== 'string') {
// Infer name from strategy
options = strategy;
strategy = name;
name = strategy && strategy.name;
class AuthTokenRefresh {
constructor() {
this._strategies = {};
}

if (strategy == null) {
throw new Error('Cannot register: strategy is null');
}

if (!name) {
throw new Error(
'Cannot register: name must be specified, or strategy must include name',
);
}

options = options || {};

let refreshOAuth2 = undefined;

if (strategy._oauth2) {
// Try to use the internal oauth2 adapter, setting some sane defaults.
// Use the strategy's OAuth2 object, since it might have been overwritten.
// https://github.com/fiznool/passport-oauth2-refresh/issues/3
const OAuth2 = strategy._oauth2.constructor;

// Generate our own oauth2 object for use later.
// Use the strategy's _refreshURL, if defined,
// otherwise use the regular accessTokenUrl.
refreshOAuth2 = new OAuth2(
strategy._oauth2._clientId,
strategy._oauth2._clientSecret,
strategy._oauth2._baseSite,
strategy._oauth2._authorizeUrl,
strategy._refreshURL || strategy._oauth2._accessTokenUrl,
strategy._oauth2._customHeaders,
);

// Some strategies overwrite the getOAuthAccessToken function to set headers
// https://github.com/fiznool/passport-oauth2-refresh/issues/10
refreshOAuth2.getOAuthAccessToken = strategy._oauth2.getOAuthAccessToken;
}

// See if we need to customise the OAuth2 object any further
if (typeof options.setRefreshOAuth2 === 'function') {
refreshOAuth2 = options.setRefreshOAuth2({
strategyOAuth2: strategy._oauth2,
/**
* Register a passport strategy so it can refresh an access token,
* with optional `name`, overridding the strategy's default name.
*
* A third optional options parameter is available, which can be
* used to create a custom OAuth2 adapter, or modify the one
* which is automatically created. This is useful if the
* strategy does not expose its internal OAuth2 adapter, or
* customizes the adapter in some way that needs to be replicated.
*
* Examples:
*
* refresh.use(strategy);
* refresh.use('facebook', strategy);
* refresh.use('activedirectory', strategy, {
* setRefreshOAuth2() {
* return new OAuth2(...);
* }
* });
*
* @param {String|Strategy} name
* @param {Strategy} passport strategy
* @param {Object} options
* @param {OAuth2} options.setRefreshOAuth2 a callback to modify the oauth2 adapter. Should return the oauth2 adapter to use when refreshing the token.
*/
use(name, strategy, options) {
if (typeof name !== 'string') {
// Infer name from strategy
options = strategy;
strategy = name;
name = strategy && strategy.name;
}

if (strategy == null) {
throw new Error('Cannot register: strategy is null');
}

if (!name) {
throw new Error(
'Cannot register: name must be specified, or strategy must include name',
);
}

options = options || {};

let refreshOAuth2 = undefined;

if (strategy._oauth2) {
// Try to use the internal oauth2 adapter, setting some sane defaults.
// Use the strategy's OAuth2 object, since it might have been overwritten.
// https://github.com/fiznool/passport-oauth2-refresh/issues/3
const OAuth2 = strategy._oauth2.constructor;

// Generate our own oauth2 object for use later.
// Use the strategy's _refreshURL, if defined,
// otherwise use the regular accessTokenUrl.
refreshOAuth2 = new OAuth2(
strategy._oauth2._clientId,
strategy._oauth2._clientSecret,
strategy._oauth2._baseSite,
strategy._oauth2._authorizeUrl,
strategy._refreshURL || strategy._oauth2._accessTokenUrl,
strategy._oauth2._customHeaders,
);

// Some strategies overwrite the getOAuthAccessToken function to set headers
// https://github.com/fiznool/passport-oauth2-refresh/issues/10
refreshOAuth2.getOAuthAccessToken = strategy._oauth2.getOAuthAccessToken;
}

// See if we need to customise the OAuth2 object any further
if (typeof options.setRefreshOAuth2 === 'function') {
refreshOAuth2 = options.setRefreshOAuth2({
strategyOAuth2: strategy._oauth2,
refreshOAuth2,
});
}

if (!refreshOAuth2) {
throw new Error(
'The OAuth2 adapter used to refresh the token is not configured correctly. Use the setRefreshOAuth2 option to return a OAuth 2.0 adapter.',
);
}

// Set the strategy and oauth2 adapter for use later
this._strategies[name] = {
strategy,
refreshOAuth2,
});
};
}

if (!refreshOAuth2) {
throw new Error(
'The OAuth2 adapter used to refresh the token is not configured correctly. Use the setRefreshOAuth2 option to return a OAuth 2.0 adapter.',
);
/**
* Check if a strategy is registered for refreshing.
* @param {String} name Strategy name
* @return {Boolean}
*/
has(name) {
return !!this._strategies[name];
}

// Set the strategy and oauth2 adapter for use later
AuthTokenRefresh._strategies[name] = {
strategy,
refreshOAuth2,
};
};

/**
* Check if a strategy is registered for refreshing.
* @param {String} name Strategy name
* @return {Boolean}
*/
AuthTokenRefresh.has = function (name) {
return !!AuthTokenRefresh._strategies[name];
};

/**
* Request a new access token, using the passed refreshToken,
* for the given strategy.
* @param {String} name Strategy name. Must have already
* been registered.
* @param {String} refreshToken Refresh token to be sent to request
* a new access token.
* @param {Object} params (optional) an object containing additional
* params to use when requesting the token.
* @param {Function} done Callback when all is done.
*/
AuthTokenRefresh.requestNewAccessToken = function (
name,
refreshToken,
params,
done,
) {
if (arguments.length === 3) {
done = params;
params = {};
/**
* Request a new access token, using the passed refreshToken,
* for the given strategy.
* @param {String} name Strategy name. Must have already
* been registered.
* @param {String} refreshToken Refresh token to be sent to request
* a new access token.
* @param {Object} params (optional) an object containing additional
* params to use when requesting the token.
* @param {Function} done Callback when all is done.
*/
requestNewAccessToken(name, refreshToken, params, done) {
if (arguments.length === 3) {
done = params;
params = {};
}

// Send a request to refresh an access token, and call the passed
// callback with the result.
const strategy = this._strategies[name];
if (!strategy) {
return done(new Error('Strategy was not registered to refresh a token'));
}

params = params || {};
params.grant_type = 'refresh_token';

strategy.refreshOAuth2.getOAuthAccessToken(refreshToken, params, done);
}
}

// Send a request to refresh an access token, and call the passed
// callback with the result.
const strategy = AuthTokenRefresh._strategies[name];
if (!strategy) {
return done(new Error('Strategy was not registered to refresh a token'));
}

params = params || {};
params.grant_type = 'refresh_token';

strategy.refreshOAuth2.getOAuthAccessToken(refreshToken, params, done);
};
// Default export, for typical case & backwards compatibility
module.exports = new AuthTokenRefresh();

module.exports = AuthTokenRefresh;
// Export a constructor, just like Passport does
module.exports.AuthTokenRefresh = AuthTokenRefresh;
Loading

0 comments on commit b8e5e06

Please sign in to comment.