├── .github └── workflows │ └── test.yml ├── README.md ├── config.go ├── csrf.go ├── csrf_test.go ├── examples └── app.go ├── go.mod ├── go.sum └── utils.go /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | # Triggers the workflow on push or pull request events 4 | on: [push, pull_request] 5 | 6 | jobs: 7 | test: 8 | strategy: 9 | matrix: 10 | go: ["1.13", "1.14", "1.15", "1.16"] 11 | 12 | runs-on: ubuntu-20.04 13 | 14 | name: Go ${{ matrix.go }} Tests 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Setup Go 19 | uses: actions/setup-go@v2 20 | with: 21 | go-version: ${{ matrix.go }} 22 | 23 | - name: Run Test 24 | run: go test -v ./... 25 | 26 | - name: Run Coverage 27 | run: go test -v -cover ./... -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fastglue-csrf 2 | 3 | ## Overview [![Zerodha Tech](https://zerodha.tech/static/images/github-badge.svg)](https://zerodha.tech) 4 | 5 | fastglue-csrf implements CSRF middleware for [fastglue](https://github.com/zerodha/fastglue). 6 | 7 | 8 | ## Install 9 | 10 | ``` 11 | go get github.com/zerodha/fastglue-csrf 12 | ``` 13 | 14 | ## Usage 15 | 16 | ### Short 17 | ```golang 18 | g := fastglue.NewGlue() 19 | csrf := csrf.New(csrf.Config{ 20 | AuthKey: []byte(`12345678901234567890123456789012`), // random 32 length key for encrypting 21 | Name: "custom_csrf", 22 | MaxAge: 100, 23 | Path: "/", 24 | }) 25 | g.GET("/get", csrf.Inject(handlerGetSample)) 26 | g.POST("/post", csrf.Protect(handlerPostSample)) 27 | ``` 28 | 29 | ### Long 30 | ```golang 31 | package main 32 | 33 | import ( 34 | "log" 35 | "time" 36 | 37 | "github.com/zerodha/fastglue-csrf" 38 | "github.com/valyala/fasthttp" 39 | "github.com/zerodha/fastglue" 40 | ) 41 | 42 | func main() { 43 | g := fastglue.NewGlue() 44 | shutDownCh := make(chan struct{}) 45 | s := &fasthttp.Server{ 46 | Name: "test-server", 47 | ReadTimeout: 5 * time.Second, 48 | WriteTimeout: 5 * time.Second, 49 | MaxKeepaliveDuration: 100 * time.Second, 50 | MaxRequestBodySize: 512000, 51 | ReadBufferSize: 512000, 52 | } 53 | 54 | csrf := csrf.New(csrf.Config{ 55 | AuthKey: []byte(`12345678901234567890123456789012`), 56 | Name: "custom_csrf", 57 | MaxAge: 100, 58 | Path: "/", 59 | }) 60 | 61 | g.GET("/get", csrf.Inject(handlerGetSample)) 62 | g.POST("/post", csrf.Protect(handlerPostSample)) 63 | 64 | if err := g.ListenServeAndWaitGracefully(":8888", "", s, shutDownCh); err != nil { 65 | log.Fatalf("error starting server: %v", err) 66 | } 67 | } 68 | 69 | func handlerGetSample(r *fastglue.Request) error { 70 | return r.SendEnvelope(map[string]string{ 71 | "csrf": r.RequestCtx.UserValue("custom_csrf").(string), 72 | }) 73 | } 74 | 75 | func handlerPostSample(r *fastglue.Request) error { 76 | return r.SendEnvelope("success") 77 | } 78 | ``` 79 | 80 | ## References 81 | 82 | * [gorilla/csrf](https://github.com/gorilla/csrf) implementation for all frameworks implementing `http.Handler` -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package csrf 2 | 3 | // Config represents the configs for csrf 4 | type Config struct { 5 | // securecookie key 6 | AuthKey []byte 7 | // cookie name 8 | Name string 9 | // cookie Max-Age, defaults to 12hrs 10 | MaxAge int 11 | // cookie Same-site 12 | SameSite int 13 | // cookie path 14 | Path string 15 | // cookie domain 16 | Domain string 17 | // set this true for non https 18 | Unsecure bool 19 | } 20 | -------------------------------------------------------------------------------- /csrf.go: -------------------------------------------------------------------------------- 1 | package csrf 2 | 3 | import ( 4 | "crypto/subtle" 5 | "encoding/base64" 6 | "fmt" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/gorilla/securecookie" 11 | "github.com/valyala/fasthttp" 12 | "github.com/zerodha/fastglue" 13 | ) 14 | 15 | // CSRF consts 16 | const ( 17 | CSRFCookieName = "csrf" 18 | CSRFTokenLength = 32 19 | CSRFCookieMaxAge = 3600 * 12 20 | ) 21 | 22 | type tokenCookie struct { 23 | Token []byte 24 | } 25 | 26 | type CSRF struct { 27 | sc *securecookie.SecureCookie 28 | 29 | cfg Config 30 | } 31 | 32 | // New returns a new instance of `CSRF` with securecookie store. 33 | func New(cfg Config) CSRF { 34 | if cfg.Name == "" { 35 | cfg.Name = CSRFCookieName 36 | } 37 | 38 | if cfg.MaxAge == 0 { 39 | cfg.MaxAge = CSRFCookieMaxAge 40 | } 41 | 42 | sc := securecookie.New(cfg.AuthKey, nil) 43 | sc.MaxAge(cfg.MaxAge) 44 | 45 | return CSRF{ 46 | sc: sc, 47 | cfg: cfg, 48 | } 49 | } 50 | 51 | // Inject injects csrf token to the GET handlers 52 | func (c *CSRF) Inject(handler fastglue.FastRequestHandler) fastglue.FastRequestHandler { 53 | return func(r *fastglue.Request) error { 54 | // Generate token 55 | tk, err := generateRandomString(CSRFTokenLength) 56 | if err != nil { 57 | c.deny(r) 58 | return nil 59 | } 60 | 61 | // Encode and set the cookie in the header 62 | value, err := c.sc.Encode(c.cfg.Name, tokenCookie{Token: tk}) 63 | if err != nil { 64 | c.deny(r) 65 | return err 66 | } 67 | 68 | cookie := &http.Cookie{ 69 | Name: c.cfg.Name, 70 | Value: value, 71 | MaxAge: c.cfg.MaxAge, 72 | Path: c.cfg.Path, 73 | Secure: !c.cfg.Unsecure, 74 | HttpOnly: true, 75 | SameSite: http.SameSite(c.cfg.SameSite), 76 | Domain: c.cfg.Domain, 77 | } 78 | 79 | if c.cfg.MaxAge > 0 { 80 | cookie.Expires = time.Now().Add( 81 | time.Duration(c.cfg.MaxAge) * time.Second) 82 | } 83 | 84 | if err = setCookie(cookie, r); err != nil { 85 | c.deny(r) 86 | return err 87 | } 88 | 89 | // Mask csrf token 90 | maskedTk, err := c.mask(tk) 91 | if err != nil { 92 | c.deny(r) 93 | return err 94 | } 95 | 96 | r.RequestCtx.SetUserValue(c.cfg.Name, maskedTk) 97 | 98 | return handler(r) 99 | } 100 | } 101 | 102 | // Protect checks if the Set-Cookie headers from the GET request is same as the one from form values. 103 | func (c *CSRF) Protect(handler fastglue.FastRequestHandler) fastglue.FastRequestHandler { 104 | return func(r *fastglue.Request) error { 105 | var ( 106 | csrfCookie = r.RequestCtx.Request.Header.Cookie(c.cfg.Name) 107 | 108 | decoded tokenCookie 109 | ) 110 | 111 | // Decode the cookie 112 | if err := c.sc.Decode(c.cfg.Name, string(csrfCookie), &decoded); err != nil || len(decoded.Token) != CSRFTokenLength { 113 | c.deny(r) 114 | return err 115 | } 116 | 117 | // Validations 118 | if decoded.Token == nil { 119 | c.deny(r) 120 | return fmt.Errorf("invalid decoded token") 121 | } 122 | 123 | // Get csrf token from the form, unmask the token 124 | xcsrfToken := string(r.RequestCtx.FormValue(c.cfg.Name)) 125 | 126 | issued, err := base64.StdEncoding.DecodeString(xcsrfToken) 127 | if err != nil { 128 | c.deny(r) 129 | return err 130 | } 131 | 132 | requestToken := c.unmask(issued) 133 | 134 | if !compareTokens(requestToken, decoded.Token) { 135 | c.deny(r) 136 | return fmt.Errorf("token mismatch") 137 | } 138 | 139 | // disable caching 140 | r.RequestCtx.Response.Header.Add("Vary", "Cookie") 141 | 142 | return handler(r) 143 | } 144 | } 145 | 146 | // deny clears the cookie and sets the status_code to forbidden. 147 | func (c *CSRF) deny(r *fastglue.Request) { 148 | // 1. clear cookie 149 | // 2. Set forbidden status 150 | setCookie(&http.Cookie{ //nolint 151 | Name: c.cfg.Name, 152 | Value: "", 153 | Expires: fasthttp.CookieExpireDelete, 154 | Path: "/", 155 | Secure: !c.cfg.Unsecure, 156 | HttpOnly: true, 157 | }, r) 158 | 159 | r.RequestCtx.SetStatusCode(fasthttp.StatusForbidden) 160 | } 161 | 162 | // mask adds a OTP to the original token. 163 | func (c *CSRF) mask(realToken []byte) (string, error) { 164 | otp, err := generateRandomString(CSRFTokenLength) 165 | if err != nil { 166 | return "", err 167 | } 168 | 169 | // XOR the OTP with the real token to generate a masked token. Append the 170 | // OTP to the front of the masked token to allow unmasking in the subsequent 171 | // request. 172 | return base64.StdEncoding.EncodeToString(append(otp, xorToken(otp, realToken)...)), nil 173 | } 174 | 175 | // unmask splits the issued token (one-time-pad + masked token) and returns the 176 | // unmasked request token for comparison. 177 | func (c *CSRF) unmask(issued []byte) []byte { 178 | // Issued tokens are always masked and combined with the pad. 179 | if len(issued) != CSRFTokenLength*2 { 180 | return nil 181 | } 182 | 183 | // We now know the length of the byte slice. 184 | var ( 185 | otp = issued[CSRFTokenLength:] 186 | masked = issued[:CSRFTokenLength] 187 | ) 188 | 189 | // Unmask the token by XOR'ing it against the OTP used to mask it. 190 | return xorToken(otp, masked) 191 | } 192 | 193 | // xorToken XORs tokens ([]byte) to provide unique-per-request CSRF tokens. It 194 | // will return a masked token if the base token is XOR'ed with a one-time-pad. 195 | // An unmasked token will be returned if a masked token is XOR'ed with the 196 | // one-time-pad used to mask it. 197 | func xorToken(a, b []byte) []byte { 198 | var n = len(a) 199 | 200 | if len(b) < n { 201 | n = len(b) 202 | } 203 | 204 | res := make([]byte, n) 205 | 206 | for i := 0; i < n; i++ { 207 | res[i] = a[i] ^ b[i] 208 | } 209 | 210 | return res 211 | } 212 | 213 | // compare securely (constant-time) compares the unmasked token from the request 214 | // against the real token from the session. 215 | func compareTokens(a, b []byte) bool { 216 | // This is required as subtle.ConstantTimeCompare does not check for equal 217 | // lengths in Go versions prior to 1.3. 218 | if len(a) != len(b) { 219 | return false 220 | } 221 | 222 | return subtle.ConstantTimeCompare(a, b) == 1 223 | } 224 | -------------------------------------------------------------------------------- /csrf_test.go: -------------------------------------------------------------------------------- 1 | package csrf 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "net/http/cookiejar" 11 | "net/url" 12 | "os" 13 | "testing" 14 | "time" 15 | 16 | "github.com/stretchr/testify/assert" 17 | "github.com/valyala/fasthttp" 18 | "github.com/zerodha/fastglue" 19 | ) 20 | 21 | var ( 22 | testServer *fastglue.Fastglue 23 | shutDownCh chan struct{} 24 | testHTTPClient *http.Client 25 | ) 26 | 27 | func setupTest() { 28 | jar, _ := cookiejar.New(nil) 29 | 30 | testHTTPClient = &http.Client{Jar: jar} 31 | 32 | testServer = fastglue.NewGlue() 33 | shutDownCh = make(chan struct{}) 34 | s := &fasthttp.Server{ 35 | Name: "test-server", 36 | ReadTimeout: 5 * time.Second, 37 | WriteTimeout: 5 * time.Second, 38 | MaxKeepaliveDuration: 100 * time.Second, 39 | MaxRequestBodySize: 512000, 40 | ReadBufferSize: 512000, 41 | } 42 | 43 | sampleKey, _ := generateRandomString(32) 44 | 45 | csrf := New(Config{ 46 | AuthKey: sampleKey, 47 | Name: "custom_csrf", 48 | MaxAge: 100, 49 | Path: "/", 50 | }) 51 | 52 | testServer.GET("/get", csrf.Inject(handlerGetSample)) 53 | testServer.POST("/post", csrf.Protect(handlerPostSample)) 54 | 55 | go testServer.ListenServeAndWaitGracefully(":8888", "", s, shutDownCh) 56 | } 57 | 58 | func teardownTest() { 59 | shutDownCh <- struct{}{} 60 | } 61 | 62 | func TestMain(m *testing.M) { 63 | setupTest() 64 | code := m.Run() 65 | teardownTest() 66 | os.Exit(code) 67 | } 68 | 69 | func TestCSRF(t *testing.T) { 70 | // GET request handler injects set-cookie header and return a masked csrf token 71 | resp, err := doTestRequest("GET", "/get", url.Values{}, nil) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | 76 | var ck *http.Cookie 77 | for _, c := range resp.Cookies() { 78 | if c.Name == "custom_csrf" { 79 | ck = c 80 | break 81 | } 82 | } 83 | 84 | data, _ := ioutil.ReadAll(resp.Body) 85 | var r fastglue.Envelope 86 | json.Unmarshal(data, &r) 87 | 88 | assert.Contains(t, ck.Path, "/", "cookie path should be `/`") 89 | 90 | // POST request should go through only if the correct csrf token, cookie header exist 91 | v := url.Values{} 92 | v.Add("custom_csrf", r.Data.(map[string]interface{})["csrf"].(string)) 93 | 94 | h := http.Header{} 95 | h.Add("Cookie", resp.Header.Get("Set-Cookie")) 96 | resp, err = doTestRequest("POST", "/post", v, h) 97 | if err != nil { 98 | t.Fatal(err) 99 | } 100 | 101 | assert.Equal(t, resp.StatusCode, http.StatusOK, "status code should be 200") 102 | } 103 | 104 | func handlerGetSample(r *fastglue.Request) error { 105 | return r.SendEnvelope(map[string]string{ 106 | "csrf": r.RequestCtx.UserValue("custom_csrf").(string), 107 | }) 108 | } 109 | 110 | func handlerPostSample(r *fastglue.Request) error { 111 | return r.SendEnvelope("success") 112 | } 113 | 114 | func doTestRequest(method, url string, params url.Values, headers http.Header) (*http.Response, error) { 115 | var ( 116 | postBody io.Reader 117 | reqBody = []byte(params.Encode()) 118 | ) 119 | 120 | // Encode POST / PUT params. 121 | if method == fasthttp.MethodPost || method == fasthttp.MethodPut { 122 | postBody = bytes.NewReader(reqBody) 123 | } 124 | 125 | req, err := http.NewRequest(method, "http://localhost:8888"+url, postBody) 126 | if err != nil { 127 | return nil, fmt.Errorf("Error forming batch alert request: %v", err) 128 | } 129 | 130 | if method == fasthttp.MethodPost || method == fasthttp.MethodPut { 131 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 132 | } 133 | 134 | // If the request method is GET or DELETE, add the params as QueryString. 135 | if method == fasthttp.MethodGet || method == fasthttp.MethodDelete { 136 | req.URL.RawQuery = string(reqBody) 137 | } 138 | 139 | for k, v := range headers { 140 | req.Header.Add(k, v[0]) 141 | } 142 | 143 | resp, err := testHTTPClient.Do(req) 144 | if err != nil { 145 | return nil, fmt.Errorf("Error performing batch alert request: %v", err) 146 | } 147 | 148 | return resp, nil 149 | } 150 | -------------------------------------------------------------------------------- /examples/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/valyala/fasthttp" 8 | "github.com/zerodha/fastglue" 9 | csrf "github.com/zerodha/fastglue-csrf" 10 | ) 11 | 12 | func main() { 13 | testServer := fastglue.NewGlue() 14 | shutDownCh := make(chan struct{}) 15 | s := &fasthttp.Server{ 16 | Name: "test-server", 17 | ReadTimeout: 5 * time.Second, 18 | WriteTimeout: 5 * time.Second, 19 | MaxKeepaliveDuration: 100 * time.Second, 20 | MaxRequestBodySize: 512000, 21 | ReadBufferSize: 512000, 22 | } 23 | 24 | csrf := csrf.New(csrf.Config{ 25 | AuthKey: []byte(`12345678901234567890123456789012`), 26 | Name: "custom_csrf", 27 | MaxAge: 100, 28 | Path: "/", 29 | }) 30 | 31 | testServer.GET("/get", csrf.Inject(handlerGetSample)) 32 | testServer.POST("/post", csrf.Protect(handlerPostSample)) 33 | 34 | if err := testServer.ListenServeAndWaitGracefully(":8888", "", s, shutDownCh); err != nil { 35 | log.Fatalf("error starting server: %v", err) 36 | } 37 | 38 | } 39 | 40 | func handlerGetSample(r *fastglue.Request) error { 41 | return r.SendEnvelope(map[string]string{ 42 | "csrf": r.RequestCtx.UserValue("custom_csrf").(string), 43 | }) 44 | } 45 | 46 | func handlerPostSample(r *fastglue.Request) error { 47 | return r.SendEnvelope("success") 48 | } 49 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zerodha/fastglue-csrf 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/gorilla/securecookie v1.1.1 7 | github.com/stretchr/testify v1.6.0 8 | github.com/valyala/fasthttp v1.27.0 9 | github.com/zerodha/fastglue v1.6.6 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E= 2 | github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/fasthttp/router v1.0.2 h1:rdYdcAmwOLqWuFgc4afa409SYmuw4t0A66K5Ib+GT3I= 6 | github.com/fasthttp/router v1.0.2/go.mod h1:Myk/ofrwtfiLSCIfbE44+e+PyP3mR6JhZg3AYzqwJI0= 7 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 8 | github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= 9 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= 10 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 11 | github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 12 | github.com/klauspost/compress v1.10.6/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 13 | github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8= 14 | github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= 15 | github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 16 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 17 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 18 | github.com/savsgio/gotils v0.0.0-20200319105752-a9cc718f6a3f/go.mod h1:lHhJedqxCoHN+zMtwGNTXWmF0u9Jt363FYRhV6g0CdY= 19 | github.com/savsgio/gotils v0.0.0-20200413113635-8c468ce75cca h1:Qe7Mtuhjkk38HVpRtvWdziZJcwG3Qup1mfyvyOrcnyM= 20 | github.com/savsgio/gotils v0.0.0-20200413113635-8c468ce75cca/go.mod h1:TWNAOTaVzGOXq8RbEvHnhzA/A2sLZzgn0m6URjnukY8= 21 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 22 | github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho= 23 | github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 24 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 25 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 26 | github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= 27 | github.com/valyala/fasthttp v1.27.0 h1:gDefRDL9aqSiwXV6aRW8aSBPs82y4KizSzHrBLf4NDI= 28 | github.com/valyala/fasthttp v1.27.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA= 29 | github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= 30 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 31 | github.com/zerodha/fastglue v1.6.6 h1:VfafozxgXducPilavn5aTnHJDd1vX1wRn9MR3GddLfs= 32 | github.com/zerodha/fastglue v1.6.6/go.mod h1:s/2wO1fppdffJ5zl6f64xyD/QqKmhHYB7NS7vUpqXFc= 33 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 34 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 35 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 36 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 37 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 38 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 39 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 40 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 42 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 43 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 44 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 45 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 46 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 47 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 48 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 49 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 50 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 51 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package csrf 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/valyala/fasthttp" 9 | "github.com/zerodha/fastglue" 10 | ) 11 | 12 | const ( 13 | randomString = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 14 | ) 15 | 16 | // setCookie implements the cookie interface. 17 | func setCookie(cookie *http.Cookie, w interface{}) error { 18 | req, ok := w.(*fastglue.Request) 19 | if !ok { 20 | return fmt.Errorf("invalid param for w") 21 | } 22 | 23 | // Acquire cookie 24 | fck := fasthttp.AcquireCookie() 25 | defer fasthttp.ReleaseCookie(fck) 26 | fck.SetKey(cookie.Name) 27 | fck.SetValue(cookie.Value) 28 | fck.SetMaxAge(cookie.MaxAge) 29 | fck.SetPath(cookie.Path) 30 | fck.SetSecure(cookie.Secure) 31 | fck.SetHTTPOnly(cookie.HttpOnly) 32 | fck.SetSameSite(fasthttp.CookieSameSite(cookie.SameSite)) 33 | fck.SetDomain(cookie.Domain) 34 | fck.SetExpire(cookie.Expires) 35 | 36 | req.RequestCtx.Response.Header.SetCookie(fck) 37 | 38 | return nil 39 | } 40 | 41 | // generateRandomString generates a cryptographically random, 42 | // alphanumeric string of length n. 43 | func generateRandomString(n int) ([]byte, error) { 44 | var bytes = make([]byte, n) 45 | if _, err := rand.Read(bytes); err != nil { 46 | return []byte{}, err 47 | } 48 | 49 | for k, v := range bytes { 50 | bytes[k] = randomString[v%byte(len(randomString))] 51 | } 52 | 53 | return bytes, nil 54 | } 55 | --------------------------------------------------------------------------------