feat: add 'proof of React' challenge (#1038)
* feat: add 'proof of React' challenge Signed-off-by: Xe Iaso <me@xeiaso.net> * fix(challenge/preact): use JSX fragments Signed-off-by: Xe Iaso <me@xeiaso.net> * fix(challenge/preact): ensure that the client waits as long as it needs to Signed-off-by: Xe Iaso <me@xeiaso.net> * docs: fix spelling Signed-off-by: Xe Iaso <me@xeiaso.net> * fix(challenges/xeact): add noscript warning Signed-off-by: Xe Iaso <me@xeiaso.net> * fix(challenges/xeact): add default loading message Signed-off-by: Xe Iaso <me@xeiaso.net> * fix(challenges/xeact): make a UI render without JS Signed-off-by: Xe Iaso <me@xeiaso.net> * fix(challenges/xeact): use %s here, not %w Signed-off-by: Xe Iaso <me@xeiaso.net> * fix(test/healthcheck): run asset build Signed-off-by: Xe Iaso <me@xeiaso.net> * fix(challenge/preact): fix build in ci 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>
This commit is contained in:
parent
00afa72c4b
commit
0e0847cbeb
17 changed files with 518 additions and 4 deletions
74
lib/challenge/preact/preact.go
Normal file
74
lib/challenge/preact/preact.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package preact
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/TecharoHQ/anubis"
|
||||
"github.com/TecharoHQ/anubis/internal"
|
||||
"github.com/TecharoHQ/anubis/lib/challenge"
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
"github.com/a-h/templ"
|
||||
)
|
||||
|
||||
//go:generate ./build.sh
|
||||
//go:generate go tool github.com/a-h/templ/cmd/templ generate
|
||||
|
||||
//go:embed static/app.js
|
||||
var appJS []byte
|
||||
|
||||
func renderAppJS(ctx context.Context, out io.Writer) error {
|
||||
fmt.Fprint(out, `<script type="module">`)
|
||||
out.Write(appJS)
|
||||
fmt.Fprint(out, "</script>")
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
challenge.Register("preact", &impl{})
|
||||
}
|
||||
|
||||
type impl struct{}
|
||||
|
||||
func (i *impl) Setup(mux *http.ServeMux) {}
|
||||
|
||||
func (i *impl) Issue(r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
||||
u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't render page: %w", err)
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
q.Set("redir", r.URL.String())
|
||||
q.Set("id", in.Challenge.ID)
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
loc := localization.GetLocalizer(r)
|
||||
|
||||
result := page(u.String(), in.Challenge.RandomData, in.Rule.Challenge.Difficulty, loc)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (i *impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
|
||||
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 95 * time.Millisecond)
|
||||
|
||||
if time.Now().Before(wantTime) {
|
||||
return challenge.NewError("validate", "insufficent time", fmt.Errorf("%w: wanted user to wait until at least %s", challenge.ErrFailed, wantTime.Format(time.RFC3339)))
|
||||
}
|
||||
|
||||
got := r.FormValue("result")
|
||||
want := internal.SHA256sum(in.Challenge.RandomData)
|
||||
|
||||
if subtle.ConstantTimeCompare([]byte(want), []byte(got)) != 1 {
|
||||
return challenge.NewError("validate", "invalid response", fmt.Errorf("%w: wanted response %s but got %s", challenge.ErrFailed, want, got))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue