REST APIによるスパム対策とCAPTCHAのサポート

モデルを REST API 経由で変更できる場合、スパム可能な属性やスパム関連の属性を変更する可能性のある、関連する API エンドポイントすべてにサポートを追加しなければなりません。これにはPOSTPUT の変異が間違いなく含まれますが、モデルの機密/公開フラグの変更に関するものなど、他のものも含まれるかもしれません。

RESTエンドポイントへのサポートの追加

主な手順は以下の通りです:

  1. resourcehelpers SpammableActions::CaptchaCheck::RestApiActionsSupport を追加します。
  2. Update Service クラスのコンストラクタにperform_spam_check: true を渡します。Create Serviceでは、デフォルトでtrue
  3. Spammable モデル・インスタンスを作成または更新したら、#check_spam_action_response! を呼び出し、作成または更新されたインスタンスを変数に保存します。
  4. 作成または更新が成功しなかったリクエストのfailure ケースのエラー処理ロジックを特定します。これらは、Spammable インスタンスにエラーを追加する、スパム検出の可能性を示します。エラーは通常、render_api_error! またはrender_validation_error!に似ています。
  5. 既存のエラー処理ロジックをwith_captcha_check_rest_api(spammable: my_spammable_instance) 呼び出しでラップし、変数に保存したSpammable モデルインスタンスをspammable: の名前付き引数として渡します。この呼び出しは
    1. モデルに必要なスパムチェックを実行します。
    2. スパムが検出された場合
      • スパム特有のエラーメッセージとともに、Grape#error! 例外を発生させます。
      • エラーフィールドとして追加された関連情報をレスポンスに含めます。これらのフィールドの詳細については、REST API ドキュメントの「Resolve requests detected as spam」のセクションを参照してください。
    note
    上記の標準的なApolloLinkまたはAxiosインターセプターのCAPTCHAサポートを使用する場合、フィールドの詳細は自動的に処理されるため、無視することができます。これらは、GraphQL APIを直接使用して潜在的なスパムのチェックに失敗した処理を行い、解決されたCAPTCHAレスポンスでリクエストを再送信しようとした場合に関係します。

以下はsnippets リソースに対するpostput アクションの例です:

module API
  class Snippets < ::API::Base
    #...
    resource :snippets do
      # This helper provides `#with_captcha_check_rest_api`
      helpers SpammableActions::CaptchaCheck::RestApiActionsSupport

      post do
        #...
        service_response = ::Snippets::CreateService.new(project: nil, current_user: current_user, params: attrs).execute
        snippet = service_response.payload[:snippet]

        if service_response.success?
          present snippet, with: Entities::PersonalSnippet, current_user: current_user
        else
          # Wrap the normal error response in a `with_captcha_check_rest_api(spammable: snippet)` block
          with_captcha_check_rest_api(spammable: snippet) do
            # If possible spam was detected, an exception would have been thrown by
            # `#with_captcha_check_rest_api` for Grape to handle via `error!`
            render_api_error!({ error: service_response.message }, service_response.http_status)
          end
        end
      end

      put ':id' do
        #...
        service_response = ::Snippets::UpdateService.new(project: nil, current_user: current_user, params: attrs, perform_spam_check: true).execute(snippet)

        snippet = service_response.payload[:snippet]

        if service_response.success?
          present snippet, with: Entities::PersonalSnippet, current_user: current_user
        else
          # Wrap the normal error response in a `with_captcha_check_rest_api(spammable: snippet)` block
          with_captcha_check_rest_api(spammable: snippet) do
            # If possible spam was detected, an exception would have been thrown by
            # `#with_captcha_check_rest_api` for Grape to handle via `error!`
            render_api_error!({ error: service_response.message }, service_response.http_status)
          end
        end
      end