Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Github Integration #42

Open
wants to merge 37 commits into
base: integrations
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0fc5993
feat: [WIP] Github Integration
souravdasslg Dec 9, 2019
282b544
feat: [WIP] Github Integration
souravdasslg Dec 9, 2019
5b135d3
Created Webhook Route For Slack
souravdasslg Dec 9, 2019
722e3c0
WIP Github Callback
souravdasslg Dec 9, 2019
bfc6508
feat: Github Authorization Implemented
souravdasslg Dec 10, 2019
9164cca
Merge branch 'sourav-integration-github' of https://github.com/pesto-…
souravdasslg Dec 10, 2019
5313cd2
feat: [WIP] Github Integration
souravdasslg Dec 9, 2019
6f2d709
Created Webhook Route For Slack
souravdasslg Dec 9, 2019
1c79cd6
WIP Github Callback
souravdasslg Dec 9, 2019
93e3b40
feat: Github Authorization Implemented
souravdasslg Dec 10, 2019
e65d214
Working trigger and action workflow
RakeshUP Dec 10, 2019
6f819eb
APIs for create relay page
RakeshUP Dec 10, 2019
bdb1751
Added authentication middleware for specific routes
RakeshUP Dec 11, 2019
03eebd8
fix: Github authorization will take auth token as identifier
souravdasslg Dec 11, 2019
ff74f67
Merge branch 'sourav-integration-github' of https://github.com/pesto-…
souravdasslg Dec 11, 2019
3c54e27
feat: [WIP] Github Integration
souravdasslg Dec 9, 2019
f705c3c
Created Webhook Route For Slack
souravdasslg Dec 9, 2019
4cf8875
WIP Github Callback
souravdasslg Dec 9, 2019
3d90142
feat: Github Authorization Implemented
souravdasslg Dec 10, 2019
50c1be1
fix: Github authorization will take auth token as identifier
souravdasslg Dec 11, 2019
fadaa57
Created Webhook Route For Slack
souravdasslg Dec 9, 2019
714c728
WIP Github Callback
souravdasslg Dec 9, 2019
3fe159e
feat: Github Authorization Implemented
souravdasslg Dec 10, 2019
566c10c
Merge branch 'sourav-integration-github' of https://github.com/pesto-…
souravdasslg Dec 11, 2019
b85d4cc
Removed Inputs model, changes still in progress
RakeshUP Dec 11, 2019
cafa608
Added endpoints to edit and delete relays
RakeshUP Dec 11, 2019
e0626a9
feat: [WIP] Github Integration
souravdasslg Dec 9, 2019
8075fa2
Created Webhook Route For Slack
souravdasslg Dec 9, 2019
c084a09
WIP Github Callback
souravdasslg Dec 9, 2019
8d6bac9
feat: Github Authorization Implemented
souravdasslg Dec 10, 2019
8ba427c
fix: Github authorization will take auth token as identifier
souravdasslg Dec 11, 2019
4bfe9de
Created Webhook Route For Slack
souravdasslg Dec 9, 2019
2945963
WIP Github Callback
souravdasslg Dec 9, 2019
9859e7c
feat: Github Authorization Implemented
souravdasslg Dec 10, 2019
baa4d77
WIP Github Callback
souravdasslg Dec 9, 2019
eb61325
feat: Github Authorization Implemented
souravdasslg Dec 10, 2019
b3f8c7c
Merge branch 'sourav-integration-github' of https://github.com/pesto-…
souravdasslg Dec 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/favicon.ico
Binary file not shown.
Binary file added assets/slackIcon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,22 @@ import cors from 'cors';
import logger from './utils/logger';
import authorize from './thirdparty/routes/authorize';
import slackRouter from './thirdparty/routes/slackRouter';
import githubRouter from './thirdparty/routes/githubRouter';
import IntegrationService from './controller/IntegrationService';
import AuthenticationMiddleware from './middlewares/authentication';

dotenv.config();

const app = express();
const modelDirPath = path.join(__dirname, 'models');
const routeDirPath = path.join(__dirname, 'routes');
const publicFolder = path.join(__dirname, '../assets');

