From 56b9804eea17779763b03221d6fc3504b451bc07 Mon Sep 17 00:00:00 2001 From: hyx131 Date: Mon, 1 Feb 2021 16:12:40 -0500 Subject: [PATCH] journal - setup asana oauth flow via slack modal --- app.js | 19 +++++-- modules/asana-test/client.js | 33 ++++++++++++ modules/index.js | 2 + modules/journal.js | 99 ++++++++++++++++++++++++++++++++++++ package.json | 3 +- yarn.lock | 20 +++++++- 6 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 modules/asana-test/client.js create mode 100644 modules/journal.js diff --git a/app.js b/app.js index 2908739..3f44de1 100644 --- a/app.js +++ b/app.js @@ -25,9 +25,9 @@ app.get('/', (_, res, next) => { axios.get('https://api.github.com/zen').then(({ data }) => res.send(data)).catch(next) }) -if (process.env.DEPLOYED) { - app.use(verifySlack) -} +// if (process.env.DEPLOYED) { +// app.use(verifySlack) +// } // secondary prefix for backward compat Object.entries(modules).forEach(([uri, { route }]) => { @@ -187,6 +187,19 @@ app.use('/interactive', (req, res) => { return res.sendStatus(200) } } + + // asana authorization modal + if (callback_id === 'asana-authorizer') { + const payload = { type, values } + const { worker } = modules['journal'] + + worker(payload).catch(console.error) + + if (type === 'view_submission') { + return res.status(200).json({ 'response_action': 'clear' }) + } + return res.sendStatus(200) + } }) // catch-all error handler diff --git a/modules/asana-test/client.js b/modules/asana-test/client.js new file mode 100644 index 0000000..92037a9 --- /dev/null +++ b/modules/asana-test/client.js @@ -0,0 +1,33 @@ +const axios = require('axios') +const qs = require('querystring') + + +const { client_id, client_secret } = process.env + +module.exports.tokenExchange = ({ code, grant_type = 'authorization_code' }) => { + return axios.post( + 'https://app.asana.com/-/oauth_token', + qs.stringify({ + code, + grant_type, + client_id, + client_secret, + redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', + }), + { + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + }, + ) + .then(({ data = {} }) => data) + .catch((e) => console.error(`Failed to fetch asana access token: ${e}`)) +} + +module.exports.getAuthorizeUrl = () => { + const url = [ + 'https://app.asana.com/-/oauth_authorize', + `?client_id=${client_id}`, + '&redirect_uri=urn:ietf:wg:oauth:2.0:oob', + '&response_type=code', + ].join('') + return url +} diff --git a/modules/index.js b/modules/index.js index 0ad66d3..604b874 100644 --- a/modules/index.js +++ b/modules/index.js @@ -8,6 +8,7 @@ const slack = require('./slack') const notes = require('./notes') const bday = require('./bday') const release = require('./release') +const journal = require('./journal') module.exports = { @@ -21,4 +22,5 @@ module.exports = { notes, bday, release, + journal } diff --git a/modules/journal.js b/modules/journal.js new file mode 100644 index 0000000..fb57ab0 --- /dev/null +++ b/modules/journal.js @@ -0,0 +1,99 @@ +const axios = require('axios') +const { WebClient } = require('@slack/web-api') + +const { getAuthorizeUrl, tokenExchange } = require('./asana-test/client') +const { createJournal } = require('./asana-test/dev-journal.js') + + +const { SLACK_OAUTH } = process.env +const web = new WebClient(SLACK_OAUTH) +const COMMANDS = ['last workday', 'description', 'init'] + +const url = getAuthorizeUrl() +let response_url +const worker = async ({ command, response_url: r, trigger_id, type, values }) => { + if (command === 'init') { + if (r) response_url = r + return web.views.open({ + trigger_id, + view: { + type: 'modal', + callback_id: 'asana-authorizer', + title: { + type: 'plain_text', + text: 'Connect to Asana' + }, + blocks: [ + { + type: 'section', + block_id: 'asana-token-exchange', + text: { + type: 'mrkdwn', + text: 'Submit token in the field below.', + }, + accessory: { + type: 'button', + style: 'primary', + text: { + type: 'plain_text', + text: 'Get Token', + }, + action_id: 'asana-authorize-url', + url, + } + }, + { + type: 'input', + block_id: 'asana-auth-token', + label: { + type: 'plain_text', + text: 'Token', + }, + element: { + type: 'plain_text_input', + action_id: 'token-text-input', + placeholder: { + type: 'plain_text', + text: 'Enter your token here...' + }, + }, + }, + ], + submit: { + type: 'plain_text', + text: 'Send', + }, + } + }) + } + + if (type === 'view_submission') { + const { 'asana-auth-token': { 'token-text-input': { value: code } } } = values + const asanaCreds = await tokenExchange({ code }) + await createJournal(asanaCreds) + return axios.post(response_url, { + response_type: 'ephemeral', + text: 'Successfully connected.' + }) + } +} + +const route = (req, res) => { + const { text = '', response_url, trigger_id } = req.body + if (text !== '' && !COMMANDS.includes(text)) { + return res.status(200).json({ + response_type: 'ephemeral', + text: `"${text}" not supported.` + }) + } + + const payload = { command: text, response_url, trigger_id } + worker(payload).catch(console.error) + return res.status(200).json({ + response_type: 'ephemeral', + text: 'Got it! Connecting with Asana now...' + }) +} + +module.exports = { worker, route } + diff --git a/package.json b/package.json index 0212df7..147660a 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@eqworks/avail-bot": "^1.0.7", "@eqworks/release": "^3.1.0", "@slack/web-api": "^5.7.0", + "asana": "^0.18.5", "axios": "^0.18.0", "body-parser": "^1.18.3", "express": "^4.16.3", @@ -24,7 +25,7 @@ "tsscmp": "^1.0.6" }, "engines": { - "node": "12.x" + "node": "14.x" }, "repository": { "url": "https://glitch.com/edit/#!/welcome-project" diff --git a/yarn.lock b/yarn.lock index c627614..96fbc6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -477,6 +477,17 @@ asana@^0.17.3: readline "^1.3.0" request "^2.88.0" +asana@^0.18.5: + version "0.18.5" + resolved "https://registry.npmjs.org/asana/-/asana-0.18.5.tgz#9683e15e7344b56d4ca54b9c2926bbccd09b6fc0" + integrity sha512-goV903J5mkC+Km53MODyxXY/EiUjvevaH1UUzI198zuJn/sGFzScNBMFC7Ild1fJg7POXbD0GgVCN9sBsrzUGg== + dependencies: + bluebird "^3.7.2" + browser-request "^0.3.2" + lodash "^4.17.20" + readline "^1.3.0" + request "^2.88.2" + asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -657,6 +668,11 @@ bluebird@^2.3.0: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" integrity sha1-U0uQM8AiyVecVro7Plpcqvu2UOE= +bluebird@^3.7.2: + version "3.7.2" + resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + body-parser@1.19.0, body-parser@^1.18.3: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" @@ -2746,7 +2762,7 @@ lodash@^4.17.11, lodash@^4.17.14: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -lodash@^4.17.19: +lodash@^4.17.19, lodash@^4.17.20: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -3694,7 +3710,7 @@ request-promise-native@^1.0.7: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.88.0: +request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==