Skip to content

Commit

Permalink
added userToken functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
rmgraham committed May 11, 2020
1 parent 91ba2a5 commit 516e075
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 10 deletions.
21 changes: 14 additions & 7 deletions app/apollo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ const buildCommonApolloContext = async ({ models, req, res, connection, logger }
if (connection) {
const upgradeReq = connection.context.upgradeReq;
const apiKey = connection.context.orgKey;
context = { apiKey: apiKey, req: upgradeReq, req_id: upgradeReq ? upgradeReq.id : undefined, ...context };
const userToken = connection.context.userToken;
context = { apiKey: apiKey, req: upgradeReq, req_id: upgradeReq ? upgradeReq.id : undefined, userToken, ...context };
} else if (req) {
context = { req, req_id: req.id, ...context};
}
Expand Down Expand Up @@ -100,24 +101,30 @@ const createApolloServer = () => {
path: GRAPHQL_PATH,
onConnect: async (connectionParams, webSocket, context) => {
const req_id = webSocket.upgradeReq.id;

let orgKey;
if(connectionParams.headers && connectionParams.headers['razee-org-key']) {
orgKey = connectionParams.headers['razee-org-key'];
}
let userToken;
if(connectionParams.headers && connectionParams.headers['userToken']) {
userToken = connectionParams.headers['userToken'];
}

logger.trace({ req_id, connectionParams, context }, 'subscriptions:onConnect');
const me = await models.User.getMeFromConnectionParams(
connectionParams,
{req_id, models, logger, ...context},
{req_id, models, logger, orgKey, userToken, ...context},
);
logger.debug({ me }, 'subscriptions:onConnect upgradeReq getMe');
if (me === undefined) {
throw Error(
'Can not find the session for this subscription request.',
);
}
let orgKey;
if(connectionParams.headers && connectionParams.headers['razee-org-key']) {
orgKey = connectionParams.headers['razee-org-key'];
}

// add original upgrade request to the context
return { me, upgradeReq: webSocket.upgradeReq, logger, orgKey };
return { me, upgradeReq: webSocket.upgradeReq, logger, orgKey, userToken };
},
onDisconnect: (webSocket, context) => {
logger.debug(
Expand Down
9 changes: 7 additions & 2 deletions app/apollo/models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@

const bunyan = require('bunyan');
const mongoose = require('mongoose');
const _ = require('lodash');

const User = require('./user');
module.exports = {};

const { User } = require('./user');
const Resource = require('./resource');
const ResourceSchema = require('./resource.schema');
const Cluster = require('./cluster');
Expand Down Expand Up @@ -147,4 +150,6 @@ async function setupDistributedCollections(mongoUrlsString) {
});
}

module.exports = { models, connectDb, setupDistributedCollections, closeDistributedConnections };
_.assign(module.exports, {
models, connectDb, setupDistributedCollections, closeDistributedConnections,
});
4 changes: 4 additions & 0 deletions app/apollo/models/user.default.schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ UserDefaultSchema.statics.getMeFromConnectionParams = async function(
return null;
};

UserDefaultSchema.statics.userTokenIsAuthorized = async function(me, orgId, action, type, attributes, context) {
return this.isAuthorized(me.user, orgId, action, type, attributes, context);
};

UserDefaultSchema.statics.isAuthorized = async function(me, orgId, action, type, attributes, req_id) {
logger.debug({ req_id: req_id },`default isAuthorized ${me} ${action} ${type} ${attributes}`);
if (AUTH_MODEL === AUTH_MODELS.DEFAULT) {
Expand Down
66 changes: 65 additions & 1 deletion app/apollo/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,75 @@
*/

const mongoose = require('mongoose');
const jwt = require('jsonwebtoken');
const { AuthenticationError } = require('apollo-server');

const { AUTH_MODEL } = require('./const');
const UserSchema = require(`./user.${AUTH_MODEL}.schema`);
const _ = require('lodash');

const loadMeFromUserToken = async function(userToken, models){
let obj, userId, orgId;
try {
obj = jwt.decode(userToken);
userId = obj.userId;
orgId = obj.orgId;
}catch(err){
throw new AuthenticationError('Failed to parse userToken');
}
if(!userId){
throw new AuthenticationError('No user id found in userToken');
}
const user = await this.findById(userId, {}, { lean:true });
if(!user){
throw new AuthenticationError('No user found for userToken');
}
const org = await models.Organization.findById(orgId);
if(!org){
throw new AuthenticationError('No org found for userToken');
}
const hasVerifiedToken = _.some(org.orgKeys, (orgKey)=>{
try{
jwt.verify(userToken, orgKey);
return true;
}
catch(err){
return false;
}
});
if(!hasVerifiedToken){
throw new AuthenticationError('userToken could not be verified');
}
return {
type: 'userToken',
user,
};
};

const getMeFromConnectionParamsBase = UserSchema.statics.getMeFromConnectionParams;
UserSchema.statics.getMeFromRequest = async function(...args){
const [req, {models, req_id, logger}] = args;
const userToken = req.get('userToken');

if(userToken){
return await loadMeFromUserToken.bind(this)(userToken, models);
}

return await getMeFromConnectionParamsBase.bind(this)(...args);
};

const getMeFromRequestBase = UserSchema.statics.getMeFromRequest;
UserSchema.statics.getMeFromRequest = async function(...args){
const [req, {models, req_id, logger}] = args;
const userToken = req.get('userToken');

if(userToken){
return await loadMeFromUserToken.bind(this)(userToken, models);
}

return await getMeFromRequestBase.bind(this)(...args);
};

UserSchema.statics.getBasicUsersByIds = async function(ids){
if(!ids || ids.length < 1){
return [];
Expand All @@ -39,4 +103,4 @@ UserSchema.statics.getBasicUsersByIds = async function(ids){

const User = mongoose.model('users', UserSchema);

module.exports = User;
module.exports = { User };
6 changes: 6 additions & 0 deletions app/apollo/models/user.local.schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ UserLocalSchema.statics.getMeFromConnectionParams = async function(
return null;
};



UserLocalSchema.statics.userTokenIsAuthorized = async function(me, orgId, action, type, attributes, context) {
return this.isAuthorized(me.user, orgId, action, type, attributes, context);
};

UserLocalSchema.statics.isAuthorized = async function(me, orgId, action, type, attributes, context) {
const { req_id, logger } = context;
logger.debug({ req_id },`local isAuthorized ${me} ${action} ${type} ${attributes}`);
Expand Down
4 changes: 4 additions & 0 deletions app/apollo/models/user.passport.local.schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ UserPassportLocalSchema.statics.getMeFromConnectionParams = async function(
return null;
};

UserPassportLocalSchema.statics.userTokenIsAuthorized = async function(me, orgId, action, type, attributes, context) {
return this.isAuthorized(me.user, orgId, action, type, attributes, context);
};

UserPassportLocalSchema.statics.isAuthorized = async function(me, orgId, action, type, attributes, context) {
const { req_id, logger } = context;
logger.debug({req_id}, `passport.local isAuthorized ${me} ${action} ${type} ${attributes}`);
Expand Down
11 changes: 11 additions & 0 deletions app/apollo/resolvers/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ const whoIs = me => {
// Throw exception if not.
const validAuth = async (me, org_id, action, type, queryName, context) => {
const {req_id, models, logger} = context;

if(me && me.type == 'userToken'){
const result = await models.User.userTokenIsAuthorized(me, org_id, action, type, null, context);
if(!result){
throw new AuthenticationError(
`You are not allowed to ${action} on ${type} under organization ${org_id} for the query ${queryName}. (using userToken)`,
);
}
return;
}

if (me === null || !(await models.User.isAuthorized(me, org_id, action, type, null, context))) {
logger.error({req_id, me: whoIs(me), org_id, action, type}, `AuthenticationError - ${queryName}`);
throw new AuthenticationError(
Expand Down

0 comments on commit 516e075

Please sign in to comment.