Skip to content

Commit

Permalink
Merge pull request #360 from razee-io/cluster_subscription_gql
Browse files Browse the repository at this point in the history
Breaking: Enable GraphQL by default
  • Loading branch information
dalehille authored Jun 8, 2020
2 parents fd48203 + 4587931 commit 290933f
Show file tree
Hide file tree
Showing 44 changed files with 776 additions and 1,737 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,5 @@ dev/
# vscode settings
.vscode
.DS_Store

.nvmrc
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ script:
- if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then npx audit-ci --low; else npx audit-ci --low || true; fi
- npm run lint
- npm test
- npm run test:apollo:default
- npm run test:apollo:local
- npm run test:apollo:passport.local
- docker build --rm -t "quay.io/razee/razeedash-api:${TRAVIS_COMMIT}" .
Expand Down
11 changes: 2 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ Razeedash-API is the interface used by
| S3_BUCKET_PREFIX | no | 'razee'|
| ORG_ADMIN_KEY | no | n/a |
| ADD_CLUSTER_WEBHOOK_URL | no | n/a |
| ENABLE_GRAPHQL | no | false, [true , false] are supported |
| AUTH_MODEL | no | n/a, [local, passport.local] are supported |
| AUTH_MODEL | no | 'default' [default, local, passport.local] are supported |

If S3_ENDPOINT is defined then encrypted cluster YAML is stored in S3 otherwise
it will be stored in the mongoDB.
Expand Down Expand Up @@ -537,15 +536,9 @@ the augmented data to the resource.
The resource will have a new attribute `badges`. The badge will replace any
existing badge with the same webhook_id or if it does not exist, add to the array.

## Graphql for local development

