mirror of
https://github.com/TecharoHQ/anubis.git
synced 2025-10-04 13:52:08 +08:00
* feat(lib/challenge): expose ResponseWriter to challenge issuers Signed-off-by: Xe Iaso <me@xeiaso.net> * feat(metarefresh): randomly use the Refresh header There are several ways to trigger an automatic refresh without JavaScript. One of them is the "meta refresh" method[1], but the other is with the Refresh header[2]. Both are semantically identical and supported with browsers as old as Chrome version 1. Given that they are basically the same thing, this patch makes Anubis randomly select between them by using the challenge random data's first character. This will fire about 50% of the time. I expect this to have no impact. If this works out fine, then I will implement some kind of fallback logic for the fast challenge such that admins can opt into allowing clients with a no-js configuration to pass the fast challenge. This needs to bake in the oven though. [1]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/http-equiv [2]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Refresh Signed-off-by: Xe Iaso <me@xeiaso.net> * docs: update CHANGELOG Signed-off-by: Xe Iaso <me@xeiaso.net> * feat(metarefresh): simplify random logic Signed-off-by: Xe Iaso <me@xeiaso.net> --------- Signed-off-by: Xe Iaso <me@xeiaso.net> Signed-off-by: Xe Iaso <xe.iaso@techaro.lol>
81 lines
2.4 KiB
Go
81 lines
2.4 KiB
Go
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/localization"
|
|
"github.com/a-h/templ"
|
|
)
|
|
|
|
//go:generate go tool github.com/a-h/templ/cmd/templ generate
|
|
|
|
func init() {
|
|
chall.Register("fast", &Impl{Algorithm: "fast"})
|
|
chall.Register("slow", &Impl{Algorithm: "slow"})
|
|
}
|
|
|
|
type Impl struct {
|
|
Algorithm string
|
|
}
|
|
|
|
func (i *Impl) Setup(mux *http.ServeMux) {}
|
|
|
|
func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *chall.IssueInput) (templ.Component, error) {
|
|
loc := localization.GetLocalizer(r)
|
|
return page(loc), nil
|
|
}
|
|
|
|
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.ValidateInput) error {
|
|
rule := in.Rule
|
|
challenge := in.Challenge.RandomData
|
|
|
|
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
|
|
}
|