4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CBcloudAdvent Calendar 2024

Day 2

Sendbird WebhookリクエストをCloud Functionsで受信する際にハマった事

Last updated at Posted at 2024-12-02

はじめに

  • SendbirdのWebhookをCloudFunctionsで受け付けたい

  • リクエストがSendbirdからのリクエストであるか、または改ざんされていないかを確認するために署名を検証する必要がある

  • 上記をやろうとした際にハマったこととその解決策を共有します

実装した内容

Cloud FunctionsにてSendbirdからのWebhookリクエストを受け付ける関数を実装する中で、データが改ざんなどされていないかを検証する必要がある

具体的には...

リクエストされた際にSendbirdサーバー側にてPOSTリクエスト本文とAPIトークンを利用して暗号化を行いハッシュ値を生成します
生成されたハッシュ値は x-sendbird-signature に設定されて送られてきます

それを、Cloud Functions側で同じAPIトークンとPOSTリクエスト本文でハッシュ値を生成して内容が同じかどうかを比較し改ざんを検証します

Sendbird公式で紹介されている実装方法

const express = require('express');
const crypto = require('crypto');

const app = express();
const PORT = 5001;
const API_TOKEN = 'SENDBIRD_MASTER_API_TOKEN';

// The body should be a string.
// Validation fails if the body is passed as a JSON object and then stringified.
app.post('/sendbird-xsig', express.text({ type: 'json' }), (req, res) => {
  const body = req.body;
  const signature = req.get('x-sendbird-signature');
  const hash = crypto.createHmac('sha256', API_TOKEN).update(body).digest('hex');

  req.body = JSON.parse(req.body);
  signature == hash ? res.send(200) : res.send(401);
});

app.listen(PORT, () => {
  console.log(`App is listening on port ${PORT}`);
});

期待した値でリクエストが飛んでこない

APIを呼び出す側の引数にはしっかりとJSON文字列を設定して呼び出しており、
Cloud Functions側ではその値が飛んでくるはずですが、飛んでこない...

console.log()など仕込んでデバッグしてみると、JSONオブジェクトで来ているっぽかった
ここはなんでなのかはわかりませんでしたが、とりあえずどうするか...?
(わかる方いたら教えて頂きたいです...🙇‍♂️)

TypeError [ERR_INVALID_ARG_TYPE]: The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received an instance of Object
const express = require('express');
const crypto = require('crypto');

const app = express();
const PORT = 5001;
const API_TOKEN = 'SENDBIRD_MASTER_API_TOKEN';

app.post('/sendbird-xsig', express.text({ type: 'json' }), (req, res) => {
  const body = req.body;
  const signature = req.get('x-sendbird-signature');
  const hash = crypto.createHmac('sha256', API_TOKEN).update(body).digest('hex');
    // update()メソッドにて以下のエラーが発生する
    // TypeError [ERR_INVALID_ARG_TYPE]: The "data" argument must be of type string or 
    // an instance of Buffer, TypedArray, or DataView. Received an instance of Object
  req.body = JSON.parse(req.body);
  signature == hash ? res.send(200) : res.send(401);
});

app.listen(PORT, () => {
  console.log(`App is listening on port ${PORT}`);
});

JSON文字列で受け付けているが...

express.text({ type: ~ }) というミドルウェア関数の一つであり、
type: ~に設定したContent-Typeの値を文字列として扱う挙動をするがそれが、
機能していないのか他要素で邪魔されているのかが不明

今回は、express.text({ type: json })となっており
Content-Type: application/jsonは文字列で受け取るようになっているはず

対策として、verify オプションを使用すると良い

一旦対策としては、Express側ではverifyオプションを使用するといいらしい

verifyでは、verify(req, res, buf, encoding)のように、
引数にbufferデータを持つことができるので、
update() メソッドは stringbuffer を引数で受け付けているので今回は buffer を渡すことに切り替えました

app.use(express.json({
  verify: (req, _res, buf, _encoding) => {
    const signature = req.headers['x-sendbird-signature']
    const hash = crypto.createHmac('sha256', apiToken).update(buf).digest('hex')
    if (signature !== hash) {
      console.log('unmatched!')
    }
  }
}))

無事、ハッシュ値を検証できてデータ改ざんチェックを行うことができました!🎉

宣伝

自分が所属している会社についても紹介させてください 🙇‍♂️
弊社では物流課題解決や荷物を配送するドライバーの価値を向上させるために
「ピックゴー」 というサービスを運用しています

(ファイルアップロード制限?かかって画像アップロードできない...!ごめんなさい...🙇‍♂️)

代わりに弊社HPを...

あと、那覇Baseが本店になりました

私が在籍している那覇Baseが本店に移転しました🎉
エンジニアも募集中です!
気になる方は会社説明会も実施してますので気軽にお話聞きに来てくれたら嬉しいです😎

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?