20211130のNode.jsに関する記事は5件です。

LINEで画像を集めて、素敵なモザイクアートを作ろう

はじめに まもなく年末が近づいてきました。 今年の思い出にモザイクアートなどを作ったら盛り上がるんじゃないかといった提案です。 クリスマスパーティーとか年賀状とか、いろんなところで友達や家族、クラスで集めた画像を一つにまとめて、みんなでいい思い出を作れると楽しいですよね。 以前挑戦して大成功した こちらは今年の頭に、高校の卒業記念に卒業生みんなで作ったモザイクアートです。 8700枚ぐらいの画像を使って作ったのですが、すごくいいものが出来上がってみんな喜んでくれました。メインになっている画像は、母校の校舎です。 今回の特徴 高校の公式イベントとして行ったため、いくつかの制約がありました。 みんなが簡単にかつ大量に提出できること 集めた画像は学校が管理するサーバーなどで管理すること モザイクアートにする前に、不適切な画像などは教員側で確認し、削除できること こちらを守ることを約束に企画は進みました。 この制約を突破するために LINE公式アカウントに画像を送信するだけで応募できる 画像はS3などにアップロードせず、学校のGoogleDriveにアップロードする GoogleDriveの中なら先生も簡単に画像の確認&分別ができる 技術構成 GoogleDriveに画像をアップロードする LINEBot▶️googleDrive 画像のアップロードに成功した#lineapi pic.twitter.com/PC59b6CmfG— ようかん / Yosuke Inoue (@inoue2002) January 20, 2021 LINEBotをつくる AWS Lambdaを使って、LINEbotを構築する手法をLINEBotをみんなで作ろうの記事にて解説しています。 サンプルコードもこの記事の後半記事のこちらに記載しています。 画像を受け取って、GoogleDriveにアップロードする まずLambdaからgoogleDriveAPIを操作を参考にLambdaからgoogleDriveを操作する手法を確認してください。 - GoogleDriveAPIの有効化 - privatekey.jsonの生成 連携ができたら LINEBotに送った画像をGoogleDriveに保存して共有URLを返すまでの記事の通りに実装を進めると実際にLINEBotに送信された画像をそのままGoogleDriveに送信することが可能になります! //1行でユーザーの送信した画像を取得可能 const imageStream = await client.getMessageContent(event.message.id); モザイクアートを生成する 今回はこちらのmosaic-node-generatorモジュールを利用させていただきました。 細かい設定も幅広くできますが、とりあえず試すには2行のコードを書いて、コマンドを叩くだけで生成されます! index.js const mosaic = require("mosaic-node-generator"); mosaic.mosaic("元画像.jpg","素材フォルダ名") node index.js //大掛かりなものは軽く1hぐらいかかって生成される 終わりに 是非みなさんもLINEで複数人から画像を楽に集めて、素敵なモザイクアートを生成してみてください! それでは、良い年末を!! 説明を端折っている箇所もいくつかあるので、わかりにくいところなどありましたらコメントいただけますと追記いたします!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FirebaseAuthを使った自作APIの認証

