Passed
Push — main ( 92b682...b31d26 )
by Acho
02:23
created

validators.*TurnstileTokenValidator.ValidateToken   B

Complexity

Conditions 6

Size

Total Lines 43
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 30
dl 0
loc 43
rs 8.2266
c 0
b 0
f 0
nop 3
1
package validators
2
3
import (
4
	"bytes"
5
	"context"
6
	"encoding/json"
7
	"fmt"
8
	"io"
9
	"net/http"
10
	"time"
11
12
	"github.com/NdoleStudio/httpsms/pkg/telemetry"
13
	"github.com/palantir/stacktrace"
14
)
15
16
// TurnstileTokenValidator validates the token used to validate captchas from cloudflare
17
type TurnstileTokenValidator struct {
18
	logger     telemetry.Logger
19
	tracer     telemetry.Tracer
20
	secretKey  string
21
	httpClient *http.Client
22
}
23
24
type turnstileVerifyResponse struct {
25
	Success     bool      `json:"success"`
26
	ChallengeTs time.Time `json:"challenge_ts"`
27
	Hostname    string    `json:"hostname"`
28
	ErrorCodes  []any     `json:"error-codes"`
29
	Action      string    `json:"action"`
30
	Cdata       string    `json:"cdata"`
31
	Metadata    struct {
32
		EphemeralID string `json:"ephemeral_id"`
33
	} `json:"metadata"`
34
}
35
36
// NewTurnstileTokenValidator creates a new TurnstileTokenValidator
37
func NewTurnstileTokenValidator(logger telemetry.Logger, tracer telemetry.Tracer, secretKey string, httpClient *http.Client) *TurnstileTokenValidator {
38
	return &TurnstileTokenValidator{
39
		logger.WithService(fmt.Sprintf("%T", &TurnstileTokenValidator{})),
40
		tracer,
41
		secretKey,
42
		httpClient,
43
	}
44
}
45
46
// ValidateToken validates the cloudflare turnstile token
47
// https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
48
func (v *TurnstileTokenValidator) ValidateToken(ctx context.Context, ipAddress, token string) bool {
49
	ctx, span, ctxLogger := v.tracer.StartWithLogger(ctx, v.logger)
50
	defer span.End()
51
52
	payload, err := json.Marshal(map[string]string{
53
		"secret":   v.secretKey,
54
		"response": token,
55
		"remoteip": ipAddress,
56
	})
57
	if err != nil {
58
		ctxLogger.Error(stacktrace.Propagate(err, "failed to marshal payload"))
59
		return false
60
	}
61
62
	request, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://challenges.cloudflare.com/turnstile/v0/siteverify", bytes.NewBuffer(payload))
63
	if err != nil {
64
		ctxLogger.Error(stacktrace.Propagate(err, "failed to create http request request"))
65
		return false
66
	}
67
68
	request.Header.Set("Content-Type", "application/json")
69
	response, err := v.httpClient.Do(request)
70
	if err != nil {
71
		ctxLogger.Error(stacktrace.Propagate(err, fmt.Sprintf("failed to send http request to [%s]", request.URL.String())))
72
		return false
73
	}
74
	defer response.Body.Close()
75
76
	body, err := io.ReadAll(response.Body)
77
	if err != nil {
78
		ctxLogger.Error(stacktrace.Propagate(err, "failed to read response body from cloudflare turnstile"))
79
		return false
80
	}
81
82
	ctxLogger.Info(fmt.Sprintf("successfully validated token with cloudflare with response [%s]", body))
83
84
	data := new(turnstileVerifyResponse)
85
	if err = json.Unmarshal(body, data); err != nil {
86
		ctxLogger.Error(stacktrace.Propagate(err, "failed to unmarshal response from cloudflare turnstile"))
87
		return false
88
	}
89
90
	return data.Success
91
}
92