summaryrefslogtreecommitdiff
path: root/srv/api
diff options
context:
space:
mode:
Diffstat (limited to 'srv/api')
-rw-r--r--srv/api/api.go188
-rw-r--r--srv/api/apiutils/apiutils.go112
-rw-r--r--srv/api/chat.go211
-rw-r--r--srv/api/csrf.go58
-rw-r--r--srv/api/mailinglist.go88
-rw-r--r--srv/api/middleware.go96
-rw-r--r--srv/api/pow.go53
7 files changed, 0 insertions, 806 deletions
diff --git a/srv/api/api.go b/srv/api/api.go
deleted file mode 100644
index 56f33b2..0000000
--- a/srv/api/api.go
+++ /dev/null
@@ -1,188 +0,0 @@
-// Package api implements the HTTP-based api for the mediocre-blog.
-package api
-
-import (
- "context"
- "errors"
- "fmt"
- "net"
- "net/http"
- "net/http/httputil"
- "net/url"
- "os"
-
- "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
- "github.com/mediocregopher/blog.mediocregopher.com/srv/chat"
- "github.com/mediocregopher/blog.mediocregopher.com/srv/mailinglist"
- "github.com/mediocregopher/blog.mediocregopher.com/srv/pow"
- "github.com/mediocregopher/mediocre-go-lib/v2/mctx"
- "github.com/mediocregopher/mediocre-go-lib/v2/mlog"
-)
-
-// Params are used to instantiate a new API instance. All fields are required
-// unless otherwise noted.
-type Params struct {
- Logger *mlog.Logger
- PowManager pow.Manager
- MailingList mailinglist.MailingList
- GlobalRoom chat.Room
- UserIDCalculator *chat.UserIDCalculator
-
- // ListenProto and ListenAddr are passed into net.Listen to create the
- // API's listener. Both "tcp" and "unix" protocols are explicitly
- // supported.
- ListenProto, ListenAddr string
-
- // StaticDir and StaticProxy are mutually exclusive.
- //
- // If StaticDir is set then that directory on the filesystem will be used to
- // serve the static site.
- //
- // Otherwise if StaticProxy is set all requests for the static site will be
- // reverse-proxied there.
- StaticDir string
- StaticProxy *url.URL
-}
-
-// SetupCfg implement the cfg.Cfger interface.
-func (p *Params) SetupCfg(cfg *cfg.Cfg) {
-
- cfg.StringVar(&p.ListenProto, "listen-proto", "tcp", "Protocol to listen for HTTP requests with")
- cfg.StringVar(&p.ListenAddr, "listen-addr", ":4000", "Address/path to listen for HTTP requests on")
-
- cfg.StringVar(&p.StaticDir, "static-dir", "", "Directory from which static files are served (mutually exclusive with -static-proxy-url)")
- staticProxyURLStr := cfg.String("static-proxy-url", "", "HTTP address from which static files are served (mutually exclusive with -static-dir)")
-
- cfg.OnInit(func(ctx context.Context) error {
- if *staticProxyURLStr != "" {
- var err error
- if p.StaticProxy, err = url.Parse(*staticProxyURLStr); err != nil {
- return fmt.Errorf("parsing -static-proxy-url: %w", err)
- }
-
- } else if p.StaticDir == "" {
- return errors.New("-static-dir or -static-proxy-url is required")
- }
-
- return nil
- })
-}
-
-// Annotate implements mctx.Annotator interface.
-func (p *Params) Annotate(a mctx.Annotations) {
- a["listenProto"] = p.ListenProto
- a["listenAddr"] = p.ListenAddr
-
- if p.StaticProxy != nil {
- a["staticProxy"] = p.StaticProxy.String()
- return
- }
-
- a["staticDir"] = p.StaticDir
-}
-
-// API will listen on the port configured for it, and serve HTTP requests for
-// the mediocre-blog.
-type API interface {
- Shutdown(ctx context.Context) error
-}
-
-type api struct {
- params Params
- srv *http.Server
-}
-
-// New initializes and returns a new API instance, including setting up all
-// listening ports.
-func New(params Params) (API, error) {
-
- l, err := net.Listen(params.ListenProto, params.ListenAddr)
- if err != nil {
- return nil, fmt.Errorf("creating listen socket: %w", err)
- }
-
- if params.ListenProto == "unix" {
- if err := os.Chmod(params.ListenAddr, 0777); err != nil {
- return nil, fmt.Errorf("chmod-ing unix socket: %w", err)
- }
- }
-
- a := &api{
- params: params,
- }
-
- a.srv = &http.Server{Handler: a.handler()}
-
- go func() {
-
- err := a.srv.Serve(l)
- if err != nil && !errors.Is(err, http.ErrServerClosed) {
- ctx := mctx.Annotate(context.Background(), a.params)
- params.Logger.Fatal(ctx, "serving http server", err)
- }
- }()
-
- return a, nil
-}
-
-func (a *api) Shutdown(ctx context.Context) error {
- if err := a.srv.Shutdown(ctx); err != nil {
- return err
- }
-
- return nil
-}
-
-func (a *api) handler() http.Handler {
-
- var staticHandler http.Handler
- if a.params.StaticDir != "" {
- staticHandler = http.FileServer(http.Dir(a.params.StaticDir))
- } else {
- staticHandler = httputil.NewSingleHostReverseProxy(a.params.StaticProxy)
- }
-
- staticHandler = setCSRFMiddleware(staticHandler)
-
- // sugar
- requirePow := func(h http.Handler) http.Handler {
- return a.requirePowMiddleware(h)
- }
-
- mux := http.NewServeMux()
-
- mux.Handle("/", staticHandler)
-
- apiMux := http.NewServeMux()
- apiMux.Handle("/pow/challenge", a.newPowChallengeHandler())
- apiMux.Handle("/pow/check",
- requirePow(
- http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {}),
- ),
- )
-
- apiMux.Handle("/mailinglist/subscribe", requirePow(a.mailingListSubscribeHandler()))
- apiMux.Handle("/mailinglist/finalize", a.mailingListFinalizeHandler())
- apiMux.Handle("/mailinglist/unsubscribe", a.mailingListUnsubscribeHandler())
-
- apiMux.Handle("/chat/global/", http.StripPrefix("/chat/global", newChatHandler(
- a.params.GlobalRoom,
- a.params.UserIDCalculator,
- a.requirePowMiddleware,
- )))
-
- var apiHandler http.Handler = apiMux
- apiHandler = postOnlyMiddleware(apiHandler)
- apiHandler = checkCSRFMiddleware(apiHandler)
- apiHandler = logMiddleware(a.params.Logger, apiHandler)
- apiHandler = annotateMiddleware(apiHandler)
- apiHandler = addResponseHeaders(map[string]string{
- "Cache-Control": "no-store, max-age=0",
- "Pragma": "no-cache",
- "Expires": "0",
- }, apiHandler)
-
- mux.Handle("/api/", http.StripPrefix("/api", apiHandler))
-
- return mux
-}
diff --git a/srv/api/apiutils/apiutils.go b/srv/api/apiutils/apiutils.go
deleted file mode 100644
index 223c2b9..0000000
--- a/srv/api/apiutils/apiutils.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Package apiutils contains utilities which are useful for implementing api
-// endpoints.
-package apiutils
-
-import (
- "context"
- "crypto/rand"
- "encoding/hex"
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "strconv"
-
- "github.com/mediocregopher/mediocre-go-lib/v2/mlog"
-)
-
-type loggerCtxKey int
-
-// SetRequestLogger sets the given Logger onto the given Request's Context,
-// returning a copy.
-func SetRequestLogger(r *http.Request, logger *mlog.Logger) *http.Request {
- ctx := r.Context()
- ctx = context.WithValue(ctx, loggerCtxKey(0), logger)
- return r.WithContext(ctx)
-}
-
-// GetRequestLogger returns the Logger which was set by SetRequestLogger onto
-// this Request, or nil.
-func GetRequestLogger(r *http.Request) *mlog.Logger {
- ctx := r.Context()
- logger, _ := ctx.Value(loggerCtxKey(0)).(*mlog.Logger)
- if logger == nil {
- logger = mlog.Null
- }
- return logger
-}
-
-// JSONResult writes the JSON encoding of the given value as the response body.
-func JSONResult(rw http.ResponseWriter, r *http.Request, v interface{}) {
- b, err := json.Marshal(v)
- if err != nil {
- InternalServerError(rw, r, err)
- return
- }
- b = append(b, '\n')
-
- rw.Header().Set("Content-Type", "application/json")
- rw.Write(b)
-}
-
-// BadRequest writes a 400 status and a JSON encoded error struct containing the
-// given error as the response body.
-func BadRequest(rw http.ResponseWriter, r *http.Request, err error) {
- GetRequestLogger(r).Warn(r.Context(), "bad request", err)
-
- rw.WriteHeader(400)
- JSONResult(rw, r, struct {
- Error string `json:"error"`
- }{
- Error: err.Error(),
- })
-}
-
-// InternalServerError writes a 500 status and a JSON encoded error struct
-// containing a generic error as the response body (though it will log the given
-// one).
-func InternalServerError(rw http.ResponseWriter, r *http.Request, err error) {
- GetRequestLogger(r).Error(r.Context(), "internal server error", err)
-
- rw.WriteHeader(500)
- JSONResult(rw, r, struct {
- Error string `json:"error"`
- }{
- Error: "internal server error",
- })
-}
-
-// StrToInt parses the given string as an integer, or returns the given default
-// integer if the string is empty.
-func StrToInt(str string, defaultVal int) (int, error) {
- if str == "" {
- return defaultVal, nil
- }
- return strconv.Atoi(str)
-}
-
-// GetCookie returns the namd cookie's value, or the given default value if the
-// cookie is not set.
-//
-// This will only return an error if there was an unexpected error parsing the
-// Request's cookies.
-func GetCookie(r *http.Request, cookieName, defaultVal string) (string, error) {
- c, err := r.Cookie(cookieName)
- if errors.Is(err, http.ErrNoCookie) {
- return defaultVal, nil
- } else if err != nil {
- return "", fmt.Errorf("reading cookie %q: %w", cookieName, err)
- }
-
- return c.Value, nil
-}
-
-// RandStr returns a human-readable random string with the given number of bytes
-// of randomness.
-func RandStr(numBytes int) string {
- b := make([]byte, numBytes)
- if _, err := rand.Read(b); err != nil {
- panic(err)
- }
- return hex.EncodeToString(b)
-}
diff --git a/srv/api/chat.go b/srv/api/chat.go
deleted file mode 100644
index a1acc5a..0000000
--- a/srv/api/chat.go
+++ /dev/null
@@ -1,211 +0,0 @@
-package api
-
-import (
- "context"
- "errors"
- "fmt"
- "net/http"
- "strings"
- "unicode"
-
- "github.com/gorilla/websocket"
- "github.com/mediocregopher/blog.mediocregopher.com/srv/api/apiutils"
- "github.com/mediocregopher/blog.mediocregopher.com/srv/chat"
-)
-
-type chatHandler struct {
- *http.ServeMux
-
- room chat.Room
- userIDCalc *chat.UserIDCalculator
-
- wsUpgrader websocket.Upgrader
-}
-
-func newChatHandler(
- room chat.Room, userIDCalc *chat.UserIDCalculator,
- requirePowMiddleware func(http.Handler) http.Handler,
-) http.Handler {
- c := &chatHandler{
- ServeMux: http.NewServeMux(),
- room: room,
- userIDCalc: userIDCalc,
-
- wsUpgrader: websocket.Upgrader{},
- }
-
- c.Handle("/history", c.historyHandler())
- c.Handle("/user-id", requirePowMiddleware(c.userIDHandler()))
- c.Handle("/append", requirePowMiddleware(c.appendHandler()))
- c.Handle("/listen", c.listenHandler())
-
- return c
-}
-
-func (c *chatHandler) historyHandler() http.Handler {
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
- limit, err := apiutils.StrToInt(r.PostFormValue("limit"), 0)
- if err != nil {
- apiutils.BadRequest(rw, r, fmt.Errorf("invalid limit parameter: %w", err))
- return
- }
-
- cursor := r.PostFormValue("cursor")
-
- cursor, msgs, err := c.room.History(r.Context(), chat.HistoryOpts{
- Limit: limit,
- Cursor: cursor,
- })
-
- if argErr := (chat.ErrInvalidArg{}); errors.As(err, &argErr) {
- apiutils.BadRequest(rw, r, argErr.Err)
- return
- } else if err != nil {
- apiutils.InternalServerError(rw, r, err)
- }
-
- apiutils.JSONResult(rw, r, struct {
- Cursor string `json:"cursor"`
- Messages []chat.Message `json:"messages"`
- }{
- Cursor: cursor,
- Messages: msgs,
- })
- })
-}
-
-func (c *chatHandler) userID(r *http.Request) (chat.UserID, error) {
- name := r.PostFormValue("name")
- if l := len(name); l == 0 {
- return chat.UserID{}, errors.New("name is required")
- } else if l > 16 {
- return chat.UserID{}, errors.New("name too long")
- }
-
- nameClean := strings.Map(func(r rune) rune {
- if !unicode.IsPrint(r) {
- return -1
- }
- return r
- }, name)
-
- if nameClean != name {
- return chat.UserID{}, errors.New("name contains invalid characters")
- }
-
- password := r.PostFormValue("password")
- if l := len(password); l == 0 {
- return chat.UserID{}, errors.New("password is required")
- } else if l > 128 {
- return chat.UserID{}, errors.New("password too long")
- }
-
- return c.userIDCalc.Calculate(name, password), nil
-}
-
-func (c *chatHandler) userIDHandler() http.Handler {
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
- userID, err := c.userID(r)
- if err != nil {
- apiutils.BadRequest(rw, r, err)
- return
- }
-
- apiutils.JSONResult(rw, r, struct {
- UserID chat.UserID `json:"userID"`
- }{
- UserID: userID,
- })
- })
-}
-
-func (c *chatHandler) appendHandler() http.Handler {
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
- userID, err := c.userID(r)
- if err != nil {
- apiutils.BadRequest(rw, r, err)
- return
- }
-
- body := r.PostFormValue("body")
-
- if l := len(body); l == 0 {
- apiutils.BadRequest(rw, r, errors.New("body is required"))
- return
-
- } else if l > 300 {
- apiutils.BadRequest(rw, r, errors.New("body too long"))
- return
- }
-
- msg, err := c.room.Append(r.Context(), chat.Message{
- UserID: userID,
- Body: body,
- })
-
- if err != nil {
- apiutils.InternalServerError(rw, r, err)
- return
- }
-
- apiutils.JSONResult(rw, r, struct {
- MessageID string `json:"messageID"`
- }{
- MessageID: msg.ID,
- })
- })
-}
-
-func (c *chatHandler) listenHandler() http.Handler {
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
-
- ctx := r.Context()
- sinceID := r.FormValue("sinceID")
-
- conn, err := c.wsUpgrader.Upgrade(rw, r, nil)
- if err != nil {
- apiutils.BadRequest(rw, r, err)
- return
- }
- defer conn.Close()
-
- it, err := c.room.Listen(ctx, sinceID)
-
- if errors.As(err, new(chat.ErrInvalidArg)) {
- apiutils.BadRequest(rw, r, err)
- return
-
- } else if errors.Is(err, context.Canceled) {
- return
-
- } else if err != nil {
- apiutils.InternalServerError(rw, r, err)
- return
- }
-
- defer it.Close()
-
- for {
-
- msg, err := it.Next(ctx)
- if errors.Is(err, context.Canceled) {
- return
-
- } else if err != nil {
- apiutils.InternalServerError(rw, r, err)
- return
- }
-
- err = conn.WriteJSON(struct {
- Message chat.Message `json:"message"`
- }{
- Message: msg,
- })
-
- if err != nil {
- apiutils.GetRequestLogger(r).Error(ctx, "couldn't write message", err)
- return
- }
- }
- })
-}
diff --git a/srv/api/csrf.go b/srv/api/csrf.go
deleted file mode 100644
index 13b6ec6..0000000
--- a/srv/api/csrf.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package api
-
-import (
- "errors"
- "net/http"
-
- "github.com/mediocregopher/blog.mediocregopher.com/srv/api/apiutils"
-)
-
-const (
- csrfTokenCookieName = "csrf_token"
- csrfTokenHeaderName = "X-CSRF-Token"
-)
-
-func setCSRFMiddleware(h http.Handler) http.Handler {
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
-
- csrfTok, err := apiutils.GetCookie(r, csrfTokenCookieName, "")
-
- if err != nil {
- apiutils.InternalServerError(rw, r, err)
- return
-
- } else if csrfTok == "" {
- http.SetCookie(rw, &http.Cookie{
- Name: csrfTokenCookieName,
- Value: apiutils.RandStr(32),
- Secure: true,
- })
- }
-
- h.ServeHTTP(rw, r)
- })
-}
-
-func checkCSRFMiddleware(h http.Handler) http.Handler {
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
-
- csrfTok, err := apiutils.GetCookie(r, csrfTokenCookieName, "")
-
- if err != nil {
- apiutils.InternalServerError(rw, r, err)
- return
- }
-
- givenCSRFTok := r.Header.Get(csrfTokenHeaderName)
- if givenCSRFTok == "" {
- givenCSRFTok = r.FormValue("csrfToken")
- }
-
- if csrfTok == "" || givenCSRFTok != csrfTok {
- apiutils.BadRequest(rw, r, errors.New("invalid CSRF token"))
- return
- }
-
- h.ServeHTTP(rw, r)
- })
-}
diff --git a/srv/api/mailinglist.go b/srv/api/mailinglist.go
deleted file mode 100644
index d89fe2a..0000000
--- a/srv/api/mailinglist.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package api
-
-import (
- "errors"
- "net/http"
- "strings"
-
- "github.com/mediocregopher/blog.mediocregopher.com/srv/api/apiutils"
- "github.com/mediocregopher/blog.mediocregopher.com/srv/mailinglist"
-)
-
-func (a *api) mailingListSubscribeHandler() http.Handler {
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
- email := r.PostFormValue("email")
- if parts := strings.Split(email, "@"); len(parts) != 2 ||
- parts[0] == "" ||
- parts[1] == "" ||
- len(email) >= 512 {
- apiutils.BadRequest(rw, r, errors.New("invalid email"))
- return
- }
-
- err := a.params.MailingList.BeginSubscription(email)
-
- if errors.Is(err, mailinglist.ErrAlreadyVerified) {
- // just eat the error, make it look to the user like the
- // verification email was sent.
- } else if err != nil {
- apiutils.InternalServerError(rw, r, err)
- return
- }
-
- apiutils.JSONResult(rw, r, struct{}{})
- })
-}
-
-func (a *api) mailingListFinalizeHandler() http.Handler {
- var errInvalidSubToken = errors.New("invalid subToken")
-
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
- subToken := r.PostFormValue("subToken")
- if l := len(subToken); l == 0 || l > 128 {
- apiutils.BadRequest(rw, r, errInvalidSubToken)
- return
- }
-
- err := a.params.MailingList.FinalizeSubscription(subToken)
-
- if errors.Is(err, mailinglist.ErrNotFound) {
- apiutils.BadRequest(rw, r, errInvalidSubToken)
- return
-
- } else if errors.Is(err, mailinglist.ErrAlreadyVerified) {
- // no problem
-
- } else if err != nil {
- apiutils.InternalServerError(rw, r, err)
- return
- }
-
- apiutils.JSONResult(rw, r, struct{}{})
- })
-}
-
-func (a *api) mailingListUnsubscribeHandler() http.Handler {
- var errInvalidUnsubToken = errors.New("invalid unsubToken")
-
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
- unsubToken := r.PostFormValue("unsubToken")
- if l := len(unsubToken); l == 0 || l > 128 {
- apiutils.BadRequest(rw, r, errInvalidUnsubToken)
- return
- }
-
- err := a.params.MailingList.Unsubscribe(unsubToken)
-
- if errors.Is(err, mailinglist.ErrNotFound) {
- apiutils.BadRequest(rw, r, errInvalidUnsubToken)
- return
-
- } else if err != nil {
- apiutils.InternalServerError(rw, r, err)
- return
- }
-
- apiutils.JSONResult(rw, r, struct{}{})
- })
-}
diff --git a/srv/api/middleware.go b/srv/api/middleware.go
deleted file mode 100644
index 6ea0d13..0000000
--- a/srv/api/middleware.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package api
-
-import (
- "net"
- "net/http"
- "time"
-
- "github.com/mediocregopher/blog.mediocregopher.com/srv/api/apiutils"
- "github.com/mediocregopher/mediocre-go-lib/v2/mctx"
- "github.com/mediocregopher/mediocre-go-lib/v2/mlog"
-)
-
-func addResponseHeaders(headers map[string]string, h http.Handler) http.Handler {
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
- for k, v := range headers {
- rw.Header().Set(k, v)
- }
- h.ServeHTTP(rw, r)
- })
-}
-
-func annotateMiddleware(h http.Handler) http.Handler {
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
-
- type reqInfoKey string
-
- ip, _, _ := net.SplitHostPort(r.RemoteAddr)
-
- ctx := r.Context()
- ctx = mctx.Annotate(ctx,
- reqInfoKey("remote_ip"), ip,
- reqInfoKey("url"), r.URL,
- reqInfoKey("method"), r.Method,
- )
-
- r = r.WithContext(ctx)
- h.ServeHTTP(rw, r)
- })
-}
-
-type logResponseWriter struct {
- http.ResponseWriter
- http.Hijacker
- statusCode int
-}
-
-func newLogResponseWriter(rw http.ResponseWriter) *logResponseWriter {
- h, _ := rw.(http.Hijacker)
- return &logResponseWriter{
- ResponseWriter: rw,
- Hijacker: h,
- statusCode: 200,
- }
-}
-
-func (lrw *logResponseWriter) WriteHeader(statusCode int) {
- lrw.statusCode = statusCode
- lrw.ResponseWriter.WriteHeader(statusCode)
-}
-
-func logMiddleware(logger *mlog.Logger, h http.Handler) http.Handler {
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
-
- r = apiutils.SetRequestLogger(r, logger)
-
- lrw := newLogResponseWriter(rw)
-
- started := time.Now()
- h.ServeHTTP(lrw, r)
- took := time.Since(started)
-
- type logCtxKey string
-
- ctx := r.Context()
- ctx = mctx.Annotate(ctx,
- logCtxKey("took"), took.String(),
- logCtxKey("response_code"), lrw.statusCode,
- )
-
- logger.Info(ctx, "handled HTTP request")
- })
-}
-
-func postOnlyMiddleware(h http.Handler) http.Handler {
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
-
- // we allow websockets to not be POSTs because, well, they can't be
- if r.Method == "POST" || r.Header.Get("Upgrade") == "websocket" {
- h.ServeHTTP(rw, r)
- return
- }
-
- apiutils.GetRequestLogger(r).WarnString(r.Context(), "method not allowed")
- rw.WriteHeader(405)
- })
-}
diff --git a/srv/api/pow.go b/srv/api/pow.go
deleted file mode 100644
index 1b232b1..0000000
--- a/srv/api/pow.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package api
-
-import (
- "encoding/hex"
- "errors"
- "fmt"
- "net/http"
-
- "github.com/mediocregopher/blog.mediocregopher.com/srv/api/apiutils"
-)
-
-func (a *api) newPowChallengeHandler() http.Handler {
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
-
- challenge := a.params.PowManager.NewChallenge()
-
- apiutils.JSONResult(rw, r, struct {
- Seed string `json:"seed"`
- Target uint32 `json:"target"`
- }{
- Seed: hex.EncodeToString(challenge.Seed),
- Target: challenge.Target,
- })
- })
-}
-
-func (a *api) requirePowMiddleware(h http.Handler) http.Handler {
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
-
- seedHex := r.FormValue("powSeed")
- seed, err := hex.DecodeString(seedHex)
- if err != nil || len(seed) == 0 {
- apiutils.BadRequest(rw, r, errors.New("invalid powSeed"))
- return
- }
-
- solutionHex := r.FormValue("powSolution")
- solution, err := hex.DecodeString(solutionHex)
- if err != nil || len(seed) == 0 {
- apiutils.BadRequest(rw, r, errors.New("invalid powSolution"))
- return
- }
-
- err = a.params.PowManager.CheckSolution(seed, solution)
-
- if err != nil {
- apiutils.BadRequest(rw, r, fmt.Errorf("checking proof-of-work solution: %w", err))
- return
- }
-
- h.ServeHTTP(rw, r)
- })
-}