From e4250f1e881454ddde9d55c97fb3e566aedfa057 Mon Sep 17 00:00:00 2001 From: pan261 Date: Wed, 19 Jan 2022 14:41:16 -0500 Subject: [PATCH 1/7] added 'npm run docker:build' script --- package-lock.json | 12 ++++++------ package.json | 3 ++- src/server.ts | 5 +++++ src/taskRunner.ts | 44 ++++++++++++++++++++++---------------------- src/watcher2.ts | 22 ++++++++++++++++++++++ 5 files changed, 57 insertions(+), 29 deletions(-) create mode 100644 src/watcher2.ts diff --git a/package-lock.json b/package-lock.json index 3f1ddbb..6f315b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1453,9 +1453,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.14.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", - "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==", + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", + "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", "funding": [ { "type": "individual", @@ -4296,9 +4296,9 @@ "dev": true }, "follow-redirects": { - "version": "1.14.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", - "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==" + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", + "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" }, "forever-agent": { "version": "0.6.1", diff --git a/package.json b/package.json index cc46461..9e68cc0 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "start": "node /src/server.js", "test": "echo \"Error: no test specified\" && exit 1", - "tsc": "tsc" + "tsc": "tsc", + "docker:build": "tsc && docker-compose up --build" }, "repository": { "type": "git", diff --git a/src/server.ts b/src/server.ts index 2aa3001..ef48fe2 100644 --- a/src/server.ts +++ b/src/server.ts @@ -99,6 +99,11 @@ const createServer = () => { } }); + app.post('/hook', function (req: any, res: any) { + console.log(req.body); + res.status(200).end; + }); + // Default endpoint app.use((err: any, req: any, res: any, next: any) => { console.error(err); diff --git a/src/taskRunner.ts b/src/taskRunner.ts index 1e4b9ec..2ee3c55 100644 --- a/src/taskRunner.ts +++ b/src/taskRunner.ts @@ -100,33 +100,33 @@ const getCredentials = () => { updateInterval = setInterval(fetchToken, 3300 * 1000); }; +getCredentials(); + /** * Function to dispatch the watcher */ -const dispatchWatcher = async (interval: any) => { - if (credentials === null) { - console.log( - '\x1b[93mtaskRunner.js::dispatchWatcher:', - '\x1b[0mNo credentials' - ); - return; - } +// const dispatchWatcher = async (interval: any) => { +// if (credentials === null) { +// console.log( +// '\x1b[93mtaskRunner.js::dispatchWatcher:', +// '\x1b[0mNo credentials' +// ); +// return; +// } - const items = await watcher.watch(config.projectID, folderID, credentials, interval); +// const items = await watcher.watch(config.projectID, folderID, credentials, interval); - if (Object.keys(items).length == 0) { - return; // no items to download - } - - for (var index in items) { - // dispatch the downloader here - downloader.download(items[index].location, items[index].name, __dirname + '/' + localDir, credentials); - } -}; +// if (Object.keys(items).length == 0) { +// return; // no items to download +// } -getCredentials(); +// for (var index in items) { +// // dispatch the downloader here +// downloader.download(items[index].location, items[index].name, __dirname + '/' + localDir, credentials); +// } +// }; -// There should be a buffer between how far back the watcher checks -// and how often it runs. We may need to test this out. -setInterval(dispatchWatcher, 5000, 7000); +// // There should be a buffer between how far back the watcher checks +// // and how often it runs. We may need to test this out. +// setInterval(dispatchWatcher, 5000, 7000); diff --git a/src/watcher2.ts b/src/watcher2.ts new file mode 100644 index 0000000..12ff73b --- /dev/null +++ b/src/watcher2.ts @@ -0,0 +1,22 @@ +// const axios = require('axios'); + +exports.watch = (credentials: any) => { + axios({ + method: 'POST', + url: 'https://developer.api.autodesk.com/webhooks/v1/systems/data/events/dm.version.added/hooks', + headers: { + Authorization: `Bearer ${credentials.access_token}` + }, + body: { + callbackUrl: "http://localhost:3000/hook", + autoReactivateHook: false, + scope: { + folder: "urn:adsk.wipprod:fs.folder:co.IxiedfeBTOGg6NSIGQhXxQ" // Gantry folder + } + } + }).then((res: any) => { + console.log(res.data); + }).catch((err: any) => { + console.log(err); + }); +} \ No newline at end of file From 0e61ae56ca65351f101e6fb4481e6cf4a6734e00 Mon Sep 17 00:00:00 2001 From: pan261 Date: Thu, 20 Jan 2022 13:04:16 -0500 Subject: [PATCH 2/7] parsing incomming message, docker with npm --- docker-compose.yml | 66 +++++++++++++++++++++++----------------------- package.json | 2 +- src/server.ts | 21 +++++++++++++-- src/taskRunner.ts | 5 ++++ src/watcher2.ts | 13 +++++---- 5 files changed, 66 insertions(+), 41 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1f1d65e..170764d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,38 +25,38 @@ services: network_mode: host command: "node taskRunner.js gantry" - task_runner_lathe: - build: - context: . - dockerfile: Dockerfile - env_file: ./.env - volumes: - - ./src/build:/ffs/ - - /ffs/node_modules - - ${LOCAL_DIR_LATHE}:/ffs/FileShare/Lathe - network_mode: host - command: "node taskRunner.js lathe" + # task_runner_lathe: + # build: + # context: . + # dockerfile: Dockerfile + # env_file: ./.env + # volumes: + # - ./src/build:/ffs/ + # - /ffs/node_modules + # - ${LOCAL_DIR_LATHE}:/ffs/FileShare/Lathe + # network_mode: host + # command: "node taskRunner.js lathe" - task_runner_mill: - build: - context: . - dockerfile: Dockerfile - env_file: ./.env - volumes: - - ./src/build:/ffs/ - - /ffs/node_modules - - ${LOCAL_DIR_MILL}:/ffs/FileShare/Mill - network_mode: host - command: "node taskRunner.js mill" + # task_runner_mill: + # build: + # context: . + # dockerfile: Dockerfile + # env_file: ./.env + # volumes: + # - ./src/build:/ffs/ + # - /ffs/node_modules + # - ${LOCAL_DIR_MILL}:/ffs/FileShare/Mill + # network_mode: host + # command: "node taskRunner.js mill" - task_runner_waterjet: - build: - context: . - dockerfile: Dockerfile - env_file: ./.env - volumes: - - ./src/build:/ffs/ - - /ffs/node_modules - - ${LOCAL_DIR_WATERJET}:/ffs/FileShare/Waterjet - network_mode: host - command: "node taskRunner.js waterjet" \ No newline at end of file + # task_runner_waterjet: + # build: + # context: . + # dockerfile: Dockerfile + # env_file: ./.env + # volumes: + # - ./src/build:/ffs/ + # - /ffs/node_modules + # - ${LOCAL_DIR_WATERJET}:/ffs/FileShare/Waterjet + # network_mode: host + # command: "node taskRunner.js waterjet" \ No newline at end of file diff --git a/package.json b/package.json index 9e68cc0..00ee711 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "start": "node /src/server.js", "test": "echo \"Error: no test specified\" && exit 1", "tsc": "tsc", - "docker:build": "tsc && docker-compose up --build" + "docker:build": "tsc && docker-compose up --build --remove-orphans" }, "repository": { "type": "git", diff --git a/src/server.ts b/src/server.ts index ef48fe2..81ba333 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,5 +1,6 @@ import express = require('express'); import * as config from './config'; +const fs = require('fs'); /** @@ -100,8 +101,24 @@ const createServer = () => { }); app.post('/hook', function (req: any, res: any) { - console.log(req.body); - res.status(200).end; + // console.log(req.data); + // console.log(req.payload); + // console.log(req.res); + // fs.writeFile(__dirname + '/FileShare/res.json', req, (err: any) => { + // if (err) { + // throw err; + // } + // }) + let body: any = []; + req.on('error', (err: any) => { + console.error(err); + }).on('data', (chunk: any) => { + body.push(chunk); + }).on('end', () => { + body = Buffer.concat(body).toString(); + console.log(body) + }) + res.status(204).send(); }); // Default endpoint diff --git a/src/taskRunner.ts b/src/taskRunner.ts index 2ee3c55..a67b554 100644 --- a/src/taskRunner.ts +++ b/src/taskRunner.ts @@ -1,5 +1,6 @@ const axios = require('axios'); const watcher = require('./watcher'); +const watcher2 = require('./watcher2') const downloader = require('./downloader'); const fs = require('fs'); import * as config from './config'; @@ -68,6 +69,7 @@ const fetchToken = () => { '\x1b[92mtaskRunner.js::fetchToken:', '\x1b[0mCredentials received' ); + watcher2.createHook(credentials); } else { console.log( '\x1b[96mtaskRunner.js::fetchToken:', @@ -102,6 +104,9 @@ const getCredentials = () => { getCredentials(); + +// Below is the previous watcher implementation + /** * Function to dispatch the watcher */ diff --git a/src/watcher2.ts b/src/watcher2.ts index 12ff73b..1af27f4 100644 --- a/src/watcher2.ts +++ b/src/watcher2.ts @@ -1,21 +1,24 @@ -// const axios = require('axios'); -exports.watch = (credentials: any) => { + +exports.createHook = (credentials: any) => { + const axios = require('axios'); + axios({ method: 'POST', url: 'https://developer.api.autodesk.com/webhooks/v1/systems/data/events/dm.version.added/hooks', headers: { + 'Content-Type': 'application/json', Authorization: `Bearer ${credentials.access_token}` }, - body: { - callbackUrl: "http://localhost:3000/hook", + data: { + callbackUrl: "http://6ae7-128-210-106-76.ngrok.io/hook", autoReactivateHook: false, scope: { folder: "urn:adsk.wipprod:fs.folder:co.IxiedfeBTOGg6NSIGQhXxQ" // Gantry folder } } }).then((res: any) => { - console.log(res.data); + console.log(res); }).catch((err: any) => { console.log(err); }); From c046ee3dd6d2c6d95f60d44952ca8432493ed54b Mon Sep 17 00:00:00 2001 From: pan261 Date: Mon, 24 Jan 2022 16:08:31 -0500 Subject: [PATCH 3/7] webhook management w/server --- docker-compose.yml | 24 +++++----- src/config.ts | 3 +- src/downloader.ts | 21 ++++++++- src/server.ts | 17 ++++--- src/taskRunner.ts | 5 ++- src/watcher2.ts | 25 ----------- src/webhooks.ts | 109 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 154 insertions(+), 50 deletions(-) delete mode 100644 src/watcher2.ts create mode 100644 src/webhooks.ts diff --git a/docker-compose.yml b/docker-compose.yml index 170764d..bc21142 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,18 +13,18 @@ services: - ${PORT}:${PORT} command: "node server.js" - task_runner_gantry: - build: - context: . - dockerfile: Dockerfile - env_file: ./.env - volumes: - - ./src/build:/ffs/ - - /ffs/node_modules - - ${LOCAL_DIR_GANTRY}:/ffs/FileShare/Gantry - network_mode: host - command: "node taskRunner.js gantry" - + # task_runner_gantry: + # build: + # context: . + # dockerfile: Dockerfile + # env_file: ./.env + # volumes: + # - ./src/build:/ffs/ + # - /ffs/node_modules + # - ${LOCAL_DIR_GANTRY}:/ffs/FileShare/Gantry + # network_mode: host + # command: "node taskRunner.js gantry" + # # task_runner_lathe: # build: # context: . diff --git a/src/config.ts b/src/config.ts index 51e3beb..34ace86 100644 --- a/src/config.ts +++ b/src/config.ts @@ -48,4 +48,5 @@ export const folderMap: any = { export const port = process.env.PORT; export const forgeClientId = process.env.FORGE_CLIENT_ID; export const forgeClientSecret = process.env.FORGE_CLIENT_SECRET; -export const forgeCallbackURL = process.env.FORGE_CALLBACK_URL; \ No newline at end of file +export const forgeCallbackURL = process.env.FORGE_CALLBACK_URL; +export const hookCallbackURL = `${process.env.HOOK_CALLBACK_HOSTNAME}/hook`; \ No newline at end of file diff --git a/src/downloader.ts b/src/downloader.ts index 144a167..18ac19a 100644 --- a/src/downloader.ts +++ b/src/downloader.ts @@ -14,7 +14,8 @@ const ObjectsApi = new ForgeSDK.ObjectsApi(); * @param {string} destination * @param {Object} credentials */ -exports.download = (storageLocation: string, fileName: string, destination: string, credentials: any) => { +exports.download = async (projectID: string, itemID: string, fileName: string, destination: string, credentials: any) => { + const storageLocation = await getStorageLocation(projectID, itemID, credentials); ObjectsApi.getObject('wip.dm.prod', storageLocation, {}, config.authClient, credentials) .then((res: any) => { @@ -27,3 +28,21 @@ exports.download = (storageLocation: string, fileName: string, destination: stri }); } +const getStorageLocation = (projectID: string, itemID: string, credentials: any) => { + axios({ + method: 'GET', + url: `https://developer.api.autodesk.com/data/v1/projects/${projectID}/items/${itemID}`, + headers: { + Authorization: `Bearer ${credentials.access_token}` + }, + }).then((res: any) => { + return res.data.included; + }).then((data: any) => { + var storageID = data.relationships.storage.data.id; + storageID = storageID.substring(storageID.indexOf('/') + 1); + return storageID; + }).catch((err: any) => { + console.log(err) + }) +} + diff --git a/src/server.ts b/src/server.ts index 81ba333..6401c0d 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,6 +1,7 @@ import express = require('express'); +const webhooks = require('./webhooks'); import * as config from './config'; -const fs = require('fs'); +import fs = require('fs'); /** @@ -64,6 +65,9 @@ const createServer = () => { ); }) .then(() => { + // run the setup function for webhooks + webhooks.setup(credentials); + // sets refresh() function to run on an interval every 55 minutes intervalID = setInterval(() => refresh(), refreshTime * 1000); // 55 seconds to ms @@ -101,14 +105,6 @@ const createServer = () => { }); app.post('/hook', function (req: any, res: any) { - // console.log(req.data); - // console.log(req.payload); - // console.log(req.res); - // fs.writeFile(__dirname + '/FileShare/res.json', req, (err: any) => { - // if (err) { - // throw err; - // } - // }) let body: any = []; req.on('error', (err: any) => { console.error(err); @@ -144,6 +140,9 @@ const refresh = () => { '\x1b[0mnew token generated from refresh token:' ); console.log(credentials); + + // check on webhooks + webhooks.setup(credentials); }) .catch(err => { console.log(err); diff --git a/src/taskRunner.ts b/src/taskRunner.ts index a67b554..8686fd7 100644 --- a/src/taskRunner.ts +++ b/src/taskRunner.ts @@ -1,6 +1,6 @@ const axios = require('axios'); const watcher = require('./watcher'); -const watcher2 = require('./watcher2') +// const watcher2 = require('./watcher2') const downloader = require('./downloader'); const fs = require('fs'); import * as config from './config'; @@ -69,7 +69,8 @@ const fetchToken = () => { '\x1b[92mtaskRunner.js::fetchToken:', '\x1b[0mCredentials received' ); - watcher2.createHook(credentials); + // watcher2.createHook(credentials); + // watcher2.setup(credentials); } else { console.log( '\x1b[96mtaskRunner.js::fetchToken:', diff --git a/src/watcher2.ts b/src/watcher2.ts deleted file mode 100644 index 1af27f4..0000000 --- a/src/watcher2.ts +++ /dev/null @@ -1,25 +0,0 @@ - - -exports.createHook = (credentials: any) => { - const axios = require('axios'); - - axios({ - method: 'POST', - url: 'https://developer.api.autodesk.com/webhooks/v1/systems/data/events/dm.version.added/hooks', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${credentials.access_token}` - }, - data: { - callbackUrl: "http://6ae7-128-210-106-76.ngrok.io/hook", - autoReactivateHook: false, - scope: { - folder: "urn:adsk.wipprod:fs.folder:co.IxiedfeBTOGg6NSIGQhXxQ" // Gantry folder - } - } - }).then((res: any) => { - console.log(res); - }).catch((err: any) => { - console.log(err); - }); -} \ No newline at end of file diff --git a/src/webhooks.ts b/src/webhooks.ts new file mode 100644 index 0000000..cea9053 --- /dev/null +++ b/src/webhooks.ts @@ -0,0 +1,109 @@ +const axios = require('axios'); +import * as config from './config'; + + +// instead of using this file to constanly poll the api, it will setup and maintain the webhooks + +const FileShareID = "urn:adsk.wipprod:fs.folder:co.T0n0mYQeS16K0lq1VuuYVQ" + +/** + * Function to manage hook lifecycle + */ +exports.setup = (credentials: any) => { + axios({ + method: 'GET', + url: 'https://developer.api.autodesk.com/webhooks/v1/hooks', + headers: { + Authorization: `Bearer ${credentials.access_token}` + } + }).then((res: any) => { + return res.data.data; + }).then((data: any) => { + var hookEvents = []; + for (var index in data) { + + const hook = data[index]; + + if (hook.callbackUrl != config.hookCallbackURL) { // hooks with invalid callbacks will be deleted, not added to current list of hooks + deleteHook(hook.event, hook.hookId, credentials); + } else { + hookEvents.push(hook.event); + if (hook.status === 'inactive') { + console.log("reactivating " + hook.event + " hook"); + reactivateHook(hook.event, hook.hookId, credentials); + } + } + } + + // these are the events we want, if not in the list, then register the hook + if (!hookEvents.includes('dm.version.added')) { + createHook('dm.version.added', credentials); + } + + if (!hookEvents.includes('dm.version.modified')) { + createHook('dm.version.modified', credentials); + } + + if (!hookEvents.includes('dm.version.deleted')) { + createHook('dm.version.deleted', credentials); + } + }).catch((err: any) => { + console.log(err); + }) +} + + +const createHook = (event: string, credentials: any) => { + axios({ + method: 'POST', + url: `https://developer.api.autodesk.com/webhooks/v1/systems/data/events/${event}/hooks`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${credentials.access_token}` + }, + data: { + autoReactivateHook: true, + callbackUrl: config.hookCallbackURL, + scope: { + folder: FileShareID + } + } + }).then((res: any) => { + console.log("sucessfully created hook for " + event); + }).catch((err: any) => { + console.log("error creating hook for " + event + ": " + err.response.status); + }); +} + +const reactivateHook = (event: string, hookID: string, credentials: any) => { + axios({ + method: 'PATCH', + url: `https://developer.api.autodesk.com/webhooks/v1/systems/data/events/${event}/hooks/${hookID}`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${credentials.access_token}` + }, + data: { + status: 'active', + autoReactivateHook: true, + } + }).then((res: any) => { + console.log("successfully reactivated " + event + " hook"); + }).catch((err: any) => { + console.log("error reactivating hook for " + event + ": " + err.response.status); + }); +} + +const deleteHook = (event:string, hookID: string, credentials: any) => { + axios({ + method: 'DELETE', + url: `https://developer.api.autodesk.com/webhooks/v1/systems/data/events/${event}/hooks/${hookID}`, + headers: { + Authorization: `Bearer ${credentials.access_token}` + } + }).then((res: any) => { + console.log("deleted " + event + " hook with bad callback url"); + }).catch((err: any) => { + console.log("error deleting hook for " + event + ": " + err.response.status); + }); +} \ No newline at end of file From 92c276630f25067fe516d450f5200db9d8523830 Mon Sep 17 00:00:00 2001 From: pan261 Date: Mon, 24 Jan 2022 17:45:12 -0500 Subject: [PATCH 4/7] rearraged files and updated tsconfig --- package.json | 4 ++-- src/downloader.ts | 1 + src/{ => old}/taskRunner.ts | 45 ++++++++++++++++++------------------- src/{ => old}/watcher.ts | 2 +- src/webhooks.ts | 1 + tsconfig.json | 7 ++++-- 6 files changed, 32 insertions(+), 28 deletions(-) rename src/{ => old}/taskRunner.ts (77%) rename src/{ => old}/watcher.ts (98%) diff --git a/package.json b/package.json index 00ee711..cd51b70 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,10 @@ "version": "1.0.0", "main": "/src/server.js", "scripts": { - "start": "node /src/server.js", + "start": "docker-compose up", "test": "echo \"Error: no test specified\" && exit 1", "tsc": "tsc", - "docker:build": "tsc && docker-compose up --build --remove-orphans" + "docker:build": "tsc --build && docker-compose up --build --remove-orphans" }, "repository": { "type": "git", diff --git a/src/downloader.ts b/src/downloader.ts index 18ac19a..8a78bfa 100644 --- a/src/downloader.ts +++ b/src/downloader.ts @@ -1,4 +1,5 @@ const fs = require('fs').promises; +const axios = require('axios'); const ForgeSDK = require('forge-apis'); const config = require('./config'); diff --git a/src/taskRunner.ts b/src/old/taskRunner.ts similarity index 77% rename from src/taskRunner.ts rename to src/old/taskRunner.ts index 8686fd7..0a84cb5 100644 --- a/src/taskRunner.ts +++ b/src/old/taskRunner.ts @@ -1,9 +1,8 @@ const axios = require('axios'); const watcher = require('./watcher'); -// const watcher2 = require('./watcher2') const downloader = require('./downloader'); const fs = require('fs'); -import * as config from './config'; +import * as config from '../config'; /** @@ -111,28 +110,28 @@ getCredentials(); /** * Function to dispatch the watcher */ -// const dispatchWatcher = async (interval: any) => { -// if (credentials === null) { -// console.log( -// '\x1b[93mtaskRunner.js::dispatchWatcher:', -// '\x1b[0mNo credentials' -// ); -// return; -// } +const dispatchWatcher = async (interval: any) => { + if (credentials === null) { + console.log( + '\x1b[93mtaskRunner.js::dispatchWatcher:', + '\x1b[0mNo credentials' + ); + return; + } -// const items = await watcher.watch(config.projectID, folderID, credentials, interval); + const items = await watcher.watch(config.projectID, folderID, credentials, interval); -// if (Object.keys(items).length == 0) { -// return; // no items to download -// } - -// for (var index in items) { -// // dispatch the downloader here -// downloader.download(items[index].location, items[index].name, __dirname + '/' + localDir, credentials); -// } -// }; + if (Object.keys(items).length == 0) { + return; // no items to download + } + + for (var index in items) { + // dispatch the downloader here + downloader.download(items[index].location, items[index].name, __dirname + '/' + localDir, credentials); + } +}; -// // There should be a buffer between how far back the watcher checks -// // and how often it runs. We may need to test this out. -// setInterval(dispatchWatcher, 5000, 7000); +// There should be a buffer between how far back the watcher checks +// and how often it runs. We may need to test this out. +setInterval(dispatchWatcher, 5000, 7000); diff --git a/src/watcher.ts b/src/old/watcher.ts similarity index 98% rename from src/watcher.ts rename to src/old/watcher.ts index 4824607..1e9ed3a 100644 --- a/src/watcher.ts +++ b/src/old/watcher.ts @@ -1,4 +1,4 @@ -const axios = require('axios'); +// const axios = require('axios'); /** * Kevin Pan | pan261@purdue.edu | Last Modified: 12/2/2021 diff --git a/src/webhooks.ts b/src/webhooks.ts index cea9053..738026d 100644 --- a/src/webhooks.ts +++ b/src/webhooks.ts @@ -10,6 +10,7 @@ const FileShareID = "urn:adsk.wipprod:fs.folder:co.T0n0mYQeS16K0lq1VuuYVQ" * Function to manage hook lifecycle */ exports.setup = (credentials: any) => { + console.log("checking hooks"); axios({ method: 'GET', url: 'https://developer.api.autodesk.com/webhooks/v1/hooks', diff --git a/tsconfig.json b/tsconfig.json index 0b315b7..7f1a7ff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,7 +25,7 @@ /* Modules */ "module": "commonjs", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ + "rootDir": "./src", /* Specify the root folder within your source files. */ // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ @@ -99,5 +99,8 @@ "skipLibCheck": true, /* Skip type checking all .d.ts files. */ "outDir": "./src/build", "sourceMap": true - } + }, + "exclude": [ + "./src/old" + ] } From 5da17802397500650260ca521ab70561e16af5c1 Mon Sep 17 00:00:00 2001 From: pan261 Date: Mon, 24 Jan 2022 22:35:28 -0500 Subject: [PATCH 5/7] downloading file on 'added' event --- docker-compose.yml | 3 ++- package.json | 2 +- src/config.ts | 8 ++++++++ src/downloader.ts | 8 ++++---- src/server.ts | 16 ++++++++++++---- src/webhooks.ts | 16 ++++++++++++++-- 6 files changed, 41 insertions(+), 12 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index bc21142..87c052f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.8" services: - auth_server: + server: build: context: . dockerfile: Dockerfile @@ -9,6 +9,7 @@ services: volumes: - ./src/build:/ffs/ - /ffs/node_modules + - ./FileShare:/ffs/FileShare/ ports: - ${PORT}:${PORT} command: "node server.js" diff --git a/package.json b/package.json index cd51b70..ab72752 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "start": "docker-compose up", "test": "echo \"Error: no test specified\" && exit 1", "tsc": "tsc", - "docker:build": "tsc --build && docker-compose up --build --remove-orphans" + "build": "tsc --build && docker-compose up --build --remove-orphans" }, "repository": { "type": "git", diff --git a/src/config.ts b/src/config.ts index 34ace86..abac609 100644 --- a/src/config.ts +++ b/src/config.ts @@ -45,6 +45,14 @@ export const folderMap: any = { } }; +// dictionary for looking up local directory path based on folder ID +export const folderIDtoLocal: any = { + "urn:adsk.wipprod:fs.folder:co.IxiedfeBTOGg6NSIGQhXxQ" : "/Gantry", + "urn:adsk.wipprod:fs.folder:co.FDWCeyBGQX2rv8xSNWo5lg": "/Lathe", + "urn:adsk.wipprod:fs.folder:co.nEihcpHUSW-ZsVzU-__1iw": "/Mill", + "urn:adsk.wipprod:fs.folder:co.vJLXAKGbQQayeljr-nwztQ": "/Waterjet" +} + export const port = process.env.PORT; export const forgeClientId = process.env.FORGE_CLIENT_ID; export const forgeClientSecret = process.env.FORGE_CLIENT_SECRET; diff --git a/src/downloader.ts b/src/downloader.ts index 8a78bfa..3aeabaf 100644 --- a/src/downloader.ts +++ b/src/downloader.ts @@ -20,7 +20,7 @@ exports.download = async (projectID: string, itemID: string, fileName: string, d ObjectsApi.getObject('wip.dm.prod', storageLocation, {}, config.authClient, credentials) .then((res: any) => { - console.log(res) + console.log("downloaded file " + fileName + " to " + destination); fs.writeFile(`${destination}/${fileName}`, res.body) }) .catch((err: any) => { @@ -30,20 +30,20 @@ exports.download = async (projectID: string, itemID: string, fileName: string, d } const getStorageLocation = (projectID: string, itemID: string, credentials: any) => { - axios({ + return axios({ method: 'GET', url: `https://developer.api.autodesk.com/data/v1/projects/${projectID}/items/${itemID}`, headers: { Authorization: `Bearer ${credentials.access_token}` }, }).then((res: any) => { - return res.data.included; + return res.data.included[0]; }).then((data: any) => { var storageID = data.relationships.storage.data.id; storageID = storageID.substring(storageID.indexOf('/') + 1); return storageID; }).catch((err: any) => { - console.log(err) + console.log("unable to get storage location for " + itemID + ": " + err.response.status); }) } diff --git a/src/server.ts b/src/server.ts index 6401c0d..6973a50 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,11 +1,12 @@ import express = require('express'); -const webhooks = require('./webhooks'); import * as config from './config'; import fs = require('fs'); +const webhooks = require('./webhooks'); +const downloader = require('./downloader'); /** - * Kevin Pan | pan261@purdue.edu | Last Modified: 12/2/2021 + * Kevin Pan | pan261@purdue.edu | Last Modified: 1/24/2022 * * Authentication server used to perform 3-legged auth through browser. * @@ -111,8 +112,15 @@ const createServer = () => { }).on('data', (chunk: any) => { body.push(chunk); }).on('end', () => { - body = Buffer.concat(body).toString(); - console.log(body) + body = JSON.parse(Buffer.concat(body).toString()); + + if (credentials && body.hook.event === "dm.version.added") { + const itemID = body.payload.lineageUrn; + const fileName = body.payload.name; + const destination = __dirname + '/FileShare' + config.folderIDtoLocal[body.payload.parentFolderUrn]; + + downloader.download(config.projectID, itemID, fileName, destination, credentials); + } }) res.status(204).send(); }); diff --git a/src/webhooks.ts b/src/webhooks.ts index 738026d..c21f68a 100644 --- a/src/webhooks.ts +++ b/src/webhooks.ts @@ -2,7 +2,19 @@ const axios = require('axios'); import * as config from './config'; -// instead of using this file to constanly poll the api, it will setup and maintain the webhooks +/** + * Kevin Pan | pan261@purdue.edu | Last Modified: 1/24/2022 + * + * Functions to manage hook lifecycle + * setup() checks if hooks are valid and active, if not it will reactivated + * and/or recreate the specified hook. There is one hook for each of the + * following events: + * + * - 'dm.version.added' (file added) + * - 'dm.version.modified' (file modified) + * - dm.version.deleted (file deleted) + * + */ const FileShareID = "urn:adsk.wipprod:fs.folder:co.T0n0mYQeS16K0lq1VuuYVQ" @@ -63,7 +75,7 @@ const createHook = (event: string, credentials: any) => { Authorization: `Bearer ${credentials.access_token}` }, data: { - autoReactivateHook: true, + autoReactivateHook: "true", callbackUrl: config.hookCallbackURL, scope: { folder: FileShareID From 864e6a92e04c0123c15b8bde4a410dcee5dc29dd Mon Sep 17 00:00:00 2001 From: pan261 Date: Fri, 28 Jan 2022 16:28:28 -0500 Subject: [PATCH 6/7] added ngrok script, updated npm scripts and docker --- Dockerfile | 2 +- package-lock.json | 16 ++++++++++++++++ package.json | 9 ++++++--- src/ngrok_script.js | 25 +++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 src/ngrok_script.js diff --git a/Dockerfile b/Dockerfile index c2bebc1..3bfc742 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ FROM node:16 WORKDIR /ffs/ COPY package.json ./ -RUN npm install +RUN npm install --only=prod COPY /src/build ./ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6f315b2..89acd0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "eslint-plugin-import": "^2.25.3", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.1", + "opener": "^1.5.2", "typescript": "^4.5.2" } }, @@ -2335,6 +2336,15 @@ "wrappy": "1" } }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -4927,6 +4937,12 @@ "wrappy": "1" } }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true + }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", diff --git a/package.json b/package.json index ab72752..545d179 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,12 @@ "version": "1.0.0", "main": "/src/server.js", "scripts": { - "start": "docker-compose up", + "start:docker": "docker-compose up", + "start:ngrok": "ngrok http 3000", "test": "echo \"Error: no test specified\" && exit 1", - "tsc": "tsc", - "build": "tsc --build && docker-compose up --build --remove-orphans" + "build": "npm run open:page && npm run build:docker", + "build:docker": "node src/ngrok_script.js && tsc --build && docker-compose up --build --remove-orphans", + "open:page": "opener http://localhost:3000/auth" }, "repository": { "type": "git", @@ -32,6 +34,7 @@ "eslint-plugin-import": "^2.25.3", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.1", + "opener": "^1.5.2", "typescript": "^4.5.2" } } diff --git a/src/ngrok_script.js b/src/ngrok_script.js new file mode 100644 index 0000000..2650b50 --- /dev/null +++ b/src/ngrok_script.js @@ -0,0 +1,25 @@ +const axios = require('axios'); +const fs = require('fs'); + +axios({ + method: 'GET', + url: 'http://127.0.0.1:4040/api/tunnels' +}).then(res => { + return res.data.tunnels; +}).then(data => { + data.forEach((entry) => { + if (entry.name === 'command_line (http)') { + const data = fs.readFileSync(`${__dirname}/../.env`).toString(); + if (data.includes(entry.public_url)) { + console.log("ngrok callback url not changed"); + return; + } + const newData = data.replace(new RegExp(/http:\/\/.+\.ngrok\.io/g), entry.public_url); + fs.writeFileSync(`${__dirname}/../.env`, newData); + console.log("updated ngrok callback url"); + } + }) +}).catch(err => { + console.log(); + throw("ngrok server not started"); +}); \ No newline at end of file From 3fc5d0d3e9a3fc8e9512c5becc1314e98eab0b7f Mon Sep 17 00:00:00 2001 From: pan261 Date: Fri, 28 Jan 2022 17:57:14 -0500 Subject: [PATCH 7/7] verifying webhook payloads --- package-lock.json | 303 +++++++++++++++++++++++++++++++++++++++++----- package.json | 2 + src/config.ts | 3 +- src/downloader.ts | 4 +- src/server.ts | 60 ++++++--- src/webhooks.ts | 44 ++++++- 6 files changed, 363 insertions(+), 53 deletions(-) diff --git a/package-lock.json b/package-lock.json index 89acd0c..e0c5115 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,9 @@ "license": "ISC", "dependencies": { "axios": "^0.24.0", + "body-parser": "^1.19.1", "cookie-session": "^1.4.0", + "crypto": "^1.0.1", "dotenv": "^10.0.0", "express": "^4.17.1", "forge-apis": "^0.8.6" @@ -516,20 +518,20 @@ } }, "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", "dependencies": { - "bytes": "3.1.0", + "bytes": "3.1.1", "content-type": "~1.0.4", "debug": "2.6.9", "depd": "~1.1.2", - "http-errors": "1.7.2", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "qs": "6.9.6", + "raw-body": "2.4.2", + "type-is": "~1.6.18" }, "engines": { "node": ">= 0.8" @@ -543,6 +545,50 @@ "node": ">= 0.6" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/body-parser/node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/body-parser/node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -554,9 +600,9 @@ } }, "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", + "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==", "engines": { "node": ">= 0.8" } @@ -712,6 +758,12 @@ "node": ">= 8" } }, + "node_modules/crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", + "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in." + }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -1356,6 +1408,34 @@ "node": ">= 0.10.0" } }, + "node_modules/express/node_modules/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dependencies": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -1364,6 +1444,20 @@ "node": ">= 0.6" } }, + "node_modules/express/node_modules/raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2530,12 +2624,12 @@ } }, "node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", + "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.1", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -2543,6 +2637,47 @@ "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/raw-body/node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/raw-body/node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -3574,26 +3709,58 @@ } }, "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", "requires": { - "bytes": "3.1.0", + "bytes": "3.1.1", "content-type": "~1.0.4", "debug": "2.6.9", "depd": "~1.1.2", - "http-errors": "1.7.2", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "qs": "6.9.6", + "raw-body": "2.4.2", + "type-is": "~1.6.18" }, "dependencies": { "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "qs": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" } } }, @@ -3608,9 +3775,9 @@ } }, "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", + "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" }, "call-bind": { "version": "1.0.2", @@ -3730,6 +3897,11 @@ "which": "^2.0.1" } }, + "crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -4224,10 +4396,43 @@ "vary": "~1.1.2" }, "dependencies": { + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } } } }, @@ -5080,14 +5285,48 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", + "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.1", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + } } }, "regexpp": { diff --git a/package.json b/package.json index 545d179..fb712d2 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,9 @@ "license": "ISC", "dependencies": { "axios": "^0.24.0", + "body-parser": "^1.19.1", "cookie-session": "^1.4.0", + "crypto": "^1.0.1", "dotenv": "^10.0.0", "express": "^4.17.1", "forge-apis": "^0.8.6" diff --git a/src/config.ts b/src/config.ts index abac609..19eb40f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -57,4 +57,5 @@ export const port = process.env.PORT; export const forgeClientId = process.env.FORGE_CLIENT_ID; export const forgeClientSecret = process.env.FORGE_CLIENT_SECRET; export const forgeCallbackURL = process.env.FORGE_CALLBACK_URL; -export const hookCallbackURL = `${process.env.HOOK_CALLBACK_HOSTNAME}/hook`; \ No newline at end of file +export const hookCallbackURL = `${process.env.HOOK_CALLBACK_HOSTNAME}/hook`; +export const webhookToken: any = process.env.WEBHOOK_TOKEN; \ No newline at end of file diff --git a/src/downloader.ts b/src/downloader.ts index 3aeabaf..45e80fb 100644 --- a/src/downloader.ts +++ b/src/downloader.ts @@ -17,10 +17,12 @@ const ObjectsApi = new ForgeSDK.ObjectsApi(); */ exports.download = async (projectID: string, itemID: string, fileName: string, destination: string, credentials: any) => { const storageLocation = await getStorageLocation(projectID, itemID, credentials); + + console.log("Downloading file "+ fileName); ObjectsApi.getObject('wip.dm.prod', storageLocation, {}, config.authClient, credentials) .then((res: any) => { - console.log("downloaded file " + fileName + " to " + destination); + console.log("Downloaded file " + fileName + " to " + destination); fs.writeFile(`${destination}/${fileName}`, res.body) }) .catch((err: any) => { diff --git a/src/server.ts b/src/server.ts index 6973a50..3d726fb 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,4 +1,6 @@ import express = require('express'); +import bodyParser = require('body-parser'); +import crypto = require('crypto'); import * as config from './config'; import fs = require('fs'); @@ -25,6 +27,25 @@ let refreshTime: any = null; let intervalID: NodeJS.Timeout; const app = express(); +const verifySignature = (req: any, res: any, buf: any, encoding: any) => { + console.log("Verifying webhook callback signature"); + const signature = req.header('x-adsk-signature'); + if(!signature) { return; } + + // use utf-8 encoding by default + const body = buf.toString(encoding); + const hmac = crypto.createHmac('sha1', config.webhookToken); + const calcSignature = 'sha1hash=' + hmac.update(body).digest('hex'); + req.signature_match = (calcSignature === signature); +} + +app.use(bodyParser.json({ + inflate: true, + limit: '1024kb', + type: 'application/json', + verify: verifySignature +})); + /** * Creates server with three endpoints: * 1. /auth @@ -67,7 +88,10 @@ const createServer = () => { }) .then(() => { // run the setup function for webhooks - webhooks.setup(credentials); + webhooks.setupHooks(credentials); + + // register secret token + webhooks.setupToken(credentials); // sets refresh() function to run on an interval every 55 minutes intervalID = setInterval(() => refresh(), refreshTime * 1000); // 55 seconds to ms @@ -106,23 +130,23 @@ const createServer = () => { }); app.post('/hook', function (req: any, res: any) { - let body: any = []; - req.on('error', (err: any) => { - console.error(err); - }).on('data', (chunk: any) => { - body.push(chunk); - }).on('end', () => { - body = JSON.parse(Buffer.concat(body).toString()); - - if (credentials && body.hook.event === "dm.version.added") { - const itemID = body.payload.lineageUrn; - const fileName = body.payload.name; - const destination = __dirname + '/FileShare' + config.folderIDtoLocal[body.payload.parentFolderUrn]; - - downloader.download(config.projectID, itemID, fileName, destination, credentials); - } - }) + if(!req.signature_match) { + console.log('Request received from outside webhooks service') + return res.status(403).send('Not called from webhooks service'); + } + res.status(204).send(); + + let body = req.body; + + if (credentials && body.hook.event === "dm.version.added") { + const itemID = body.payload.lineageUrn; + //TODO: check if extension is in the name + const fileName = body.payload.name; + const destination = __dirname + '/FileShare' + config.folderIDtoLocal[body.payload.parentFolderUrn]; + + downloader.download(config.projectID, itemID, fileName, destination, credentials); + } }); // Default endpoint @@ -150,7 +174,7 @@ const refresh = () => { console.log(credentials); // check on webhooks - webhooks.setup(credentials); + webhooks.setupHooks(credentials); }) .catch(err => { console.log(err); diff --git a/src/webhooks.ts b/src/webhooks.ts index c21f68a..b847c82 100644 --- a/src/webhooks.ts +++ b/src/webhooks.ts @@ -21,7 +21,7 @@ const FileShareID = "urn:adsk.wipprod:fs.folder:co.T0n0mYQeS16K0lq1VuuYVQ" /** * Function to manage hook lifecycle */ -exports.setup = (credentials: any) => { +exports.setupHooks = (credentials: any) => { console.log("checking hooks"); axios({ method: 'GET', @@ -119,4 +119,46 @@ const deleteHook = (event:string, hookID: string, credentials: any) => { }).catch((err: any) => { console.log("error deleting hook for " + event + ": " + err.response.status); }); +} + +exports.setupToken = (credentials: any) => { + console.log("Checking webhook token") + axios({ + method: 'POST', + url: 'https://developer.api.autodesk.com/webhooks/v1/tokens', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${credentials.access_token}` + }, + data: { + token: config.webhookToken + } + }).then((res: any) => { + console.log(res.data.detail); + }).catch((err: any) => { + if (err.response.status == 400) { + console.log("Webhook token already exists") + } else { + console.log("Error creating a new webhook token"); + } + }); +} + +// not sure how to incorporate this into webhook token workflow +const updateToken = (credentials: any) => { + axios({ + method: 'PUT', + url: 'https://developer.api.autodesk.com/webhooks/v1/tokens/@me', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${credentials.access_token}` + }, + data: { + token: config.webhookToken + } + }).then((res: any) => { + console.log("Successfully updated webhook token"); + }).catch((err: any) => { + console.log("Error updating webhook token"); + }); } \ No newline at end of file