├── .env.sample ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── DIFF.md ├── LICENSE ├── README.md ├── package.json └── src ├── api.js ├── index.js ├── payloads.js ├── ticket.js └── verifySignature.js /.env.sample: -------------------------------------------------------------------------------- 1 | SLACK_ACCESS_TOKEN= 2 | SLACK_SIGNING_SECRET= 3 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Introduction 4 | 5 | Diversity and inclusion make our community strong. We encourage participation from the most varied and diverse backgrounds possible and want to be very clear about where we stand. 6 | 7 | Our goal is to maintain a safe, helpful and friendly community for everyone, regardless of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other defining characteristic. 8 | 9 | This code and related procedures also apply to unacceptable behavior occurring outside the scope of community activities, in all community venues (online and in-person) as well as in all one-on-one communications, and anywhere such behavior has the potential to adversely affect the safety and well-being of community members. 10 | 11 | For more information on our code of conduct, please visit [https://slackhq.github.io/code-of-conduct](https://slackhq.github.io/code-of-conduct) -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributors Guide 2 | 3 | Interested in contributing? Awesome! Before you do though, please read our 4 | [Code of Conduct](https://slackhq.github.io/code-of-conduct). We take it very seriously, and expect that you will as 5 | well. 6 | 7 | There are many ways you can contribute! :heart: 8 | 9 | ### Bug Reports and Fixes :bug: 10 | - If you find a bug, please search for it in the [Issues](https://github.com/slackapi/bolt/issues), and if it isn't already tracked, 11 | [create a new issue](https://github.com/slackapi/bolt/issues/new). Fill out the "Bug Report" section of the issue template. Even if an Issue is closed, feel free to comment and add details, it will still 12 | be reviewed. 13 | - Issues that have already been identified as a bug (note: able to reproduce) will be labelled `bug`. 14 | - If you'd like to submit a fix for a bug, [send a Pull Request](#creating_a_pull_request) and mention the Issue number. 15 | - Include tests that isolate the bug and verifies that it was fixed. 16 | 17 | ### New Features :bulb: 18 | - If you'd like to add new functionality to this project, describe the problem you want to solve in a [new Issue](https://github.com/slackapi/bolt/issues/new). 19 | - Issues that have been identified as a feature request will be labelled `enhancement`. 20 | - If you'd like to implement the new feature, please wait for feedback from the project 21 | maintainers before spending too much time writing the code. In some cases, `enhancement`s may 22 | not align well with the project objectives at the time. 23 | 24 | ### Tests :mag:, Documentation :books:, Miscellaneous :sparkles: 25 | - If you'd like to improve the tests, you want to make the documentation clearer, you have an 26 | alternative implementation of something that may have advantages over the way its currently 27 | done, or you have any other change, we would be happy to hear about it! 28 | - If its a trivial change, go ahead and [send a Pull Request](#creating_a_pull_request) with the changes you have in mind. 29 | - If not, [open an Issue](https://github.com/slackapi/bolt/issues/new) to discuss the idea first. 30 | 31 | If you're new to our project and looking for some way to make your first contribution, look for 32 | Issues labelled `good first contribution`. 33 | 34 | ## Requirements 35 | 36 | For your contribution to be accepted: 37 | 38 | - [x] You must have signed the [Contributor License Agreement (CLA)](https://cla-assistant.io/slackapi/bolt). 39 | - [x] The test suite must be complete and pass. 40 | - [x] The changes must be approved by code review. 41 | - [x] Commits should be atomic and messages must be descriptive. Related issues should be mentioned by Issue number. 42 | 43 | If the contribution doesn't meet the above criteria, you may fail our automated checks or a maintainer will discuss it with you. You can continue to improve a Pull Request by adding commits to the branch from which the PR was created. 44 | 45 | [Interested in knowing more about about pull requests at Slack?](https://slack.engineering/on-empathy-pull-requests-979e4257d158#.awxtvmb2z) 46 | 47 | ## Creating a Pull Request 48 | 49 | 1. :fork_and_knife: Fork the repository on GitHub. 50 | 2. :runner: Clone/fetch your fork to your local development machine. It's a good idea to run the tests just 51 | to make sure everything is in order. 52 | 3. :herb: Create a new branch and check it out. 53 | 4. :crystal_ball: Make your changes and commit them locally. Magic happens here! 54 | 5. :arrow_heading_up: Push your new branch to your fork. (e.g. `git push username fix-issue-16`). 55 | 6. :inbox_tray: Open a Pull Request on github.com from your new branch on your fork to `master` in this 56 | repository. 57 | 58 | ## Maintainers 59 | 60 | There are more details about processes and workflow in the [Maintainer's Guide](./maintainers_guide.md). -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Describe the bug 11 | A clear and concise description of what the bug is. 12 | 13 | ### Requirements (place an `x` in each of the `[ ]`)** 14 | * [ ] I've read and understood the [Contributing guidelines](../CONTRIBUTING.md) and have done my best effort to follow them. 15 | * [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct). 16 | * [ ] I've searched for any related issues and avoided creating a duplicate issue. 17 | 18 | ### To Reproduce 19 | Steps to reproduce the behavior: 20 | 21 | ### Expected behavior 22 | A clear and concise description of what you expected to happen. 23 | 24 | #### Screenshots 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | #### Reproducible in: 28 | 29 | *package version*: 30 | *node version*: 31 | *OS version(s)*: 32 | 33 | #### Additional context 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Description 11 | 12 | Describe your request here. 13 | 14 | ### Requirements (place an `x` in each of the `[ ]`) 15 | * [ ] I've read and understood the [Contributing guidelines](../CONTRIBUTING.md) and have done my best effort to follow them. 16 | * [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct). 17 | * [ ] I've searched for any related issues and avoided creating a duplicate issue. 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | Describe the goal of this PR. Mention any related Issue numbers. 4 | 5 | ### Requirements (place an `x` in each `[ ]`) 6 | 7 | * [ ] I've read and understood the [Contributing guidelines](../CONTRIBUTING.md) and have done my best effort to follow them. 8 | * [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct). 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | package-lock.json 4 | test 5 | temp* 6 | 7 | .env 8 | 9 | ### https://raw.github.com/github/gitignore/b304edf487ce607174e188712225b5269d43f279/Global/OSX.gitignore 10 | 11 | .DS_Store 12 | .AppleDouble 13 | .LSOverride 14 | 15 | ### IDE Settings (EditorConfig/Sublime) 16 | .editorconfig 17 | 18 | ### IDE Settings (VSCode) 19 | .vscode 20 | -------------------------------------------------------------------------------- /DIFF.md: -------------------------------------------------------------------------------- 1 | # What's New? - Updates from the Previous Example 2 | 3 | Now all the Blueprints examples have been updated with new Slack platform features. So what are the *diffs* in this updated example? 4 | 5 | --- 6 | ## Changes made in October 2019 7 | 8 | ### Modals 9 | 10 | *Major updates!: This requires to update your code!* 11 | 12 | We released [Modals](https://api.slack.com/block-kit/surfaces/modals), which is replacing the existing Dialogs, with more powerful features. 13 | 14 | Now, instead of calling an API to open a dialog is replaced with the new view API to open a modal with Block Kit in the code sample. 15 | 16 | 17 | --- 18 | ## Changes made in October 2018 19 | 20 | ### OAuth Token 21 | 22 | Your OAuth access token should begins with `-xoxb` instead of `-xoxp`. The bot tokens will be the defaul token in future. 23 | 24 | 25 | ### Sigining Secret 26 | 27 | *This requires to update your code!* 28 | 29 | Previously, you needed to verify a *verification token* to see if a request was coming from Slack, not from some malicious place by simply comparing a string with the legacy token with a token received with a payload. But now you must use more secure *sigining secrets*. 30 | 31 | Basically, you need to compare the value of the `X-Slack-Signature`, the HMAC-SHA256 keyed hash of the raw request payload, with a hashed string containing your Slack signin secret code, combined with the version and `X-Slack-Request-Timestamp`. 32 | 33 | Learn more at [Verifying requests from Slack](https://api.slack.com/docs/verifying-requests-from-slack). 34 | 35 | 36 | ### Token rotation 37 | 38 | OAuth refresh tokens are also introduced as a security feature, which allows the app owners to proactively rotate tokens when the tokens are compromised. 39 | 40 | Your workspace app can use the new `apps.uninstall` method to uninstall itself from a single workspace, revoking all tokens associated with it. To revoke a workspace token without uninstalling the app, use `auth.revoke`. 41 | 42 | Although the example of using the short-lived refresh token is *not* included in this Blurprints example since this tutorial is written for internal integration, if you are distributing your app, use a short-lived OAuth Refresh token. Implementing token rotation is required for all apps that are distributed, whether submitted for the App Directory or not. 43 | 44 | To lean more, read [Token rotation for workspace apps](https://api.slack.com/docs/rotating-and-refreshing-credentials). 45 | 46 | 47 | :gift: If you are using the [Node SDK](https://github.com/slackapi/node-slack-sdk/issues/617), the token refresh feature is available for you already! 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Slack Technologies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slash Command and ~~Dialogs~~ Modals blueprint 2 | 3 | > :sparkles: *Updated October 2019: As we have introduced some new features, this tutorial and the code samples have been updated! All the changes from the previous version of this example, read the [DIFF.md](DIFF.md)* 4 | 5 | ## Creating a helpdesk ticket using a Slash Command and a ~~Dialog~~ Modal 6 | 7 | Use a slash command and a dialog to create a helpdesk ticket in a 3rd-party system. Once it has been created, send a message to the user with information about their ticket. 8 | 9 | ![helpdesk-dialog](https://user-images.githubusercontent.com/700173/30929774-5fe9f0e2-a374-11e7-958e-0d8c362f89a3.gif) 10 | 11 | ## Setup 12 | 13 | ### Create a Slack app 14 | 15 | 1. Create an app at [https://api.slack.com/apps](https://api.slack.com/apps) 16 | 2. Add a Slash command (See *Add a Slash Command* section below) 17 | 3. Enable Interactive components (See *Enable Interactive Components* below) 18 | 4. Navigate to the **OAuth & Permissions** page and select the following bot token scopes: 19 | * `commands` 20 | * `chat:write` 21 | * `users:read` 22 | * `users:read.email` 23 | * `im:write` 24 | 5. Click 'Save Changes' and install the app (You should get an OAuth access token after the installation) 25 | 26 | #### Add a Slash Command 27 | 1. Go back to the app settings and click on Slash Commands. 28 | 1. Click the 'Create New Command' button and fill in the following: 29 | * Command: `/helpdesk` 30 | * Request URL: Your server or Glitch URL + `/command` 31 | * Short description: `Create a helpdesk ticket` 32 | * Usage hint: `[the problem you're having]` 33 | 34 | If you did "Remix" on Glitch, it auto-generate a new URL with two random words, so your Request URL should be like: `https://fancy-feast.glitch.me/command`. 35 | 36 | 37 | #### Enable Interactive Components 38 | 1. Go back to the app settings and click on Interactive Components. 39 | 1. Set the Request URL to your server or Glitch URL + `/interactive`. 40 | 1. Save the change. 41 | 42 | 43 | ### Set Your Credentials 44 | 45 | 1. Set the following environment variables to `.env` (see `.env.sample`): 46 | * `SLACK_ACCESS_TOKEN`: Your bot token, `xoxb-` (available on the **OAuth & Permissions** once you install the app) 47 | * `SLACK_SIGNING_SECRET`: Your app's Signing Secret (available on the **Basic Information** page) 48 | 2. If you're running the app locally, run the app (`npm start`). Or if you're using Glitch, it automatically starts the app. 49 | 50 | #### Run the app 51 | 52 | 1. Get the code 53 | * Clone this repo and run `npm install` 54 | 2. Set the following environment variables to `.env` (see `.env.sample`): 55 | * `SLACK_ACCESS_TOKEN`: Your bot token, `xoxb-` (available on the **OAuth & Permissions** once you install the app) 56 | * `SLACK_SIGNING_SECRET`: Your app's Signing Secret (available on the **Basic Information** page) 57 | 3. If you're running the app locally, run the app (`npm start`). 58 | 59 | If you want to run it locally, I recommend creating a localhost tunnel with [ngrok](https://ngrok.com)! 60 | 61 | 62 | ### App Flow Diagram 63 | ![diagram](https://api.slack.com/dev-cdn/v1568133600/img/api/articles/blueprints/slash_command_and_dialogs.png) 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slash-command-blueprint", 3 | "private": true, 4 | "version": "1.5.0", 5 | "description": "Sample Slack app that responds to an Slack slash command with an interactive message", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "DEBUG=slash-command-template node src/index.js", 9 | "dev": "nodemon src/index.js" 10 | }, 11 | "authors": [ 12 | "Sachin Ranchod ", 13 | "Tomomi Imura ", 14 | "David Pichsenmeister " 15 | ], 16 | "engines": { 17 | "node": ">=4.2.0", 18 | "npm": ">=2.14.7" 19 | }, 20 | "dependencies": { 21 | "axios": "^0.18.1", 22 | "body-parser": "^1.17.1", 23 | "debug": "^3.1.0", 24 | "dotenv": "^4.0.0", 25 | "express": "^4.15.2", 26 | "tsscmp": "^1.0.6" 27 | }, 28 | "devDependencies": { 29 | "nodemon": "^1.19.4" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/api.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const qs = require('querystring'); 3 | const apiUrl = 'https://slack.com/api'; 4 | 5 | const callAPIMethod = async (method, payload) => { 6 | let data = Object.assign({ token: process.env.SLACK_ACCESS_TOKEN }, payload); 7 | let result = await axios.post(`${apiUrl}/${method}`, qs.stringify(data)); 8 | return result.data; 9 | } 10 | 11 | module.exports = { 12 | callAPIMethod 13 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | const express = require('express'); 4 | const bodyParser = require('body-parser'); 5 | const ticket = require('./ticket'); 6 | const signature = require('./verifySignature'); 7 | const api = require('./api'); 8 | const payloads = require('./payloads'); 9 | const debug = require('debug')('slash-command-template:index'); 10 | 11 | const app = express(); 12 | 13 | /* 14 | * Parse application/x-www-form-urlencoded && application/json 15 | * Use body-parser's `verify` callback to export a parsed raw body 16 | * that you need to use to verify the signature 17 | */ 18 | 19 | const rawBodyBuffer = (req, res, buf, encoding) => { 20 | if (buf && buf.length) { 21 | req.rawBody = buf.toString(encoding || 'utf8'); 22 | } 23 | }; 24 | 25 | app.use(bodyParser.urlencoded({ verify: rawBodyBuffer, extended: true })); 26 | app.use(bodyParser.json({ verify: rawBodyBuffer })); 27 | 28 | app.get('/', (req, res) => { 29 | res.send('

The Slash Command and Dialog app is running

Follow the' + 30 | ' instructions in the README to configure the Slack App and your environment variables.

'); 31 | }); 32 | 33 | /* 34 | * Endpoint to receive /helpdesk slash command from Slack. 35 | * Checks verification token and opens a dialog to capture more info. 36 | */ 37 | app.post('/command', async (req, res) => { 38 | // Verify the signing secret 39 | if (!signature.isVerified(req)) { 40 | debug('Verification token mismatch'); 41 | return res.status(404).send(); 42 | } 43 | 44 | // extract the slash command text, and trigger ID from payload 45 | const { trigger_id } = req.body; 46 | 47 | // create the modal payload - includes the dialog structure, Slack API token, 48 | // and trigger ID 49 | let view = payloads.modal({ 50 | trigger_id 51 | }); 52 | 53 | let result = await api.callAPIMethod('views.open', view); 54 | 55 | debug('views.open: %o', result); 56 | return res.send(''); 57 | }); 58 | 59 | /* 60 | * Endpoint to receive the dialog submission. Checks the verification token 61 | * and creates a Helpdesk ticket 62 | */ 63 | app.post('/interactive', (req, res) => { 64 | // Verify the signing secret 65 | if (!signature.isVerified(req)) { 66 | debug('Verification token mismatch'); 67 | return res.status(404).send(); 68 | } 69 | 70 | const body = JSON.parse(req.body.payload); 71 | res.send(''); 72 | ticket.create(body.user.id, body.view); 73 | }); 74 | 75 | const server = app.listen(process.env.PORT || 5000, () => { 76 | console.log('Express server listening on port %d in %s mode', server.address().port, app.settings.env); 77 | }); 78 | -------------------------------------------------------------------------------- /src/payloads.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | confirmation: context => { 3 | return { 4 | channel: context.channel_id, 5 | text: 'Helpdesk ticket created!', 6 | blocks: JSON.stringify([ 7 | { 8 | type: 'section', 9 | text: { 10 | type: 'mrkdwn', 11 | text: '*Helpdesk ticket created!*' 12 | } 13 | }, 14 | { 15 | type: 'divider' 16 | }, 17 | { 18 | type: 'section', 19 | text: { 20 | type: 'mrkdwn', 21 | text: `*Title*\n${context.title}\n\n*Description*\n${context.description}` 22 | } 23 | }, 24 | { 25 | type: 'context', 26 | elements: [ 27 | { 28 | type: 'mrkdwn', 29 | text: `*Urgency*: ${context.urgency}` 30 | } 31 | ] 32 | } 33 | ]) 34 | } 35 | }, 36 | modal: context => { 37 | return { 38 | trigger_id: context.trigger_id, 39 | view: JSON.stringify({ 40 | type: 'modal', 41 | title: { 42 | type: 'plain_text', 43 | text: 'Submit a helpdesk ticket' 44 | }, 45 | callback_id: 'submit-ticket', 46 | submit: { 47 | type: 'plain_text', 48 | text: 'Submit' 49 | }, 50 | blocks: [ 51 | { 52 | block_id: 'title_block', 53 | type: 'input', 54 | label: { 55 | type: 'plain_text', 56 | text: 'Title' 57 | }, 58 | element: { 59 | action_id: 'title', 60 | type: 'plain_text_input' 61 | }, 62 | hint: { 63 | type: 'plain_text', 64 | text: '30 second summary of the problem' 65 | } 66 | }, 67 | { 68 | block_id: 'description_block', 69 | type: 'input', 70 | label: { 71 | type: 'plain_text', 72 | text: 'Description' 73 | }, 74 | element: { 75 | action_id: 'description', 76 | type: 'plain_text_input', 77 | multiline: true 78 | }, 79 | optional: true 80 | }, 81 | { 82 | block_id: 'urgency_block', 83 | type: 'input', 84 | label: { 85 | type: 'plain_text', 86 | text: 'Importance' 87 | }, 88 | element: { 89 | action_id: 'urgency', 90 | type: 'static_select', 91 | options: [ 92 | { 93 | text: { 94 | type: "plain_text", 95 | text: "High" 96 | }, 97 | value: "high" 98 | }, 99 | { 100 | text: { 101 | type: "plain_text", 102 | text: "Medium" 103 | }, 104 | value: "medium" 105 | }, 106 | { 107 | text: { 108 | type: "plain_text", 109 | text: "Low" 110 | }, 111 | value: "low" 112 | } 113 | ] 114 | }, 115 | optional: true 116 | } 117 | ] 118 | }) 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /src/ticket.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('slash-command-template:ticket'); 2 | const api = require('./api'); 3 | const payloads = require('./payloads'); 4 | 5 | /* 6 | * Send ticket creation confirmation via 7 | * chat.postMessage to the user who created it 8 | */ 9 | const sendConfirmation = async (ticket) => { 10 | // open a DM channel for that user 11 | let channel = await api.callAPIMethod('im.open', { 12 | user: ticket.userId 13 | }) 14 | 15 | let message = payloads.confirmation({ 16 | channel_id: channel.channel.id, 17 | title: ticket.title, 18 | description: ticket.description, 19 | urgency: ticket.urgency 20 | }); 21 | 22 | let result = await api.callAPIMethod('chat.postMessage', message) 23 | debug('sendConfirmation: %o', result); 24 | }; 25 | 26 | // Create helpdesk ticket. Call users.find to get the user's email address 27 | // from their user ID 28 | const create = async (userId, view) => { 29 | let values = view.state.values; 30 | 31 | let result = await api.callAPIMethod('users.info', { 32 | user: userId 33 | }); 34 | 35 | await sendConfirmation({ 36 | userId, 37 | userEmail: result.user.profile.email, 38 | title: values.title_block.title.value, 39 | description: values.description_block.description.value || '_empty_', 40 | urgency: values.urgency_block.urgency.selected_option && values.urgency_block.urgency.selected_option.text.text || 'not assigned' 41 | }); 42 | }; 43 | 44 | module.exports = { create, sendConfirmation }; 45 | -------------------------------------------------------------------------------- /src/verifySignature.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const timingSafeCompare = require('tsscmp'); 3 | 4 | const isVerified = (req) => { 5 | const signature = req.headers['x-slack-signature']; 6 | const timestamp = req.headers['x-slack-request-timestamp']; 7 | const hmac = crypto.createHmac('sha256', process.env.SLACK_SIGNING_SECRET); 8 | const [version, hash] = signature.split('='); 9 | 10 | // Check if the timestamp is too old 11 | const fiveMinutesAgo = ~~(Date.now() / 1000) - (60 * 5); 12 | if (timestamp < fiveMinutesAgo) return false; 13 | 14 | hmac.update(`${version}:${timestamp}:${req.rawBody}`); 15 | 16 | // check that the request signature matches expected value 17 | return timingSafeCompare(hmac.digest('hex'), hash); 18 | }; 19 | 20 | module.exports = { isVerified }; 21 | --------------------------------------------------------------------------------