Merge pull request #647 from coralproject/auth-token-hooks

Added new tokenUserNotFound plugin hook
This commit is contained in:
David Erwin
2017-06-05 18:05:56 -04:00
committed by GitHub
3 changed files with 100 additions and 3 deletions
+35
View File
@@ -280,6 +280,41 @@ send data to the client. If the type in question contains args, clients may subs
For more information, see the [Apollo Docs](https://github.com/apollographql/graphql-subscriptions).
#### Field: `tokenUserNotFound`
```js
tokenUserNotFound: async ({jwt, token}) => {
let profile = await someExternalService(token);
if (!profile) {
return null;
}
let user = await UserModel.findOneAndUpdate({
id: profile.id
}, {
id: profile.id,
username: profile.username,
lowercaseUsername: profile.username.toLowerCase(),
roles: [],
profiles: []
}, {
setDefaultsOnInsert: true,
new: true,
upsert: true
});
return user;
}
```
The `tokenUserNotFound` hook allows auth integrations to hook into the event
when a valid token is provided but a user can't be found in the database that
matches the provided id.
The function is async, and should return the user object that was created in the
database, or null if the user wasn't found. The `jwt` paramenter of the object
is the unpacked token, while `token` is the original jwt token string.
#### Field: `router`
```js
+19 -3
View File
@@ -172,6 +172,7 @@ const CheckBlacklisted = (jwt) => new Promise((resolve, reject) => {
});
});
const jwt = require('jsonwebtoken');
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
@@ -185,6 +186,19 @@ let cookieExtractor = function(req) {
return token;
};
// Override the JwtVerifier method on the JwtStrategy so we can pack the
// original token into the payload.
JwtStrategy.JwtVerifier = (token, secretOrKey, options, callback) => {
return jwt.verify(token, secretOrKey, options, (err, jwt) => {
if (err) {
return callback(err);
}
// Attach the original token onto the payload.
return callback(false, {token, jwt});
});
};
// Extract the JWT from the 'Authorization' header with the 'Bearer' scheme.
passport.use(new JwtStrategy({
@@ -207,10 +221,10 @@ passport.use(new JwtStrategy({
// Enable only the HS256 algorithm.
algorithms: ['HS256'],
// Pass the request objecto back to the callback so we can attach the JWT to
// Pass the request object back to the callback so we can attach the JWT to
// it.
passReqToCallback: true
}, async (req, jwt, done) => {
}, async (req, {token, jwt}, done) => {
// Load the user from the environment, because we just got a user from the
// header.
@@ -219,7 +233,9 @@ passport.use(new JwtStrategy({
// Check to see if the token has been revoked
await CheckBlacklisted(jwt);
let user = await UsersService.findById(jwt.sub);
// Try to get the user from the database or crack it from the token and
// plugin integrations.
let user = await UsersService.findOrCreateByIDToken(jwt.sub, {token, jwt});
// Attach the JWT to the request.
req.jwt = jwt;
+46
View File
@@ -9,6 +9,7 @@ const {
JWT_SECRET,
ROOT_URL
} = require('../config');
const debug = require('debug')('talk:services:users');
const redis = require('./redis');
const redisClient = redis.createClient();
@@ -526,6 +527,29 @@ module.exports = class UsersService {
return UserModel.findOne({id});
}
/**
*
* @param {String} id the id of the current user
* @param {Object} token a jwt token used to sign in the user
*/
static async findOrCreateByIDToken(id, token) {
// Try to get the user.
let user = await UserModel.findOne({
id
});
// If the user was not found, try to look it up.
if (user === null) {
// If the user wasn't found, it will return null and the variable will be
// unchanged.
user = await lookupUserNotFound(token);
}
return user;
}
/**
* Finds users in an array of ids.
* @param {Array} ids array of user identifiers (uuid)
@@ -903,3 +927,25 @@ module.exports = class UsersService {
});
}
};
// Extract all the tokenUserNotFound plugins so we can integrate with other
// providers.
const tokenUserNotFoundHooks = require('./plugins')
.get('server', 'tokenUserNotFound')
.map(({plugin, tokenUserNotFound}) => {
debug(`added plugin '${plugin.name}' to tokenUserNotFound hooks`);
return tokenUserNotFound;
});
// Provide a function that
const lookupUserNotFound = async (token) => {
for (let hook of tokenUserNotFoundHooks) {
let user = await hook(token);
if (user !== null && typeof user !== 'undefined') {
return user;
}
}
return null;
};