Skip to content

Commit

Permalink
feat: added csrf, xss and caching middlewares
Browse files Browse the repository at this point in the history
  • Loading branch information
hokamsingh committed Aug 26, 2024
1 parent a95bebe commit 8ac168d
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 0 deletions.
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ module github.com/hokamsingh/lessgo
go 1.22.5

require (
github.com/go-redis/redis/v8 v8.11.5
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.3
github.com/joho/godotenv v1.5.1
github.com/robfig/cron/v3 v3.0.1
go.uber.org/dig v1.18.0
)

require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
)
24 changes: 24 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
Expand All @@ -14,5 +28,15 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw=
go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
63 changes: 63 additions & 0 deletions internal/core/middleware/CSRF.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package middleware

import (
"crypto/rand"
"encoding/base64"
"io"
"net/http"
)

type CSRFProtection struct{}

func NewCSRFProtection() *CSRFProtection {
return &CSRFProtection{}
}

func (csrf *CSRFProtection) Handle(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
// Generate and set CSRF token for GET requests
token, err := GenerateCSRFToken()
if err != nil {
http.Error(w, "Failed to generate CSRF token", http.StatusInternalServerError)
return
}
SetCSRFCookie(w, token)
} else if r.Method == http.MethodPost || r.Method == http.MethodPut || r.Method == http.MethodDelete {
// Validate CSRF token for state-changing requests
if !ValidateCSRFToken(r) {
http.Error(w, "Invalid CSRF token", http.StatusForbidden)
return
}
}
next.ServeHTTP(w, r)
})
}

func GenerateCSRFToken() (string, error) {
token := make([]byte, 32) // 32 bytes = 256 bits
if _, err := io.ReadFull(rand.Reader, token); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(token), nil
}

// SetCSRFCookie sets a CSRF token as a secure cookie.
func SetCSRFCookie(w http.ResponseWriter, token string) {
http.SetCookie(w, &http.Cookie{
Name: "csrf_token",
Value: token,
Path: "/",
HttpOnly: true, // Prevent access from JavaScript
Secure: true, // Ensure the cookie is only sent over HTTPS
})
}

func ValidateCSRFToken(r *http.Request) bool {
cookie, err := r.Cookie("csrf_token")
if err != nil {
return false
}
csrfToken := r.Header.Get("X-CSRF-Token") // Or retrieve from form data
return csrfToken == cookie.Value
}
27 changes: 27 additions & 0 deletions internal/core/middleware/XSS.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package middleware

import (
"net/http"
"strings"
)

type XSSProtection struct{}

func NewXSSProtection() *XSSProtection {
return &XSSProtection{}
}

func (xss *XSSProtection) Handle(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for _, values := range r.URL.Query() {
for _, value := range values {
if strings.Contains(value, "<script>") {
http.Error(w, "XSS detected", http.StatusBadRequest)
return
}
}
}

next.ServeHTTP(w, r)
})
}
56 changes: 56 additions & 0 deletions internal/core/middleware/cacher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package middleware

import (
"context"
"net/http"
"time"

"github.com/go-redis/redis/v8"
)

type Caching struct {
client *redis.Client
ttl time.Duration
}

func NewCaching(redisAddr string, ttl time.Duration) *Caching {
client := redis.NewClient(&redis.Options{
Addr: redisAddr, // e.g., "localhost:6379"
})
return &Caching{
client: client,
ttl: ttl,
}
}

func (c *Caching) Handle(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()

// Try to get the cached response from Redis
data, err := c.client.Get(ctx, r.RequestURI).Result()
if err == nil {
// If found in cache, write it directly to the response
w.Write([]byte(data))
return
}

// If not cached, create a response writer to capture the response
rec := &responseRecorder{ResponseWriter: w, statusCode: http.StatusOK}
next.ServeHTTP(rec, r)

// Cache the response in Redis
c.client.Set(ctx, r.RequestURI, rec.body, c.ttl)
})
}

type responseRecorder struct {
http.ResponseWriter
statusCode int
body []byte
}

func (rec *responseRecorder) Write(p []byte) (int, error) {
rec.body = append(rec.body, p...)
return rec.ResponseWriter.Write(p)
}
81 changes: 81 additions & 0 deletions internal/core/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,87 @@ func WithJSONParser(options middleware.ParserOptions) Option {
}
}

// WithCaching is an option function that enables caching for the router using Redis.
//
// This function returns an Option that can be passed to the Router to enable
// response caching with Redis. Cached responses will be stored in Redis with a
// specified Time-To-Live (TTL), meaning they will automatically expire after the
// specified duration.
//
// Parameters:
// - redisAddr (string): The address of the Redis server, e.g., "localhost:6379".
// - ttl (time.Duration): The Time-To-Live for cached responses. Responses will
// be removed from the cache after this duration.
//
// Returns:
// - Option: An option that applies caching middleware to the router.
//
// Example usage:
//
// router := NewRouter(
// WithCaching("localhost:6379", 5*time.Minute),
// )
//
// This will enable caching for the router, storing responses in Redis for 5 minutes.
//
// Note: Ensure that the Redis server is running and accessible at the specified
// address.
func WithCaching(redisAddr string, ttl time.Duration) Option {
return func(r *Router) {
caching := middleware.NewCaching(redisAddr, ttl)
r.Use(caching)
}
}

// WithCsrf is an option function that enables CSRF protection for the router.
//
// This function returns an Option that can be passed to the Router to enable
// Cross-Site Request Forgery (CSRF) protection using a middleware. The middleware
// generates and validates CSRF tokens to protect against malicious cross-origin
// requests, ensuring that requests are coming from legitimate users.
//
// Returns:
// - Option: An option that applies CSRF protection middleware to the router.
//
// Example usage:
//
// router := NewRouter(
// WithCsrf(),
// )
//
// This will enable CSRF protection for all routes in the router.
func WithCsrf() Option {
return func(r *Router) {
csrf := middleware.NewCSRFProtection()
r.Use(csrf)
}
}

// WithXss is an option function that enables XSS protection for the router.
//
// This function returns an Option that can be passed to the Router to enable
// Cross-Site Scripting (XSS) protection using a middleware. The middleware
// helps to sanitize and filter out malicious scripts from user input, thereby
// preventing XSS attacks.
//
// Returns:
// - Option: An option that applies XSS protection middleware to the router.
//
// Example usage:
//
// router := NewRouter(
// WithXss(),
// )
//
// This will enable XSS protection for all routes in the router, ensuring that
// user input is sanitized and secure.
func WithXss() Option {
return func(r *Router) {
xss := middleware.NewXSSProtection()
r.Use(xss)
}
}

// WithCookieParser enables cookie parsing middleware.
// This option ensures that cookies are parsed and available in the request context.
//
Expand Down

0 comments on commit 8ac168d

Please sign in to comment.