1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283 |
- package proofofwork
- import (
- "crypto/subtle"
- "fmt"
- "log/slog"
- "net/http"
- "strconv"
- "strings"
- "github.com/TecharoHQ/anubis/internal"
- chall "github.com/TecharoHQ/anubis/lib/challenge"
- "github.com/TecharoHQ/anubis/lib/policy"
- "github.com/TecharoHQ/anubis/web"
- "github.com/a-h/templ"
- )
- func init() {
- chall.Register("fast", &Impl{Algorithm: "fast"})
- chall.Register("slow", &Impl{Algorithm: "slow"})
- }
- type Impl struct {
- Algorithm string
- }
- func (i *Impl) Fail(w http.ResponseWriter, r *http.Request) error {
- return nil
- }
- func (i *Impl) Issue(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string, ogTags map[string]string) (templ.Component, error) {
- component, err := web.BaseWithChallengeAndOGTags("Making sure you're not a bot!", web.Index(), challenge, rule.Challenge, ogTags)
- if err != nil {
- return nil, fmt.Errorf("can't render page: %w", err)
- }
- return component, nil
- }
- func (i *Impl) Validate(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string) error {
- nonceStr := r.FormValue("nonce")
- if nonceStr == "" {
- return chall.NewError("validate", "invalid response", fmt.Errorf("%w nonce", chall.ErrMissingField))
- }
- nonce, err := strconv.Atoi(nonceStr)
- if err != nil {
- return chall.NewError("validate", "invalid response", fmt.Errorf("%w: nonce: %w", chall.ErrInvalidFormat, err))
- }
- elapsedTimeStr := r.FormValue("elapsedTime")
- if elapsedTimeStr == "" {
- return chall.NewError("validate", "invalid response", fmt.Errorf("%w elapsedTime", chall.ErrMissingField))
- }
- elapsedTime, err := strconv.ParseFloat(elapsedTimeStr, 64)
- if err != nil {
- return chall.NewError("validate", "invalid response", fmt.Errorf("%w: elapsedTime: %w", chall.ErrInvalidFormat, err))
- }
- response := r.FormValue("response")
- if response == "" {
- return chall.NewError("validate", "invalid response", fmt.Errorf("%w response", chall.ErrMissingField))
- }
- calcString := fmt.Sprintf("%s%d", challenge, nonce)
- calculated := internal.SHA256sum(calcString)
- if subtle.ConstantTimeCompare([]byte(response), []byte(calculated)) != 1 {
- return chall.NewError("validate", "invalid response", fmt.Errorf("%w: wanted response %s but got %s", chall.ErrFailed, calculated, response))
- }
- // compare the leading zeroes
- if !strings.HasPrefix(response, strings.Repeat("0", rule.Challenge.Difficulty)) {
- return chall.NewError("validate", "invalid response", fmt.Errorf("%w: wanted %d leading zeros but got %s", chall.ErrFailed, rule.Challenge.Difficulty, response))
- }
- lg.Debug("challenge took", "elapsedTime", elapsedTime)
- chall.TimeTaken.WithLabelValues(i.Algorithm).Observe(elapsedTime)
- return nil
- }
|