dff24e9fca
* txpool svc * change mod github path * tag-tool * codeowners
102 lines
2.7 KiB
Go
102 lines
2.7 KiB
Go
package op_txproxy
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
)
|
|
|
|
var (
|
|
defaultBodyLimit = 5 * 1024 * 1024 // default in op-geth
|
|
|
|
DefaultAuthHeaderKey = "X-Optimism-Signature"
|
|
)
|
|
|
|
type authHandler struct {
|
|
headerKey string
|
|
next http.Handler
|
|
}
|
|
|
|
// This middleware detects when authentication information is present on the request. If
|
|
// so, it will validate and set the caller in the request context. It does not reject
|
|
// if authentication information is missing. It is up to the request handler to do so via
|
|
// the missing `AuthContext`
|
|
// - NOTE: only up to the default body limit (5MB) is read when constructing the text hash
|
|
// that is signed over by the caller
|
|
func AuthMiddleware(headerKey string) func(next http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return &authHandler{headerKey, next}
|
|
}
|
|
}
|
|
|
|
type authContextKey struct{}
|
|
|
|
type AuthContext struct {
|
|
Caller common.Address
|
|
}
|
|
|
|
// ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler
|
|
func (h *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
authHeader := r.Header.Get(h.headerKey)
|
|
if authHeader == "" {
|
|
h.next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
authElems := strings.Split(authHeader, ":")
|
|
if len(authElems) != 2 {
|
|
http.Error(w, "misformatted auth header", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if r.Body == nil {
|
|
// edge case from unit tests
|
|
r.Body = io.NopCloser(bytes.NewBuffer(nil))
|
|
}
|
|
|
|
// Since this middleware runs prior to the server, we need to manually apply the body limit when reading.
|
|
bodyBytes, err := io.ReadAll(io.LimitReader(r.Body, int64(defaultBodyLimit)))
|
|
if err != nil {
|
|
http.Error(w, "unable to parse request body", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
r.Body = struct {
|
|
io.Reader
|
|
io.Closer
|
|
}{
|
|
io.MultiReader(bytes.NewReader(bodyBytes), r.Body),
|
|
r.Body,
|
|
}
|
|
|
|
txtHash := accounts.TextHash(bodyBytes)
|
|
caller, signature := common.HexToAddress(authElems[0]), common.FromHex(authElems[1])
|
|
sigPubKey, err := crypto.SigToPub(txtHash, signature)
|
|
if err != nil {
|
|
http.Error(w, "invalid authentication signature", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if caller != crypto.PubkeyToAddress(*sigPubKey) {
|
|
http.Error(w, "mismatched recovered signer", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Set the authenticated caller in the context
|
|
newCtx := context.WithValue(r.Context(), authContextKey{}, &AuthContext{caller})
|
|
h.next.ServeHTTP(w, r.WithContext(newCtx))
|
|
}
|
|
|
|
func AuthFromContext(ctx context.Context) *AuthContext {
|
|
auth, ok := ctx.Value(authContextKey{}).(*AuthContext)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return auth
|
|
}
|