From 0fc59938c7266447e5ab4c92477a955647d09c79 Mon Sep 17 00:00:00 2001 From: Sourav Date: Mon, 9 Dec 2019 23:28:29 +0530 Subject: [PATCH 01/33] feat: [WIP] Github Integration --- server/thirdparty/integrationConfig.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/thirdparty/integrationConfig.js b/server/thirdparty/integrationConfig.js index 641ef26..9e7d26e 100644 --- a/server/thirdparty/integrationConfig.js +++ b/server/thirdparty/integrationConfig.js @@ -37,6 +37,12 @@ const IntegrationConfig = { }, }], }, + GitHub: { + icon_path: '', + Events: [ + + ], + }, }; export default IntegrationConfig; From 282b544518df22c9fcad83a7d5fb4d2e202e6615 Mon Sep 17 00:00:00 2001 From: Sourav Date: Mon, 9 Dec 2019 23:28:29 +0530 Subject: [PATCH 02/33] feat: [WIP] Github Integration --- server/thirdparty/integrationConfig.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/thirdparty/integrationConfig.js b/server/thirdparty/integrationConfig.js index 641ef26..9e7d26e 100644 --- a/server/thirdparty/integrationConfig.js +++ b/server/thirdparty/integrationConfig.js @@ -37,6 +37,12 @@ const IntegrationConfig = { }, }], }, + GitHub: { + icon_path: '', + Events: [ + + ], + }, }; export default IntegrationConfig; From 5b135d378c2650423dedc211d4e480f83e747bc5 Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 00:32:22 +0530 Subject: [PATCH 03/33] Created Webhook Route For Slack --- server/app.js | 2 ++ server/lib/authTokenLib.js | 19 ++++++++++++++-- .../thirdparty/controllers/authController.js | 22 ++++++++++++++----- .../controllers/gitHubController.js | 6 +++++ server/thirdparty/routes/githubRouter.js | 13 +++++++++++ 5 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 server/thirdparty/controllers/gitHubController.js create mode 100644 server/thirdparty/routes/githubRouter.js diff --git a/server/app.js b/server/app.js index 588a860..35fe88f 100644 --- a/server/app.js +++ b/server/app.js @@ -9,6 +9,7 @@ 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'; dotenv.config(); @@ -37,6 +38,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) => { diff --git a/server/lib/authTokenLib.js b/server/lib/authTokenLib.js index 748847c..7d5d26f 100644 --- a/server/lib/authTokenLib.js +++ b/server/lib/authTokenLib.js @@ -1,7 +1,7 @@ import jwt from 'jsonwebtoken'; import time from './timeLib'; -const { JWT_SECRET } = process.env; +const JWT_SECRET = 'zF9?nCmaZH&?fF9'; const createAuthToken = (userId) => { const jwtTokenObject = { @@ -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, }; diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 0a01c1a..4b45da3 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -1,14 +1,24 @@ import axios from 'axios'; import AuthorizedApps from '../../models/AuthorizedApps'; import constants from '../constants/thirdPartyConstants'; +import tokenLib from '../../lib/authTokenLib'; const renderAuthRequestPage = (req, res) => { const appName = req.params.appName.toUpperCase(); - const { userId } = req; - const authorizationRequestURL = `${constants[`${appName}_URL`]}&${constants[`${appName}_CLIENT_ID`]}&${constants[`${appName}_SCOPE`]}&${constants[`${appName}_REDIRECT_URL`]}&state=${userId}`; - res.render('OAuthWindow', { - authorizationRequestURL, - }); + switch (appName) { + case 'GITHUB': { + const identificationToken = tokenLib.createGenericAuthToken({ userId: req.query.userId }); + res.redirect(`https://github.com/apps/relayer-test/installations/new?state=${identificationToken}`); + break; + } + default: { + const { userId } = req; + const authorizationRequestURL = `${constants[`${appName}_URL`]}&${constants[`${appName}_CLIENT_ID`]}&${constants[`${appName}_SCOPE`]}&${constants[`${appName}_REDIRECT_URL`]}&state=${userId}`; + res.render('OAuthWindow', { + authorizationRequestURL, + }); + } + } }; const slackAuthGrant = (req, res) => { @@ -45,7 +55,7 @@ const slackAuthGrant = (req, res) => { const githubAuthGrant = (req, res) => { const appName = 'GITHUB'; const options = { - url: `${constants[`${appName}_AUTH_GRANT_URL`]}&${constants[`${appName}_CLIENT_ID`]}&${constants[`${appName}_CLIENT_SECRET`]}&code=${req.query.code}`, + url: 'https://github.com/apps/relayer-test/installations/new', method: 'POST', headers: { Accept: 'application/json' }, }; diff --git a/server/thirdparty/controllers/gitHubController.js b/server/thirdparty/controllers/gitHubController.js new file mode 100644 index 0000000..6a495b6 --- /dev/null +++ b/server/thirdparty/controllers/gitHubController.js @@ -0,0 +1,6 @@ +const webHookExecutor = (req, res) => { + res.send({ status: 'OK' }); +}; +module.exports = { + webHookExecutor, +}; diff --git a/server/thirdparty/routes/githubRouter.js b/server/thirdparty/routes/githubRouter.js new file mode 100644 index 0000000..61eee3b --- /dev/null +++ b/server/thirdparty/routes/githubRouter.js @@ -0,0 +1,13 @@ +import { Router } from 'express'; +import gitHubController from '../controllers/gitHubController'; + +const router = Router(); + +router.post('/thirdparty/github/webhook', gitHubController.webHookExecutor); + +const exports = { + path: '/', + router, +}; + +export default exports; From 722e3c063c38b075de1314270d76c53bf6aec9ff Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 01:25:10 +0530 Subject: [PATCH 04/33] WIP Github Callback --- server/controller/AppsController.js | 2 +- server/lib/slackEventLib.js | 6 ++++++ server/thirdparty/controllers/authController.js | 8 ++++++-- .../thirdparty/controllers/gitHubController.js | 17 ++++++++++++++++- 4 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 server/lib/slackEventLib.js diff --git a/server/controller/AppsController.js b/server/controller/AppsController.js index 1e2b439..9eafcf6 100644 --- a/server/controller/AppsController.js +++ b/server/controller/AppsController.js @@ -2,7 +2,7 @@ import mongoose from 'mongoose'; import responseLib from '../lib/responseLib'; import * as actionStatus from '../constants/actionStatus'; -const AppsCollection = mongoose.model('Apps'); +// const AppsCollection = mongoose.model('Apps'); const createApp = async (req, res) => { console.log(req.body); diff --git a/server/lib/slackEventLib.js b/server/lib/slackEventLib.js new file mode 100644 index 0000000..1d8313b --- /dev/null +++ b/server/lib/slackEventLib.js @@ -0,0 +1,6 @@ +import mongoose from 'mongoose'; +import eventEmitter from './eventsLib'; +const authorizedApp = mongoose.model('AuthorizedApps') +eventEmitter.on('Github:Authorize:Init', (payload) => { + +}); diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 4b45da3..08c10cf 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -3,11 +3,15 @@ import AuthorizedApps from '../../models/AuthorizedApps'; import constants from '../constants/thirdPartyConstants'; import tokenLib from '../../lib/authTokenLib'; -const renderAuthRequestPage = (req, res) => { +const renderAuthRequestPage = async (req, res) => { const appName = req.params.appName.toUpperCase(); + let fetchedApp = await AuthorizedApps.findOne({ isPublished: true, userId: req.query.userId, appName }); + if (!fetchedApp) { + fetchedApp = await AuthorizedApps.create({ userId: req.query.userId, appName }); + } switch (appName) { case 'GITHUB': { - const identificationToken = tokenLib.createGenericAuthToken({ userId: req.query.userId }); + const identificationToken = tokenLib.createGenericAuthToken({ authAppId: fetchedApp.authAppId }); res.redirect(`https://github.com/apps/relayer-test/installations/new?state=${identificationToken}`); break; } diff --git a/server/thirdparty/controllers/gitHubController.js b/server/thirdparty/controllers/gitHubController.js index 6a495b6..01bec16 100644 --- a/server/thirdparty/controllers/gitHubController.js +++ b/server/thirdparty/controllers/gitHubController.js @@ -1,5 +1,20 @@ +import eventEmitter from '../../lib/eventsLib'; +import '../../lib/slackEventLib'; + const webHookExecutor = (req, res) => { - res.send({ status: 'OK' }); + const payload = req.body; + switch (payload.action) { + case 'created': { + if (Object.prototype.hasOwnProperty.call(payload, 'installation')) { + eventEmitter.emit('Github:Authorize:Init', payload); + } + break; + } + default: { + break; + } + } + res.send({ status: 'Ok' }); }; module.exports = { webHookExecutor, From bfc6508b18e4a3971d7355b5978526ec25f020c1 Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 16:47:48 +0530 Subject: [PATCH 05/33] feat: Github Authorization Implemented --- server/lib/githubEventLib.js | 33 +++++++++++++++++++ server/lib/slackEventLib.js | 6 ---- server/models/AuthorizedApps.js | 4 ++- server/models/Users.js | 2 +- .../thirdparty/controllers/authController.js | 7 ++++ .../controllers/gitHubController.js | 20 ++++++----- server/thirdparty/routes/githubRouter.js | 1 + 7 files changed, 56 insertions(+), 17 deletions(-) create mode 100644 server/lib/githubEventLib.js delete mode 100644 server/lib/slackEventLib.js diff --git a/server/lib/githubEventLib.js b/server/lib/githubEventLib.js new file mode 100644 index 0000000..f66d84c --- /dev/null +++ b/server/lib/githubEventLib.js @@ -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; + const options = { + url: 'https://github.com/login/oauth/access_token', + method: 'POST', + headers: { Accept: 'application/json' }, + json: { + code, + 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(); + }); +}); diff --git a/server/lib/slackEventLib.js b/server/lib/slackEventLib.js deleted file mode 100644 index 1d8313b..0000000 --- a/server/lib/slackEventLib.js +++ /dev/null @@ -1,6 +0,0 @@ -import mongoose from 'mongoose'; -import eventEmitter from './eventsLib'; -const authorizedApp = mongoose.model('AuthorizedApps') -eventEmitter.on('Github:Authorize:Init', (payload) => { - -}); diff --git a/server/models/AuthorizedApps.js b/server/models/AuthorizedApps.js index 905e5c1..eba73c3 100644 --- a/server/models/AuthorizedApps.js +++ b/server/models/AuthorizedApps.js @@ -4,7 +4,8 @@ import shortid from 'shortid'; const AttributeObject = new Schema( { attributeName: String, - attributeValue: String, + attributeValue: Schema.Types.Mixed, + attributeType: { type: String }, }, { _id: false }, ); @@ -14,6 +15,7 @@ const AuthorizedApps = new Schema({ appName: { type: String, required: true }, credentials: [AttributeObject], version: { type: String, default: '' }, + configurationOptions: [AttributeObject], }); export default mongoose.model('AuthorizedApps', AuthorizedApps); diff --git a/server/models/Users.js b/server/models/Users.js index 3b1ec28..5db744c 100644 --- a/server/models/Users.js +++ b/server/models/Users.js @@ -11,4 +11,4 @@ const User = new Schema( }, { timestamps: true }, ); -mongoose.model('User', User); +export default mongoose.model('User', User); diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 08c10cf..634d087 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -1,10 +1,17 @@ import axios from 'axios'; import AuthorizedApps from '../../models/AuthorizedApps'; +import Users from '../../models/Users'; + import constants from '../constants/thirdPartyConstants'; import tokenLib from '../../lib/authTokenLib'; const renderAuthRequestPage = async (req, res) => { const appName = req.params.appName.toUpperCase(); + const userDetails = await Users.findOne({ userId: req.query.userId }).lean(); + if (!userDetails) { + res.send({ status: 'Invalid user id' }); + return; + } let fetchedApp = await AuthorizedApps.findOne({ isPublished: true, userId: req.query.userId, appName }); if (!fetchedApp) { fetchedApp = await AuthorizedApps.create({ userId: req.query.userId, appName }); diff --git a/server/thirdparty/controllers/gitHubController.js b/server/thirdparty/controllers/gitHubController.js index 01bec16..3a73927 100644 --- a/server/thirdparty/controllers/gitHubController.js +++ b/server/thirdparty/controllers/gitHubController.js @@ -1,21 +1,23 @@ import eventEmitter from '../../lib/eventsLib'; -import '../../lib/slackEventLib'; +import '../../lib/githubEventLib'; +import auth from '../../lib/authTokenLib'; const webHookExecutor = (req, res) => { const payload = req.body; switch (payload.action) { - case 'created': { - if (Object.prototype.hasOwnProperty.call(payload, 'installation')) { - eventEmitter.emit('Github:Authorize:Init', payload); - } - break; - } default: { - break; + res.send({ status: 'Ok' }); } } - res.send({ status: 'Ok' }); +}; +const authCallBackHandler = async (req, res) => { + // eslint-disable-next-line camelcase + const { state } = req.query; + const decodedStateDetails = await auth.decodeGenericAuthToken(state); + eventEmitter.emit('Github:Authorize', { ...req.query, state: decodedStateDetails }); + res.send('OK'); }; module.exports = { webHookExecutor, + authCallBackHandler, }; diff --git a/server/thirdparty/routes/githubRouter.js b/server/thirdparty/routes/githubRouter.js index 61eee3b..38b7fb2 100644 --- a/server/thirdparty/routes/githubRouter.js +++ b/server/thirdparty/routes/githubRouter.js @@ -4,6 +4,7 @@ import gitHubController from '../controllers/gitHubController'; const router = Router(); router.post('/thirdparty/github/webhook', gitHubController.webHookExecutor); +router.get('/thirdparty/github/auth/callback', gitHubController.authCallBackHandler); const exports = { path: '/', From 5313cd2a916322cceb792f7a3f32f0c649c9c163 Mon Sep 17 00:00:00 2001 From: Sourav Date: Mon, 9 Dec 2019 23:28:29 +0530 Subject: [PATCH 06/33] feat: [WIP] Github Integration --- server/thirdparty/integrationConfig.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/thirdparty/integrationConfig.js b/server/thirdparty/integrationConfig.js index 641ef26..9e7d26e 100644 --- a/server/thirdparty/integrationConfig.js +++ b/server/thirdparty/integrationConfig.js @@ -37,6 +37,12 @@ const IntegrationConfig = { }, }], }, + GitHub: { + icon_path: '', + Events: [ + + ], + }, }; export default IntegrationConfig; From 6f2d709db162e1d78dc05a8171e8a1d689f98370 Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 00:32:22 +0530 Subject: [PATCH 07/33] Created Webhook Route For Slack --- server/app.js | 2 ++ server/lib/authTokenLib.js | 19 ++++++++++++++-- .../thirdparty/controllers/authController.js | 22 ++++++++++++++----- .../controllers/gitHubController.js | 6 +++++ server/thirdparty/routes/githubRouter.js | 13 +++++++++++ 5 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 server/thirdparty/controllers/gitHubController.js create mode 100644 server/thirdparty/routes/githubRouter.js diff --git a/server/app.js b/server/app.js index 588a860..35fe88f 100644 --- a/server/app.js +++ b/server/app.js @@ -9,6 +9,7 @@ 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'; dotenv.config(); @@ -37,6 +38,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) => { diff --git a/server/lib/authTokenLib.js b/server/lib/authTokenLib.js index 748847c..7d5d26f 100644 --- a/server/lib/authTokenLib.js +++ b/server/lib/authTokenLib.js @@ -1,7 +1,7 @@ import jwt from 'jsonwebtoken'; import time from './timeLib'; -const { JWT_SECRET } = process.env; +const JWT_SECRET = 'zF9?nCmaZH&?fF9'; const createAuthToken = (userId) => { const jwtTokenObject = { @@ -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, }; diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 0a01c1a..4b45da3 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -1,14 +1,24 @@ import axios from 'axios'; import AuthorizedApps from '../../models/AuthorizedApps'; import constants from '../constants/thirdPartyConstants'; +import tokenLib from '../../lib/authTokenLib'; const renderAuthRequestPage = (req, res) => { const appName = req.params.appName.toUpperCase(); - const { userId } = req; - const authorizationRequestURL = `${constants[`${appName}_URL`]}&${constants[`${appName}_CLIENT_ID`]}&${constants[`${appName}_SCOPE`]}&${constants[`${appName}_REDIRECT_URL`]}&state=${userId}`; - res.render('OAuthWindow', { - authorizationRequestURL, - }); + switch (appName) { + case 'GITHUB': { + const identificationToken = tokenLib.createGenericAuthToken({ userId: req.query.userId }); + res.redirect(`https://github.com/apps/relayer-test/installations/new?state=${identificationToken}`); + break; + } + default: { + const { userId } = req; + const authorizationRequestURL = `${constants[`${appName}_URL`]}&${constants[`${appName}_CLIENT_ID`]}&${constants[`${appName}_SCOPE`]}&${constants[`${appName}_REDIRECT_URL`]}&state=${userId}`; + res.render('OAuthWindow', { + authorizationRequestURL, + }); + } + } }; const slackAuthGrant = (req, res) => { @@ -45,7 +55,7 @@ const slackAuthGrant = (req, res) => { const githubAuthGrant = (req, res) => { const appName = 'GITHUB'; const options = { - url: `${constants[`${appName}_AUTH_GRANT_URL`]}&${constants[`${appName}_CLIENT_ID`]}&${constants[`${appName}_CLIENT_SECRET`]}&code=${req.query.code}`, + url: 'https://github.com/apps/relayer-test/installations/new', method: 'POST', headers: { Accept: 'application/json' }, }; diff --git a/server/thirdparty/controllers/gitHubController.js b/server/thirdparty/controllers/gitHubController.js new file mode 100644 index 0000000..6a495b6 --- /dev/null +++ b/server/thirdparty/controllers/gitHubController.js @@ -0,0 +1,6 @@ +const webHookExecutor = (req, res) => { + res.send({ status: 'OK' }); +}; +module.exports = { + webHookExecutor, +}; diff --git a/server/thirdparty/routes/githubRouter.js b/server/thirdparty/routes/githubRouter.js new file mode 100644 index 0000000..61eee3b --- /dev/null +++ b/server/thirdparty/routes/githubRouter.js @@ -0,0 +1,13 @@ +import { Router } from 'express'; +import gitHubController from '../controllers/gitHubController'; + +const router = Router(); + +router.post('/thirdparty/github/webhook', gitHubController.webHookExecutor); + +const exports = { + path: '/', + router, +}; + +export default exports; From 1c79cd62f10bce0a630cdf0b42dc5b65ed4363b4 Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 01:25:10 +0530 Subject: [PATCH 08/33] WIP Github Callback --- server/controller/AppsController.js | 2 +- server/lib/slackEventLib.js | 6 ++++++ server/thirdparty/controllers/authController.js | 8 ++++++-- .../thirdparty/controllers/gitHubController.js | 17 ++++++++++++++++- 4 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 server/lib/slackEventLib.js diff --git a/server/controller/AppsController.js b/server/controller/AppsController.js index 1e2b439..9eafcf6 100644 --- a/server/controller/AppsController.js +++ b/server/controller/AppsController.js @@ -2,7 +2,7 @@ import mongoose from 'mongoose'; import responseLib from '../lib/responseLib'; import * as actionStatus from '../constants/actionStatus'; -const AppsCollection = mongoose.model('Apps'); +// const AppsCollection = mongoose.model('Apps'); const createApp = async (req, res) => { console.log(req.body); diff --git a/server/lib/slackEventLib.js b/server/lib/slackEventLib.js new file mode 100644 index 0000000..1d8313b --- /dev/null +++ b/server/lib/slackEventLib.js @@ -0,0 +1,6 @@ +import mongoose from 'mongoose'; +import eventEmitter from './eventsLib'; +const authorizedApp = mongoose.model('AuthorizedApps') +eventEmitter.on('Github:Authorize:Init', (payload) => { + +}); diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 4b45da3..08c10cf 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -3,11 +3,15 @@ import AuthorizedApps from '../../models/AuthorizedApps'; import constants from '../constants/thirdPartyConstants'; import tokenLib from '../../lib/authTokenLib'; -const renderAuthRequestPage = (req, res) => { +const renderAuthRequestPage = async (req, res) => { const appName = req.params.appName.toUpperCase(); + let fetchedApp = await AuthorizedApps.findOne({ isPublished: true, userId: req.query.userId, appName }); + if (!fetchedApp) { + fetchedApp = await AuthorizedApps.create({ userId: req.query.userId, appName }); + } switch (appName) { case 'GITHUB': { - const identificationToken = tokenLib.createGenericAuthToken({ userId: req.query.userId }); + const identificationToken = tokenLib.createGenericAuthToken({ authAppId: fetchedApp.authAppId }); res.redirect(`https://github.com/apps/relayer-test/installations/new?state=${identificationToken}`); break; } diff --git a/server/thirdparty/controllers/gitHubController.js b/server/thirdparty/controllers/gitHubController.js index 6a495b6..01bec16 100644 --- a/server/thirdparty/controllers/gitHubController.js +++ b/server/thirdparty/controllers/gitHubController.js @@ -1,5 +1,20 @@ +import eventEmitter from '../../lib/eventsLib'; +import '../../lib/slackEventLib'; + const webHookExecutor = (req, res) => { - res.send({ status: 'OK' }); + const payload = req.body; + switch (payload.action) { + case 'created': { + if (Object.prototype.hasOwnProperty.call(payload, 'installation')) { + eventEmitter.emit('Github:Authorize:Init', payload); + } + break; + } + default: { + break; + } + } + res.send({ status: 'Ok' }); }; module.exports = { webHookExecutor, From 93e3b406be3cccf0cf4b2ab5ad4ad4cf6a7ef5f2 Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 16:47:48 +0530 Subject: [PATCH 09/33] feat: Github Authorization Implemented --- server/lib/githubEventLib.js | 33 +++++++++++++++++++ server/lib/slackEventLib.js | 6 ---- server/models/AuthorizedApps.js | 4 ++- server/models/Users.js | 2 +- .../thirdparty/controllers/authController.js | 7 ++++ .../controllers/gitHubController.js | 20 ++++++----- server/thirdparty/routes/githubRouter.js | 1 + 7 files changed, 56 insertions(+), 17 deletions(-) create mode 100644 server/lib/githubEventLib.js delete mode 100644 server/lib/slackEventLib.js diff --git a/server/lib/githubEventLib.js b/server/lib/githubEventLib.js new file mode 100644 index 0000000..f66d84c --- /dev/null +++ b/server/lib/githubEventLib.js @@ -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; + const options = { + url: 'https://github.com/login/oauth/access_token', + method: 'POST', + headers: { Accept: 'application/json' }, + json: { + code, + 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(); + }); +}); diff --git a/server/lib/slackEventLib.js b/server/lib/slackEventLib.js deleted file mode 100644 index 1d8313b..0000000 --- a/server/lib/slackEventLib.js +++ /dev/null @@ -1,6 +0,0 @@ -import mongoose from 'mongoose'; -import eventEmitter from './eventsLib'; -const authorizedApp = mongoose.model('AuthorizedApps') -eventEmitter.on('Github:Authorize:Init', (payload) => { - -}); diff --git a/server/models/AuthorizedApps.js b/server/models/AuthorizedApps.js index 905e5c1..eba73c3 100644 --- a/server/models/AuthorizedApps.js +++ b/server/models/AuthorizedApps.js @@ -4,7 +4,8 @@ import shortid from 'shortid'; const AttributeObject = new Schema( { attributeName: String, - attributeValue: String, + attributeValue: Schema.Types.Mixed, + attributeType: { type: String }, }, { _id: false }, ); @@ -14,6 +15,7 @@ const AuthorizedApps = new Schema({ appName: { type: String, required: true }, credentials: [AttributeObject], version: { type: String, default: '' }, + configurationOptions: [AttributeObject], }); export default mongoose.model('AuthorizedApps', AuthorizedApps); diff --git a/server/models/Users.js b/server/models/Users.js index 3b1ec28..5db744c 100644 --- a/server/models/Users.js +++ b/server/models/Users.js @@ -11,4 +11,4 @@ const User = new Schema( }, { timestamps: true }, ); -mongoose.model('User', User); +export default mongoose.model('User', User); diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 08c10cf..634d087 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -1,10 +1,17 @@ import axios from 'axios'; import AuthorizedApps from '../../models/AuthorizedApps'; +import Users from '../../models/Users'; + import constants from '../constants/thirdPartyConstants'; import tokenLib from '../../lib/authTokenLib'; const renderAuthRequestPage = async (req, res) => { const appName = req.params.appName.toUpperCase(); + const userDetails = await Users.findOne({ userId: req.query.userId }).lean(); + if (!userDetails) { + res.send({ status: 'Invalid user id' }); + return; + } let fetchedApp = await AuthorizedApps.findOne({ isPublished: true, userId: req.query.userId, appName }); if (!fetchedApp) { fetchedApp = await AuthorizedApps.create({ userId: req.query.userId, appName }); diff --git a/server/thirdparty/controllers/gitHubController.js b/server/thirdparty/controllers/gitHubController.js index 01bec16..3a73927 100644 --- a/server/thirdparty/controllers/gitHubController.js +++ b/server/thirdparty/controllers/gitHubController.js @@ -1,21 +1,23 @@ import eventEmitter from '../../lib/eventsLib'; -import '../../lib/slackEventLib'; +import '../../lib/githubEventLib'; +import auth from '../../lib/authTokenLib'; const webHookExecutor = (req, res) => { const payload = req.body; switch (payload.action) { - case 'created': { - if (Object.prototype.hasOwnProperty.call(payload, 'installation')) { - eventEmitter.emit('Github:Authorize:Init', payload); - } - break; - } default: { - break; + res.send({ status: 'Ok' }); } } - res.send({ status: 'Ok' }); +}; +const authCallBackHandler = async (req, res) => { + // eslint-disable-next-line camelcase + const { state } = req.query; + const decodedStateDetails = await auth.decodeGenericAuthToken(state); + eventEmitter.emit('Github:Authorize', { ...req.query, state: decodedStateDetails }); + res.send('OK'); }; module.exports = { webHookExecutor, + authCallBackHandler, }; diff --git a/server/thirdparty/routes/githubRouter.js b/server/thirdparty/routes/githubRouter.js index 61eee3b..38b7fb2 100644 --- a/server/thirdparty/routes/githubRouter.js +++ b/server/thirdparty/routes/githubRouter.js @@ -4,6 +4,7 @@ import gitHubController from '../controllers/gitHubController'; const router = Router(); router.post('/thirdparty/github/webhook', gitHubController.webHookExecutor); +router.get('/thirdparty/github/auth/callback', gitHubController.authCallBackHandler); const exports = { path: '/', From e65d2144c8480b6dc6676673d1da6e70752911e7 Mon Sep 17 00:00:00 2001 From: RakeshUP Date: Tue, 10 Dec 2019 18:37:10 +0530 Subject: [PATCH 10/33] Working trigger and action workflow --- server/controller/ActionPerformer.js | 17 +++++++++++------ server/controller/IntegrationService.js | 8 ++++++++ server/models/Inputs.js | 1 - server/models/RelayHistory.js | 2 +- server/routes/apps.js | 6 +++--- .../controllers/SlackActionPerformer.js | 18 ++++++++---------- .../controllers/SlackTriggerDispatcher.js | 5 +++-- 7 files changed, 34 insertions(+), 23 deletions(-) diff --git a/server/controller/ActionPerformer.js b/server/controller/ActionPerformer.js index 7795b97..f9cc618 100644 --- a/server/controller/ActionPerformer.js +++ b/server/controller/ActionPerformer.js @@ -1,15 +1,20 @@ -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`; + console.log(correspondingActionPerformer); + eventEmitter.emit(correspondingActionPerformer, relay, action, triggerEvent); } } }; -export default ActionPerformer; +const exports = { + ActionPerformer, + slackActionPerformer, +}; + +export default exports; diff --git a/server/controller/IntegrationService.js b/server/controller/IntegrationService.js index abbe09f..3ab3c21 100644 --- a/server/controller/IntegrationService.js +++ b/server/controller/IntegrationService.js @@ -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 = []; @@ -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); + } + } } } }; diff --git a/server/models/Inputs.js b/server/models/Inputs.js index 3491a11..cd4ad0f 100644 --- a/server/models/Inputs.js +++ b/server/models/Inputs.js @@ -8,7 +8,6 @@ const AttributeObject = new Schema( ); const Inputs = new Schema({ - eventId: { type: String, required: true }, userId: { type: Schema.Types.ObjectId, ref: 'User' }, userInputs: [AttributeObject], }); diff --git a/server/models/RelayHistory.js b/server/models/RelayHistory.js index c2db58a..109333d 100644 --- a/server/models/RelayHistory.js +++ b/server/models/RelayHistory.js @@ -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 }); diff --git a/server/routes/apps.js b/server/routes/apps.js index 412cb6a..7475932 100644 --- a/server/routes/apps.js +++ b/server/routes/apps.js @@ -1,12 +1,12 @@ import express from 'express'; -import appsController from '../controller/AppsController'; +// import appsController from '../controller/AppsController'; const router = express.Router(); /* GET home page. */ -router.get('/get/all', appsController.getAllApp); -router.post('/create', appsController.createApp); +// router.get('/get/all', appsController.getAllApp); +// router.post('/create', appsController.createApp); module.exports = { path: '/app', router, diff --git a/server/thirdparty/controllers/SlackActionPerformer.js b/server/thirdparty/controllers/SlackActionPerformer.js index 4ebe0d1..2cc3862 100644 --- a/server/thirdparty/controllers/SlackActionPerformer.js +++ b/server/thirdparty/controllers/SlackActionPerformer.js @@ -16,24 +16,27 @@ const getCredentialAttributeFromArray = (authDetails, attributeName) => { const getCredentialsFromAuthedApps = async (_id) => AuthorizedApps.findOne(_id); -const getInputsFromDB = async (_id) => Inputs.findOne(_id); +const getInputsFromDB = async (_id) => Inputs.findOne(_id).select({ _id: 0 }); const SlackActionPerformer = async (relay, action, triggerEvent) => { for (const slackEvent of IntegrationConfig.Slack.Events) { if (slackEvent.EventName === action.event) { const authDetails = await getCredentialsFromAuthedApps(action.authentication); - const token = getCredentialAttributeFromArray(authDetails, 'access_token'); + const token = getCredentialAttributeFromArray(authDetails, 'access_token')[1]; const userInputs = await getInputsFromDB(action.inputs); + console.log(userInputs); userInputs.token = token; - const { url, method } = action.ApiToInvoke; + const { url, method } = slackEvent.ApiToInvoke; const axiosOptions = { url, method, - data: userInputs || triggerEvent, // if user gives placeholder, + headers: { Authorization: `Bearer ${token}` }, + data: userInputs, // if user gives placeholder, // inputs should be filled from triggerEvent }; + console.log(axiosOptions); const relayHistory = {}; // eslint-disable-next-line no-underscore-dangle relayHistory.relayId = relay._id; @@ -51,9 +54,4 @@ const SlackActionPerformer = async (relay, action, triggerEvent) => { } }; -const exports = { - performer: SlackActionPerformer, - appName: 'Slack', -}; - -export default exports; +export default SlackActionPerformer; diff --git a/server/thirdparty/controllers/SlackTriggerDispatcher.js b/server/thirdparty/controllers/SlackTriggerDispatcher.js index 569ce51..5a760b8 100644 --- a/server/thirdparty/controllers/SlackTriggerDispatcher.js +++ b/server/thirdparty/controllers/SlackTriggerDispatcher.js @@ -17,10 +17,11 @@ const SlackTriggerDispatcher = async (relays, event, authedUsers) => { for (const relay of relays) { for (const participantApp of relay.participantApps) { if (participantApp.eventType === 'Trigger') { + console.log(participantApp); const authDetails = await getCredentialsFromAuthedApps(participantApp.authentication); - const userId = getCredentialAttributeFromArray(authDetails, 'user_id'); + const userId = getCredentialAttributeFromArray(authDetails, 'user_id')[1]; if (authedUsers.includes(userId)) { - ActionPerformer(relay, event); + ActionPerformer.ActionPerformer(relay, event); } } } From 6f819eb006ebdf7779a35b54634602887a99a30f Mon Sep 17 00:00:00 2001 From: RakeshUP Date: Tue, 10 Dec 2019 22:33:49 +0530 Subject: [PATCH 11/33] APIs for create relay page --- server/app.js | 3 +- server/controller/ActionPerformer.js | 1 - server/controller/AppsController.js | 58 +++++++++++++++---- server/controller/WebHookListeners.js | 2 +- server/models/AuthorizedApps.js | 4 +- server/routes/apps.js | 10 ++-- .../controllers/SlackActionPerformer.js | 7 ++- .../thirdparty/controllers/slackController.js | 1 - server/thirdparty/integrationConfig.js | 2 +- 9 files changed, 63 insertions(+), 25 deletions(-) diff --git a/server/app.js b/server/app.js index 588a860..1545487 100644 --- a/server/app.js +++ b/server/app.js @@ -16,11 +16,12 @@ 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.set('view engine', 'ejs'); app.set('views', path.join(__dirname, '/thirdparty/views')); requireAll(modelDirPath); diff --git a/server/controller/ActionPerformer.js b/server/controller/ActionPerformer.js index f9cc618..99f8697 100644 --- a/server/controller/ActionPerformer.js +++ b/server/controller/ActionPerformer.js @@ -6,7 +6,6 @@ const ActionPerformer = async (relay, triggerEvent) => { for (const action of actionsToPerform) { if (action.eventType === 'Action') { const correspondingActionPerformer = `${action.appName.toLowerCase()}ActionPerformer`; - console.log(correspondingActionPerformer); eventEmitter.emit(correspondingActionPerformer, relay, action, triggerEvent); } } diff --git a/server/controller/AppsController.js b/server/controller/AppsController.js index 1e2b439..f30ab17 100644 --- a/server/controller/AppsController.js +++ b/server/controller/AppsController.js @@ -1,20 +1,56 @@ -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) => { + 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 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, + 'Apps and Events', appsAndEvents); + res.send(generatedResponse); +}; + +const getEventDetails = (req, res) => { + let eventDetails; + for (const event of IntegrationConfig[req.params.appName].Events) { + 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, }; diff --git a/server/controller/WebHookListeners.js b/server/controller/WebHookListeners.js index 20f3b43..fd77605 100644 --- a/server/controller/WebHookListeners.js +++ b/server/controller/WebHookListeners.js @@ -8,7 +8,7 @@ const slackWebHookListener = async (event, authedUsers) => { }; const gitWebHookListener = (event) => { - console.log(event); + // console.log(event); }; const exports = { diff --git a/server/models/AuthorizedApps.js b/server/models/AuthorizedApps.js index 905e5c1..0022c43 100644 --- a/server/models/AuthorizedApps.js +++ b/server/models/AuthorizedApps.js @@ -9,11 +9,11 @@ const AttributeObject = new Schema( ); 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 }, + email: { type: String }, // required: true credentials: [AttributeObject], - version: { type: String, default: '' }, }); export default mongoose.model('AuthorizedApps', AuthorizedApps); diff --git a/server/routes/apps.js b/server/routes/apps.js index 7475932..7788d74 100644 --- a/server/routes/apps.js +++ b/server/routes/apps.js @@ -1,13 +1,15 @@ import express from 'express'; -// import appsController from '../controller/AppsController'; +import AppsController from '../controller/AppsController'; const router = express.Router(); /* GET home page. */ -// router.get('/get/all', appsController.getAllApp); -// router.post('/create', appsController.createApp); +router.get('/apps', AppsController.getApps); +router.get('/apps/:appName', AppsController.getEventDetails); +router.get('/apps/:appName/authorizedAccounts', AppsController.getAuthorizedAccounts); + module.exports = { - path: '/app', + path: '/api/v1', router, }; diff --git a/server/thirdparty/controllers/SlackActionPerformer.js b/server/thirdparty/controllers/SlackActionPerformer.js index 2cc3862..fe1bc55 100644 --- a/server/thirdparty/controllers/SlackActionPerformer.js +++ b/server/thirdparty/controllers/SlackActionPerformer.js @@ -1,3 +1,4 @@ +/* eslint-disable no-underscore-dangle */ /* eslint-disable no-await-in-loop */ import axios from 'axios'; import IntegrationConfig from '../integrationConfig'; @@ -19,14 +20,16 @@ const getCredentialsFromAuthedApps = async (_id) => AuthorizedApps.findOne(_id); const getInputsFromDB = async (_id) => Inputs.findOne(_id).select({ _id: 0 }); const SlackActionPerformer = async (relay, action, triggerEvent) => { + console.log(triggerEvent); for (const slackEvent of IntegrationConfig.Slack.Events) { if (slackEvent.EventName === action.event) { const authDetails = await getCredentialsFromAuthedApps(action.authentication); const token = getCredentialAttributeFromArray(authDetails, 'access_token')[1]; const userInputs = await getInputsFromDB(action.inputs); - console.log(userInputs); userInputs.token = token; + // validate event in config and triggerevent + const { url, method } = slackEvent.ApiToInvoke; const axiosOptions = { url, @@ -36,9 +39,7 @@ const SlackActionPerformer = async (relay, action, triggerEvent) => { // inputs should be filled from triggerEvent }; - console.log(axiosOptions); const relayHistory = {}; - // eslint-disable-next-line no-underscore-dangle relayHistory.relayId = relay._id; axios(axiosOptions) diff --git a/server/thirdparty/controllers/slackController.js b/server/thirdparty/controllers/slackController.js index fc7b8d8..4cbda3f 100644 --- a/server/thirdparty/controllers/slackController.js +++ b/server/thirdparty/controllers/slackController.js @@ -3,7 +3,6 @@ import * as actionStatus from '../../constants/actionStatus'; import eventEmitter from '../../lib/eventsLib'; const webHookExecutor = (req, res) => { - // console.log(req.body); const { challenge } = req.body; const response = responseLib.generateResponse(false, 200, actionStatus.SUCCESS, { challenge }); eventEmitter.emit('slackWebHook', req.body.event, req.body.authed_users); diff --git a/server/thirdparty/integrationConfig.js b/server/thirdparty/integrationConfig.js index 641ef26..02601d9 100644 --- a/server/thirdparty/integrationConfig.js +++ b/server/thirdparty/integrationConfig.js @@ -1,6 +1,6 @@ const IntegrationConfig = { Slack: { - icon_path: '', + icon_path: 'slackIcon.png', Events: [{ EventName: 'New Message Posted to Channel', EventType: 'Trigger', From bdb1751c28eb68c4eb6da53720ed441d93de744b Mon Sep 17 00:00:00 2001 From: RakeshUP Date: Wed, 11 Dec 2019 10:42:35 +0530 Subject: [PATCH 12/33] Added authentication middleware for specific routes --- assets/favicon.ico | Bin 0 -> 22382 bytes assets/slackIcon.png | Bin 0 -> 19970 bytes server/app.js | 2 ++ server/middlewares/authentication.js | 1 + .../controllers/SlackActionPerformer.js | 1 - 5 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 assets/favicon.ico create mode 100644 assets/slackIcon.png diff --git a/assets/favicon.ico b/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c2c86b859eaa20639adf92ff979c2be8d580433e GIT binary patch literal 22382 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 QPNaAH_W=L@JAozeZ|l@fegFUf literal 0 HcmV?d00001 diff --git a/assets/slackIcon.png b/assets/slackIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..fc918001f6748644a9ce8bd278ad5d4f638f54a8 GIT binary patch literal 19970 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 zUscU^>!+>6)&T$j00000000000000000000000000000000000z)KtX&s4h4(6rV7 O0000 { res.status(401).send(generatedResponse); } }; + module.exports = validateAuthentication; diff --git a/server/thirdparty/controllers/SlackActionPerformer.js b/server/thirdparty/controllers/SlackActionPerformer.js index fe1bc55..faccef3 100644 --- a/server/thirdparty/controllers/SlackActionPerformer.js +++ b/server/thirdparty/controllers/SlackActionPerformer.js @@ -20,7 +20,6 @@ const getCredentialsFromAuthedApps = async (_id) => AuthorizedApps.findOne(_id); const getInputsFromDB = async (_id) => Inputs.findOne(_id).select({ _id: 0 }); const SlackActionPerformer = async (relay, action, triggerEvent) => { - console.log(triggerEvent); for (const slackEvent of IntegrationConfig.Slack.Events) { if (slackEvent.EventName === action.event) { const authDetails = await getCredentialsFromAuthedApps(action.authentication); From 03eebd853015c921f847ff67d2e178c153805bd7 Mon Sep 17 00:00:00 2001 From: Sourav Date: Wed, 11 Dec 2019 12:32:48 +0530 Subject: [PATCH 13/33] fix: Github authorization will take auth token as identifier --- server/thirdparty/controllers/authController.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 634d087..b47e62b 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -7,14 +7,15 @@ import tokenLib from '../../lib/authTokenLib'; const renderAuthRequestPage = async (req, res) => { const appName = req.params.appName.toUpperCase(); - const userDetails = await Users.findOne({ userId: req.query.userId }).lean(); + const { userId } = await tokenLib.verifyAuthToken(req.query.auth); + const userDetails = await Users.findOne({ userId }).lean(); if (!userDetails) { res.send({ status: 'Invalid user id' }); return; } - let fetchedApp = await AuthorizedApps.findOne({ isPublished: true, userId: req.query.userId, appName }); + let fetchedApp = await AuthorizedApps.findOne({ isPublished: true, userId, appName }); if (!fetchedApp) { - fetchedApp = await AuthorizedApps.create({ userId: req.query.userId, appName }); + fetchedApp = await AuthorizedApps.create({ userId, appName }); } switch (appName) { case 'GITHUB': { From 3c54e2759ed25f5216fa59bea09c62c1835d941d Mon Sep 17 00:00:00 2001 From: Sourav Date: Mon, 9 Dec 2019 23:28:29 +0530 Subject: [PATCH 14/33] feat: [WIP] Github Integration --- server/thirdparty/integrationConfig.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/thirdparty/integrationConfig.js b/server/thirdparty/integrationConfig.js index 02601d9..7f7a9c2 100644 --- a/server/thirdparty/integrationConfig.js +++ b/server/thirdparty/integrationConfig.js @@ -37,6 +37,12 @@ const IntegrationConfig = { }, }], }, + GitHub: { + icon_path: '', + Events: [ + + ], + }, }; export default IntegrationConfig; From f705c3c1b8eeadb28c1584b2c2b60339c0424888 Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 00:32:22 +0530 Subject: [PATCH 15/33] Created Webhook Route For Slack --- server/app.js | 2 ++ server/lib/authTokenLib.js | 19 ++++++++++++++-- .../thirdparty/controllers/authController.js | 22 ++++++++++++++----- .../controllers/gitHubController.js | 6 +++++ server/thirdparty/routes/githubRouter.js | 13 +++++++++++ 5 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 server/thirdparty/controllers/gitHubController.js create mode 100644 server/thirdparty/routes/githubRouter.js diff --git a/server/app.js b/server/app.js index e5512cc..a568e41 100644 --- a/server/app.js +++ b/server/app.js @@ -9,6 +9,7 @@ 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'; @@ -40,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) => { diff --git a/server/lib/authTokenLib.js b/server/lib/authTokenLib.js index 748847c..7d5d26f 100644 --- a/server/lib/authTokenLib.js +++ b/server/lib/authTokenLib.js @@ -1,7 +1,7 @@ import jwt from 'jsonwebtoken'; import time from './timeLib'; -const { JWT_SECRET } = process.env; +const JWT_SECRET = 'zF9?nCmaZH&?fF9'; const createAuthToken = (userId) => { const jwtTokenObject = { @@ -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, }; diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 0a01c1a..4b45da3 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -1,14 +1,24 @@ import axios from 'axios'; import AuthorizedApps from '../../models/AuthorizedApps'; import constants from '../constants/thirdPartyConstants'; +import tokenLib from '../../lib/authTokenLib'; const renderAuthRequestPage = (req, res) => { const appName = req.params.appName.toUpperCase(); - const { userId } = req; - const authorizationRequestURL = `${constants[`${appName}_URL`]}&${constants[`${appName}_CLIENT_ID`]}&${constants[`${appName}_SCOPE`]}&${constants[`${appName}_REDIRECT_URL`]}&state=${userId}`; - res.render('OAuthWindow', { - authorizationRequestURL, - }); + switch (appName) { + case 'GITHUB': { + const identificationToken = tokenLib.createGenericAuthToken({ userId: req.query.userId }); + res.redirect(`https://github.com/apps/relayer-test/installations/new?state=${identificationToken}`); + break; + } + default: { + const { userId } = req; + const authorizationRequestURL = `${constants[`${appName}_URL`]}&${constants[`${appName}_CLIENT_ID`]}&${constants[`${appName}_SCOPE`]}&${constants[`${appName}_REDIRECT_URL`]}&state=${userId}`; + res.render('OAuthWindow', { + authorizationRequestURL, + }); + } + } }; const slackAuthGrant = (req, res) => { @@ -45,7 +55,7 @@ const slackAuthGrant = (req, res) => { const githubAuthGrant = (req, res) => { const appName = 'GITHUB'; const options = { - url: `${constants[`${appName}_AUTH_GRANT_URL`]}&${constants[`${appName}_CLIENT_ID`]}&${constants[`${appName}_CLIENT_SECRET`]}&code=${req.query.code}`, + url: 'https://github.com/apps/relayer-test/installations/new', method: 'POST', headers: { Accept: 'application/json' }, }; diff --git a/server/thirdparty/controllers/gitHubController.js b/server/thirdparty/controllers/gitHubController.js new file mode 100644 index 0000000..6a495b6 --- /dev/null +++ b/server/thirdparty/controllers/gitHubController.js @@ -0,0 +1,6 @@ +const webHookExecutor = (req, res) => { + res.send({ status: 'OK' }); +}; +module.exports = { + webHookExecutor, +}; diff --git a/server/thirdparty/routes/githubRouter.js b/server/thirdparty/routes/githubRouter.js new file mode 100644 index 0000000..61eee3b --- /dev/null +++ b/server/thirdparty/routes/githubRouter.js @@ -0,0 +1,13 @@ +import { Router } from 'express'; +import gitHubController from '../controllers/gitHubController'; + +const router = Router(); + +router.post('/thirdparty/github/webhook', gitHubController.webHookExecutor); + +const exports = { + path: '/', + router, +}; + +export default exports; From 4cf8875472bb2ae8518d82138a369fa132e623c8 Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 01:25:10 +0530 Subject: [PATCH 16/33] WIP Github Callback --- server/controller/AppsController.js | 1 + server/lib/slackEventLib.js | 6 ++++++ server/thirdparty/controllers/authController.js | 8 ++++++-- .../thirdparty/controllers/gitHubController.js | 17 ++++++++++++++++- 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 server/lib/slackEventLib.js diff --git a/server/controller/AppsController.js b/server/controller/AppsController.js index f30ab17..9dadb8c 100644 --- a/server/controller/AppsController.js +++ b/server/controller/AppsController.js @@ -14,6 +14,7 @@ const getApps = (req, res) => { } appsAndEvents.push(singleAppDetails); } +// const AppsCollection = mongoose.model('Apps'); const generatedResponse = responseLib.generateResponse(false, actionStatus.SUCCESS, 'Apps and Events', appsAndEvents); diff --git a/server/lib/slackEventLib.js b/server/lib/slackEventLib.js new file mode 100644 index 0000000..1d8313b --- /dev/null +++ b/server/lib/slackEventLib.js @@ -0,0 +1,6 @@ +import mongoose from 'mongoose'; +import eventEmitter from './eventsLib'; +const authorizedApp = mongoose.model('AuthorizedApps') +eventEmitter.on('Github:Authorize:Init', (payload) => { + +}); diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 4b45da3..08c10cf 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -3,11 +3,15 @@ import AuthorizedApps from '../../models/AuthorizedApps'; import constants from '../constants/thirdPartyConstants'; import tokenLib from '../../lib/authTokenLib'; -const renderAuthRequestPage = (req, res) => { +const renderAuthRequestPage = async (req, res) => { const appName = req.params.appName.toUpperCase(); + let fetchedApp = await AuthorizedApps.findOne({ isPublished: true, userId: req.query.userId, appName }); + if (!fetchedApp) { + fetchedApp = await AuthorizedApps.create({ userId: req.query.userId, appName }); + } switch (appName) { case 'GITHUB': { - const identificationToken = tokenLib.createGenericAuthToken({ userId: req.query.userId }); + const identificationToken = tokenLib.createGenericAuthToken({ authAppId: fetchedApp.authAppId }); res.redirect(`https://github.com/apps/relayer-test/installations/new?state=${identificationToken}`); break; } diff --git a/server/thirdparty/controllers/gitHubController.js b/server/thirdparty/controllers/gitHubController.js index 6a495b6..01bec16 100644 --- a/server/thirdparty/controllers/gitHubController.js +++ b/server/thirdparty/controllers/gitHubController.js @@ -1,5 +1,20 @@ +import eventEmitter from '../../lib/eventsLib'; +import '../../lib/slackEventLib'; + const webHookExecutor = (req, res) => { - res.send({ status: 'OK' }); + const payload = req.body; + switch (payload.action) { + case 'created': { + if (Object.prototype.hasOwnProperty.call(payload, 'installation')) { + eventEmitter.emit('Github:Authorize:Init', payload); + } + break; + } + default: { + break; + } + } + res.send({ status: 'Ok' }); }; module.exports = { webHookExecutor, From 3d901421a8a9e808e450ec77e99c143bcf14b14c Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 16:47:48 +0530 Subject: [PATCH 17/33] feat: Github Authorization Implemented --- server/lib/githubEventLib.js | 33 +++++++++++++++++++ server/lib/slackEventLib.js | 6 ---- server/models/AuthorizedApps.js | 5 ++- server/models/Users.js | 2 +- .../thirdparty/controllers/authController.js | 7 ++++ .../controllers/gitHubController.js | 20 ++++++----- server/thirdparty/routes/githubRouter.js | 1 + 7 files changed, 57 insertions(+), 17 deletions(-) create mode 100644 server/lib/githubEventLib.js delete mode 100644 server/lib/slackEventLib.js diff --git a/server/lib/githubEventLib.js b/server/lib/githubEventLib.js new file mode 100644 index 0000000..f66d84c --- /dev/null +++ b/server/lib/githubEventLib.js @@ -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; + const options = { + url: 'https://github.com/login/oauth/access_token', + method: 'POST', + headers: { Accept: 'application/json' }, + json: { + code, + 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(); + }); +}); diff --git a/server/lib/slackEventLib.js b/server/lib/slackEventLib.js deleted file mode 100644 index 1d8313b..0000000 --- a/server/lib/slackEventLib.js +++ /dev/null @@ -1,6 +0,0 @@ -import mongoose from 'mongoose'; -import eventEmitter from './eventsLib'; -const authorizedApp = mongoose.model('AuthorizedApps') -eventEmitter.on('Github:Authorize:Init', (payload) => { - -}); diff --git a/server/models/AuthorizedApps.js b/server/models/AuthorizedApps.js index 0022c43..73e5a42 100644 --- a/server/models/AuthorizedApps.js +++ b/server/models/AuthorizedApps.js @@ -4,7 +4,8 @@ import shortid from 'shortid'; const AttributeObject = new Schema( { attributeName: String, - attributeValue: String, + attributeValue: Schema.Types.Mixed, + attributeType: { type: String }, }, { _id: false }, ); @@ -14,6 +15,8 @@ const AuthorizedApps = new Schema({ appName: { type: String, required: true }, email: { type: String }, // required: true credentials: [AttributeObject], + version: { type: String, default: '' }, + configurationOptions: [AttributeObject], }); export default mongoose.model('AuthorizedApps', AuthorizedApps); diff --git a/server/models/Users.js b/server/models/Users.js index 3b1ec28..5db744c 100644 --- a/server/models/Users.js +++ b/server/models/Users.js @@ -11,4 +11,4 @@ const User = new Schema( }, { timestamps: true }, ); -mongoose.model('User', User); +export default mongoose.model('User', User); diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 08c10cf..634d087 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -1,10 +1,17 @@ import axios from 'axios'; import AuthorizedApps from '../../models/AuthorizedApps'; +import Users from '../../models/Users'; + import constants from '../constants/thirdPartyConstants'; import tokenLib from '../../lib/authTokenLib'; const renderAuthRequestPage = async (req, res) => { const appName = req.params.appName.toUpperCase(); + const userDetails = await Users.findOne({ userId: req.query.userId }).lean(); + if (!userDetails) { + res.send({ status: 'Invalid user id' }); + return; + } let fetchedApp = await AuthorizedApps.findOne({ isPublished: true, userId: req.query.userId, appName }); if (!fetchedApp) { fetchedApp = await AuthorizedApps.create({ userId: req.query.userId, appName }); diff --git a/server/thirdparty/controllers/gitHubController.js b/server/thirdparty/controllers/gitHubController.js index 01bec16..3a73927 100644 --- a/server/thirdparty/controllers/gitHubController.js +++ b/server/thirdparty/controllers/gitHubController.js @@ -1,21 +1,23 @@ import eventEmitter from '../../lib/eventsLib'; -import '../../lib/slackEventLib'; +import '../../lib/githubEventLib'; +import auth from '../../lib/authTokenLib'; const webHookExecutor = (req, res) => { const payload = req.body; switch (payload.action) { - case 'created': { - if (Object.prototype.hasOwnProperty.call(payload, 'installation')) { - eventEmitter.emit('Github:Authorize:Init', payload); - } - break; - } default: { - break; + res.send({ status: 'Ok' }); } } - res.send({ status: 'Ok' }); +}; +const authCallBackHandler = async (req, res) => { + // eslint-disable-next-line camelcase + const { state } = req.query; + const decodedStateDetails = await auth.decodeGenericAuthToken(state); + eventEmitter.emit('Github:Authorize', { ...req.query, state: decodedStateDetails }); + res.send('OK'); }; module.exports = { webHookExecutor, + authCallBackHandler, }; diff --git a/server/thirdparty/routes/githubRouter.js b/server/thirdparty/routes/githubRouter.js index 61eee3b..38b7fb2 100644 --- a/server/thirdparty/routes/githubRouter.js +++ b/server/thirdparty/routes/githubRouter.js @@ -4,6 +4,7 @@ import gitHubController from '../controllers/gitHubController'; const router = Router(); router.post('/thirdparty/github/webhook', gitHubController.webHookExecutor); +router.get('/thirdparty/github/auth/callback', gitHubController.authCallBackHandler); const exports = { path: '/', From 50c1be1d57ee56cd324ced9d25de7d6ce54748d9 Mon Sep 17 00:00:00 2001 From: Sourav Date: Wed, 11 Dec 2019 12:32:48 +0530 Subject: [PATCH 18/33] fix: Github authorization will take auth token as identifier --- server/thirdparty/controllers/authController.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 634d087..b47e62b 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -7,14 +7,15 @@ import tokenLib from '../../lib/authTokenLib'; const renderAuthRequestPage = async (req, res) => { const appName = req.params.appName.toUpperCase(); - const userDetails = await Users.findOne({ userId: req.query.userId }).lean(); + const { userId } = await tokenLib.verifyAuthToken(req.query.auth); + const userDetails = await Users.findOne({ userId }).lean(); if (!userDetails) { res.send({ status: 'Invalid user id' }); return; } - let fetchedApp = await AuthorizedApps.findOne({ isPublished: true, userId: req.query.userId, appName }); + let fetchedApp = await AuthorizedApps.findOne({ isPublished: true, userId, appName }); if (!fetchedApp) { - fetchedApp = await AuthorizedApps.create({ userId: req.query.userId, appName }); + fetchedApp = await AuthorizedApps.create({ userId, appName }); } switch (appName) { case 'GITHUB': { From fadaa57314abd6d28acc71f6c353f3a1ae407671 Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 00:32:22 +0530 Subject: [PATCH 19/33] Created Webhook Route For Slack --- server/thirdparty/routes/githubRouter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server/thirdparty/routes/githubRouter.js b/server/thirdparty/routes/githubRouter.js index 38b7fb2..f5aaa6c 100644 --- a/server/thirdparty/routes/githubRouter.js +++ b/server/thirdparty/routes/githubRouter.js @@ -5,7 +5,6 @@ const router = Router(); router.post('/thirdparty/github/webhook', gitHubController.webHookExecutor); router.get('/thirdparty/github/auth/callback', gitHubController.authCallBackHandler); - const exports = { path: '/', router, From 714c728f80dd4eda9275dfc9e11e1c5098d546df Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 01:25:10 +0530 Subject: [PATCH 20/33] WIP Github Callback --- server/lib/slackEventLib.js | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 server/lib/slackEventLib.js diff --git a/server/lib/slackEventLib.js b/server/lib/slackEventLib.js new file mode 100644 index 0000000..1d8313b --- /dev/null +++ b/server/lib/slackEventLib.js @@ -0,0 +1,6 @@ +import mongoose from 'mongoose'; +import eventEmitter from './eventsLib'; +const authorizedApp = mongoose.model('AuthorizedApps') +eventEmitter.on('Github:Authorize:Init', (payload) => { + +}); From 3fe159ee84d62fb3a21b31df82616a38a4f7876d Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 16:47:48 +0530 Subject: [PATCH 21/33] feat: Github Authorization Implemented --- server/lib/slackEventLib.js | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 server/lib/slackEventLib.js diff --git a/server/lib/slackEventLib.js b/server/lib/slackEventLib.js deleted file mode 100644 index 1d8313b..0000000 --- a/server/lib/slackEventLib.js +++ /dev/null @@ -1,6 +0,0 @@ -import mongoose from 'mongoose'; -import eventEmitter from './eventsLib'; -const authorizedApp = mongoose.model('AuthorizedApps') -eventEmitter.on('Github:Authorize:Init', (payload) => { - -}); From b85d4cc2f385a2dceab541305250282a9f1af8e4 Mon Sep 17 00:00:00 2001 From: RakeshUP Date: Wed, 11 Dec 2019 15:12:58 +0530 Subject: [PATCH 22/33] Removed Inputs model, changes still in progress --- server/controller/RelayController.js | 53 ++++++++++++++++++- server/models/AuthorizedApps.js | 9 +--- server/models/Inputs.js | 15 ------ server/models/Relays.js | 3 +- server/routes/relay.js | 3 +- .../controllers/SlackActionPerformer.js | 1 - 6 files changed, 56 insertions(+), 28 deletions(-) delete mode 100644 server/models/Inputs.js diff --git a/server/controller/RelayController.js b/server/controller/RelayController.js index cc4fb64..fd4fcb0 100644 --- a/server/controller/RelayController.js +++ b/server/controller/RelayController.js @@ -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 = {}; @@ -44,14 +47,62 @@ 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); +}; + module.exports = { getRelays, + getSingleRelay, getRunningRelays, getRunningRelaysWithTriggerApp, }; diff --git a/server/models/AuthorizedApps.js b/server/models/AuthorizedApps.js index 0022c43..78bf815 100644 --- a/server/models/AuthorizedApps.js +++ b/server/models/AuthorizedApps.js @@ -1,19 +1,12 @@ 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 }, authAppId: { type: String, default: shortid.generate, unique: true }, appName: { type: String, required: true }, email: { type: String }, // required: true - credentials: [AttributeObject], + credentials: { type: Map, of: String }, }); export default mongoose.model('AuthorizedApps', AuthorizedApps); diff --git a/server/models/Inputs.js b/server/models/Inputs.js deleted file mode 100644 index cd4ad0f..0000000 --- a/server/models/Inputs.js +++ /dev/null @@ -1,15 +0,0 @@ -import mongoose, { Schema } from 'mongoose'; - -const AttributeObject = new Schema( - { - attributeName: String, - attributeValue: String, - }, { _id: false }, -); - -const Inputs = new Schema({ - userId: { type: Schema.Types.ObjectId, ref: 'User' }, - userInputs: [AttributeObject], -}); - -export default mongoose.model('Inputs', Inputs); diff --git a/server/models/Relays.js b/server/models/Relays.js index 987eff2..e14561c 100644 --- a/server/models/Relays.js +++ b/server/models/Relays.js @@ -6,10 +6,9 @@ const RelayParticipantApp = new Schema( appName: { type: String }, event: { type: String, required: true }, eventType: { type: String, required: true }, - inputs: { type: Schema.Types.ObjectId, ref: 'Inputs' }, + inputs: { type: Map }, authentication: { type: Schema.Types.ObjectId, ref: 'AuthenticatedApps' }, }, - { timestamps: true }, ); const Relays = new Schema( diff --git a/server/routes/relay.js b/server/routes/relay.js index ca1d92e..499c448 100644 --- a/server/routes/relay.js +++ b/server/routes/relay.js @@ -4,9 +4,10 @@ import RelayController from '../controller/RelayController'; const router = express.Router(); router.get('/relays', RelayController.getRelays); +router.get('/relays/:relayId', RelayController.getSingleRelay); // router.put('/change/status', RelayController.toggleRelayStatus); module.exports = { - path: '/', + path: '/api/v1', router, }; diff --git a/server/thirdparty/controllers/SlackActionPerformer.js b/server/thirdparty/controllers/SlackActionPerformer.js index faccef3..b397025 100644 --- a/server/thirdparty/controllers/SlackActionPerformer.js +++ b/server/thirdparty/controllers/SlackActionPerformer.js @@ -4,7 +4,6 @@ import axios from 'axios'; import IntegrationConfig from '../integrationConfig'; import AuthorizedApps from '../../models/AuthorizedApps'; import RelayHistory from '../../models/RelayHistory'; -import Inputs from '../../models/Inputs'; const getCredentialAttributeFromArray = (authDetails, attributeName) => { for (const attributeObject of authDetails.credentials) { From cafa608895ad12c952544d721101d88f1015c9e5 Mon Sep 17 00:00:00 2001 From: RakeshUP Date: Wed, 11 Dec 2019 19:17:37 +0530 Subject: [PATCH 23/33] Added endpoints to edit and delete relays Still inputs collection refactoring needs to be done --- server/controller/RelayController.js | 42 ++++++++++++++++++++++++++++ server/models/AuthorizedApps.js | 3 +- server/models/Relays.js | 4 +-- server/routes/relay.js | 4 ++- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/server/controller/RelayController.js b/server/controller/RelayController.js index fd4fcb0..6a235e9 100644 --- a/server/controller/RelayController.js +++ b/server/controller/RelayController.js @@ -99,10 +99,52 @@ const getSingleRelay = async (req, res) => { 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, }; diff --git a/server/models/AuthorizedApps.js b/server/models/AuthorizedApps.js index 78bf815..9407ba5 100644 --- a/server/models/AuthorizedApps.js +++ b/server/models/AuthorizedApps.js @@ -6,7 +6,8 @@ const AuthorizedApps = new Schema({ authAppId: { type: String, default: shortid.generate, unique: true }, appName: { type: String, required: true }, email: { type: String }, // required: true - credentials: { type: Map, of: String }, + auth_token: { type: String }, + credentials: { type: Map }, }); export default mongoose.model('AuthorizedApps', AuthorizedApps); diff --git a/server/models/Relays.js b/server/models/Relays.js index e14561c..553c326 100644 --- a/server/models/Relays.js +++ b/server/models/Relays.js @@ -7,7 +7,7 @@ const RelayParticipantApp = new Schema( event: { type: String, required: true }, eventType: { type: String, required: true }, inputs: { type: Map }, - authentication: { type: Schema.Types.ObjectId, ref: 'AuthenticatedApps' }, + authorizedApp: { type: Schema.Types.ObjectId, ref: 'AuthorizedApps' }, }, ); @@ -19,7 +19,7 @@ const Relays = new Schema( isRunning: { type: Boolean, default: false }, isDeleted: { type: Boolean, default: false }, isPublished: { type: Boolean, default: false }, - userId: { type: String, required: true }, + userId: { type: String }, // required: true }, { timestamps: true }, ); diff --git a/server/routes/relay.js b/server/routes/relay.js index 499c448..6537c5a 100644 --- a/server/routes/relay.js +++ b/server/routes/relay.js @@ -5,7 +5,9 @@ const router = express.Router(); router.get('/relays', RelayController.getRelays); router.get('/relays/:relayId', RelayController.getSingleRelay); -// router.put('/change/status', RelayController.toggleRelayStatus); +router.post('/relays', RelayController.createNewRelay); +router.put('/relays/:relayId', RelayController.updateExistingRelay); +router.delete('/relays/:relayId', RelayController.moveRelayToTrash); module.exports = { path: '/api/v1', From e0626a91ce4de675783a8601b043fed7f9d54363 Mon Sep 17 00:00:00 2001 From: Sourav Date: Mon, 9 Dec 2019 23:28:29 +0530 Subject: [PATCH 24/33] feat: [WIP] Github Integration --- server/thirdparty/integrationConfig.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/thirdparty/integrationConfig.js b/server/thirdparty/integrationConfig.js index 02601d9..7f7a9c2 100644 --- a/server/thirdparty/integrationConfig.js +++ b/server/thirdparty/integrationConfig.js @@ -37,6 +37,12 @@ const IntegrationConfig = { }, }], }, + GitHub: { + icon_path: '', + Events: [ + + ], + }, }; export default IntegrationConfig; From 8075fa2298e8813acef6ed444b7416203cee158d Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 00:32:22 +0530 Subject: [PATCH 25/33] Created Webhook Route For Slack --- server/app.js | 2 ++ server/lib/authTokenLib.js | 19 ++++++++++++++-- .../thirdparty/controllers/authController.js | 22 ++++++++++++++----- .../controllers/gitHubController.js | 6 +++++ server/thirdparty/routes/githubRouter.js | 13 +++++++++++ 5 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 server/thirdparty/controllers/gitHubController.js create mode 100644 server/thirdparty/routes/githubRouter.js diff --git a/server/app.js b/server/app.js index e5512cc..a568e41 100644 --- a/server/app.js +++ b/server/app.js @@ -9,6 +9,7 @@ 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'; @@ -40,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) => { diff --git a/server/lib/authTokenLib.js b/server/lib/authTokenLib.js index 748847c..7d5d26f 100644 --- a/server/lib/authTokenLib.js +++ b/server/lib/authTokenLib.js @@ -1,7 +1,7 @@ import jwt from 'jsonwebtoken'; import time from './timeLib'; -const { JWT_SECRET } = process.env; +const JWT_SECRET = 'zF9?nCmaZH&?fF9'; const createAuthToken = (userId) => { const jwtTokenObject = { @@ -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, }; diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 0a01c1a..4b45da3 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -1,14 +1,24 @@ import axios from 'axios'; import AuthorizedApps from '../../models/AuthorizedApps'; import constants from '../constants/thirdPartyConstants'; +import tokenLib from '../../lib/authTokenLib'; const renderAuthRequestPage = (req, res) => { const appName = req.params.appName.toUpperCase(); - const { userId } = req; - const authorizationRequestURL = `${constants[`${appName}_URL`]}&${constants[`${appName}_CLIENT_ID`]}&${constants[`${appName}_SCOPE`]}&${constants[`${appName}_REDIRECT_URL`]}&state=${userId}`; - res.render('OAuthWindow', { - authorizationRequestURL, - }); + switch (appName) { + case 'GITHUB': { + const identificationToken = tokenLib.createGenericAuthToken({ userId: req.query.userId }); + res.redirect(`https://github.com/apps/relayer-test/installations/new?state=${identificationToken}`); + break; + } + default: { + const { userId } = req; + const authorizationRequestURL = `${constants[`${appName}_URL`]}&${constants[`${appName}_CLIENT_ID`]}&${constants[`${appName}_SCOPE`]}&${constants[`${appName}_REDIRECT_URL`]}&state=${userId}`; + res.render('OAuthWindow', { + authorizationRequestURL, + }); + } + } }; const slackAuthGrant = (req, res) => { @@ -45,7 +55,7 @@ const slackAuthGrant = (req, res) => { const githubAuthGrant = (req, res) => { const appName = 'GITHUB'; const options = { - url: `${constants[`${appName}_AUTH_GRANT_URL`]}&${constants[`${appName}_CLIENT_ID`]}&${constants[`${appName}_CLIENT_SECRET`]}&code=${req.query.code}`, + url: 'https://github.com/apps/relayer-test/installations/new', method: 'POST', headers: { Accept: 'application/json' }, }; diff --git a/server/thirdparty/controllers/gitHubController.js b/server/thirdparty/controllers/gitHubController.js new file mode 100644 index 0000000..6a495b6 --- /dev/null +++ b/server/thirdparty/controllers/gitHubController.js @@ -0,0 +1,6 @@ +const webHookExecutor = (req, res) => { + res.send({ status: 'OK' }); +}; +module.exports = { + webHookExecutor, +}; diff --git a/server/thirdparty/routes/githubRouter.js b/server/thirdparty/routes/githubRouter.js new file mode 100644 index 0000000..61eee3b --- /dev/null +++ b/server/thirdparty/routes/githubRouter.js @@ -0,0 +1,13 @@ +import { Router } from 'express'; +import gitHubController from '../controllers/gitHubController'; + +const router = Router(); + +router.post('/thirdparty/github/webhook', gitHubController.webHookExecutor); + +const exports = { + path: '/', + router, +}; + +export default exports; From c084a097dd18ea8e48ce66dca6d67d2b5cb8e5b8 Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 01:25:10 +0530 Subject: [PATCH 26/33] WIP Github Callback --- server/controller/AppsController.js | 1 + server/lib/slackEventLib.js | 6 ++++++ server/thirdparty/controllers/authController.js | 8 ++++++-- .../thirdparty/controllers/gitHubController.js | 17 ++++++++++++++++- 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 server/lib/slackEventLib.js diff --git a/server/controller/AppsController.js b/server/controller/AppsController.js index f30ab17..9dadb8c 100644 --- a/server/controller/AppsController.js +++ b/server/controller/AppsController.js @@ -14,6 +14,7 @@ const getApps = (req, res) => { } appsAndEvents.push(singleAppDetails); } +// const AppsCollection = mongoose.model('Apps'); const generatedResponse = responseLib.generateResponse(false, actionStatus.SUCCESS, 'Apps and Events', appsAndEvents); diff --git a/server/lib/slackEventLib.js b/server/lib/slackEventLib.js new file mode 100644 index 0000000..1d8313b --- /dev/null +++ b/server/lib/slackEventLib.js @@ -0,0 +1,6 @@ +import mongoose from 'mongoose'; +import eventEmitter from './eventsLib'; +const authorizedApp = mongoose.model('AuthorizedApps') +eventEmitter.on('Github:Authorize:Init', (payload) => { + +}); diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 4b45da3..08c10cf 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -3,11 +3,15 @@ import AuthorizedApps from '../../models/AuthorizedApps'; import constants from '../constants/thirdPartyConstants'; import tokenLib from '../../lib/authTokenLib'; -const renderAuthRequestPage = (req, res) => { +const renderAuthRequestPage = async (req, res) => { const appName = req.params.appName.toUpperCase(); + let fetchedApp = await AuthorizedApps.findOne({ isPublished: true, userId: req.query.userId, appName }); + if (!fetchedApp) { + fetchedApp = await AuthorizedApps.create({ userId: req.query.userId, appName }); + } switch (appName) { case 'GITHUB': { - const identificationToken = tokenLib.createGenericAuthToken({ userId: req.query.userId }); + const identificationToken = tokenLib.createGenericAuthToken({ authAppId: fetchedApp.authAppId }); res.redirect(`https://github.com/apps/relayer-test/installations/new?state=${identificationToken}`); break; } diff --git a/server/thirdparty/controllers/gitHubController.js b/server/thirdparty/controllers/gitHubController.js index 6a495b6..01bec16 100644 --- a/server/thirdparty/controllers/gitHubController.js +++ b/server/thirdparty/controllers/gitHubController.js @@ -1,5 +1,20 @@ +import eventEmitter from '../../lib/eventsLib'; +import '../../lib/slackEventLib'; + const webHookExecutor = (req, res) => { - res.send({ status: 'OK' }); + const payload = req.body; + switch (payload.action) { + case 'created': { + if (Object.prototype.hasOwnProperty.call(payload, 'installation')) { + eventEmitter.emit('Github:Authorize:Init', payload); + } + break; + } + default: { + break; + } + } + res.send({ status: 'Ok' }); }; module.exports = { webHookExecutor, From 8d6bac9e679f688ce59d41d92c5eb34321bf532b Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 16:47:48 +0530 Subject: [PATCH 27/33] feat: Github Authorization Implemented --- server/lib/githubEventLib.js | 33 +++++++++++++++++++ server/lib/slackEventLib.js | 6 ---- server/models/Users.js | 2 +- .../thirdparty/controllers/authController.js | 7 ++++ .../controllers/gitHubController.js | 20 ++++++----- server/thirdparty/routes/githubRouter.js | 1 + 6 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 server/lib/githubEventLib.js delete mode 100644 server/lib/slackEventLib.js diff --git a/server/lib/githubEventLib.js b/server/lib/githubEventLib.js new file mode 100644 index 0000000..f66d84c --- /dev/null +++ b/server/lib/githubEventLib.js @@ -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; + const options = { + url: 'https://github.com/login/oauth/access_token', + method: 'POST', + headers: { Accept: 'application/json' }, + json: { + code, + 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(); + }); +}); diff --git a/server/lib/slackEventLib.js b/server/lib/slackEventLib.js deleted file mode 100644 index 1d8313b..0000000 --- a/server/lib/slackEventLib.js +++ /dev/null @@ -1,6 +0,0 @@ -import mongoose from 'mongoose'; -import eventEmitter from './eventsLib'; -const authorizedApp = mongoose.model('AuthorizedApps') -eventEmitter.on('Github:Authorize:Init', (payload) => { - -}); diff --git a/server/models/Users.js b/server/models/Users.js index 3b1ec28..5db744c 100644 --- a/server/models/Users.js +++ b/server/models/Users.js @@ -11,4 +11,4 @@ const User = new Schema( }, { timestamps: true }, ); -mongoose.model('User', User); +export default mongoose.model('User', User); diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 08c10cf..634d087 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -1,10 +1,17 @@ import axios from 'axios'; import AuthorizedApps from '../../models/AuthorizedApps'; +import Users from '../../models/Users'; + import constants from '../constants/thirdPartyConstants'; import tokenLib from '../../lib/authTokenLib'; const renderAuthRequestPage = async (req, res) => { const appName = req.params.appName.toUpperCase(); + const userDetails = await Users.findOne({ userId: req.query.userId }).lean(); + if (!userDetails) { + res.send({ status: 'Invalid user id' }); + return; + } let fetchedApp = await AuthorizedApps.findOne({ isPublished: true, userId: req.query.userId, appName }); if (!fetchedApp) { fetchedApp = await AuthorizedApps.create({ userId: req.query.userId, appName }); diff --git a/server/thirdparty/controllers/gitHubController.js b/server/thirdparty/controllers/gitHubController.js index 01bec16..3a73927 100644 --- a/server/thirdparty/controllers/gitHubController.js +++ b/server/thirdparty/controllers/gitHubController.js @@ -1,21 +1,23 @@ import eventEmitter from '../../lib/eventsLib'; -import '../../lib/slackEventLib'; +import '../../lib/githubEventLib'; +import auth from '../../lib/authTokenLib'; const webHookExecutor = (req, res) => { const payload = req.body; switch (payload.action) { - case 'created': { - if (Object.prototype.hasOwnProperty.call(payload, 'installation')) { - eventEmitter.emit('Github:Authorize:Init', payload); - } - break; - } default: { - break; + res.send({ status: 'Ok' }); } } - res.send({ status: 'Ok' }); +}; +const authCallBackHandler = async (req, res) => { + // eslint-disable-next-line camelcase + const { state } = req.query; + const decodedStateDetails = await auth.decodeGenericAuthToken(state); + eventEmitter.emit('Github:Authorize', { ...req.query, state: decodedStateDetails }); + res.send('OK'); }; module.exports = { webHookExecutor, + authCallBackHandler, }; diff --git a/server/thirdparty/routes/githubRouter.js b/server/thirdparty/routes/githubRouter.js index 61eee3b..38b7fb2 100644 --- a/server/thirdparty/routes/githubRouter.js +++ b/server/thirdparty/routes/githubRouter.js @@ -4,6 +4,7 @@ import gitHubController from '../controllers/gitHubController'; const router = Router(); router.post('/thirdparty/github/webhook', gitHubController.webHookExecutor); +router.get('/thirdparty/github/auth/callback', gitHubController.authCallBackHandler); const exports = { path: '/', From 8ba427c79b77f64471882c43ff1724d66b51edef Mon Sep 17 00:00:00 2001 From: Sourav Date: Wed, 11 Dec 2019 12:32:48 +0530 Subject: [PATCH 28/33] fix: Github authorization will take auth token as identifier --- server/thirdparty/controllers/authController.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/thirdparty/controllers/authController.js b/server/thirdparty/controllers/authController.js index 634d087..b47e62b 100644 --- a/server/thirdparty/controllers/authController.js +++ b/server/thirdparty/controllers/authController.js @@ -7,14 +7,15 @@ import tokenLib from '../../lib/authTokenLib'; const renderAuthRequestPage = async (req, res) => { const appName = req.params.appName.toUpperCase(); - const userDetails = await Users.findOne({ userId: req.query.userId }).lean(); + const { userId } = await tokenLib.verifyAuthToken(req.query.auth); + const userDetails = await Users.findOne({ userId }).lean(); if (!userDetails) { res.send({ status: 'Invalid user id' }); return; } - let fetchedApp = await AuthorizedApps.findOne({ isPublished: true, userId: req.query.userId, appName }); + let fetchedApp = await AuthorizedApps.findOne({ isPublished: true, userId, appName }); if (!fetchedApp) { - fetchedApp = await AuthorizedApps.create({ userId: req.query.userId, appName }); + fetchedApp = await AuthorizedApps.create({ userId, appName }); } switch (appName) { case 'GITHUB': { From 4bfe9de252f02cd26e5a5c9ff20fba54d2864379 Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 00:32:22 +0530 Subject: [PATCH 29/33] Created Webhook Route For Slack --- server/thirdparty/routes/githubRouter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server/thirdparty/routes/githubRouter.js b/server/thirdparty/routes/githubRouter.js index 38b7fb2..f5aaa6c 100644 --- a/server/thirdparty/routes/githubRouter.js +++ b/server/thirdparty/routes/githubRouter.js @@ -5,7 +5,6 @@ const router = Router(); router.post('/thirdparty/github/webhook', gitHubController.webHookExecutor); router.get('/thirdparty/github/auth/callback', gitHubController.authCallBackHandler); - const exports = { path: '/', router, From 2945963752881bff653855207c5411d0cb18b398 Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 01:25:10 +0530 Subject: [PATCH 30/33] WIP Github Callback --- server/lib/slackEventLib.js | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 server/lib/slackEventLib.js diff --git a/server/lib/slackEventLib.js b/server/lib/slackEventLib.js new file mode 100644 index 0000000..1d8313b --- /dev/null +++ b/server/lib/slackEventLib.js @@ -0,0 +1,6 @@ +import mongoose from 'mongoose'; +import eventEmitter from './eventsLib'; +const authorizedApp = mongoose.model('AuthorizedApps') +eventEmitter.on('Github:Authorize:Init', (payload) => { + +}); From 9859e7cbb9c398e56a9bae723a0eaceb7bf7f087 Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 16:47:48 +0530 Subject: [PATCH 31/33] feat: Github Authorization Implemented --- server/lib/slackEventLib.js | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 server/lib/slackEventLib.js diff --git a/server/lib/slackEventLib.js b/server/lib/slackEventLib.js deleted file mode 100644 index 1d8313b..0000000 --- a/server/lib/slackEventLib.js +++ /dev/null @@ -1,6 +0,0 @@ -import mongoose from 'mongoose'; -import eventEmitter from './eventsLib'; -const authorizedApp = mongoose.model('AuthorizedApps') -eventEmitter.on('Github:Authorize:Init', (payload) => { - -}); From baa4d7728e5adc77c8ddc507da8810dbd14be53a Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 01:25:10 +0530 Subject: [PATCH 32/33] WIP Github Callback --- server/lib/slackEventLib.js | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 server/lib/slackEventLib.js diff --git a/server/lib/slackEventLib.js b/server/lib/slackEventLib.js new file mode 100644 index 0000000..1d8313b --- /dev/null +++ b/server/lib/slackEventLib.js @@ -0,0 +1,6 @@ +import mongoose from 'mongoose'; +import eventEmitter from './eventsLib'; +const authorizedApp = mongoose.model('AuthorizedApps') +eventEmitter.on('Github:Authorize:Init', (payload) => { + +}); From eb61325acf6bd943b84635c1b692153ee7ea40c4 Mon Sep 17 00:00:00 2001 From: Sourav Date: Tue, 10 Dec 2019 16:47:48 +0530 Subject: [PATCH 33/33] feat: Github Authorization Implemented --- server/lib/slackEventLib.js | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 server/lib/slackEventLib.js diff --git a/server/lib/slackEventLib.js b/server/lib/slackEventLib.js deleted file mode 100644 index 1d8313b..0000000 --- a/server/lib/slackEventLib.js +++ /dev/null @@ -1,6 +0,0 @@ -import mongoose from 'mongoose'; -import eventEmitter from './eventsLib'; -const authorizedApp = mongoose.model('AuthorizedApps') -eventEmitter.on('Github:Authorize:Init', (payload) => { - -});