app.use(pino());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(publicFolder));
// app.use('/api/v1', AuthenticationMiddleware);
app.set('view engine', 'ejs');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you need a view engine?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as we are serving our OAuth pages from the backend server. Also, the authorization callbacks are directly handled by our backend. :)

app.set('views', path.join(__dirname, '/thirdparty/views'));
requireAll(modelDirPath);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all require should happen before any logic. Ideally, they should be the first thing

Expand All @@ -37,6 +41,7 @@ for (const route in routes) {
// authorize router
app.use('/', authorize);
app.use(slackRouter.path, slackRouter.router);
app.use(githubRouter.path, githubRouter.router);

// catch 404 and forward to error handler
app.use((req, res, next) => {
Expand Down
16 changes: 10 additions & 6 deletions server/controller/ActionPerformer.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import SlackActionPerformer from '../thirdparty/controllers/SlackActionPerformer';

const ActionPerformers = {};
ActionPerformers[SlackActionPerformer.appName] = SlackActionPerformer.performer;
import eventEmitter from '../lib/eventsLib';
import slackActionPerformer from '../thirdparty/controllers/SlackActionPerformer';

const ActionPerformer = async (relay, triggerEvent) => {
const actionsToPerform = relay.participantApps.slice(1);
for (const action of actionsToPerform) {
if (action.eventType === 'Action') {
ActionPerformer[action.appName](relay, action, triggerEvent);
const correspondingActionPerformer = `${action.appName.toLowerCase()}ActionPerformer`;
eventEmitter.emit(correspondingActionPerformer, relay, action, triggerEvent);
}
}
};

export default ActionPerformer;
const exports = {
ActionPerformer,
slackActionPerformer,
};

export default exports;
59 changes: 48 additions & 11 deletions server/controller/AppsController.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,57 @@
import mongoose from 'mongoose';
import responseLib from '../lib/responseLib';
import * as actionStatus from '../constants/actionStatus';
import IntegrationConfig from '../thirdparty/integrationConfig';
import AuthorizedApps from '../models/AuthorizedApps';

const AppsCollection = mongoose.model('Apps');
const getApps = (req, res) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add some line breaks, it is not readable

const appsAndEvents = [];
for (const [appName, appData] of Object.entries(IntegrationConfig)) {
const singleAppDetails = { Trigger: [], Action: [] };
singleAppDetails.appName = appName;
singleAppDetails.icon = appData.icon_path;
for (const event of appData.Events) {
singleAppDetails[event.EventType].push(event.EventName);
}
appsAndEvents.push(singleAppDetails);
}
// const AppsCollection = mongoose.model('Apps');

const createApp = async (req, res) => {
console.log(req.body);
const createdApp = await AppsCollection.create(req.body);
res.send(createdApp);
const generatedResponse = responseLib.generateResponse(false, actionStatus.SUCCESS,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pass in an object with values instead of multiple params. It's easier to update/ changes params later.
Also the code is more readable

'Apps and Events', appsAndEvents);
res.send(generatedResponse);
};

const getEventDetails = (req, res) => {
let eventDetails;
for (const event of IntegrationConfig[req.params.appName].Events) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if IntegrationConfig[req.params.appName].Events is undefined/ null?
What is appName is not present in params. The server will crash. Please handle the errors

if (event.EventName === req.query.eventName) {
eventDetails = event;
}
}

const generatedResponse = responseLib.generateResponse(false, actionStatus.SUCCESS,
'Event Details', eventDetails);
res.send(generatedResponse);
};
const getAllApp = async (req, res) => {
const fetchedAppList = await AppsCollection.find({});
const generatedResponse = responseLib.generateResponse(false, actionStatus.SUCCESS, 'List Generated', fetchedAppList);

const getAuthorizedAccounts = async (req, res) => {
const authorizedAccountsForApp = await AuthorizedApps.find({
userId: req.userId,
appName: req.params.appName,
}).select({
_id: 0,
appName: 1,
email: 1,
credentials: 1,
});

const generatedResponse = responseLib.generateResponse(false, actionStatus.SUCCESS,
'Authorized accounts for app', authorizedAccountsForApp);
res.send(generatedResponse);
};

module.exports = {
createApp,
getAllApp,
getApps,
getEventDetails,
getAuthorizedAccounts,
};
8 changes: 8 additions & 0 deletions server/controller/IntegrationService.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import eventEmitter from '../lib/eventsLib';
import RelayController from './RelayController';
import IntegrationConfig from '../thirdparty/integrationConfig';
import WebHookListeners from './WebHookListeners';
import ActionPerformer from './ActionPerformer';

const installedListeners = [];

Expand All @@ -21,6 +22,13 @@ const InitializeIntegrationService = async () => {
installedListeners.push(correspondingListener);
}
}
if (eventData.EventType === 'Action') {
const ActionPerformerName = `${appName.toLowerCase()}ActionPerformer`;
if (installedListeners.indexOf(ActionPerformerName) === -1) {
eventEmitter.on(ActionPerformerName, ActionPerformer[ActionPerformerName]);
installedListeners.push(ActionPerformerName);
}
}
}
}
};
Expand Down
95 changes: 94 additions & 1 deletion server/controller/RelayController.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable no-underscore-dangle */

