summaryrefslogtreecommitdiff
path: root/doc/development/spam_protection_and_captcha/rest_api.md
blob: ad74977eb67d80811c2d43b3e464eec24c1b0eb3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
---
stage: Manage
group: Authentication and Authorization
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---

# REST API spam protection and CAPTCHA support

If the model can be modified via the REST API, you must also add support to all of the
relevant API endpoints which may modify spammable or spam-related attributes. This
definitely includes the `POST` and `PUT` mutations, but may also include others, such as those
related to changing a model's confidential/public flag.

## Add support to the REST endpoints

The main steps are:

1. Add `helpers SpammableActions::CaptchaCheck::RestApiActionsSupport` in your `resource`.
1. Create a `spam_params` instance based on the request.
1. Pass `spam_params` to the relevant Service class constructor.
1. After you create or update the `Spammable` model instance, call `#check_spam_action_response!`,
   save the created or updated instance in a variable.
1. Identify the error handling logic for the `failure` case of the request,
   when create or update was not successful. These indicate possible spam detection,
   which adds an error to the `Spammable` instance.
   The error is usually similar to `render_api_error!` or `render_validation_error!`.
1. Wrap the existing error handling logic in a
   `with_captcha_check_rest_api(spammable: my_spammable_instance)` call, passing the `Spammable`
   model instance you saved in a variable as the `spammable:` named argument. This call will:
   1. Perform the necessary spam checks on the model.
   1. If spam is detected:
      - Raise a Grape `#error!` exception with a descriptive spam-specific error message.
      - Include the relevant information added as error fields to the response.
        For more details on these fields, refer to the section in the REST API documentation on
        [Resolve requests detected as spam](../../api/index.md#resolve-requests-detected-as-spam).

   NOTE:
   If you use the standard ApolloLink or Axios interceptor CAPTCHA support described
   above, you can ignore the field details, because they are handled
   automatically. They become relevant if you attempt to use the GraphQL API directly to
   process a failed check for potential spam, and resubmit the request with a solved
   CAPTCHA response.

Here is an example for the `post` and `put` actions on the `snippets` resource:

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

      post do
        #...
        spam_params = ::Spam::SpamParams.new_from_request(request: request)
        service_response = ::Snippets::CreateService.new(project: nil, current_user: current_user, params: attrs, spam_params: spam_params).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
        #...
        spam_params = ::Spam::SpamParams.new_from_request(request: request)
        service_response = ::Snippets::UpdateService.new(project: nil, current_user: current_user, params: attrs, spam_params: spam_params).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
```