@aoberoi/passport-slack
Advanced tools
Comparing version 1.0.5 to 2.0.0-beta.1
@@ -1,86 +0,10 @@ | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
require('babel-polyfill'); | ||
var _passportOauth = require('passport-oauth2'); | ||
var _passportOauth2 = _interopRequireDefault(_passportOauth); | ||
var _lodash = require('lodash.defaults'); | ||
var _lodash2 = _interopRequireDefault(_lodash); | ||
var _lodash3 = require('lodash.pickby'); | ||
var _lodash4 = _interopRequireDefault(_lodash3); | ||
var _lodash5 = require('lodash.isfunction'); | ||
var _lodash6 = _interopRequireDefault(_lodash5); | ||
var _needle = require('needle'); | ||
var _needle2 = _interopRequireDefault(_needle); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } | ||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } | ||
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const passport_oauth2_1 = __importDefault(require("passport-oauth2")); // tslint:disable-line:import-name | ||
const needle_1 = __importDefault(require("needle")); | ||
const object_entries_1 = __importDefault(require("object.entries")); | ||
/** | ||
* Verify Wrapper | ||
* | ||
* Adapts the verify callback that the super class expects to the verify callback API this | ||
* strategy presents to the user. | ||
* @private | ||
* @param {Object} slackAuthOptions | ||
* @param {boolean} slackAuthOptions.passReqToCallback | ||
* @param {SlackStrategy~verifyCallback} slackAuthOptions.verify | ||
* @return {function} oauth2VerifyCallbackWithRequest | ||
*/ | ||
function wrapVerify(slackAuthOptions) { | ||
return function _verify(req, accessToken, refreshToken, params, profile, verified) { | ||
var team = { | ||
id: params.team_id || params.team && params.team.id | ||
}; | ||
var teamName = params.team_name || params.team && params.team.name; | ||
if (teamName) team.name = teamName; | ||
var scopes = new Set(params.scope.split(',')); | ||
var extra = {}; | ||
if (params.bot) { | ||
extra.bot = { | ||
id: params.bot.bot_user_id, | ||
accessToken: params.bot.bot_access_token | ||
}; | ||
} | ||
if (params.incoming_webhook) { | ||
extra.incomingWebhook = { | ||
url: params.incoming_webhook.url, | ||
channel: { | ||
name: params.incoming_webhook.channel | ||
}, | ||
configurationUrl: params.incoming_webhook.configuration_url | ||
}; | ||
if (params.incoming_webhook.channel_id) { | ||
extra.incomingWebhook.channel.id = params.incoming_webhook.channel_id; | ||
} | ||
} | ||
if (!slackAuthOptions.passReqToCallback) { | ||
slackAuthOptions.verify(accessToken, scopes, team, extra, profile, verified); | ||
} else { | ||
slackAuthOptions.verify(req, accessToken, scopes, team, extra, profile, verified); | ||
} | ||
}; | ||
} | ||
/** | ||
* Slack Authentication Passport Strategy | ||
@@ -91,198 +15,139 @@ * | ||
*/ | ||
var SlackStrategy = function (_OAuth2Strategy) { | ||
_inherits(SlackStrategy, _OAuth2Strategy); | ||
/** | ||
* Creates an instance of the SlackStrategy | ||
* @param {Object} options | ||
* @param {string} options.clientID - Your Slack App's client ID. | ||
* @param {string} options.clientSecret - Your Slack App's client secret. | ||
* @param {string} [options.callbackURL] - The default URL for your webserver to handle | ||
* authorization grants. You will typically use the `passport.authorize('slack')` middleware to | ||
* implement this route, which handles exchanging the authorization grant for an access token. | ||
* This can be overridden using options for `passport.authenticate()` or `passport.authorize()`. | ||
* @param {(string|Array<string>)} [options.scope=identity.basic] - The default scopes used for | ||
* authorization when the `passport.authenticate()` or `passport.authorize()` method options | ||
* don't specify. | ||
* @param {string} [options.team] - The default team for which your application will request | ||
* authorization. This can be overridden using options for `passporrt.authenticate()` or | ||
* `passport.authorize()`. | ||
* @param {boolean} [options.skipUserProfile=false] - Whether or not to retreive a response from | ||
* the `users.identity` Slack API method before invoking the verify callback. | ||
* @param {string} [options.tokenURL=https://slack.com/api/oauth.access] | ||
* @param {string} [options.authorizationURL=https://slack.com/oauth/authorize] | ||
* @param {string} [options.profileURL=https://slack.com/api/users.identity] | ||
* @param {Object} [options.customHeaders={}] - A dictionary of HTTP header names and values to | ||
* be used in all requests made to the Slack API from this Strategy. | ||
* @param {string} [options.name=slack] - The name for this strategy within passport. | ||
* @param {boolean} [options.passReqToCallback=false] - Set to true to give your verify callback | ||
* access to the incoming request. | ||
* @param {string} [options.scopeSeparator=,] | ||
* @param {string} [options.sessionKey] - The key for this strategy to use in a state store. | ||
* @param {Store} [options.store] - **TODO** | ||
* @param {boolean} [options.trustProxy] | ||
* @param {SlackStrategy~verifyCallback} verify - The callback that creates the value to be stored | ||
* in `req.user`, `req.account`, or the customized `options.assignProperty`. | ||
*/ | ||
function SlackStrategy(options, verify) { | ||
_classCallCheck(this, SlackStrategy); | ||
if (!options.clientSecret) { | ||
throw new TypeError('SlackStrategy requires a clientSecret option'); | ||
} | ||
if (!(0, _lodash6.default)(verify)) { | ||
throw new TypeError('SlackStrategy requires a verify callback'); | ||
} | ||
var mergedOptions = (0, _lodash2.default)(options || {}, { | ||
tokenURL: 'https://slack.com/api/oauth.access', | ||
authorizationURL: 'https://slack.com/oauth/authorize', | ||
profileURL: 'https://slack.com/api/users.identity', | ||
passReqToCallback: false, | ||
skipUserProfile: false, | ||
scope: 'identity.basic', | ||
scopeSeparator: ',' | ||
}); | ||
var slackAuthOptions = { | ||
passReqToCallback: mergedOptions.passReqToCallback, | ||
profileURL: mergedOptions.profileURL, | ||
team: mergedOptions.team, | ||
verify: verify | ||
}; | ||
// We saved the user's preference about whether to pass the request to the callback, and now to | ||
// simplify the implementation of wrapVerify, we tell the super class that we always want the | ||
// request passed to the callback. | ||
mergedOptions.passReqToCallback = true; | ||
if (!mergedOptions.skipUserProfile) { | ||
var scopes = mergedOptions.scope; | ||
if (!Array.isArray(mergedOptions.scope)) { | ||
scopes = [mergedOptions.scope]; | ||
} | ||
if (!scopes.includes('identity.basic')) { | ||
throw new TypeError('SlackStrategy cannot retrieve user profiles without \'identity.basic\' scope'); | ||
} | ||
} | ||
var _this = _possibleConstructorReturn(this, (SlackStrategy.__proto__ || Object.getPrototypeOf(SlackStrategy)).call(this, mergedOptions, wrapVerify(slackAuthOptions))); | ||
_this.name = mergedOptions.name || 'slack'; | ||
_this.slackAuthOptions = slackAuthOptions; | ||
return _this; | ||
} | ||
/** | ||
* Retrieve user and team profile from Slack | ||
* | ||
* @param {string} accessToken | ||
* @param {Function} done | ||
*/ | ||
_createClass(SlackStrategy, [{ | ||
key: 'userProfile', | ||
value: function userProfile(accessToken, done) { | ||
_needle2.default.request('get', this.slackAuthOptions.profileURL, { token: accessToken }, function (error, response, body) { | ||
// TODO: better errors | ||
if (error) { | ||
done(error); | ||
} else if (!body.ok) { | ||
done(new Error(body.error)); | ||
} else { | ||
// eslint-disable-next-line no-param-reassign | ||
delete body.ok; | ||
done(null, body); | ||
class SlackStrategy extends passport_oauth2_1.default { | ||
/** | ||
* Creates an instance of the SlackStrategy | ||
*/ | ||
constructor(options, verify) { | ||
if (!options.clientSecret) { | ||
throw new TypeError('SlackStrategy requires a clientSecret option'); | ||
} | ||
}); | ||
// Resolve options by merging in the defaults | ||
const resolvedOptions = Object.assign({ | ||
// These are defaults that this strategy provides which the super class does not | ||
tokenURL: 'https://slack.com/api/oauth.access', | ||
authorizationURL: 'https://slack.com/oauth/authorize', | ||
profileURL: 'https://slack.com/api/users.identity', | ||
name: 'slack', | ||
// Apply a default since the wrapVerify behavior depends on resolving this option | ||
passReqToCallback: false, | ||
// Apply a default since warning about a missing scope depends on this option | ||
skipUserProfile: false, | ||
// TODO: we might want to assign a new default value. | ||
// `identity.basic` is the scope needed to fetch the profile info for a user-token app (SIWS), but for Add to | ||
// Slack flows, you wouldn't typically want this scope. | ||
// in workspace apps the equivalent SIWS scope is `identity:read:user`. but again, its very rare that someone | ||
// trying to use the Add to Slack flow would want this scope. | ||
scope: 'identity.basic', | ||
}, options); | ||
// When a user profile is needed, ensure that the scope is set to one of the scopes that can be used for | ||
// `users.identity`, or that its an array that includes one of those scopes. | ||
if (!resolvedOptions.skipUserProfile && | ||
resolvedOptions.scope !== undefined && | ||
((typeof resolvedOptions.scope === 'string' && | ||
!(resolvedOptions.scope === 'identity.basic' || resolvedOptions.scope === 'identity:read:user')) || | ||
!(resolvedOptions.scope.includes('identity.basic') || resolvedOptions.scope.includes('identity:read:user')))) { | ||
throw new TypeError('SlackStrategy cannot retrieve user profiles without \'identity.basic\' scope'); | ||
} | ||
const overrideOptions = { passReqToCallback: true }; | ||
super(Object.assign({}, resolvedOptions, overrideOptions), wrapVerify(verify, resolvedOptions.passReqToCallback)); | ||
this.slack = { | ||
profileURL: resolvedOptions.profileURL, | ||
team: resolvedOptions.team, | ||
}; | ||
} | ||
/** | ||
* Return extra parameters to be included in the authorization request. | ||
* | ||
* @param {Object} options | ||
* @return {Object} | ||
* Retrieve user and team profile from Slack | ||
*/ | ||
}, { | ||
key: 'authorizationParams', | ||
value: function authorizationParams(options) { | ||
return (0, _lodash4.default)((0, _lodash2.default)({ | ||
team: options.team | ||
}, { | ||
team: this.slackAuthOptions.team | ||
})); | ||
userProfile(accessToken, done) { | ||
needle_1.default.request('get', this.slack.profileURL, { token: accessToken }, (error, _res, body) => { | ||
if (error) { | ||
done(error); | ||
} | ||
else if (!body.ok) { | ||
done(new Error(body.error)); | ||
} | ||
else { | ||
done(null, body); | ||
} | ||
}); | ||
} | ||
/** | ||
* A callback your application implements to create the value stored on an authenticated request | ||
* as `req.user`, `req.account` (when using the `passport.authorize()` flow), or the customized | ||
* `options.assignProperty` (from instantiaton a {@link SlackStrategy}). You must call the `done` | ||
* function with either an error as the first argument, or the result value as the second | ||
* argument. | ||
* | ||
* @typedef {Function} SlackStrategy~verifyCallback | ||
* @param {http.IncomingMessage} [req] - The HTTP Request. Not present by default. Only exists | ||
* if the `passReqToCallback` option was set when instantiating the strategy. | ||
* @param {string} accessToken - The authenticated user's access token. | ||
* @param {Set<string>} scopes - The set of scopes for which the accessToken is authorized. | ||
* @param {SlackStrategy~Team} team - The Slack Team for which the user has granted your | ||
* application access. | ||
* @param {Object} extra | ||
* @param {?SlackStrategy~BotAuthorization} extra.bot - Details for the authorized Bot User for | ||
* which the user has granted your application in the Slack Team (see scope `bot`). | ||
* @param {?SlackStrategy~IncomingWebhookAuthorization} extra.incomingWebhook - Details for the | ||
* authorized Incoming Webhook for which the user has granted your application in the Slack Team | ||
* (see scope `incoming-webhook`). | ||
* @param {?SlackStrategy~Profile} profile - The User and Team profiles (if they were requested). | ||
* @param {function(?error: Error, ?user: Object): void} done | ||
* Return extra parameters to be included in the authorization request. `state` and `redirect_url` are handled by | ||
* the super class. | ||
*/ | ||
/** | ||
* @typedef {Object} SlackStrategy~Team | ||
* @property {string} id | ||
* @property {?string} name - May not always be present, see scope `identity.team` | ||
*/ | ||
/** | ||
* @typedef {Object} SlackStrategy~BotAuthorization | ||
* @property {string} userId - The Bot User's user ID. | ||
* @property {string} accessToken - The Bot User's access token. | ||
*/ | ||
/** | ||
* @typedef {Object} SlackStrategy~IncomingWebhookAuthorization | ||
* @property {string} url - The URL where your application is authorized by the user to post | ||
* messages. | ||
* @property {string} configurationUrl - The URL where your application can direct a user to | ||
* configure your Incoming WebHook. | ||
* @property {SlackStrategy~Channel} channel - The Channel in the Slack Team where the user has | ||
* authorized your application to post messages. | ||
*/ | ||
/** | ||
* @typedef {Object} SlackStrategy~Profile | ||
* @property {Object} user - The representation of the User returned from the `users.identity` | ||
* Web API method. The actual contents depend on the scopes for which your access token is | ||
* authorized. See scopes `identity.basic`, `identity.email`, `identity.avatar`. | ||
* @property {Object} team - The representation of the Team returned from the `users.identity` | ||
* Web API method. The actual contents depends on the scopes for which your access token is | ||
* authorized. See scope `identity.team`. | ||
*/ | ||
// TODO: channel.id, is it in there or not? when might it not be? | ||
/** | ||
* @typedef {Object} SlackStrategy~Channel | ||
* @property {string} id | ||
* @property {string} name | ||
*/ | ||
/** | ||
* @external {http.IncomingMessage} https://nodejs.org/dist/latest-v6.x/docs/api/http.html#http_class_http_incomingmessage | ||
*/ | ||
}]); | ||
return SlackStrategy; | ||
}(_passportOauth2.default); | ||
exports.default = SlackStrategy; | ||
authorizationParams(options) { | ||
const team = options.team || this.slack.team; | ||
if (team !== undefined) { | ||
options.team = team; | ||
} | ||
return options; | ||
} | ||
} | ||
exports.SlackStrategy = SlackStrategy; | ||
/** | ||
* Verify Wrapper | ||
* | ||
* Adapts the verify callback that the super class expects to the verify callback API this strategy presents to the user | ||
*/ | ||
function wrapVerify(verify, passReqToCallback) { | ||
return function _verify(req, accessToken, refreshToken, results, // TODO: define some types for the oauth.access response shapes | ||
profile, verified) { | ||
const info = { | ||
access_token: accessToken, | ||
refresh_token: refreshToken, | ||
user: { | ||
// will be undefined for user-token apps that don't fetch the profile | ||
id: results.installer_user ? results.installer_user.user_id : (profile && profile.user && profile.user.id), | ||
name: profile !== undefined && profile.user !== undefined ? profile.user.id : undefined, | ||
}, | ||
team: { | ||
id: results.team_id || (results.team && results.team.id), | ||
name: results.team_name || (results.team && results.team.name), | ||
}, | ||
scopes: [], | ||
}; | ||
// Copy all user profile properties into the user | ||
if (profile !== undefined && profile.user !== undefined) { | ||
for (const [key, val] of object_entries_1.default(profile.user)) { | ||
if (info.user[key] === undefined) { | ||
info.user[key] = val; | ||
} | ||
} | ||
} | ||
// Build scopes info | ||
if (results.current_grant) { | ||
// in workspace apps, a structured object is returned for scopes | ||
info.scopes = results.current_grant.permissions.reduce((scopes, permission) => (scopes.concat(permission.scopes)), info.scopes); | ||
} | ||
else if (results.scope && typeof results.scope === 'string') { | ||
// in all other apps an array is returned, by splitting a string on the comma separator | ||
info.scopes = results.scope.split(','); | ||
} | ||
else { | ||
// TODO: log a warning | ||
} | ||
// TODO: in workspace apps, there's a whole bunch of very important properties that are not | ||
// being passed to the verification callback | ||
// installer_user, authorizing_user, app_id, app_user_id | ||
// Attach info related to bot user | ||
if (results.bot) { | ||
info.bot = { | ||
user_id: results.bot.bot_user_id, | ||
access_token: results.bot.bot_access_token, | ||
}; | ||
} | ||
// Attach info related to incoming webhook | ||
if (results.incoming_webhook) { | ||
info.incoming_webhook = results.incoming_webhook; | ||
} | ||
// Invoke the verify callback using the preference for having the req passed or not | ||
if (!passReqToCallback) { | ||
verify(info, verified); | ||
} | ||
else { | ||
verify(req, info, verified); | ||
} | ||
}; | ||
} | ||
exports.Strategy = SlackStrategy; // tslint:disable-line:variable-name | ||
//# sourceMappingURL=strategy.js.map |
{ | ||
"name": "@aoberoi/passport-slack", | ||
"version": "1.0.5", | ||
"version": "2.0.0-beta.1", | ||
"description": "Slack authentication strategy for Passport", | ||
"main": "dist/index.js", | ||
"main": "dist/strategy.js", | ||
"repository": "https://github.com/aoberoi/passport-slack.git", | ||
@@ -10,28 +10,29 @@ "author": "Ankur Oberoi <aoberoi@gmail.com>", | ||
"engines": { | ||
"node": ">=4.2.0" | ||
"node": ">=6" | ||
}, | ||
"files": [ | ||
"dist/**/*" | ||
], | ||
"scripts": { | ||
"build": "babel src -d dist", | ||
"lint": "tslint --project .", | ||
"build": "npm run build:clean && tsc", | ||
"build:clean": "shx rm -rf ./dist", | ||
"docs": "esdoc -c ./esdoc.json", | ||
"prepare": "npm run build" | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"dependencies": { | ||
"babel-polyfill": "^6.16.0", | ||
"lodash.defaults": "^4.2.0", | ||
"lodash.isfunction": "^3.0.8", | ||
"lodash.pickby": "^4.6.0", | ||
"needle": "^1.4.2", | ||
"passport-oauth2": "^1.3.0" | ||
"@types/express": "^4.16.0", | ||
"@types/needle": "^2.0.2", | ||
"@types/passport-oauth2": "^1.4.5", | ||
"needle": "^2.2.3", | ||
"object.entries": "^1.0.4", | ||
"passport-oauth2": "^1.4.0" | ||
}, | ||
"devDependencies": { | ||
"babel-cli": "^6.18.0", | ||
"babel-preset-es2015": "^6.18.0", | ||
"babel-preset-es2016": "^6.16.0", | ||
"esdoc": "^0.4.8", | ||
"eslint": "^3.9.0", | ||
"eslint-config-airbnb": "^12.0.0", | ||
"eslint-plugin-import": "^1.16.0", | ||
"eslint-plugin-jsx-a11y": "^2.2.3", | ||
"eslint-plugin-react": "^6.8.0" | ||
"esdoc": "^1.1.0", | ||
"shx": "^0.3.2", | ||
"tslint": "^5.11.0", | ||
"tslint-config-airbnb": "^5.11.0", | ||
"typescript": "^3.0.3" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
5
1
27656
6
358
3
+ Added@types/express@^4.16.0
+ Added@types/needle@^2.0.2
+ Addedobject.entries@^1.0.4
+ Added@types/body-parser@1.19.5(transitive)
+ Added@types/connect@3.4.38(transitive)
+ Added@types/express@4.17.21(transitive)
+ Added@types/express-serve-static-core@4.19.6(transitive)
+ Added@types/http-errors@2.0.4(transitive)
+ Added@types/mime@1.3.5(transitive)
+ Added@types/needle@2.5.3(transitive)
+ Added@types/node@22.10.2(transitive)
+ Added@types/oauth@0.9.6(transitive)
+ Added@types/passport@1.0.17(transitive)
+ Added@types/passport-oauth2@1.4.17(transitive)
+ Added@types/qs@6.9.17(transitive)
+ Added@types/range-parser@1.2.7(transitive)
+ Added@types/send@0.17.4(transitive)
+ Added@types/serve-static@1.15.7(transitive)
+ Addedcall-bind@1.0.8(transitive)
+ Addedcall-bind-apply-helpers@1.0.1(transitive)
+ Addeddebug@3.2.7(transitive)
+ Addeddefine-data-property@1.1.4(transitive)
+ Addeddefine-properties@1.2.1(transitive)
+ Addeddunder-proto@1.0.1(transitive)
+ Addedes-define-property@1.0.1(transitive)
+ Addedes-errors@1.3.0(transitive)
+ Addedes-object-atoms@1.0.0(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedget-intrinsic@1.2.6(transitive)
+ Addedgopd@1.2.0(transitive)
+ Addedhas-property-descriptors@1.0.2(transitive)
+ Addedhas-symbols@1.1.0(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedmath-intrinsics@1.1.0(transitive)
+ Addedms@2.1.3(transitive)
+ Addedneedle@2.9.1(transitive)
+ Addedobject-keys@1.1.1(transitive)
+ Addedobject.entries@1.1.8(transitive)
+ Addedsax@1.4.1(transitive)
+ Addedset-function-length@1.2.2(transitive)
+ Addedundici-types@6.20.0(transitive)
- Removedbabel-polyfill@^6.16.0
- Removedlodash.defaults@^4.2.0
- Removedlodash.isfunction@^3.0.8
- Removedlodash.pickby@^4.6.0
- Removedbabel-polyfill@6.26.0(transitive)
- Removedbabel-runtime@6.26.0(transitive)
- Removedcore-js@2.6.12(transitive)
- Removeddebug@2.6.9(transitive)
- Removedlodash.defaults@4.2.0(transitive)
- Removedlodash.isfunction@3.0.9(transitive)
- Removedlodash.pickby@4.6.0(transitive)
- Removedms@2.0.0(transitive)
- Removedneedle@1.6.0(transitive)
- Removedregenerator-runtime@0.10.50.11.1(transitive)
Updatedneedle@^2.2.3
Updatedpassport-oauth2@^1.4.0