import response from '../lib/responseLib';
import * as actionStatus from '../constants/actionStatus';
import RelayCollection from '../models/Relays';
import AuthorizedApps from '../models/AuthorizedApps';

const getCriteria = (criteria) => {
const filter = {};
Expand Down Expand Up @@ -44,14 +47,104 @@ const getRunningRelaysWithTriggerApp = async (appName) => {
const getRelays = async (req, res) => {
const filter = getCriteria(req.query);
// filter.userId = req.userId;
const relays = await relayQuery(filter);
const relays = await relayQuery(filter, {
relayName: 1,
isRunning: 1,
isDeleted: 1,
isPublished: 1,
relayId: 1,
'participantApps.appName': 1,
'participantApps.event': 1,
'participantApps.eventType': 1,
});
const generatedResponse = response.generateResponse(false, actionStatus.SUCCESS, 'List of relays', relays);
res.send(generatedResponse);
};

const fetchInputsAndAuth = async (app) => {
const inputsAndAuth = {};
if (app.inputs) {
inputsAndAuth.inputs = await InputsCollection.findOne(app.inputs, { _id: 0 });
}
if (app.authentication) {
inputsAndAuth.authentication = await AuthorizedApps.findOne(app.authentication, { _id: 0 });
}
return inputsAndAuth;
};

const getSingleRelay = async (req, res) => {
const filter = {};
filter._id = req.params.relayId; // _id or shortid
// filter.userId = req.userId;
const singleRelay = await RelayCollection.findOne(filter, {
relayName: 1,
isRunning: 1,
isDeleted: 1,
isPublished: 1,
participantApps: 1,
});

for (const [index, app] of Object.entries(singleRelay.participantApps)) {
const inputsAndAuth = await fetchInputsAndAuth(app);
const { appName, event, eventType } = app;
singleRelay.participantApps[index] = {
appName,
event,
eventType,
...inputsAndAuth,
};
}

const generatedResponse = response.generateResponse(false, actionStatus.SUCCESS, 'Relay details', singleRelay);
res.send(generatedResponse);
};

// validate entries, or get participantApp.authentication wholly instead of id
const createNewRelay = async (req, res) => {
const relayDetails = req.body;
// relayDetails.userId = req.userId;
const createdRelay = await RelayCollection.create(relayDetails);

const generatedResponse = response.generateResponse(false, actionStatus.SUCCESS, 'Created relay', createdRelay);
res.send(generatedResponse);
};

const updateExistingRelay = async (req, res) => {
const relayIDToBeEdited = {};
relayIDToBeEdited._id = req.params.relayId;
// relayIDToBeEdited.userId = req.userId;
const relayDetailsToBeEdited = req.body;

const editedRelayDetails = await RelayCollection.findOneAndUpdate(relayIDToBeEdited,
relayDetailsToBeEdited);

const generatedResponse = response.generateResponse(false,
actionStatus.SUCCESS, 'Edited relay', editedRelayDetails);

res.send(generatedResponse);
};

const moveRelayToTrash = async (req, res) => {
const relayIDToBeDeleted = {};
relayIDToBeDeleted._id = req.params.relayId;
// relayIDToBeDeleted.userId = req.userId;

const deletedRelayDetails = await RelayCollection.findOneAndUpdate(relayIDToBeDeleted,
{ isDeleted: false });

const generatedResponse = response.generateResponse(false,
actionStatus.SUCCESS, 'Deleted relay', deletedRelayDetails);

res.send(generatedResponse);
};


module.exports = {
getRelays,
getSingleRelay,
getRunningRelays,
getRunningRelaysWithTriggerApp,
createNewRelay,
updateExistingRelay,
moveRelayToTrash,
};
2 changes: 1 addition & 1 deletion server/controller/WebHookListeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const slackWebHookListener = async (event, authedUsers) => {
};

const gitWebHookListener = (event) => {
console.log(event);
// console.log(event);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty function?

};

const exports = {
Expand Down
19 changes: 17 additions & 2 deletions server/lib/authTokenLib.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import jwt from 'jsonwebtoken';
import time from './timeLib';

const { JWT_SECRET } = process.env;
const JWT_SECRET = 'zF9?nCmaZH&?fF9';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is absolutely wrong.
Putting in secrets in code is not safe


const createAuthToken = (userId) => {
const jwtTokenObject = {
Expand All @@ -18,8 +18,23 @@ const verifyAuthToken = (authToken) => new Promise((resolve, reject) => {
resolve(decoded);
});
});

const createGenericAuthToken = (body) => {
const jwtTokenObject = {
...body,
iat: time.now(),
};
const jwtToken = jwt.sign(jwtTokenObject, JWT_SECRET, { expiresIn: 3600 });
return jwtToken;
};
const decodeGenericAuthToken = (authToken) => new Promise((resolve, reject) => {
jwt.verify(authToken, JWT_SECRET, (err, decoded) => {
if (err) reject(err);
resolve(decoded);
});
});
module.exports = {
createAuthToken,
verifyAuthToken,
createGenericAuthToken,
decodeGenericAuthToken,
};
33 changes: 33 additions & 0 deletions server/lib/githubEventLib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import request from 'request';
import eventEmitter from './eventsLib';
import AuthorizedApps from '../models/AuthorizedApps';

eventEmitter.on('Github:Authorize', async (payload) => {
// eslint-disable-next-line camelcase
const { state, installation_id, code } = payload;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can have camelcase using installation_id: installationId

const options = {
url: 'https://github.com/login/oauth/access_token',
method: 'POST',
headers: { Accept: 'application/json' },
json: {
code,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put some line breaks please

client_id: process.env.GITHUB_CLIENT_ID,
client_secret: process.env.GITHUB_CLIENT_SECRET,
},
};
request(options, async (err, response, body) => {
const authorizedApp = await AuthorizedApps.findOne({ authAppId: state.authAppId });
const credentials = [{
attributeName: 'access_token',
attributeValue: body.access_token,
}, {
attributeName: 'token_type',
attributeValue: body.token_type,
}, {
attributeName: 'installation_id',
attributeValue: installation_id,
}];
authorizedApp.credentials = credentials;
await authorizedApp.save();
});
});
1 change: 1 addition & 0 deletions server/middlewares/authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ const validateAuthentication = async (req, res, next) => {
res.status(401).send(generatedResponse);
}
};

module.exports = validateAuthentication;
14 changes: 4 additions & 10 deletions server/models/AuthorizedApps.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import mongoose, { Schema } from 'mongoose';
import shortid from 'shortid';

const AttributeObject = new Schema(
{
attributeName: String,
attributeValue: String,
}, { _id: false },
);

const AuthorizedApps = new Schema({
userId: { type: String }, // required:true
userId: { type: String, required: true },
authAppId: { type: String, default: shortid.generate, unique: true },
appName: { type: String, required: true },
credentials: [AttributeObject],
version: { type: String, default: '' },
email: { type: String }, // required: true
auth_token: { type: String },
credentials: { type: Map },
});

export default mongoose.model('AuthorizedApps', AuthorizedApps);
16 changes: 0 additions & 16 deletions server/models/Inputs.js

This file was deleted.

2 changes: 1 addition & 1 deletion server/models/RelayHistory.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import mongoose, { Schema } from 'mongoose';

const RelayHistory = new Schema({
relayId: { type: String, unique: true },
relayId: { type: String },
status: { type: String },
}, { timestamps: true });

Expand Down
Loading