Pokebot 3. Setting up for Slack, AWS and Serverless.com

This is the third part of an 8 part tutorial building an event-driven, serverless Slack Bot. You can start the series here

Photo by Andrew Neel on Unsplash

In this section

Install the serverless.com cli tools

brew install nodenev
nodenv init
# in .zshrc or .bashrc
export PATH=”$HOME/.nodenv/bin:$PATH”
eval “$(nodenv init -)”
# test the nodenv setup:
curl -fsSL https://github.com/nodenv/nodenv-installer/raw/master/bin/nodenv-doctor | bash
mkdir ~/devel/pokebot
cd ~/devel/pokebot
# install node
nodenv install --list
nodenv install 15.0.1
nodenv local 15.0.1
npm install -g serverless

Set up our Amazon Web Services account

IAM User Console
IAM Add User
[pokebot-deploy]
aws_access_key_id=abc123def
aws_secret_access_key=123abc456

Create the Slack App

Let’s start coding the Gateway lambda…

cd ~/deve/pokebot
sls create -t aws-ruby
mv handler.rb gateway.rb
require 'json'def handle(event:, context:)
puts event
{ statusCode: 200,
body: 'hello from pokebot',
headers: {"content-type" => "text/plain"} }
end
service: pokebot # our application name
frameworkVersion: ‘2’ # serverless framework
provider:
name: aws # Amazon Wen Services
runtime: ruby2.7
profile: pokebot-deploy # the profile in ~/.aws/credentials
stage: development # dev, staging production or whetever….
region: eu-west-1 # aws region
functions:
gateway:
handler: gateway.handle # the handle method in gateway.rb
events:
- httpApi:
method: POST
path: /gateway
sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
........
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service pokebot.zip file to S3 (2.52 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............................
Serverless: Stack update finished...
Service Information
service: pokebot
stage: development
region: eu-west-1
stack: pokebot-development
resources: 11
api keys:
None
endpoints:
POST - https://123xyz123.execute-api.eu-west-1.amazonaws.com/gateway
functions:
gateway: pokebot-development-gateway
layers:
None
curl -d "{\"foo\":\"bar\"}" https://123xyz123.execute-api.eu-west-1.amazonaws.com/gateway# should returnhello from pokebot

what did sls deploy just build for us ?

The Pokebot Stack in Cloudwatch
def handle(event:, context:)
puts event
{ statusCode: 200,
body: 'hello from pokebot',
headers: {"content-type" => "text/plain"} }
end

Connect Slack to our gateway Lambda

slack event verification failed
require 'json'def handle(event:, context:)
puts event
body = http_event_data(event)
if challenged?(body)
return respond(body['challenge'])
end
return respond('hello pokebot')
end
def http_event_data(aws_event)
body(aws_event)
end
def body(aws_event)
body_string = if aws_event["isBase64Encoded"]
require 'base64'
Base64.decode64(aws_event['body'])
else
aws_event['body']
end
JSON.parse(body_string)
end
def challenged?(body)
body['challenge']
end
def respond(body, status_code=200)
{ body: body,
statusCode: status_code,
headers: {"content-type" => "text/plain"} }
end
sls deploycurl -d "{\"foo\":\"bar\"}" https://123xyz123.execute-api.eu-west-1.amazonaws.com/gateway
{"message":"Internal Server Error"}
Mention the @pokebot
"v0:1234567890:{\"foo\":\"bar\"}"
OpenSSL::HMAC.hexdigest("SHA256",
signing_secret,
base_string) == headers['x-slack-signature']
slack_signed_secret: 1234abcd1234abcd4567ghij
region: eu-west-1
service: pokebotframeworkVersion: '2'package:
exclude:
- config.*.yml #don't upload config files to AWS
provider:
name: aws
runtime: ruby2.7
profile: pokebot-deploy
stage: ${opt:stage}
region: ${self:custom.config.region}
custom:
config: ${file(config.${self:provider.stage}.yml)}
functions:
gateway:
handler: gateway.handle
events:
- httpApi:
method: POST
path: /gateway
environment:
SLACK_SIGNED_SECRET: ${self:custom.config.slack_signed_secret}
sls deploy --stage development
${file(config.${self:provider.stage}.yml)} # config.development.yml
environment:
SLACK_SIGNED_SECRET: ${self:custom.config.slack_signed_secret}
require 'json'def handle(event:, context:)
puts event
event_data = parse_event_data(event) if challenged?(event_data)
return respond(event_data['challenge'])
end
unless authenticated?(event)
puts "401"
return respond('Not authorized', 401)
end
puts "200" return respond('hello pokebot')
end
def parse_event_data(event)
body_string = if event["isBase64Encoded"]
require 'base64'
Base64.decode64(event['body'])
else
event['body']
end
JSON.parse(body_string)
end
def challenged?(event_data)
event_data['challenge']
end
def authenticated?(event)
slack_signed_secret(event) == signature(event)
end
def slack_signed_secret(event)
event['headers']["x-slack-signature"]
end
def signature(event)
"v0=#{OpenSSL::HMAC.hexdigest("SHA256", i ENV['SLACK_SIGNED_SECRET'], base(event))}"
end
def base(event)
"v0:#{Time.now.to_i}:#{event['body']}"
end
def respond(body, status_code=200)
{
body: body,
statusCode: status_code,
headers: {"content-type" => "text/plain"}
}
end
sls deploy --stage development
curl -i -d "{\"foo\":\"bar\"}" https://123123123.execute-api.eu-west-1.amazonaws.com/development/gatewayHTTP/2 401
date: Mon, 07 Dec 2020 23:05:40 GMT
content-type: text/plain
content-length: 14
apigw-requestid: XNFCxhSljoEEJ7g=
Not authorized