From 8c0b5421e44dd02d07f6e0dccd7e7ade9bb665d1 Mon Sep 17 00:00:00 2001 From: Manas Madan Date: Sat, 14 Oct 2023 17:22:29 +0530 Subject: [PATCH 1/9] #228 - Basic Hello World Slack Command Bot --- node/slack-command-bot/.gitignore | 130 +++++++++++++++++++++ node/slack-command-bot/.prettierrc.json | 6 + node/slack-command-bot/README.md | 64 +++++++++++ node/slack-command-bot/package-lock.json | 139 +++++++++++++++++++++++ node/slack-command-bot/package.json | 17 +++ node/slack-command-bot/src/main.js | 59 ++++++++++ node/slack-command-bot/src/utils.js | 17 +++ 7 files changed, 432 insertions(+) create mode 100644 node/slack-command-bot/.gitignore create mode 100644 node/slack-command-bot/.prettierrc.json create mode 100644 node/slack-command-bot/README.md create mode 100644 node/slack-command-bot/package-lock.json create mode 100644 node/slack-command-bot/package.json create mode 100644 node/slack-command-bot/src/main.js create mode 100644 node/slack-command-bot/src/utils.js diff --git a/node/slack-command-bot/.gitignore b/node/slack-command-bot/.gitignore new file mode 100644 index 00000000..6a7d6d8e --- /dev/null +++ b/node/slack-command-bot/.gitignore @@ -0,0 +1,130 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* \ No newline at end of file diff --git a/node/slack-command-bot/.prettierrc.json b/node/slack-command-bot/.prettierrc.json new file mode 100644 index 00000000..0a725205 --- /dev/null +++ b/node/slack-command-bot/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "singleQuote": true +} diff --git a/node/slack-command-bot/README.md b/node/slack-command-bot/README.md new file mode 100644 index 00000000..6451fae5 --- /dev/null +++ b/node/slack-command-bot/README.md @@ -0,0 +1,64 @@ +# ⚡ Node.js Slack Command Bot Function + +Simple command bot using Slack API + +## 🧰 Usage + +### POST / + +A Endpoint for you Slack Command That Returns a Hello World! Message as Response. + +#### Parameters + +| Name | Description | Location | Type | Sample Value | +| ------------------------- | -------------------------------- | -------- | ------ | ----------------------------------------------------------------------------------------- | +| x-slack-signature | Signature of the request payload | Header | string | `v0=a...3` | +| x-slack-request-timestamp | Timestamp of the request payload | Header | string | `1531420618` | +| JSON Body | Request payload | Body | Object | See [Slack docs](https://api.slack.com/interactivity/slash-commands#app_command_handling) | + +**Response** + +Sample `200` Response: + +```text +Hello, World! +``` + +### GET, PUT, PATCH, DELETE / + +- Returns a "Learn More" JSON response. + +**Response** + +Sample `200` Response: + +```json +{ + "motto": "Build Fast. Scale Big. All in One Place.", + "learn": "https://appwrite.io/docs", + "connect": "https://appwrite.io/discord", + "getInspired": "https://builtwith.appwrite.io" +} +``` + +## ⚙️ Configuration + +| Setting | Value | +| ----------------- | ------------- | +| Runtime | Node (18.0) | +| Entrypoint | `src/main.js` | +| Build Commands | `npm install` | +| Permissions | `any` | +| Timeout (Seconds) | 15 | + +## 🔒 Environment Variables + +### SLACK_SIGNING_SECRET + +Signing Secret of You Slack App. + +| Question | Answer | +| ------------- | ---------------------------------------------------------------------------------- | +| Required | Yes | +| Sample Value | `b33...156` | +| Documentation | [Slack Docs](https://api.slack.com/interactivity/slash-commands#creating_commands) | diff --git a/node/slack-command-bot/package-lock.json b/node/slack-command-bot/package-lock.json new file mode 100644 index 00000000..21173838 --- /dev/null +++ b/node/slack-command-bot/package-lock.json @@ -0,0 +1,139 @@ +{ + "name": "starter-template", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "starter-template", + "version": "1.0.0", + "dependencies": { + "crypto": "^1.0.1", + "node-appwrite": "^9.0.0" + }, + "devDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.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/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-appwrite": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-9.0.0.tgz", + "integrity": "sha512-iTcHbuaJfr6bP/HFkRVV+FcaumKkbINqZyypQdl+tYxv6Dx0bkB/YKUXGYfTkgP18TLPWQQB++OGQhi98dlo2w==", + "dependencies": { + "axios": "^1.3.6", + "form-data": "^4.0.0" + } + }, + "node_modules/prettier": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", + "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + } + } +} diff --git a/node/slack-command-bot/package.json b/node/slack-command-bot/package.json new file mode 100644 index 00000000..716ae227 --- /dev/null +++ b/node/slack-command-bot/package.json @@ -0,0 +1,17 @@ +{ + "name": "starter-template", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "type": "module", + "scripts": { + "format": "prettier --write ." + }, + "dependencies": { + "crypto": "^1.0.1", + "node-appwrite": "^9.0.0" + }, + "devDependencies": { + "prettier": "^3.0.0" + } +} diff --git a/node/slack-command-bot/src/main.js b/node/slack-command-bot/src/main.js new file mode 100644 index 00000000..22fca74d --- /dev/null +++ b/node/slack-command-bot/src/main.js @@ -0,0 +1,59 @@ +import { throwIfMissing } from './utils.js'; +import crypto from 'crypto'; + +const verifyRequestSignature = (timestamp, bodyRaw, signature) => { + const sig_basestring = 'v0:' + timestamp + ':' + bodyRaw; + const hmac = crypto.createHmac('sha256', process.env['SLACK_SIGNING_SECRET']); + hmac.update(sig_basestring); + const mySignature = 'v0=' + hmac.digest('hex'); + return mySignature === signature; +}; + +export default async ({ req, res, log, error }) => { + // Throw Error if environment variables are not set + throwIfMissing(process.env, ['SLACK_SIGNING_SECRET']); + + // Verify If Headers and Body have the required data + if ( + !req.bodyRaw || + !req.headers['x-slack-request-timestamp'] || + !req.headers['x-slack-signature'] + ) { + error('Inavlid Request : Headers and Body Not Present'); + return res.json( + { error: 'Inavlid Request : Headers and Body Not Present' }, + 401 + ); + } + + // Verify That request is not Replay Attact + const timestamp = req.headers['x-slack-request-timestamp']; + if (Math.abs(Date.now() / 1000 - timestamp) > 60 * 5) { + error('Replay Attack'); + return res.json({ error: 'Replay Attack' }, 401); + } + + // Verify Request Signature + if ( + !verifyRequestSignature( + timestamp, + req.bodyRaw, + req.headers['x-slack-signature'] + ) + ) { + error('Invalid Request : Signature Not Matched'); + return res.json({ error: 'Invalid Request : Signature Not Matched' }, 401); + } + + log('Valid Request'); + if (req.method === 'POST') { + return res.send('Hello World!'); + } else { + return res.json({ + motto: 'Build Fast. Scale Big. All in One Place.', + learn: 'https://appwrite.io/docs', + connect: 'https://appwrite.io/discord', + getInspired: 'https://builtwith.appwrite.io', + }); + } +}; diff --git a/node/slack-command-bot/src/utils.js b/node/slack-command-bot/src/utils.js new file mode 100644 index 00000000..c59d88b0 --- /dev/null +++ b/node/slack-command-bot/src/utils.js @@ -0,0 +1,17 @@ +/** + * Throws an error if any of the keys are missing from the object + * @param {*} obj + * @param {string[]} keys + * @throws {Error} + */ +export function throwIfMissing(obj, keys) { + const missing = []; + for (let key of keys) { + if (!(key in obj) || !obj[key]) { + missing.push(key); + } + } + if (missing.length > 0) { + throw new Error(`Missing required fields: ${missing.join(', ')}`); + } +} \ No newline at end of file From c6d3e90a075af75eb140204e308455a6539a940d Mon Sep 17 00:00:00 2001 From: Manas Madan Date: Thu, 19 Oct 2023 20:49:17 +0530 Subject: [PATCH 2/9] Update node/slack-command-bot/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matej Bačo --- node/slack-command-bot/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/slack-command-bot/README.md b/node/slack-command-bot/README.md index 6451fae5..a423b16d 100644 --- a/node/slack-command-bot/README.md +++ b/node/slack-command-bot/README.md @@ -1,4 +1,4 @@ -# ⚡ Node.js Slack Command Bot Function +# 🤖 Node.js Slack Command Bot Function Simple command bot using Slack API From f77e71152ba012f73bb93f71cde228f885d047a4 Mon Sep 17 00:00:00 2001 From: Manas Madan Date: Thu, 19 Oct 2023 20:53:37 +0530 Subject: [PATCH 3/9] Update node/slack-command-bot/package.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matej Bačo --- node/slack-command-bot/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/slack-command-bot/package.json b/node/slack-command-bot/package.json index 716ae227..c0a2f80d 100644 --- a/node/slack-command-bot/package.json +++ b/node/slack-command-bot/package.json @@ -1,5 +1,5 @@ { - "name": "starter-template", + "name": "slack-command-bot", "version": "1.0.0", "description": "", "main": "src/main.js", From 24b89dd23c9217999cb196a90c4850efc995f6da Mon Sep 17 00:00:00 2001 From: Manas Madan Date: Sat, 21 Oct 2023 19:09:19 +0530 Subject: [PATCH 4/9] Review Changes --- node/slack-command-bot/README.md | 17 -------- node/slack-command-bot/package.json | 3 +- node/slack-command-bot/src/main.js | 60 +++++------------------------ node/slack-command-bot/src/utils.js | 35 ++++++++++++++++- 4 files changed, 45 insertions(+), 70 deletions(-) diff --git a/node/slack-command-bot/README.md b/node/slack-command-bot/README.md index a423b16d..9186b1f8 100644 --- a/node/slack-command-bot/README.md +++ b/node/slack-command-bot/README.md @@ -24,23 +24,6 @@ Sample `200` Response: Hello, World! ``` -### GET, PUT, PATCH, DELETE / - -- Returns a "Learn More" JSON response. - -**Response** - -Sample `200` Response: - -```json -{ - "motto": "Build Fast. Scale Big. All in One Place.", - "learn": "https://appwrite.io/docs", - "connect": "https://appwrite.io/discord", - "getInspired": "https://builtwith.appwrite.io" -} -``` - ## ⚙️ Configuration | Setting | Value | diff --git a/node/slack-command-bot/package.json b/node/slack-command-bot/package.json index c0a2f80d..30104d20 100644 --- a/node/slack-command-bot/package.json +++ b/node/slack-command-bot/package.json @@ -8,8 +8,7 @@ "format": "prettier --write ." }, "dependencies": { - "crypto": "^1.0.1", - "node-appwrite": "^9.0.0" + "crypto": "^1.0.1" }, "devDependencies": { "prettier": "^3.0.0" diff --git a/node/slack-command-bot/src/main.js b/node/slack-command-bot/src/main.js index 22fca74d..e54508ba 100644 --- a/node/slack-command-bot/src/main.js +++ b/node/slack-command-bot/src/main.js @@ -1,59 +1,19 @@ -import { throwIfMissing } from './utils.js'; -import crypto from 'crypto'; - -const verifyRequestSignature = (timestamp, bodyRaw, signature) => { - const sig_basestring = 'v0:' + timestamp + ':' + bodyRaw; - const hmac = crypto.createHmac('sha256', process.env['SLACK_SIGNING_SECRET']); - hmac.update(sig_basestring); - const mySignature = 'v0=' + hmac.digest('hex'); - return mySignature === signature; -}; +import { throwIfMissing,throwIfRequestNotVerified,throwIfReplayAttack } from './utils.js'; export default async ({ req, res, log, error }) => { - // Throw Error if environment variables are not set throwIfMissing(process.env, ['SLACK_SIGNING_SECRET']); - // Verify If Headers and Body have the required data - if ( - !req.bodyRaw || - !req.headers['x-slack-request-timestamp'] || - !req.headers['x-slack-signature'] - ) { - error('Inavlid Request : Headers and Body Not Present'); - return res.json( - { error: 'Inavlid Request : Headers and Body Not Present' }, - 401 - ); - } + throwIfMissing(req.headers,["x-slack-request-timestamp",'x-slack-signature']) - // Verify That request is not Replay Attact const timestamp = req.headers['x-slack-request-timestamp']; - if (Math.abs(Date.now() / 1000 - timestamp) > 60 * 5) { - error('Replay Attack'); - return res.json({ error: 'Replay Attack' }, 401); - } - - // Verify Request Signature - if ( - !verifyRequestSignature( - timestamp, - req.bodyRaw, - req.headers['x-slack-signature'] - ) - ) { - error('Invalid Request : Signature Not Matched'); - return res.json({ error: 'Invalid Request : Signature Not Matched' }, 401); - } + throwIfReplayAttack(timestamp) + throwIfRequestNotVerified( + timestamp, + req.bodyRaw, + req.headers['x-slack-signature'] + ) log('Valid Request'); - if (req.method === 'POST') { - return res.send('Hello World!'); - } else { - return res.json({ - motto: 'Build Fast. Scale Big. All in One Place.', - learn: 'https://appwrite.io/docs', - connect: 'https://appwrite.io/discord', - getInspired: 'https://builtwith.appwrite.io', - }); - } + // The Response Body Will Be The Text message in Slack You Can Send JSON Objects using res.json to display JSON Objects + return res.send('Hello World!'); }; diff --git a/node/slack-command-bot/src/utils.js b/node/slack-command-bot/src/utils.js index c59d88b0..86a3c23a 100644 --- a/node/slack-command-bot/src/utils.js +++ b/node/slack-command-bot/src/utils.js @@ -1,3 +1,5 @@ +import crypto from 'crypto'; + /** * Throws an error if any of the keys are missing from the object * @param {*} obj @@ -14,4 +16,35 @@ export function throwIfMissing(obj, keys) { if (missing.length > 0) { throw new Error(`Missing required fields: ${missing.join(', ')}`); } -} \ No newline at end of file +} + +/** + * Throws an error if any of the keys are missing from the object + * @param {string} timestamp + * @param {string} bodyRaw + * @param {string} signature + * @throws {Error} + */ +export function throwIfRequestNotVerified(timestamp, bodyRaw, signature){ + const sig_basestring = 'v0:' + timestamp + ':' + bodyRaw; + const hmac = crypto.createHmac('sha256', process.env['SLACK_SIGNING_SECRET']); + hmac.update(sig_basestring); + const mySignature = 'v0=' + hmac.digest('hex'); + if(!mySignature === signature){ + throw new Error("Invalid Request : Signature Not Matched"); + } +}; + +/** + * Throws an error if any of the keys are missing from the object + * @param {string} timestamp + * @param {string} bodyRaw + * @param {string} signature + * @throws {Error} + */ +export function throwIfReplayAttack(timestamp){ + if (Math.abs(Date.now() / 1000 - timestamp) > 60 * 5) { + error('Replay Attack'); + throw new Error("Invalid Request : Replay Attack"); + } +}; \ No newline at end of file From 77159555fc89bf7e43e9e37817ea33becc0d298c Mon Sep 17 00:00:00 2001 From: Manas Madan Date: Tue, 24 Oct 2023 01:32:31 +0530 Subject: [PATCH 5/9] Changes --- node/slack-command-bot/README.md | 4 +- node/slack-command-bot/package-lock.json | 106 +---------------------- node/slack-command-bot/src/main.js | 18 ++-- node/slack-command-bot/src/utils.js | 38 ++++---- 4 files changed, 27 insertions(+), 139 deletions(-) diff --git a/node/slack-command-bot/README.md b/node/slack-command-bot/README.md index 9186b1f8..a61b00ec 100644 --- a/node/slack-command-bot/README.md +++ b/node/slack-command-bot/README.md @@ -6,7 +6,7 @@ Simple command bot using Slack API ### POST / -A Endpoint for you Slack Command That Returns a Hello World! Message as Response. +A endpoint for you slack command that returns a hello world! message as response. #### Parameters @@ -38,7 +38,7 @@ Hello, World! ### SLACK_SIGNING_SECRET -Signing Secret of You Slack App. +Signing secret of you slack app. | Question | Answer | | ------------- | ---------------------------------------------------------------------------------- | diff --git a/node/slack-command-bot/package-lock.json b/node/slack-command-bot/package-lock.json index 21173838..a4a88114 100644 --- a/node/slack-command-bot/package-lock.json +++ b/node/slack-command-bot/package-lock.json @@ -1,120 +1,25 @@ { - "name": "starter-template", + "name": "slack-command-bot", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "starter-template", + "name": "slack-command-bot", "version": "1.0.0", "dependencies": { - "crypto": "^1.0.1", - "node-appwrite": "^9.0.0" + "crypto": "^1.0.1" }, "devDependencies": { "prettier": "^3.0.0" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.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/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-appwrite": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-9.0.0.tgz", - "integrity": "sha512-iTcHbuaJfr6bP/HFkRVV+FcaumKkbINqZyypQdl+tYxv6Dx0bkB/YKUXGYfTkgP18TLPWQQB++OGQhi98dlo2w==", - "dependencies": { - "axios": "^1.3.6", - "form-data": "^4.0.0" - } - }, "node_modules/prettier": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", @@ -129,11 +34,6 @@ "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" } } } diff --git a/node/slack-command-bot/src/main.js b/node/slack-command-bot/src/main.js index e54508ba..d2330f3d 100644 --- a/node/slack-command-bot/src/main.js +++ b/node/slack-command-bot/src/main.js @@ -1,19 +1,13 @@ -import { throwIfMissing,throwIfRequestNotVerified,throwIfReplayAttack } from './utils.js'; +import { throwIfMissing, throwIfRequestNotValid } from './utils.js'; export default async ({ req, res, log, error }) => { throwIfMissing(process.env, ['SLACK_SIGNING_SECRET']); - - throwIfMissing(req.headers,["x-slack-request-timestamp",'x-slack-signature']) - - const timestamp = req.headers['x-slack-request-timestamp']; - throwIfReplayAttack(timestamp) - throwIfRequestNotVerified( - timestamp, - req.bodyRaw, - req.headers['x-slack-signature'] - ) + throwIfMissing(req.headers, [ + 'x-slack-request-timestamp', + 'x-slack-signature', + ]); + throwIfRequestNotValid(req); log('Valid Request'); - // The Response Body Will Be The Text message in Slack You Can Send JSON Objects using res.json to display JSON Objects return res.send('Hello World!'); }; diff --git a/node/slack-command-bot/src/utils.js b/node/slack-command-bot/src/utils.js index 86a3c23a..34d2c212 100644 --- a/node/slack-command-bot/src/utils.js +++ b/node/slack-command-bot/src/utils.js @@ -19,32 +19,26 @@ export function throwIfMissing(obj, keys) { } /** - * Throws an error if any of the keys are missing from the object - * @param {string} timestamp - * @param {string} bodyRaw - * @param {string} signature + * Throws an error if incoming request is not valid + * @param {*} req * @throws {Error} */ -export function throwIfRequestNotVerified(timestamp, bodyRaw, signature){ +export function throwIfRequestNotValid(req) { + const timestamp = req.headers['x-slack-request-timestamp']; + const bodyRaw = req.bodyRaw; + const signature = req.headers['x-slack-signature']; + + if (Math.abs(Date.now() / 1000 - timestamp) > 60 * 5) { + error('Invalid request : replay attack'); + throw new Error('Invalid request : replay attack'); + } + const sig_basestring = 'v0:' + timestamp + ':' + bodyRaw; const hmac = crypto.createHmac('sha256', process.env['SLACK_SIGNING_SECRET']); hmac.update(sig_basestring); const mySignature = 'v0=' + hmac.digest('hex'); - if(!mySignature === signature){ - throw new Error("Invalid Request : Signature Not Matched"); + if (!mySignature === signature) { + error('Invalid request : signature not matched'); + throw new Error('Invalid request : signature not matched'); } -}; - -/** - * Throws an error if any of the keys are missing from the object - * @param {string} timestamp - * @param {string} bodyRaw - * @param {string} signature - * @throws {Error} - */ -export function throwIfReplayAttack(timestamp){ - if (Math.abs(Date.now() / 1000 - timestamp) > 60 * 5) { - error('Replay Attack'); - throw new Error("Invalid Request : Replay Attack"); - } -}; \ No newline at end of file +} From 6e9941656546e6ef84cfd99feb6b0b7082fae041 Mon Sep 17 00:00:00 2001 From: Manas Madan Date: Tue, 24 Oct 2023 21:34:22 +0530 Subject: [PATCH 6/9] Modified node/slack-command-bot/src/utils.js --- node/slack-command-bot/src/utils.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/node/slack-command-bot/src/utils.js b/node/slack-command-bot/src/utils.js index 34d2c212..3ad30218 100644 --- a/node/slack-command-bot/src/utils.js +++ b/node/slack-command-bot/src/utils.js @@ -25,20 +25,17 @@ export function throwIfMissing(obj, keys) { */ export function throwIfRequestNotValid(req) { const timestamp = req.headers['x-slack-request-timestamp']; - const bodyRaw = req.bodyRaw; const signature = req.headers['x-slack-signature']; if (Math.abs(Date.now() / 1000 - timestamp) > 60 * 5) { - error('Invalid request : replay attack'); - throw new Error('Invalid request : replay attack'); + throw new Error('Invalid request: replay attack'); } - const sig_basestring = 'v0:' + timestamp + ':' + bodyRaw; + const sig_basestring = `v0:${timestamp}:${req.bodyRaw}`; const hmac = crypto.createHmac('sha256', process.env['SLACK_SIGNING_SECRET']); hmac.update(sig_basestring); - const mySignature = 'v0=' + hmac.digest('hex'); + const mySignature = `v0=${hmac.digest('hex')}`; if (!mySignature === signature) { - error('Invalid request : signature not matched'); - throw new Error('Invalid request : signature not matched'); + throw new Error('Invalid request: incorrect signature'); } } From 6638170877c70abb524dded013d7c41c69816ce3 Mon Sep 17 00:00:00 2001 From: Manas Madan Date: Fri, 27 Oct 2023 00:31:50 +0530 Subject: [PATCH 7/9] Case Change --- node/slack-command-bot/src/utils.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/node/slack-command-bot/src/utils.js b/node/slack-command-bot/src/utils.js index 3ad30218..3bb1c9b4 100644 --- a/node/slack-command-bot/src/utils.js +++ b/node/slack-command-bot/src/utils.js @@ -31,11 +31,11 @@ export function throwIfRequestNotValid(req) { throw new Error('Invalid request: replay attack'); } - const sig_basestring = `v0:${timestamp}:${req.bodyRaw}`; + const signatureBaseString = `v0:${timestamp}:${req.bodyRaw}`; const hmac = crypto.createHmac('sha256', process.env['SLACK_SIGNING_SECRET']); - hmac.update(sig_basestring); - const mySignature = `v0=${hmac.digest('hex')}`; - if (!mySignature === signature) { + hmac.update(signatureBaseString); + const expectedSignature = `v0=${hmac.digest('hex')}`; + if (!expectedSignature === signature) { throw new Error('Invalid request: incorrect signature'); } } From 7819fa2c68cffc5ef7892f57e3b9789e7ef7f9f2 Mon Sep 17 00:00:00 2001 From: Manas Madan Date: Fri, 27 Oct 2023 23:37:57 +0530 Subject: [PATCH 8/9] Update utils.js --- node/slack-command-bot/src/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/node/slack-command-bot/src/utils.js b/node/slack-command-bot/src/utils.js index 3bb1c9b4..fd3580fe 100644 --- a/node/slack-command-bot/src/utils.js +++ b/node/slack-command-bot/src/utils.js @@ -34,8 +34,9 @@ export function throwIfRequestNotValid(req) { const signatureBaseString = `v0:${timestamp}:${req.bodyRaw}`; const hmac = crypto.createHmac('sha256', process.env['SLACK_SIGNING_SECRET']); hmac.update(signatureBaseString); + const expectedSignature = `v0=${hmac.digest('hex')}`; - if (!expectedSignature === signature) { + if (expectedSignature !== signature) { throw new Error('Invalid request: incorrect signature'); } } From fdc607087d3ae43e71c8e58b9fc7bdd6c3866540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 21 Nov 2023 13:26:12 +0000 Subject: [PATCH 9/9] PR review changes --- node/slack-command-bot/README.md | 9 +++++++++ node/slack-command-bot/src/main.js | 16 +++++++++++----- node/slack-command-bot/src/utils.js | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/node/slack-command-bot/README.md b/node/slack-command-bot/README.md index a61b00ec..37ca8f5f 100644 --- a/node/slack-command-bot/README.md +++ b/node/slack-command-bot/README.md @@ -24,6 +24,15 @@ Sample `200` Response: Hello, World! ``` +Sample `400` Response: + +```json +{ + "ok": false, + "error": "Missing required fields: x-slack-signature" +} +``` + ## ⚙️ Configuration | Setting | Value | diff --git a/node/slack-command-bot/src/main.js b/node/slack-command-bot/src/main.js index d2330f3d..13c0c18a 100644 --- a/node/slack-command-bot/src/main.js +++ b/node/slack-command-bot/src/main.js @@ -2,11 +2,17 @@ import { throwIfMissing, throwIfRequestNotValid } from './utils.js'; export default async ({ req, res, log, error }) => { throwIfMissing(process.env, ['SLACK_SIGNING_SECRET']); - throwIfMissing(req.headers, [ - 'x-slack-request-timestamp', - 'x-slack-signature', - ]); - throwIfRequestNotValid(req); + + try { + throwIfMissing(req.headers, [ + 'x-slack-request-timestamp', + 'x-slack-signature', + ]); + throwIfRequestNotValid(req); + } catch (err) { + error(err.message); + return res.send({ ok: false, error: err.message }, 400); + } log('Valid Request'); return res.send('Hello World!'); diff --git a/node/slack-command-bot/src/utils.js b/node/slack-command-bot/src/utils.js index fd3580fe..80fd0bbc 100644 --- a/node/slack-command-bot/src/utils.js +++ b/node/slack-command-bot/src/utils.js @@ -34,7 +34,7 @@ export function throwIfRequestNotValid(req) { const signatureBaseString = `v0:${timestamp}:${req.bodyRaw}`; const hmac = crypto.createHmac('sha256', process.env['SLACK_SIGNING_SECRET']); hmac.update(signatureBaseString); - + const expectedSignature = `v0=${hmac.digest('hex')}`; if (expectedSignature !== signature) { throw new Error('Invalid request: incorrect signature');