We are still actively working on graphql improvements. By default the feature is
disabled. To enable [Apollo](https://www.apollographql.com/docs/apollo-server/)
based graphql server and test it on your local machine. (WARNING: do not enable
bellow for any production environment.)
## GraphQL for local development

```shell
export ENABLE_GRAPHQL=true
export AUTH_MODEL=local
```

Expand Down
28 changes: 17 additions & 11 deletions app/apollo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ const buildCommonApolloContext = async ({ models, req, res, connection, logger }
// populate req and req_id to apollo context
if (connection) {
const upgradeReq = connection.context.upgradeReq;
context = { req: upgradeReq, req_id: upgradeReq ? upgradeReq.id : undefined, ...context};
const apiKey = connection.context.orgKey;
const userToken = connection.context.userToken;
const orgId = connection.context.orgId;
context = { apiKey: apiKey, req: upgradeReq, req_id: upgradeReq ? upgradeReq.id : undefined, userToken, orgId, ...context };
} else if (req) {
context = { req, req_id: req.id, ...context};
}
Expand Down Expand Up @@ -124,21 +127,29 @@ const createApolloServer = () => {
},
subscriptions: {
path: GRAPHQL_PATH,
keepAlive: 10000,
onConnect: async (connectionParams, webSocket, context) => {
const req_id = webSocket.upgradeReq.id;

let orgKey, orgId;
if(connectionParams.headers && connectionParams.headers['razee-org-key']) {
orgKey = connectionParams.headers['razee-org-key'];
const org = await models.Organization.findOne({ orgKeys: orgKey });
orgId = org._id;
}

logger.trace({ req_id, connectionParams, context }, 'subscriptions:onConnect');
const me = await models.User.getMeFromConnectionParams(
connectionParams,
{req_id, models, logger, ...context},
);
const me = await models.User.getMeFromConnectionParams( connectionParams, {req_id, models, logger, ...context},);

logger.debug({ me }, 'subscriptions:onConnect upgradeReq getMe');
if (me === undefined) {
throw Error(
'Can not find the session for this subscription request.',
);
}

// add original upgrade request to the context
return { me, upgradeReq: webSocket.upgradeReq, logger };
return { me, upgradeReq: webSocket.upgradeReq, logger, orgKey, orgId };
},
onDisconnect: (webSocket, context) => {
logger.debug(
Expand All @@ -162,11 +173,6 @@ const stop = async (apollo) => {

const apollo = async (options = {}) => {

if (!process.env.AUTH_MODEL) {
logger.error('apollo server is enabled, however AUTH_MODEL is not defined.');
process.exit(1);
}

try {
const db = await connectDb(options.mongo_url);
const mongoUrls =
Expand Down
2 changes: 1 addition & 1 deletion app/apollo/init.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ const buildApolloContext = async ({ models, req, res, connection, logger }) => {
return { models, me: {}, logger };
};

module.exports = { initApp, buildApolloContext };
module.exports = { initApp, buildApolloContext };
2 changes: 1 addition & 1 deletion app/apollo/init.local.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ const buildApolloContext = async ({ models, req, res, connection, logger }) => {
return { models, me: {}, logger };
};

module.exports = { initApp, buildApolloContext };
module.exports = { initApp, buildApolloContext };
1 change: 0 additions & 1 deletion app/apollo/models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

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

const User = require('./user');
const Resource = require('./resource');
const ResourceSchema = require('./resource.schema');
Expand Down
8 changes: 4 additions & 4 deletions app/apollo/models/organization.default.schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

const mongoose = require('mongoose');
const { v4: uuid } = require('uuid');
const OrganizationLocalSchema = new mongoose.Schema({
const OrganizationDefaultSchema = new mongoose.Schema({
_id: {
type: String,
},
Expand Down Expand Up @@ -44,7 +44,7 @@ const OrganizationLocalSchema = new mongoose.Schema({
},
});

OrganizationLocalSchema.statics.getRegistrationUrl = async function(org_id, context) {
OrganizationDefaultSchema.statics.getRegistrationUrl = async function(org_id, context) {
context.logger.debug({org_id}, 'getRegistrationUrl enter');
const org = await this.findById(org_id);
const protocol = context.req ? context.req.protocol : 'http';
Expand All @@ -54,7 +54,7 @@ OrganizationLocalSchema.statics.getRegistrationUrl = async function(org_id, cont
};
};

OrganizationLocalSchema.statics.createLocalOrg = async function(args) {
OrganizationDefaultSchema.statics.createLocalOrg = async function(args) {
let org = await this.findOne({
name: args.name,
});
Expand All @@ -67,4 +67,4 @@ OrganizationLocalSchema.statics.createLocalOrg = async function(args) {
return org;
};

module.exports = OrganizationLocalSchema;
module.exports = OrganizationDefaultSchema;
146 changes: 87 additions & 59 deletions app/apollo/models/user.default.schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@

const bunyan = require('bunyan');
const mongoose = require('mongoose');
const { v4: uuid } = require('uuid');
const { AuthenticationError } = require('apollo-server');

const { ForbiddenError } = require('apollo-server');
const { AUTH_MODELS, AUTH_MODEL } = require('./const');
const { getBunyanConfig } = require('../../utils/bunyan');

const _ = require('lodash');

const logger = bunyan.createLogger(
getBunyanConfig('apollo/models/user.default.schema'),
);
Expand Down Expand Up @@ -70,78 +70,82 @@ const UserDefaultSchema = new mongoose.Schema({
},
});

UserDefaultSchema.statics.createUser = async function(models, args) {

const userId = args.userId || `${uuid()}`;
const profile = args.profile || null;
const services = args.services || { default: { username: null, email: null}};
const meta = args.meta || { orgs: []};

const user = await this.create({
_id: userId,
type: 'default',
profile,
services,
meta
});
return user;
};

UserDefaultSchema.statics.signUp = async (models, args, secret, context) => {
logger.debug({ req_id: context.req_id }, `default signUp ${args}`);
logger.warn(
{ req_id: context.req_id },
`Current authorization model ${AUTH_MODEL} does not support this option.`
);
throw new AuthenticationError(
`Current authorization model ${AUTH_MODEL} does not support this option.`,
);
};

UserDefaultSchema.statics.signIn = async (models, login, password, secret, context) => {
logger.debug({ req_id: context.req_id }, `default signIn ${login}`);
logger.warn(
{ req_id: context.req_id },
`Current authorization model ${AUTH_MODEL} does not support this option.`
);
throw new AuthenticationError(
`Current authorization model ${AUTH_MODEL} does not support this option.`,
);
};

UserDefaultSchema.statics.getMeFromRequest = async function(req, context) {
const userId = req.get('x-user-id');
const apiKey = req.get('x-api-key');
const {req_id, logger} = context;
logger.debug({ req_id }, `default getMeFromRequest ${userId}`);
const apiKey = req.get('x-api-key');
const orgKey = req.get('razee-org-key');

logger.debug({ req_id }, 'default getMeFromRequest');
if (AUTH_MODEL === AUTH_MODELS.DEFAULT) {
if (userId && apiKey) {
return { userId, apiKey };
}
let type = apiKey ? 'userToken': 'cluster';
return {apiKey, orgKey, type};
}
return null;
};

UserDefaultSchema.statics.getMeFromConnectionParams = async function(
connectionParams,
context
) {
UserDefaultSchema.statics.getMeFromConnectionParams = async function(connectionParams, context){
const {req_id, logger} = context;
logger.debug({ req_id }, `default getMeFromConnectionParams ${connectionParams}`);
logger.debug({ req_id, connectionParams }, 'default getMeFromConnectionParams');
if (AUTH_MODEL === AUTH_MODELS.DEFAULT) {
const obj = connectionParams['authorization'];
const obj = connectionParams.headers['razee-org-key'];
return obj;
}
return null;
};

UserDefaultSchema.statics.userTokenIsAuthorized = async function(me, orgId, action, type, attributes, context) {
const {req_id, models, logger} = context;
logger.debug({ req_id: req_id }, `default userTokenIsAuthorized ${action} ${type} ${attributes}`);

if (AUTH_MODEL === AUTH_MODELS.DEFAULT) {
const user = await this.findOne({ apiKey: me.apiKey }).lean();
if(!user) {
logger.error('A user was not found for this apiKey');
throw new ForbiddenError('user not found');
}
const orgName = user.profile.currentOrgName;
if(!orgName) {
logger.error('An org has not been set for this user');
throw new ForbiddenError('An org has not been set for this user');
}
const org = await models.Organization.findOne({ name: orgName }).lean();
if(!org || org._id !== orgId) {
logger.error('User is not authorized for this organization');
throw new ForbiddenError('user is not authorized');
}

logger.debug('user found using apiKey', user);
return user;
}
return false;
};

UserDefaultSchema.statics.isAuthorized = async function(me, orgId, action, type, attributes, req_id) {
logger.debug({ req_id: req_id },`default isAuthorized ${me} ${action} ${type} ${attributes}`);
logger.debug({ req_id: req_id },`default isAuthorized ${action} ${type} ${attributes}`);

if (AUTH_MODEL === AUTH_MODELS.DEFAULT) {
const user = await this.findOne({ apiKey: me.apiKey }).lean();
if(!user) {
logger.error('A user was not found for this apiKey');
throw new ForbiddenError('user not found');
}
logger.debug('user found using apiKey', user);
return user;
}
return false;
};

UserDefaultSchema.statics.isValidOrgKey = async function(models, me) {
logger.debug('default isValidOrgKey');
if (AUTH_MODEL === AUTH_MODELS.DEFAULT) {
const user = await this.findOne({ _id: me.userId, apiKey: me.apiKey }).lean();
if (user && user.meta && user.meta.orgs.length > 0) {
return orgId === user.meta.orgs[0]._id;

const org = await models.Organization.findOne({ orgKeys: me.orgKey }).lean();
if(!org) {
logger.error('An org was not found for this razee-org-key');
throw new ForbiddenError('org id was not found');
}
logger.debug('org found using orgKey');
return org;
}
return false;
};
Expand All @@ -164,6 +168,31 @@ UserDefaultSchema.statics.getOrgs = async function(models, me) {
return results;
};

UserDefaultSchema.statics.getOrg = async function(models, me) {
let org;
if (AUTH_MODEL === AUTH_MODELS.DEFAULT) {
org = await models.Organization.findOne({ orgKeys: me.orgKey }).lean();
}
return org;
};

UserDefaultSchema.statics.getBasicUsersByIds = async function(ids){
if(!ids || ids.length < 1){
return [];
}
var users = await this.find({ _id: { $in: ids } }, { }, { lean: 1 });
users = users.map((user)=>{
var _id = user._id;
var name = _.get(user, 'profile.name') || _.get(user, 'services.local.username') || _id;
return {
_id,
name,
};
});
users = _.keyBy(users, '_id');
return users;
};

UserDefaultSchema.methods.getId = async function() {
return this._id;
};
Expand All @@ -189,4 +218,3 @@ UserDefaultSchema.methods.getCurrentRole = async function() {
};

module.exports = UserDefaultSchema;

18 changes: 0 additions & 18 deletions app/apollo/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,6 @@ const mongoose = require('mongoose');

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

UserSchema.statics.getBasicUsersByIds = async function(ids){
if(!ids || ids.length < 1){
return [];
}
var users = await this.find({ _id: { $in: ids } }, { }, { lean: 1 });
users = users.map((user)=>{
var _id = user._id;
var name = _.get(user, 'profile.name') || _.get(user, 'services.local.username') || _id;
return {
_id,
name,
};
});
users = _.keyBy(users, '_id');
return users;
};

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

Expand Down
Loading

0 comments on commit 290933f

Please sign in to comment.