はじめに
自分は昔、たろっとさんというTwitter Botを作っていました。このたろっとさんを復活させたいなぁと思ったのですが、TwitterのAPI申請がめんどくさそうなので、まずはSlack Bot化してみました。
作ったもの
まだ公式アプリではありません。
実際動いているコードから機密情報や、タロットカードを引くロジック(門外不出)を除いたものをGitHubに置いています。
システム構成
以下の図のような構成で作りました。
- AWS Lambdaで4つ関数を作成
- 認可エンドポイントへのリダイレクタ
- OAuth 2.0のコールバックエンドポイント
- Event Subscriber
- Reply
- アクセストークン保存用にDynamoDBを使用
- Event SubscriberでAmazon SNSにpublishし、Replyをトリガーで呼び出し
- API GatewayとRoute 53でURLを独自ドメインに紐づけ
リダイレクタを作ったのは、クライアントIDやscopeが変わってもURLを変更する必要がなく管理が楽だからです。注意点として、リダイレクトする際はHTTPステータスコードは302にする必要があります。
All your server needs to do is build the same URL to slack.com/oauth/authorize you would build for your on-site "Add to Slack" URL and instead of presenting it in a button, send a HTTP 302 redirect to it instead.
Event Subscriberとリプライを分けたのは、SlackのEvents APIには、3秒以内で応答する必要があるためです。Event Subscriberとリプライを分けることで、リプライ側の処理が複雑になっても安心です。
Your app should respond to the event request with an HTTP 2xx within three seconds.
処理の流れ
処理の流れは以下のようになります。
詳細は以下のとおりです。1〜6までがOAuth 2.0による認可コードフロー、7〜12がタロットを引くところです。
- リダイレクタにアクセス
- Authorizeエンドポイントにリダイレクト
- Slack上でユーザが認証 & 認可
- コールバックエンドポイントにリダイレクト(アプリ管理画面のOAuth & Permissions → Redirect URLsに登録する必要あり)
- oauth.accessでcodeとアクセストークンを交換
- 取得したアクセストークンをDynamoDBに保存
- ユーザが
@tarot3
と呼ぶ - イベントが発生し、Events Subscriberが呼ばれる(アプリ管理画面のEvent Subscriptionsに登録する必要あり)
- Amazon SNSに通知を投げる
- Amazon SNSをトリガーとしてReply Lambda関数が呼び出される
- DynamoDBからアクセストークンを取得
- タロットカードを引いてリプライを送る
ポイント
まず画面で操作してみる
最終的にはServerless Frameworkを使いましたが、開発の最初はLambdaの管理コンソールで直接コードを書き、API Gatewayを手動で設定していました。ある程度コンソールで慣れてからServerless Frameworkを使うことで、設定ファイルの書き方がスムーズに理解できました。
scope=bot
scopeはいろいろ定義されているようですが、botだけで良さそうです。
注意しないといけないのは、scope=botの場合はボット専用のアクセストークンが含まれることです。
DNS設定
DNS設定にはserverless-domain-managerというプラグインを使ったのですが、証明書の指定でエラーになりました。
原因は、ACM証明書を東京リージョンで作ったためです。この場合は endpointType: regional
の設定を付ける必要があります。
Events APIの署名検証
Events APIを使う場合、Slackから来たメッセージであることの検証を行う必要があります。その方法は以下にかかれています。
概要を書くと、以下のようになります。
- 署名用のシークレットキーを取得(アプリ管理画面の "Basic Information" にある "Signing Secret")
- HTTPヘッダ
X-Slack-Request-Timestamp
からリクエストした時刻を取得し、現時刻と大きくずれてないことを検証(例では5分以内の誤差はOK)。これはリプレイアタックを防止するためです。 - 署名のベースとなる文字列を
sig_basestring = 'v0:' + timestamp + ':' + request_body
のようにタイムスタンプとリクエストボディから作成します。 - この文字列とシークレットキーより、 HMAC-SHA256 の値を計算します。
- 4で計算した文字列の最初に
v0=
を付けた文字列が HTTPヘッダX-Slack-Signature
と一致することを確認します。
Serverless Frameworkが要求するメモリ
serverless.ymlのリファレンスによれば、メモリサイズ(memorySize)のデフォルトは1024です。もったいないので変更しましょう。最小の128MBで十分でした。
ロールは一括で定義(個別定義はプラグイン)
Serverless Frameworkでは、IAMのロールは一括で定義します。関数ごとに個別に定義したい場合は、serverless-iam-roles-per-functionというプラグインを使ってください(今回は未使用)
追記
Slackの審査通しました。