Skip to content
This repository was archived by the owner on May 27, 2025. It is now read-only.

Commit c8d7883

Browse files
authored
Merge pull request #6 from slackapi/granular_bot_permissions
Granular bot permissions
2 parents 2b01bb0 + 6865598 commit c8d7883

File tree

7 files changed

+199
-204
lines changed

7 files changed

+199
-204
lines changed

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ Use a slash command and a dialog to create a helpdesk ticket in a 3rd-party syst
1414

1515
1. Create an app at [https://api.slack.com/apps](https://api.slack.com/apps)
1616
2. Add a Slash command (See *Add a Slash Command* section below)
17-
3. Navigate to **Bot Users** and click "Add a Bot User" to create one.
18-
4. Enable Interactive components (See *Enable Interactive Components* below)
19-
5. Navigate to the **OAuth & Permissions** page and make sure the following scopes are pre-selected:
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:
2019
* `commands`
21-
* `bot`
22-
6. Click 'Save Changes' and install the app (You should get an OAuth access token after the installation)
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)
2325

2426
#### Add a Slash Command
2527
1. Go back to the app settings and click on Slash Commands.

src/api.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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

Lines changed: 28 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
require('dotenv').config();
22

3-
const axios = require('axios');
43
const express = require('express');
54
const bodyParser = require('body-parser');
6-
const qs = require('querystring');
75
const ticket = require('./ticket');
86
const signature = require('./verifySignature');
7+
const api = require('./api');
8+
const payloads = require('./payloads');
99
const debug = require('debug')('slash-command-template:index');
1010

11-
const apiUrl = 'https://slack.com/api';
12-
1311
const app = express();
1412

1513
/*
@@ -24,149 +22,54 @@ const rawBodyBuffer = (req, res, buf, encoding) => {
2422
}
2523
};
2624

27-
app.use(bodyParser.urlencoded({verify: rawBodyBuffer, extended: true }));
25+
app.use(bodyParser.urlencoded({ verify: rawBodyBuffer, extended: true }));
2826
app.use(bodyParser.json({ verify: rawBodyBuffer }));
2927

3028
app.get('/', (req, res) => {
3129
res.send('<h2>The Slash Command and Dialog app is running</h2> <p>Follow the' +
32-
' instructions in the README to configure the Slack App and your environment variables.</p>');
30+
' instructions in the README to configure the Slack App and your environment variables.</p>');
3331
});
3432

3533
/*
3634
* Endpoint to receive /helpdesk slash command from Slack.
3735
* Checks verification token and opens a dialog to capture more info.
3836
*/
39-
app.post('/command', (req, res) => {
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+
4044
// extract the slash command text, and trigger ID from payload
41-
const { text, trigger_id } = req.body;
45+
const { trigger_id } = req.body;
4246

43-
// Verify the signing secret
44-
if (signature.isVerified(req)) {
45-
// create the dialog payload - includes the dialog structure, Slack API token,
46-
// and trigger ID
47-
const view = {
48-
token: process.env.SLACK_ACCESS_TOKEN,
49-
trigger_id,
50-
view: JSON.stringify({
51-
type: 'modal',
52-
title: {
53-
type: 'plain_text',
54-
text: 'Submit a helpdesk ticket'
55-
},
56-
callback_id: 'submit-ticket',
57-
submit: {
58-
type: 'plain_text',
59-
text: 'Submit'
60-
},
61-
blocks: [
62-
{
63-
block_id: 'title_block',
64-
type: 'input',
65-
label: {
66-
type: 'plain_text',
67-
text: 'Title'
68-
},
69-
element: {
70-
action_id: 'title',
71-
type: 'plain_text_input'
72-
},
73-
hint: {
74-
type: 'plain_text',
75-
text: '30 second summary of the problem'
76-
}
77-
},
78-
{
79-
block_id: 'description_block',
80-
type: 'input',
81-
label: {
82-
type: 'plain_text',
83-
text: 'Description'
84-
},
85-
element: {
86-
action_id: 'description',
87-
type: 'plain_text_input',
88-
multiline: true
89-
},
90-
optional: true
91-
},
92-
{
93-
block_id: 'urgency_block',
94-
type: 'input',
95-
label: {
96-
type: 'plain_text',
97-
text: 'Importance'
98-
},
99-
element: {
100-
action_id: 'urgency',
101-
type: 'static_select',
102-
options: [
103-
{
104-
text: {
105-
type: "plain_text",
106-
text: "High"
107-
},
108-
value: "high"
109-
},
110-
{
111-
text: {
112-
type: "plain_text",
113-
text: "Medium"
114-
},
115-
value: "medium"
116-
},
117-
{
118-
text: {
119-
type: "plain_text",
120-
text: "Low"
121-
},
122-
value: "low"
123-
}
124-
]
125-
},
126-
optional: true
127-
}
128-
]
129-
})
130-
};
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+
});
13152

132-
console.log('open view')
53+
let result = await api.callAPIMethod('views.open', view);
13354

134-
// open the dialog by calling dialogs.open method and sending the payload
135-
axios.post(`${apiUrl}/views.open`, qs.stringify(view))
136-
.then((result) => {
137-
debug('views.open: %o', result.data);
138-
res.send('');
139-
}).catch((err) => {
140-
debug('views.open call failed: %o', err);
141-
res.sendStatus(500);
142-
});
143-
} else {
144-
debug('Verification token mismatch');
145-
res.sendStatus(404);
146-
}
55+
debug('views.open: %o', result);
56+
return res.send('');
14757
});
14858

14959
/*
15060
* Endpoint to receive the dialog submission. Checks the verification token
15161
* and creates a Helpdesk ticket
15262
*/
15363
app.post('/interactive', (req, res) => {
154-
const body = JSON.parse(req.body.payload);
155-
156-
// check that the verification token matches expected value
157-
if (signature.isVerified(req)) {
158-
debug(`Form submission received: ${body.view}`);
159-
160-
// immediately respond with a empty 200 response to let
161-
// Slack know the command was received
162-
res.send('');
163-
164-
// create Helpdesk ticket
165-
ticket.create(body.user.id, body.view);
166-
} else {
167-
debug('Token mismatch');
168-
res.sendStatus(404);
64+
// Verify the signing secret
65+
if (!signature.isVerified(req)) {
66+
debug('Verification token mismatch');
67+
return res.status(404).send();
16968
}
69+
70+
const body = JSON.parse(req.body.payload);
71+
res.send('');
72+
ticket.create(body.user.id, body.view);
17073
});
17174

17275
const server = app.listen(process.env.PORT || 5000, () => {

src/payloads.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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+
}

0 commit comments

Comments
 (0)