102 lines
2.8 KiB
Go
102 lines
2.8 KiB
Go
package security
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"fmt"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/supabase/auth/internal/utilities"
|
|
)
|
|
|
|
type GotrueRequest struct {
|
|
Security GotrueSecurity `json:"gotrue_meta_security"`
|
|
}
|
|
|
|
type GotrueSecurity struct {
|
|
Token string `json:"captcha_token"`
|
|
}
|
|
|
|
type VerificationResponse struct {
|
|
Success bool `json:"success"`
|
|
ErrorCodes []string `json:"error-codes"`
|
|
Hostname string `json:"hostname"`
|
|
}
|
|
|
|
var Client *http.Client
|
|
|
|
func init() {
|
|
var defaultTimeout time.Duration = time.Second * 10
|
|
timeoutStr := os.Getenv("GOTRUE_SECURITY_CAPTCHA_TIMEOUT")
|
|
if timeoutStr != "" {
|
|
if timeout, err := time.ParseDuration(timeoutStr); err != nil {
|
|
log.Fatalf("error loading GOTRUE_SECURITY_CAPTCHA_TIMEOUT: %v", err.Error())
|
|
} else if timeout != 0 {
|
|
defaultTimeout = timeout
|
|
}
|
|
}
|
|
|
|
Client = &http.Client{Timeout: defaultTimeout}
|
|
}
|
|
|
|
func VerifyRequest(requestBody *GotrueRequest, clientIP, secretKey, captchaProvider string) (VerificationResponse, error) {
|
|
captchaResponse := strings.TrimSpace(requestBody.Security.Token)
|
|
|
|
if captchaResponse == "" {
|
|
return VerificationResponse{}, errors.New("no captcha response (captcha_token) found in request")
|
|
}
|
|
|
|
captchaURL, err := GetCaptchaURL(captchaProvider)
|
|
if err != nil {
|
|
return VerificationResponse{}, err
|
|
}
|
|
|
|
return verifyCaptchaCode(captchaResponse, secretKey, clientIP, captchaURL)
|
|
}
|
|
|
|
func verifyCaptchaCode(token, secretKey, clientIP, captchaURL string) (VerificationResponse, error) {
|
|
data := url.Values{}
|
|
data.Set("secret", secretKey)
|
|
data.Set("response", token)
|
|
data.Set("remoteip", clientIP)
|
|
// TODO (darora): pipe through sitekey
|
|
|
|
r, err := http.NewRequest("POST", captchaURL, strings.NewReader(data.Encode()))
|
|
if err != nil {
|
|
return VerificationResponse{}, errors.Wrap(err, "couldn't initialize request object for captcha check")
|
|
}
|
|
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
|
r.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
|
|
res, err := Client.Do(r)
|
|
if err != nil {
|
|
return VerificationResponse{}, errors.Wrap(err, "failed to verify captcha response")
|
|
}
|
|
defer utilities.SafeClose(res.Body)
|
|
|
|
var verificationResponse VerificationResponse
|
|
|
|
if err := json.NewDecoder(res.Body).Decode(&verificationResponse); err != nil {
|
|
return VerificationResponse{}, errors.Wrap(err, "failed to decode captcha response: not JSON")
|
|
}
|
|
|
|
return verificationResponse, nil
|
|
}
|
|
|
|
func GetCaptchaURL(captchaProvider string) (string, error) {
|
|
switch captchaProvider {
|
|
case "hcaptcha":
|
|
return "https://hcaptcha.com/siteverify", nil
|
|
case "turnstile":
|
|
return "https://challenges.cloudflare.com/turnstile/v0/siteverify", nil
|
|
default:
|
|
return "", fmt.Errorf("captcha Provider %q could not be found", captchaProvider)
|
|
}
|
|
}
|