Skip to content

Commit ba7dbca

Browse files
committed
fix(api): re-enable csrf injection
1 parent 84707b1 commit ba7dbca

File tree

5 files changed

+64
-2
lines changed

5 files changed

+64
-2
lines changed

backend/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ require (
3636
github.com/gomodule/redigo v1.9.2
3737
github.com/google/go-cmp v0.7.0
3838
github.com/google/uuid v1.6.0
39+
github.com/gorilla/csrf v1.7.2
3940
github.com/gorilla/handlers v1.5.2
4041
github.com/gorilla/mux v1.8.1
4142
github.com/gorilla/websocket v1.5.3
@@ -199,6 +200,7 @@ require (
199200
github.com/google/s2a-go v0.1.9 // indirect
200201
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
201202
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
203+
github.com/gorilla/securecookie v1.1.2 // indirect
202204
github.com/hashicorp/go-bexpr v0.1.10 // indirect
203205
github.com/herumi/bls-eth-go-binary v1.31.0 // indirect
204206
github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 // indirect

backend/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,10 +433,14 @@ github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEP
433433
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
434434
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
435435
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
436+
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
437+
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
436438
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
437439
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
438440
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
439441
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
442+
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
443+
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
440444
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
441445
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
442446
github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0=

backend/pkg/api/auth.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package api
22

33
import (
4+
"encoding/hex"
45
"net/http"
56
"regexp"
67
"time"
78

89
"github.com/alexedwards/scs/redisstore"
910
"github.com/alexedwards/scs/v2"
11+
"github.com/gobitfly/beaconchain/pkg/commons/log"
1012
"github.com/gobitfly/beaconchain/pkg/commons/types"
1113
"github.com/gomodule/redigo/redis"
14+
"github.com/gorilla/csrf"
1215
)
1316

1417
var day time.Duration = time.Hour * 24
@@ -55,6 +58,37 @@ func getSlidingSessionExpirationMiddleware(scs *scs.SessionManager) func(http.Ha
5558
}
5659
}
5760

61+
// returns gorilla/csrf middleware with the given config settings
62+
func getCsrfProtectionMiddleware(cfg *types.Config) func(http.Handler) http.Handler {
63+
csrfBytes, err := hex.DecodeString(cfg.Frontend.CsrfAuthKey)
64+
if err != nil {
65+
log.Fatal(err, "error decoding cfg.Frontend.CsrfAuthKey, set it to a valid hex string", 0)
66+
}
67+
if len(csrfBytes) == 0 {
68+
log.Warn("CSRF auth key is empty, unsafe requests will not work! Set cfg.Frontend.Debug to true to disable CSRF protection or cfg.Frontend.CsrfAuthKey.")
69+
}
70+
sameSite := csrf.SameSiteStrictMode
71+
if cfg.Frontend.SessionSameSiteNone {
72+
sameSite = csrf.SameSiteNoneMode
73+
}
74+
75+
return csrf.Protect(
76+
csrfBytes,
77+
csrf.Secure(!cfg.Frontend.CsrfInsecure),
78+
csrf.Path("/"),
79+
csrf.Domain(cfg.Frontend.SessionCookieDomain),
80+
csrf.SameSite(sameSite),
81+
)
82+
}
83+
84+
// returns a middleware that injects the CSRF token into the response headers
85+
func csrfInjecterMiddleware(next http.Handler) http.Handler {
86+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
87+
w.Header().Set("X-CSRF-Token", csrf.Token(r))
88+
next.ServeHTTP(w, r)
89+
})
90+
}
91+
5892
func contentTypeMiddleware(next http.Handler) http.Handler {
5993
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
6094
// if body is not empty, check if content type is set to json

backend/pkg/api/handlers/internal.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package handlers
22

33
import (
4+
"context"
45
"errors"
6+
"io"
57
"net/http"
68

79
"github.com/gobitfly/beaconchain/pkg/api/enums"
@@ -25,6 +27,17 @@ func (h *HandlerService) InternalGetProductSummary(w http.ResponseWriter, r *htt
2527
returnOk(w, r, response)
2628
}
2729

30+
// dummy endpoint used for csrf token injection only
31+
func (i *inputGetPricing) Validate(params map[string]string, body io.ReadCloser) error {
32+
return nil
33+
}
34+
35+
type inputGetPricing struct{}
36+
37+
func (h *HandlerService) InternalGetPricing(ctx context.Context, req inputGetPricing) (types.ApiDataResponse[any], error) {
38+
return types.ApiDataResponse[any]{}, nil
39+
}
40+
2841
// --------------------------------------
2942
// API Ratelimit Weights
3043

backend/pkg/api/router.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ func NewApiRouter(dataAccessor dataaccess.DataAccessor, dummy dataaccess.DataAcc
3232
internalRouter.Use(sessionManager.LoadAndSave, getSlidingSessionExpirationMiddleware(sessionManager))
3333

3434
handlerService := handlers.NewHandlerService(dataAccessor, dummy, sessionManager, cfg)
35+
// CSRF injection
36+
if !(cfg.Frontend.CsrfInsecure || cfg.Frontend.Debug) {
37+
csrfRouter := internalRouter.NewRoute().Subrouter()
38+
csrfRouter.Use(getCsrfProtectionMiddleware(cfg), csrfInjecterMiddleware)
39+
40+
csrfRouter.HandleFunc("/pricing", handlers.Handle(http.StatusOK, handlerService.InternalGetPricing, false)).Methods(http.MethodGet)
41+
}
3542

3643
// store user id in context, if available
3744
publicRouter.Use(handlerService.StoreUserIdByApiKeyMiddleware)
@@ -57,7 +64,8 @@ func GetCorsMiddleware(allowedHosts []string) func(http.Handler) http.Handler {
5764
return gorillaHandlers.CORS(
5865
gorillaHandlers.AllowedOrigins([]string{"*"}),
5966
gorillaHandlers.AllowedMethods([]string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodOptions, http.MethodHead}),
60-
gorillaHandlers.AllowedHeaders([]string{"Content-Type", "Authorization"}),
67+
gorillaHandlers.AllowedHeaders([]string{"Content-Type", "Authorization", "X-CSRF-Token"}),
68+
gorillaHandlers.ExposedHeaders([]string{"X-CSRF-Token"}),
6169
)
6270
}
6371

@@ -81,7 +89,8 @@ func GetCorsMiddleware(allowedHosts []string) func(http.Handler) http.Handler {
8189
return false
8290
}),
8391
gorillaHandlers.AllowedMethods([]string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodOptions, http.MethodHead}),
84-
gorillaHandlers.AllowedHeaders([]string{"Content-Type", "Authorization"}),
92+
gorillaHandlers.AllowedHeaders([]string{"Content-Type", "Authorization", "X-CSRF-Token"}),
93+
gorillaHandlers.ExposedHeaders([]string{"X-CSRF-Token"}),
8594
gorillaHandlers.AllowCredentials(),
8695
)
8796
}

0 commit comments

Comments
 (0)