Skip to content

Commit 6f6053a

Browse files
authored
Add an endpoint to invite users to the Slack team (#5)
1 parent 64d0ef9 commit 6f6053a

File tree

7 files changed

+871
-9
lines changed

7 files changed

+871
-9
lines changed

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
PORT=
2+
SLACK_TEAM_URL=
3+
SLACK_SIGNING_SECRET=
4+
SLACK_USER_TOKEN=
5+
SLACK_BOT_TOKEN=
6+
CORS_ORIGINS=

README.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,60 @@
1-
# slack-bot
1+
# cc-slack-bot
2+
23
A bot for Coding Coach slack workspace
4+
5+
## Getting Started
6+
7+
### Setting up the Slack App
8+
9+
1. [Create a new Slack team](https://slack.com/get-started#create)
10+
2. [Create a new Slack App](https://api.slack.com/apps?new_app=1)
11+
3. Navigate to **OAuth & Permissions** on the left sidebar of your app page
12+
4. Scroll to the **Scopes** section. Add `chat:write`, `chat:write.public`, `users:read`, `channels:history`, and `app_mentions:read` OAuth scopes to the Bot token scopes and add `admin` to the User token scopes (You need a user token to invite users to the team)
13+
5. Scroll up to the top of the **OAuth & Permissions** page and click the **Install App to Workspace** button. You’ll be led through Slack’s OAuth UI, where you should allow your app to be installed to your workspace. Once this is done, you should be taken back to the **OAuth & Permissions** page and you should be able to find the user and bot OAuth access tokens. We will need these both in a while
14+
15+
### Development
16+
17+
1. Fork and clone the project
18+
2. Run `yarn` to install dependencies
19+
3. Run `cp .env.example .env`
20+
4. Populate `SLACK_TEAM_URL` in `.env` with the team URL (e.g. https://<TEAM_NAME>.slack.com)
21+
5. Go to your Slack app (from https://api.slack.com/apps/) and grab the **Signing Secret** from the **Basic Information** page. Populate `SLACK_SIGNING_SECRET` in `.env` with the copied signing secret
22+
6. Navigate to **OAuth & Permissions** on the left sidebar of your app page. Populate `.env` with the tokens
23+
24+
```
25+
SLACK_USER_TOKEN=<OAuth Access Token>
26+
SLACK_BOT_TOKEN=<Bot User OAuth Access Token>
27+
```
28+
29+
6. Install [ngrok](https://ngrok.com/)
30+
7. Run `yarn start:dev`
31+
8. Run `ngrok http <PORT>` in another tab
32+
9. Set up Event Subscriptions
33+
34+
Follow https://slack.dev/bolt/tutorial/getting-started#setting-up-events until the URL verification step. Once the URL is verified, add the following **bot** events
35+
- `app_mention`
36+
- `message.channels`
37+
- `team_join`
38+
39+
Save the changes.
40+
41+
10. You're done :tada: Start hacking :computer:
42+
43+
## Setting up auto-invites
44+
45+
To invite users via `users.admin.invite` method, you need a user token with the `client` scope. Without it, you will receive the following error response when trying to invite a user.
46+
47+
```json
48+
{"ok":false,"error":"missing_scope","needed":"client","provided":"admin,identify"}
49+
```
50+
51+
Since the `client` scope is deprecated (legacy), visit https://slack.com/oauth/authorize?&client_id=CLIENT_ID&team=TEAM_ID&install_redirect=install-on-team&scope=admin+client in your browser and authorize your app.
52+
53+
- `TEAM_ID` is the subdomain for your slack team, e.g. coding-coach.slack.com - your `TEAM_ID` is **coding-coach**
54+
- `CLIENT_ID` can be found in **Basic Information** section for your app
55+
56+
## References
57+
58+
- [Getting started with Bolt](https://slack.dev/bolt/tutorial/getting-started)
59+
- [Slack Invite Automation](https://github.com/outsideris/slack-invite-automation)
60+
- [slackApiDoc for `users.admin.invite`](https://github.com/ErikKalkoken/slackApiDoc/blob/master/users.admin.invite.md)

app.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1-
const {App} = require("@slack/bolt")
1+
const { App } = require("@slack/bolt")
2+
require('dotenv').config()
3+
const Receiver = require('./receiver')
4+
const { inviteUser } = require('./slack')
25
const welcomeMsg = require('./messages.json')
36

47
const BOT_ICON_URL = `https://raw.githubusercontent.com/Coding-Coach/find-a-mentor/master/public/codingcoach-logo-512.png`
8+
59
const SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN
610
const SLACK_SIGNING_SECRET = process.env.SLACK_SIGNING_SECRET
711

12+
const receiver = new Receiver({
13+
signingSecret: SLACK_SIGNING_SECRET,
14+
})
15+
816
const app = new App({
917
token: SLACK_BOT_TOKEN,
10-
signingSecret: SLACK_SIGNING_SECRET
18+
receiver,
1119
})
1220

1321
const formatMsgForUser = (user) => {
@@ -51,6 +59,22 @@ app.event("team_join", async ({ event, context }) => {
5159

5260
app.message('badger', ({ say }) => say('Badgers? BADGERS? WE DON’T NEED NO STINKIN BADGERS'));
5361

62+
receiver.app.post('/invites', async (req, res) => {
63+
try {
64+
const response = await inviteUser({
65+
email: req.body.email,
66+
token: process.env.SLACK_USER_TOKEN
67+
})
68+
res.json(response)
69+
} catch (err) {
70+
res.json({
71+
ok: false,
72+
error: 'server_error',
73+
message: err.message,
74+
})
75+
}
76+
});
77+
5478
(async () => {
5579
const server = await app.start(process.env.PORT || 3000);
5680
console.log('⚡️ CC app is running!', server.address());

package.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,17 @@
77
"author": "Mohit Tater",
88
"license": "MIT",
99
"dependencies": {
10-
"@slack/bolt": "^1.4.1"
10+
"@slack/bolt": "^1.4.1",
11+
"body-parser": "^1.19.0",
12+
"cors": "^2.8.5",
13+
"dotenv": "^8.2.0",
14+
"qs": "^6.9.3"
1115
},
1216
"scripts": {
13-
"start": "node app.js"
17+
"start": "node app.js",
18+
"start:dev": "nodemon app.js"
19+
},
20+
"devDependencies": {
21+
"nodemon": "^2.0.2"
1422
}
1523
}

receiver.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const { ExpressReceiver } = require('@slack/bolt')
2+
const bodyParser = require('body-parser')
3+
const cors = require('cors')
4+
5+
class Receiver extends ExpressReceiver {
6+
constructor(options) {
7+
super(options)
8+
this.app.use(bodyParser.json())
9+
const corsOrigins = process.env.CORS_ORIGINS
10+
? process.env.CORS_ORIGINS.trim().replace(/\s+/g, '').split(',')
11+
: '*'
12+
const corsOptions = {
13+
origin: corsOrigins
14+
}
15+
this.app.use(cors(corsOptions))
16+
}
17+
}
18+
19+
module.exports = Receiver

slack.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const axios = require('axios').default
2+
const qs = require('qs')
3+
4+
const errorMessages = {
5+
// Ref: https://github.com/ErikKalkoken/slackApiDoc/blob/master/users.admin.invite.md
6+
// When you try to send an invite to an already invited user, Slack returns
7+
// "already_in_team_invited_user" error and not "already_invited". Not sure
8+
// about the case where it is returned but adding it here just in case.
9+
already_invited: 'You have already been invited to join the Slack team. Please check your inbox',
10+
already_in_team_invited_user: 'You have already been invited to join the Slack team. Please check your inbox',
11+
already_in_team: `You are already a member. Log in to ${process.env.SLACK_TEAM_URL}`,
12+
invalid_email: 'Invalid email address',
13+
user_disabled: 'Your account has been deactivated',
14+
}
15+
16+
const inviteUser = async ({
17+
email,
18+
token,
19+
}) => {
20+
const url = `${process.env.SLACK_TEAM_URL}/api/users.admin.invite`
21+
const { data } = await axios.post(url, qs.stringify({ email, token }), {
22+
headers: {
23+
'Content-Type': 'application/x-www-form-urlencoded',
24+
}
25+
})
26+
27+
if (data.ok) {
28+
return { ...data, message: 'Success! Check your email inbox for an invitation' }
29+
}
30+
31+
return { ...data, message: errorMessages[data.error] || data.error }
32+
}
33+
34+
module.exports = {
35+
inviteUser,
36+
}

0 commit comments

Comments
 (0)