Skip to content

Commit

Permalink
feat: captcha middleware first impl
Browse files Browse the repository at this point in the history
  • Loading branch information
ingvaar committed May 18, 2022
1 parent 7e6661f commit 369a45e
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 0 deletions.
40 changes: 40 additions & 0 deletions internal/server/captcha/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package captcha

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)

func VerificationMiddleware(secret string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get the Captcha response token from default request body field 'g-recaptcha-response'.
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, fmt.Sprintf("Unauthorized: %s", err.Error()), http.StatusUnauthorized)
return
}

// Unmarshal body into struct.
var body siteVerifyRequest
if err := json.Unmarshal(bodyBytes, &body); err != nil {
http.Error(w, fmt.Sprintf("Unauthorized: %s", err.Error()), http.StatusUnauthorized)
return
}

// Restore request body to read more than once.
r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))

// Check and verify the Captcha response token.
if err := checkRecaptcha(secret, body.RecaptchaResponse); err != nil {
http.Error(w, fmt.Sprintf("Unauthorized: %s", err.Error()), http.StatusUnauthorized)
return
}

next.ServeHTTP(w, r)
})
}
}
66 changes: 66 additions & 0 deletions internal/server/captcha/verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package captcha

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"time"

"github.com/rs/zerolog/log"
)

const siteVerifyURL = "https://www.google.com/recaptcha/api/siteverify"

type siteVerifyResponse struct {
Success bool `json:"success"`
Score float64 `json:"score"`
Action string `json:"action"`
ChallengeTS time.Time `json:"challenge_ts"`
Hostname string `json:"hostname"`
ErrorCodes []string `json:"error-codes"`
}

type siteVerifyRequest struct {
RecaptchaResponse string `json:"g-recaptcha-response"`
}

func checkRecaptcha(secret, response string) error {
req, err := http.NewRequest(http.MethodPost, siteVerifyURL, nil)
if err != nil {
log.Error().Msgf("Error while creating Captcha verification request: %s", err.Error())
return fmt.Errorf("error while creating Captcha verification request: %s", err.Error())
}

q := req.URL.Query()
q.Add("secret", secret)
q.Add("response", response)
req.URL.RawQuery = q.Encode()

resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Error().Msgf("Error while requesting Captcha verification: %s", err.Error())
return fmt.Errorf("error while requesting Captcha verification: %s", err.Error())
}
defer resp.Body.Close()

var body siteVerifyResponse
if err = json.NewDecoder(resp.Body).Decode(&body); err != nil {
log.Error().Msgf("Error while decoding Captcha verification response: %s", err.Error())
return fmt.Errorf("error while decoding Captcha verification response: %s", err.Error())
}

// If success false, Captcha verification KO.
if !body.Success {
log.Debug().Msg("Captcha verification failed")
return errors.New("captcha verification failed")
}

// If score is too low, verification KO.
if body.Score > 0.5 {
log.Debug().Msg("Captcha verification failed: score is too low")
return errors.New("captcha verification failed: score is too low")
}

return nil
}
9 changes: 9 additions & 0 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/http"

"okp4/cosmos-faucet/pkg/client"
"os"

"github.com/gorilla/mux"
"github.com/rs/zerolog/log"
Expand All @@ -14,6 +15,7 @@ type Config struct {
EnableMetrics bool `mapstructure:"metrics"`
EnableHealth bool `mapstructure:"health"`
Faucet client.Faucet
CaptchaSecret string `mapstructure:"captcha-secret"`
}

// HTTPServer exposes server methods.
Expand All @@ -30,6 +32,13 @@ func NewServer(config Config) HTTPServer {
server := &httpServer{
router: mux.NewRouter().StrictSlash(true),
}
if config.CaptchaSecret == "" {
log.Info().Msgf("Captcha secret not set, checking ENV")
config.CaptchaSecret = os.Getenv("CAPTCHA_SECRET")
if config.CaptchaSecret == "" {
log.Fatal().Msg("Captcha secret not found in ENV")
}
}
server.createRoutes(config)
return server
}
Expand Down

0 comments on commit 369a45e

Please sign in to comment.