| 235 | } |
| 236 | |
| 237 | func (a *API) verifyCaptcha(w http.ResponseWriter, req *http.Request) (context.Context, error) { |
| 238 | ctx := req.Context() |
| 239 | config := a.config |
| 240 | |
| 241 | if !config.Security.Captcha.Enabled { |
| 242 | return ctx, nil |
| 243 | } |
| 244 | if _, err := a.requireAdminCredentials(w, req); err == nil { |
| 245 | // skip captcha validation if authorization header contains an admin role |
| 246 | return ctx, nil |
| 247 | } |
| 248 | if shouldIgnore := isIgnoreCaptchaRoute(req); shouldIgnore { |
| 249 | return ctx, nil |
| 250 | } |
| 251 | |
| 252 | body := &captchaRequest{} |
| 253 | if err := retrieveRequestParams(req, body); err != nil { |
| 254 | return nil, err |
| 255 | } |
| 256 | |
| 257 | token := strings.TrimSpace(body.Security.Token) |
| 258 | if token == "" { |
| 259 | return nil, apierrors.NewBadRequestError(apierrors.ErrorCodeCaptchaFailed, "captcha protection: request disallowed (no captcha_token found)") |
| 260 | } |
| 261 | |
| 262 | verificationResult, err := a.captchaVerifier.Verify( |
| 263 | ctx, |
| 264 | token, |
| 265 | utilities.GetIPAddress(req), |
| 266 | ) |
| 267 | if err != nil { |
| 268 | return nil, apierrors.NewInternalServerError("captcha verification process failed").WithInternalError(err) |
| 269 | } |
| 270 | |
| 271 | if !verificationResult.Success { |
| 272 | return nil, apierrors.NewBadRequestError(apierrors.ErrorCodeCaptchaFailed, "captcha protection: request disallowed (%s)", strings.Join(verificationResult.ErrorCodes, ", ")) |
| 273 | } |
| 274 | |
| 275 | return ctx, nil |
| 276 | } |
| 277 | |
| 278 | func isIgnoreCaptchaRoute(req *http.Request) bool { |
| 279 | if req.URL.Path != "/token" { |