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>
74 lines
1.9 KiB
Go
74 lines
1.9 KiB
Go
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(w http.ResponseWriter, 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) * 80 * 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
|
|
}
|