preact.go 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. package preact
  2. import (
  3. "context"
  4. "crypto/subtle"
  5. _ "embed"
  6. "fmt"
  7. "io"
  8. "log/slog"
  9. "net/http"
  10. "time"
  11. "github.com/TecharoHQ/anubis"
  12. "github.com/TecharoHQ/anubis/internal"
  13. "github.com/TecharoHQ/anubis/lib/challenge"
  14. "github.com/TecharoHQ/anubis/lib/localization"
  15. "github.com/a-h/templ"
  16. )
  17. //go:generate ./build.sh
  18. //go:generate go tool github.com/a-h/templ/cmd/templ generate
  19. //go:embed static/app.js
  20. var appJS []byte
  21. func renderAppJS(ctx context.Context, out io.Writer) error {
  22. fmt.Fprint(out, `<script type="module">`)
  23. out.Write(appJS)
  24. fmt.Fprint(out, "</script>")
  25. return nil
  26. }
  27. func init() {
  28. challenge.Register("preact", &impl{})
  29. }
  30. type impl struct{}
  31. func (i *impl) Setup(mux *http.ServeMux) {}
  32. func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
  33. u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
  34. if err != nil {
  35. return nil, fmt.Errorf("can't render page: %w", err)
  36. }
  37. q := u.Query()
  38. q.Set("redir", r.URL.String())
  39. q.Set("id", in.Challenge.ID)
  40. u.RawQuery = q.Encode()
  41. loc := localization.GetLocalizer(r)
  42. result := page(u.String(), in.Challenge.RandomData, in.Rule.Challenge.Difficulty, loc)
  43. return result, nil
  44. }
  45. func (i *impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
  46. wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 80 * time.Millisecond)
  47. if time.Now().Before(wantTime) {
  48. return challenge.NewError("validate", "insufficient time", fmt.Errorf("%w: wanted user to wait until at least %s", challenge.ErrFailed, wantTime.Format(time.RFC3339)))
  49. }
  50. got := r.FormValue("result")
  51. want := internal.SHA256sum(in.Challenge.RandomData)
  52. if subtle.ConstantTimeCompare([]byte(want), []byte(got)) != 1 {
  53. return challenge.NewError("validate", "invalid response", fmt.Errorf("%w: wanted response %s but got %s", challenge.ErrFailed, want, got))
  54. }
  55. return nil
  56. }