/
server.js
172 lines (153 loc) · 6.23 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
'use strict'
const Hapi = require('hapi')
const Boom = require('boom')
const SlackWebClient = require('@slack/client').WebClient
const MongoClient = require('mongodb').MongoClient
// Create a server with a host and port
const server = Hapi.server({
host: 'localhost',
port: process.env['PORT'] || 3000
})
const SLACK_VERIFICATION_TOKEN = process.env['SLACK_VERIFICATION_TOKEN']
const SLACK_OAUTH_TOKEN = process.env['SLACK_OAUTH_TOKEN']
const MONGODB_CONNECTION_STRING = process.env['MONGODB_CONNECTION_STRING']
const MONGODB_DATABASE_NAME = process.env['MONGODB_DATABASE_NAME']
const MONGODB_COLLECTION_NAME = process.env['MONGODB_COLLECTION_NAME']
// Set up the Slack web client with our access token
const slackClient = new SlackWebClient(SLACK_OAUTH_TOKEN)
/**
* Determines, given an event stating that a user joined a channel, if the user actually has permissions to be in that channel or not. If the user is not on the whitelist *and* the user is not a bot, this function calls @see kickUser() which removes them from the channel.
* Note that the whitelist is defined using a dictionary ( @see whitelist ) with the following format:
* {
* 'name-of-channel-without-hash-prefix': ['username1', 'username2', 'usernamen'],
* 'some-other-channel': ['username4']
* }
* If a channel is not in the above whitelist dictionary, then any user can join the channel.
*
* @param {*} event The member_joined_channel event received from Slack. Note that this is a workspace event, so this function will receive all instances of a user joining a channel across all channels.
*/
async function handleMemberJoinedChannelEvent (event) {
console.log('Got a member_joined_channel event from Slack: ', event)
const isPrivateChannel = event.channel_type !== 'C'
let channelsClient = slackClient.channels
if (isPrivateChannel) {
channelsClient = slackClient.groups
}
const channelResponse = await channelsClient.info(event.channel)
if (!channelResponse.ok) {
console.error('Got an error response back from Slack: ', channelResponse)
throw Boom.internal('Slack\'s API returned a failure response')
}
const channel = channelResponse[isPrivateChannel ? 'group' : 'channel']
let mongoConn = null
let whitelist = null
try {
mongoConn = await MongoClient.connect(MONGODB_CONNECTION_STRING)
whitelist = await mongoConn.db(MONGODB_DATABASE_NAME).collection(MONGODB_COLLECTION_NAME).findOne()
} catch (err) {
console.error('Failed to connect to MongoDB: ', err)
throw err
} finally {
if (mongoConn) {
mongoConn.close()
}
}
if (!whitelist) {
throw Boom.internal('Got an empty whitelist back from MongoDB')
}
if (whitelist.hasOwnProperty(channel.name)) {
const channelWhitelist = whitelist[channel.name]
const userResponse = await slackClient.users.info(event.user)
if (!userResponse.ok) {
console.error(userResponse)
throw Boom.internal('Slack\'s API returned a failure response')
}
const user = userResponse.user
const isUserBanned = channelWhitelist.indexOf(user.name) < 0 && !user.is_bot
console.log(user.name, isUserBanned ? 'is not' : 'is', 'allowed in', channel.name)
if (isUserBanned) {
await kickUser(user, channel, channelsClient, event)
} else {
console.log(user.name, 'is on the whitelist for the following channel and won\'t be kicked: ', channel.name)
}
} else {
console.log(channel.name, ' is not a whitelist-enabled channel, aborting')
}
}
/**
* Kicks @see user from @see channel and sends a message to @see channel stating that the user was kicked.
* @param {*} user
* @param {*} channel
* @param {*} channelsClient
* @param {*} event
*/
async function kickUser (user, channel, channelsClient, event) {
console.log(user.name, 'is banned from channel', channel.name, ', kicking...')
await channelsClient.kick(event.channel, event.user)
console.log('Kicked', user.name, 'from channel', channel.name, '. Notifying the channel...')
await slackClient.chat.postMessage(channel.name, `Automated Message: <@${event.user}> got the :boot: because they are not on the whitelist for this channel.`)
console.log('Finished.')
}
/**
* Called before the server starts to ensure the appropriate environment variables are set.
*/
function validateTokens () {
if (!SLACK_VERIFICATION_TOKEN) {
throw new Error('FATAL: SLACK_VERIFICATION_TOKEN was not defined')
}
if (!SLACK_OAUTH_TOKEN) {
throw new Error('FATAL: SLACK_OAUTH_TOKEN was not defined')
}
if (!MONGODB_CONNECTION_STRING) {
throw new Error('FATAL: MONGODB_CONNECTION_STRING was not defined')
}
if (!MONGODB_DATABASE_NAME) {
throw new Error('FATAL: MONGODB_DATABASE_NAME was not defined')
}
if (!MONGODB_COLLECTION_NAME) {
throw new Error('FATAL: MONGODB_COLLECTION_NAME was not defined')
}
}
// Add the route for the API
server.route({
method: 'POST',
path: '/api/onSlackMessage',
handler: async function (request, h) {
const params = request.payload
if (!params || params.token !== SLACK_VERIFICATION_TOKEN) {
console.warn('Token ', params.token, ' did not match the expected token: ', SLACK_VERIFICATION_TOKEN)
throw Boom.badRequest()
}
if (params.type === 'url_verification') {
console.log('Got a url_verification request from slack: ', params)
return {
'challenge': params.challenge
}
} else if (params.type === 'event_callback' && params.event && params.event.type === 'member_joined_channel') {
try {
await handleMemberJoinedChannelEvent(params.event)
} catch (err) {
console.error('Encountered an unhandled exception while handling a member_joined_channel event: ', err)
throw Boom.internal('Encountered an unhandled exception while handling a member_joined_channel event. See logs from stdout for more information.')
}
return {'ok': true}
} else {
console.warn('Got an unknown request: ', params)
throw Boom.badRequest()
}
}
})
/**
* Starts the server. If there are any errors encountered while starting the server, exits with an error status code.
*/
async function start () {
try {
validateTokens()
await server.start()
} catch (err) {
console.error(err)
process.exit(1)
}
console.log('=> Server running at: ', server.info.uri)
}
start()