現在Flutterアプリを開発中です。自前のサーバーを立ててAPIを作成するということが良くあると思います。その中でAPIに正規のユーザーしか受け付けないように認証を実装します。FirebaseでFlutterアプリの認証を行なっているのでAPIの認証もFirebaseに任せたいと思います。今回の構成図は以下の通りです。 JWTとは Json Web Tokenの略で読み方はジョット。JSONデータ構造で表現したトークンの仕様です。 JWTは長い文字列になっていますが . によって3つに分割することができます。構成は<ヘッダー>.<ペイロード>.<署名>となってます。JWT実際にデコードしていってみます。色々検証してみたい方はこちら↓↓↓ ヘッダー ヘッダーはJWTの署名検証を行うために必要な情報を格納しています。"typ"はJWTであることを示し、"alg"は署名アルゴリズムを示しています。公開鍵方式であり署名の作成に秘密鍵を利用。署名の検証に 公開鍵を利用。"kid"は鍵IDとなっています。 { "alg": "RS256", "kid": "xxxxxxxxxxxxxxxxxxxxxxxxxxx", "typ": "JWT" } ペイロード ペイロードはやりとりに必要な属性情報(Claim)です。送信したい情報を格納しています。 { "provider_id": "anonymous", "iss": "https://securetoken.google.com/xxxxxxxxxxxxx", "aud": "xxxxxxxxxxxxxxxxxxx", "auth_time": 1234567890, "user_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxx", "sub": "xxxxxxxxxxxxxxxxxxxxxxxxxxx", "iat": 1234567890, "exp": 1234567890, "firebase": { "identities": {}, "sign_in_provider": "anonymous" } } 署名 署名は、<エンコードしたヘッダー>.<エンコードしたペイロード>を連結したものを入力値として"alg"の署名アルゴリズムで署名し、Base64urlエンコードすることにより作成されます。これをJWTで送られてきた署名と比較して一致すれば認証成功になります。 実践編 Firebase Admin SDKを導入する サーバーサイド側(Node.js)にFirebase Admin SDKを導入します。メリットとしては比較的シンプルなコードでJWTを検証することができます。Firebaseでサポートされていない言語の場合でもサードパーティのJWTライブラリを使用して検証することもできます。 JWTを取得する クライアント側で(Flutter)でJWTを取得します。 公式に以下のような記述があります↓↓↓ Firebase Authentication セッションは長期間有効です。ユーザーがログインするたびに、ユーザー認証情報が Firebase Authentication のバックエンドに送信され、Firebase ID トークン(JWT)および更新トークンと交換されます。Firebase ID トークンの有効期間は短く、1 時間で期限切れとなります。 String idToken = await FirebaseAuth.instance.currentUser!.getIdToken(); JWTを付与してリクエストを行う クライアントがJWTをHTTPリクエストに含めて送信します。 Authorization: Bearer <JWT> JWTを検証する サーバーサイド側(Node.js)でJWTの検証はFirebase Admin SDKによって簡単にできます。Firebaseが送信したidTokenか改ざんされていないidTokenかを検証します。 admin.auth().verifyIdToken(idToken).then((decodedToken) => { // 認証成功 const uid = decodedToken.uid; }).catch((error) => { // 認証失敗 }); 参考文献 ・JWTについて ・Firebase公式 ・その他参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

モックサーバ(instant-mock)で送信できるContent-Typeメモ

モックサーバ(instant-mock)で送信できるContent-Typeメモ 前回下記の記事でinstant-mockの構築手順を(ほぼ自分用に)まとめた。 今回はこのモックサーバーはresponse bodyにどんなデータの形式を入れ込んで送信できるのかを検証してみた。 検証結果 bodyに入れ込むことができたデータの形式は以下のものであった。 JSONファイル PDFファイル EXCELファイル( .xlsx OFFICE 2007以降) CSVファイル HTMLファイル CSSファイル テキストファイル JPEGファイル(.jpg) PNGファイル Bitmapファイル bodyにデータを格納する際にContent-Typeの参考にさせていただいたのは下記サイトである。 ymlファイル APIを定義したymlファイルか下記通り。 mockサーバを初期化した際にできたymlファイルに書き加えていった。 parser-default.yml - if: query: group: team-a then: status: 200 headers: Content-Type: application/json body: team-a.json - if: query: group: team-b then: status: 200 headers: Content-Type: application/json body: team-b.json - if: query: group: team-pdf then: status: 200 headers: Content-Type: application/pdf body: PDFファイルだお.pdf - if: query: group: team-sheet then: status: 200 headers: Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet body: test.xlsx - if: query: group: team-csv then: status: 200 headers: Content-Type: text/csv body: hell.csv - if: query: group: team-html then: status: 200 headers: Content-Type: text/html body: test.html - if: query: group: team-css then: status: 200 headers: Content-Type: text/css body: test.css - if: query: group: team-text then: status: 200 headers: Content-Type: text/plain body: hello.txt - if: query: group: team-jpeg then: status: 200 headers: Content-Type: image/jpeg body: テスト.jpg - if: query: group: team-png then: status: 200 headers: Content-Type: image/png body: テスト.png - if: query: group: team-bmp then: status: 200 headers: Content-Type: image/bmp body: テスト.bmp - then: status: 200 headers: Content-Type: application/json body: all.json 実行結果 ARC(Advanced REST client)を使って定義したAPIのレスポンスを確認しようとしたところ、 レスポンス結果がバイナリーコードのまま表示され、上手く送れてるのかの判別がしにくかった。 ARCのスクショ そこで今回はコンソールでgetコマンドを使用して動作検証を行った。 JSONファイル ※team-aとteam-bはmockサーバを初期化した際にできたymlファイルに元々記載してあったもの。 http://localhost:3000/mock/bodyTest?group=team-a team-aスクショ http://localhost:3000/mock/bodyTest?group=team-b team-bスクショ PDFファイル http://localhost:3000/mock/bodyTest?group=team-pdf team-pdfスクショ EXCELファイル( .xlsx OFFICE 2007以降) ブラウザの画面の表示はymlファイルの一番最後に記述してあるthenのjsonファイルの中身が表示された。 http://localhost:3000/mock/bodyTest?group=team-sheet team-sheetスクショ CSVファイル http://localhost:3000/mock/bodyTest?group=team-csv team-csvスクショteam-csv HTMLファイル http://localhost:3000/mock/bodyTest?group=team-html team-htmlスクショ CSSファイル http://localhost:3000/mock/bodyTest?group=team-css team-cssスクショ テキストファイル http://localhost:3000/mock/bodyTest?group=team-text team-textスクショ JPEGファイル(.jpg) http://localhost:3000/mock/bodyTest?group=team-jpeg team-jpegスクショ PNGファイル http://localhost:3000/mock/bodyTest?group=team-png team-pngスクショ Bitmapファイル http://localhost:3000/mock/bodyTest?group=team-bmp team-bmpスクショ 以上が試した結果になる。 もし気が向いたら他のContent-Typeも試して追記するカモ。。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AirtableをAPIとしてnodeJSを使いJSONデータ化するまで

JSONを吐かせる簡易DB代わりにGoogleSpreadSheetを使っているのですが使っているGAS使ってるシートとそうでないシートとでごちゃごちゃしてきてしまったので無料で使えて代替手段となるものはないのかな〜ということでAirtableを使ってみることに。 (気になってたからってのもある) ワークスペースの準備 初めに、ワークスペースとしてTestテーブルを作成しておきます。シート名も"TestTable"としておきました。 APIキーの取得 「右上のアイコン > Account」へ飛ぶとAPIキーを生成できるので生成して保存しておきます。 TestテーブルのIDの取得 そして https://airtable.com/api/ へ飛ぶと自分のワークスペース専用ドキュメントができているという素晴らしきユーザー体験 画像の "Test"(あなたの任意のワークスペース) へ飛びます。 https://airtable.com/あなたのテーブルID/api/docs#javascript/ratelimits するURLにテーブルIDがあるので "あなたのテーブルID" を保存しておきます。 試しに Name の列のカラムを取得してみる ディレクトリ構成(node_modulesなどは省く) AirtableTest/ generated/ .env index.js package.json 適当に yarn init などしておきディレクトリ準備。 ライブラリをインストール yarn add -D airtable dotenv scriptsを追加して実行コマンドも設定しておく。 package.json 省略 "scripts": { "start": "node -r dotenv/config index.js" } 省略 環境変数の設定 AIRTABLE_API_KEY=あなたのAPIキー テーブルのName列のカラムを取得する処理 index.js const base = require('airtable').base('あなたのテーブルID'); const app = base("TestTable") const airtableLists = app.select({view: "Grid view"}) airtableLists.firstPage((error, records) => { const names = records.map(record => record.get("Name") ) console.log(names); }); 実行してみる yarn start yarn run v1.22.10 $ node -r dotenv/config index.js [ 'hoge', 'hage', 'fuga', undefined ] ✨ Done in 2.35s. と上のような値が返って来ればOKです。 JSON化する ここで取得したデータをJSONデータ化させてみます。 index.js const fs = require('fs'); const base = require('airtable').base('あなたのテーブルID'); const app = base("TestTable"); const airtableLists = app.select({view: "Grid view"}); const createJSON = (airtableObj) => { const JSONdata = JSON.stringify(airtableObj, null, 2); fs.writeFileSync('generated/airtable.json', JSONdata); } const getAirtableObj = (airtableLists) => { return airtableLists.firstPage((error, records) => { const airtableObj = records.map(record => ({ Name: record.get("Name"), Notes: record.get("Notes") })); createJSON(airtableObj); }); } getAirtableObj(airtableLists); ここで generated/airtable.json にルート指定して生成します。 const createJSON = (airtableObj) => { const JSONdata = JSON.stringify(airtableObj, null, 2); fs.writeFileSync('generated/airtable.json', JSONdata); } 生成されたJSONを確認してみる 空白だったカラムは空になっていますが以下のように取得できていることがわかります。 airtable.json [ { "Name": "hoge", "Notes": "ほい" }, { "Name": "hage", "Notes": "はい" }, { "Name": "fuga", "Notes": "へい" }, {} ]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Slack API のトークンローテーション完全ガイド

こんにちは、Slack で公式 SDK 開発と日本の DevRel を担当しております @seratch と申します。 こちらの記事では、今年 Slack がリリースしたトークンローテーションに関する詳細な説明を日本語でチュートリアル形式でやっていきたいと思います。 トークンローテーションとは Slack の OAuth アクセストークンは、長らく refresh token がなく、一度発行した access token は明に無効化(auth.revoke API やワークスペースのアプリ管理画面から revoke できます)しない限り、無期限で使える仕様でした。 しかし、2021 年に refresh token とともに access token を発行する機能が提供されるようになりました。 この記事ではこれを「トークンローテーション(Token Rotation)」と呼びます。なお、このトークンローテーションはデフォルトでは有効ではなく Slack アプリの管理画面から(または App Manifest の設定によって) opt-in する必要があります。この記事では、この辺の手順・注意点も改めて日本語で紹介していきます。 通常の Slack アプリのトークンローテーションを試してみる 通常の bot token / user token を持つアプリのトークンローテーションの利用法を説明していきます。 Slack アプリを作成〜設定する こちらの URL にアクセスして、新しいアプリ設定をつくります。以下の動画のように、開発用に使用するホームのワークスペースを選んだら、自動的に設定が読み込まれているはずです。まずはそのままアプリを作ってみてください。 上のリンクには App Manifest という YAML 形式の設定がクエリストリングとして仕込んでありました。その内容を以下に貼っておきます。手動で設定するときは、これをベースに設定してみてください。各項目の説明はこちらを参考にしてください。 _metadata: major_version: 1 minor_version: 1 display_information: name: token-rotation-test-app features: bot_user: display_name: token-rotation-test-app oauth_config: redirect_urls: - https://TOBEUPDATED.ngrok.io/slack/oauth_redirect scopes: user: - chat:write bot: - app_mentions:read - chat:write settings: event_subscriptions: request_url: https://TOBEUPDATED.ngrok.io/slack/events bot_events: - app_mention token_rotation_enabled: true 画面から設定する場合は Settings > OAuth & Permissions のページで Opt in します。英語で書かれている通り OAuth の Redirect URL の設定が必須となります(つまり、この管理画面からのインストールではなく OAuth フローでのみ refresh token は発行されます)。 これでアプリの設定はできたので、実際に OAuth フローによる Slack ワークスペースへのインストールを実行してローテーション可能なアクセストークンを取得してみましょう。ここからは Python と Node.js でそれぞれサンプルアプリを動かしていきます。 なお、ここでの例ではソケットモードを使っていませんが、OAuth フロー以外のイベントを処理する部分などにはソケットモードを使用することも可能です。ソケットモードを使いたい場合は、以下のサンプルや記事を参考にしてみてください。 Slack ソケットモードの最も簡単な始め方 Python での OAuth とソケットーモードの併用コード例 JavaScript での OAuth とソケットモードの併用コード例 Python でアプリを実装する Python で Slack の OAuth フロー(アクセストークンを発行して Slack ワークスペースでアプリを有事公にする手順)と Slack から来たイベントに対してトークンローテーションをしながら応答するアプリを動かしてみましょう。 プロジェクトの新規作成 まず使用する Python のバージョンが 3.6 以上であるかを確認してください。 最近では、システム標準の python3 や pip3 などのコマンドも 3.6 以上のバージョンだとは思いますが、常に最新のバージョン(この記事投稿時点で 3.10 です)を使用するために pyenv などのツールを使って Python のランタイムを管理することをお勧めします。 その 3.6 以上の Python で、以下のような依存ライブラリを解決したまっさらな環境を作ります。 echo 'slack-bolt>=1.10,<2' > requirements.txt python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt Poetry を使うなら、以下のコマンドで同じことができます。 poetry init -n poetry shell poetry add slack-bolt 仮想環境を準備できたので、ここでアプリを実装して起動していきます。 環境変数を設定して、アプリケーションを起動 以下のような Python コードを app.py という名前で保存してください。 # より詳細なログを出力するためにログレベルを DEBUG に変更します import logging logging.basicConfig(level=logging.DEBUG) import os from slack_bolt import App from slack_bolt.oauth.oauth_settings import OAuthSettings from slack_sdk.oauth.installation_store import FileInstallationStore from slack_sdk.oauth.state_store import FileOAuthStateStore oauth_settings = OAuthSettings( # ここの二つの環境変数は必須で、正しく対象のアプリのものが設定されている必要があります client_id=os.environ["SLACK_CLIENT_ID"], client_secret=os.environ["SLACK_CLIENT_SECRET"], # これは管理画面における Bot Scopes です scopes=["app_mentions:read", "chat:write"], # これは管理画面における User Scopes です user_scopes=["chat:write"], # Slack ワークスペースへのインストール情報を管理する実装、ここではローカルファイルに保存します installation_store=FileInstallationStore(base_dir="./data/installations"), # OAuth フローの state パラメーターの永続化の実装、ローカルファイルに保存します state_store=FileOAuthStateStore(expiration_seconds=600, base_dir="./data/states"), # 以下の設定は後ほど説明します # token_rotation_expiration_minutes=60 * 24, ) app = App( # これは OAuth フローでは使いません、Slack からのイベントリクエストの検証に使います signing_secret=os.environ["SLACK_SIGNING_SECRET"], # OAuth 関連の設定をここで渡します oauth_settings=oauth_settings ) # これはこのアプリの bot user をメンションしたときのイベントに対して応答するリスナーです @app.event("app_mention") def handle_app_mention_events(event, client, context, say): if context.user_token is not None: # インストールしたユーザー自身からのメンションだったとき client.chat_postMessage( token=context.user_token, channel=context.channel_id, text=f"<@{event['user']}> のユーザートークンをお預かりしているので、こんなことができます :nerd_face:", ) else: say(f"<@{event['user']}> こんにちは!") if __name__ == "__main__": # アプリを http://localhost:3000/ で起動します app.start(port=int(os.environ.get("PORT", 3000))) # このアプリは 3 つの URL をサーブします # - http://localhost:3000/slack/install # - http://localhost:3000/slack/oauth_redirect # - http://localhost:3000/slack/events まずは、そのままの状態で動作させてみましょう。この状態で以下の環境変数を設定します。 Settings > Basic Information のページに App Credentials というセクションがありますので、 そこから Client ID、Client Secret、Signing Secret の値を、それぞれ環境変数 SLACK_CLIENT_ID、SLACK_CLIENT_SECRET、SLACK_SIGNING_SECRET に設定します。 # OAuth フローのために必要 export SLACK_CLIENT_ID=1234567890.1234567890123 export SLACK_CLIENT_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXX # Slack からのリクエストか検証するために必要 export SLACK_SIGNING_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXX そして、アプリを以下のコマンドで起動してみてください。 python app.py 以下のようなログがコンソールに出力されていれば、とりあえずは OK です! INFO:slack_bolt.App:⚡️ Bolt app is running! (development server) ngrok で公開された URL を用意して設定に反映 アプリ起動の最後のステップとして公開された URL を準備するために ngrok というツールを使います。使ったことがない方は https://ngrok.com/ でダウンロードして設定してみてください。 ngrok を使えるようになったら、以下のコマンドを実行してください。これによってインターネットに公開された URL に来たリクエストを http://localhost:3000/ で起動している上の Python アプリにフォーワードすることができるようになります。 ngrok http 3000 ngrok の有料プランのアカウントをお持ちの場合は ngrok http 3000 --subdomain my-token-rotation-app のように固定されたサブドメインを使うことが可能ですが、無料利用の場合は毎回ランダムに決まります。いずれにせよ、ngrok を起動して表示された https://{あなたのサブドメイン}.ngrok.io のドメイン名で App Manifest に埋め込まれている TOBEUPDATED.ngrok.io を差し替えてください。 YAML を直接編集するモードの場合は、以下のように Edit Manifest ボタンから編集モードに移動して oauth_config.redirect_urls[0] と settings.event_subscriptions.request_url の二箇所を編集します。 settings.event_subscriptions.request_url の方は以下のようなワーニングメッセージが出てくるかと思います。 python app.py でアプリが起動していて、かつ ngrok も手元で動いている前提で Click here to verify というリンクを押すと、検証リクエストが送信されます。疎通ができたら設定完了です。 なお、この YAML 設定のエディターのやり方はちょっと慣れない・やりづらいという場合、左の方に「Revert to the old design」というリンクがありますので、ここから旧来の画面に戻すこともできます。 旧画面から設定する場合は、それぞれ Features > OAuth & Permissions > Redirect URLs と Features > Event Subscriptions > Request URL を適切に設定してください。その際、URL の末尾が OAuth の Redirect URL は /slack/oauth_redirect で Event Subscriptions の Request URL は /slack/events であることに注意してください。 Slack アプリのインストールを実行 ここまでできたら、ブラウザーを開いて https://{あなたのサブドメイン}.ngrok.io/slack/install にアクセスしてみてください。 以下のようなシンプルなボタンだけの画面が表示されれば OK です。 「Add to Slack」ボタンからインストールを実行していきます。 「Allow」ボタンをクリックして、以下のような画面に遷移すれば成功です。 これでブラウザを使ったインストール作業は完了です。 では、実際インストールによってどのようなトークンを得られたかを見てみましょう。このサンプルアプリでは、インストール結果をローカルファイルに保存するようになっていますので、以下のようなファイルがつくられているはずです。 $ tree data data ├── installations │   └── none-T12345678 │   ├── bot-1637909589.684739 │   ├── bot-latest │   ├── installer-1637909589.684739 │   ├── installer-U12345678-1637909589.684739 │   ├── installer-U12345678-latest │   └── installer-latest └── states これらのファイルについて軽く説明しておきます。none-T12345678 はワークスペースの階層(none のところは Enterprise Grid の場合に enterprise_id が入ります)で、その下のファイルは installer は一度のインストールに含まれる bot やそのユーザー固有の設定、Incoming Webhooks など全てが含まれるファイルで bot は bot に関するものに絞ったものです。履歴の保持がデフォルトでオンになっているので -latest とタイムスタンプごとのファイルがそれぞれ同じ内容で保存しされています。もう一度インストールすると -latest の方は上書きとなります。 インストール情報の中身を見てみてましょう。 cat data/installations/none-T12345678/installer-latest | jq のようにして表示してみてください。ここでの説明に関係ない null の値は削っていますが、以下のようなものが表示されるはずです。 { "app_id": "A1234567890", "team_id": "T1234567890", "team_name": "Acme Corp", "bot_token": "xoxe.xoxb-1-xxx", "bot_id": "B1234567890", "bot_user_id": "U1234567890", "bot_scopes": [ "app_mentions:read", "chat:write" ], "bot_refresh_token": "xoxe-1-xxx", "bot_token_expires_at": 1637952233, "user_id": "U2222222222", "user_token": "xoxe.xoxp-1-xxx", "user_scopes": [ "chat:write" ], "user_refresh_token": "xoxe-1-xxx", "user_token_expires_at": 1637952232, "token_type": "bot", "installed_at": 1637909589.684739 } 以下のテーブルは、トークンローテーションにおいて知っておくべき項目について簡単に解説しています。 項目 説明 bot_token bot のアクセストークンです。一定時間が経過すると(デフォルトでは 12 時間)期限切れになります bot_refresh_token bot のアクセストークンを更新するための refresh token です bot_token_expires_at token が発行されたときの Unix time に oauth.v2.access API から返された expires_in (秒) を加算したものを保持しています user_token アプリのインストールを実行したユーザー個人のアクセストークンです。一定時間が経過すると(デフォルトでは 12 時間)期限切れになります user_refresh_token ユーザー個人のアクセストークンを更新するための refresh token です user_token_expires_at token が発行されたときの Unix time に oauth.v2.access API から返された expires_in (秒) を加算したものを保持しています なお、コマンドラインで現在の Unix time (秒)を手軽に知りたい場合、以下のコマンドで値を取得することができます。 $ python3 -c 'import time; print(int(time.time()))' 1637916854 トークンローテーションの様子を確認 それでは、このアプリをインストールした Slack ワークスペースの画面を開いてください。テスト用のチャンネルにこのアプリの bot user を招待してください。@token-rotation-test-app をメンションしたら、「招待しますか?」と聞かれますので、そのまま招待してあげてください。 招待したら、そのボットユーザーを再度メンションしてみてください。すると、以下のように自分自身から返信が来るはずです。これは先程発行したトークンのうち、ユーザートークン側を使っているためです。 コードを以下のようにシンプルなものにして起動し直してみてください。 # これはこのアプリの bot user をメンションしたときのイベントに対して応答するリスナーです @app.event("app_mention") def handle_app_mention_events(event, say): say(f"<@{event['user']}> こんにちは!") 今度は bot からの返事に変わります。 ともあれ、アプリは正常に動いているようです。ここまでのアプリのコンソールログを見てみてください。トークンはローテーションされているのでしょうか? いえ、デフォルトでは、毎回ローテーションはしない挙動になっています。上の各項目を説明するテーブルでも書いた通り、発行されたトークンは 12 時間程度有効なので、それよりは少し短い時間の間、リフレッシュせずにそのまま使う挙動になっています。これはアプリの実行パフォーマンスにオーバーヘッドを与えないための配慮です。 しかし、(ローテーションの挙動を確認したいときなどのために)この設定をカスタマイズできるようになっています。最初に貼ったコードの以下の部分のコメントアウトを外して起動し直してみてください。 # 以下の設定は後ほど説明します token_rotation_expiration_minutes=60 * 24, この設定にするとアクセストークンの期限切れ 24 時間前になったら、リフレッシュするようになります。つまり、常にトークンをリフレッシュするようになるということです。 この状態でまた app_mention のイベントを送信すると、先ほどのログとは違う点として、以下の二つの API コールが追加されていることに気づくはずです。 これらがやっていることは、このワークスペースの bot token とこのアクセスユーザーに紐づく user token 両方を oauth.v2.access API を使って refresh しています。その結果は Bolt の内部実装によって自動的に InstallationStore が管理するデータに反映されます。 DEBUG:slack_sdk.web.base_client:Sending a request - url: https://www.slack.com/api/oauth.v2.access, query_params: {}, body_params: {'grant_type': 'refresh_token', 'refresh_token': 'xoxe-1-xxx'}, files: {}, json_body: None, headers: {'Authorization': '(redacted)'} DEBUG:slack_sdk.web.base_client:Received the following response - status: 200, headers: {}, body: {"ok":true,"access_token":"xoxe.xoxb-1-xxx","expires_in":43200,"refresh_token":"xoxe-1-xxx","token_type":"bot","app_id":"A1234567890","scope":"app_mentions:read,chat:write","bot_user_id":"U1234567890","team":{"id":"T1234567890","name":"Acme Corp"},"enterprise":null,"is_enterprise_install":false} DEBUG:slack_sdk.web.base_client:Sending a request - url: https://www.slack.com/api/oauth.v2.access, query_params: {}, body_params: {'grant_type': 'refresh_token', 'refresh_token': 'xoxe-1-xxx'}, files: {}, json_body: None, headers: {'Authorization': '(redacted)'} DEBUG:slack_sdk.web.base_client:Received the following response - status: 200, headers: {}, body: {"ok":true,"access_token":"xoxe.xoxp-1-xxx","expires_in":43200,"refresh_token":"xoxe-1-xxx","token_type":"user","app_id":"A1234567890","scope":"identify,chat:write","user_id":"U2222222222","team":{"id":"T1234567890","name":"Acme Corp"},"enterprise":null,"is_enterprise_install":false} 先程の data/installations が管理するデータを見ると refresh される度に -latest が更新され、また履歴データが増えていくことがわかります(もしこの挙動を変えたい場合は、履歴を全て取る実装をオフにする設定にしてください)。 実装に興味がある方は以下のコードを見てみてください。 この Bolt のコードで self.token_rotator がやっていること TokenRotator とそのテストコード 今回の例では、一度にデモするために bot token と user token を両方使っていますが、もちろん bot token だけ、user token だけを利用する場合にも TokenRotator はそのまま使用することができます。 また、実際に運用するアプリであればデータベースやより安全な場所にトークンを保持することになるかと思います。Python SDK では、組み込みでファイルに加えて SQLAlchemy、Amazon S3、SQLite3 に対応しています。これらのモジュールのソースコードはこちらを参照してください。Django のアプリケーションでトークンローテーションしたい場合は、こちらの Bolt for Python を使ったサンプルを参考にしてみてください。 Node.js でアプリを実装する 繰り返しを避けるために、上記の Python との差分のところのみ紹介しておきます。 Node.js の場合、プロジェクト自体は npm init -y npm install @slack/bolt でつくります。Node.js の利用可能バージョンは、こちらのバージョン指定で確認してください。 そして index.js として以下のコードを保存します。 const { LogLevel } = require("@slack/logger"); const { App, FileInstallationStore } = require("@slack/bolt"); const app = new App({ // Basic Information のページから設定してください clientId: process.env.SLACK_CLIENT_ID, // Basic Information のページから設定してください clientSecret: process.env.SLACK_CLIENT_SECRET, // これは管理画面における Bot Scopes です scopes: ['commands', 'chat:write'], // これは管理画面における User Scopes です userScopes: ['chat:write'], // Slack ワークスペースへのインストール情報を管理する実装、ここではローカルファイルに保存します installationStore: new FileInstallationStore({ baseDir: './data/installations', clientId: process.env.SLACK_CLIENT_ID, }), // これは OAuth フローの state パラメーターの値を生成する際に使われます stateSecret: 'my-state-secret', // これは OAuth フローでは使いません、Slack からのイベントリクエストの検証に使います signingSecret: process.env.SLACK_SIGNING_SECRET, // ログレベルを変更しています、指定しない場合はより詳細なログを出力するために DEBUG logLevel: process.env.SLACK_LOG_LEVEL || LogLevel.DEBUG, }); // これはこのアプリの bot user をメンションしたときのイベントに対して応答するリスナーです app.event("app_mention", async ({ logger, event, say }) => { logger.debug("app_mention event payload:\n\n" + JSON.stringify(event, null, 2) + "\n"); }); (async () => { // アプリを http://localhost:3000/ で起動します await app.start(process.env.PORT || 3000); console.log("⚡️ Bolt app is running!"); })(); // このアプリは 3 つの URL をサーブします // - http://localhost:3000/slack/install // - http://localhost:3000/slack/oauth_redirect // - http://localhost:3000/slack/events 上の Python と同様に環境変数を設定した上で # OAuth フローのために必要 export SLACK_CLIENT_ID=1234567890.1234567890123 export SLACK_CLIENT_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXX # Slack からのリクエストか検証するために必要 export SLACK_SIGNING_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXX 以下のコマンドでアプリを起動したら npx node index.js Python と同様に ngrok で公開 URL からリクエストがフォーワードされるようにします。インストールが完了すると、若干命名規則は異なるものの、同様にインストール情報が保存されます。 $ tree data data └── installations └── 3485157640.2764408644502 └── T1234567890 ├── app-1637925605512 ├── app-latest ├── user-U1234567890-1637925605512 └── user-U1234567890-latest 3 directories, 4 files Node の SDK での rotation の実装は、こちらのコードを参考にしてみてください。 Sign in with Slack のトークンローテーションを試してみる OpenID Connect 互換の Sign in with Slack のフローで発行されたトークンもトークンローテーションに対応しています。こちらについてもどのように処理すべきか、簡単に紹介します。 以下のような App Manifest で Slack アプリの設定を作ってください。 _metadata: major_version: 1 minor_version: 1 display_information: name: token-rotation-siws-test-app features: bot_user: display_name: token-rotation-siws-test-app oauth_config: redirect_urls: - https://TOBEUPDATED.ngrok.io/slack/oauth_redirect scopes: user: - openid - email - profile bot: - commands settings: token_rotation_enabled: true 以下は、Sing in with Slack をハンドリングする Flask の Web アプリケーション例です。必要な依存ライブラリは以下の 2 つです。 pip install flask slack-sdk 大きな違いとして、リフレッシュするための API が openid.connect.token であること、auth.test などのメソッドは使えず openid.connect.userInfo だけを使える点が異なります。ですが、基本的には同じようなフローでトークンを管理するだけで OK です。 なお、TokenRotator は Sign in with Slack (OpenID Connect) には対応していないので、以下のコード例のように直接 API を呼び出すコードを必要に応じて書いてください。 # より詳細なログを出力するためにログレベルを DEBUG に変更します import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) import json import os # 必要な設定をあらかじめ環境変数に設定しておきます client_id = os.environ["SLACK_CLIENT_ID"] client_secret = os.environ["SLACK_CLIENT_SECRET"] redirect_uri = os.environ["SLACK_REDIRECT_URI"] # 最低限必要なのは "openid" です scopes = ["openid", "email", "profile"] from slack_sdk.web import WebClient from slack_sdk.oauth import OpenIDConnectAuthorizeUrlGenerator, RedirectUriPageRenderer from slack_sdk.oauth.state_store import FileOAuthStateStore state_store = FileOAuthStateStore(expiration_seconds=300) # https://slack.com/openid/connect/authorize?... な URL を生成します authorization_url_generator = OpenIDConnectAuthorizeUrlGenerator( client_id=client_id, scopes=scopes, redirect_uri=redirect_uri, ) # Flask のアプリケーションとして実装していますが、他のフレームワークでももちろん OK です from flask import Flask, request, make_response app = Flask(__name__) app.debug = True # Sign in with Slack の画面を表示します @app.route("/slack/install", methods=["GET"]) def oauth_start(): state = state_store.issue() url = authorization_url_generator.generate(state=state) return ( '<html><head><link rel="icon" href="data:,"></head><body>' f'<a href="{url}" style="align-items:center;color:#000;background-color:#fff;border:1px solid #ddd;border-radius:4px;display:inline-flex;font-family:Lato, sans-serif;font-size:16px;font-weight:600;height:48px;justify-content:center;text-decoration:none;width:256px"><svg xmlns="http://www.w3.org/2000/svg" style="height:20px;width:20px;margin-right:12px" viewBox="0 0 122.8 122.8"><path d="M25.8 77.6c0 7.1-5.8 12.9-12.9 12.9S0 84.7 0 77.6s5.8-12.9 12.9-12.9h12.9v12.9zm6.5 0c0-7.1 5.8-12.9 12.9-12.9s12.9 5.8 12.9 12.9v32.3c0 7.1-5.8 12.9-12.9 12.9s-12.9-5.8-12.9-12.9V77.6z" fill="#e01e5a"></path><path d="M45.2 25.8c-7.1 0-12.9-5.8-12.9-12.9S38.1 0 45.2 0s12.9 5.8 12.9 12.9v12.9H45.2zm0 6.5c7.1 0 12.9 5.8 12.9 12.9s-5.8 12.9-12.9 12.9H12.9C5.8 58.1 0 52.3 0 45.2s5.8-12.9 12.9-12.9h32.3z" fill="#36c5f0"></path><path d="M97 45.2c0-7.1 5.8-12.9 12.9-12.9s12.9 5.8 12.9 12.9-5.8 12.9-12.9 12.9H97V45.2zm-6.5 0c0 7.1-5.8 12.9-12.9 12.9s-12.9-5.8-12.9-12.9V12.9C64.7 5.8 70.5 0 77.6 0s12.9 5.8 12.9 12.9v32.3z" fill="#2eb67d"></path><path d="M77.6 97c7.1 0 12.9 5.8 12.9 12.9s-5.8 12.9-12.9 12.9-12.9-5.8-12.9-12.9V97h12.9zm0-6.5c-7.1 0-12.9-5.8-12.9-12.9s5.8-12.9 12.9-12.9h32.3c7.1 0 12.9 5.8 12.9 12.9s-5.8 12.9-12.9 12.9H77.6z" fill="#ecb22e"></path></svg>Sign in with Slack</a>' "</body></html>" ) # デフォルトのエラー画面を表示するために使います redirect_page_renderer = RedirectUriPageRenderer( install_path="/slack/install", redirect_uri_path="/slack/oauth_redirect", ) # Slack の確認画面から遷移してきたときの URL です @app.route("/slack/oauth_redirect", methods=["GET"]) def oauth_callback(): # クエリストリングから code, state パラメーターを取得してチェックします if "code" in request.args: state = request.args["state"] if state_store.consume(state): code = request.args["code"] try: client = WebClient() # code パラメーターを使って access_token / refresh_token を取得します token_response = client.openid_connect_token( client_id=client_id, client_secret=client_secret, code=code, ) # refresh token を使ってリフレッシュを実施します refreshed_token_response = client.openid_connect_token( client_id=client_id, client_secret=client_secret, token=token_response.get("access_token"), refresh_token=token_response.get("refresh_token"), grant_type="refresh_token", ) # リフレッシュされた token が問題ないか openid.connect.userInfo API を呼び出してテスト refreshed_user_token = refreshed_token_response.get("access_token") user_info_response = client.openid_connect_userInfo(token=refreshed_user_token) logger.info(f"openid.connect.userInfo response: {user_info_response}") # 実際にはここで access_token / refresh_token を何らかの形で保存することになります # あくまでデモとして結果を Web ページに表示しています return f""" <html> <head> <style> body h2 {{ padding: 10px 15px; font-family: verdana; text-align: center; }} </style> </head> <body> <h2>openid.connect.userInfo response</h2> <pre>{json.dumps(user_info_response.data, indent=2)}</pre> </body> </html> """ except Exception: logger.exception("Failed to perform openid.connect.token API call") return redirect_page_renderer.render_failure_page( "Failed to perform openid.connect.token API call" ) else: return redirect_page_renderer.render_failure_page( "The state value is already expired" ) error = request.args["error"] if "error" in request.args else "" return make_response( f"Something is wrong with the installation (error: {error})", 400 ) if __name__ == "__main__": # export SLACK_CLIENT_ID=111.222 # export SLACK_CLIENT_SECRET=xxx # export FLASK_ENV=development # export SLACK_REDIRECT_URI=https://{your-domain}/slack/oauth_redirect # python3 app.py app.run("localhost", 3000) # このアプリは 2 つの URL をサーブします # - http://localhost:3000/slack/install # - http://localhost:3000/slack/oauth_redirect Node.js の実装についてはこちらのコードなどを同様に参考にしてみてください。 その他の FAQ Java のサンプルもありますか? はい、あります!以下のサンプルコードを確認してみてください。 通常のアプリのトークンローテーション Sign in with Slack のトークンローテーション Bolt を使っていないアプリやスクリプトではどうすればよいですか? なお、Bolt を使っていないアプリやスクリプト内での利用の場合は、この TokenRotator だけをアプリケーション内で利用することも可能です。 その場合は何らかの InstallationStore の実装で find_bot() / find_installation() でトークンを取得し、それを使う前に TokenRotator を呼び出して InstallationStore#save() でリフレッシュしたものを保存しておくという流れになります。 既存のアプリを切り替えるにはどうすればよいですか? 英語の方のドキュメントに詳細がありますが、refresh token とペアになっていない access token を oauth.v2.exchange という API を使って切り替えるという流れになります。この API に client_id、client_secret、token をパラメーターとして渡すと access token + refresh token がレスポンスで返ってきます。 上の Python のコード例で言うと InstallationStore から存在する access token を全部持ってきて、一つずつ切り替えて、それを保存するということになります。 トークンローテーションに opt-in したらすぐに一気にやってしまえるよう、あらかじめスクリプトを用意しておくとよいでしょう。 最後に トークンローテーションは Slack の OAuth フローを実装しないと試せない機能なので、実際に検証したことがない方も多かったのではないかと思います。 こちらの記事の手順通りにやれば簡単に試せるかと思いますので、ぜひやってみてください
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む