Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix(🩹): error handling in CSRF token storage retrieval #3021

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions middleware/csrf/csrf.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@
ErrRefererNoMatch = errors.New("referer does not match host and is not a trusted origin")
ErrOriginInvalid = errors.New("origin invalid")
ErrOriginNoMatch = errors.New("origin does not match host and is not a trusted origin")
errOriginNotFound = errors.New("origin not supplied or is null") // internal error, will not be returned to the user
dummyValue = []byte{'+'}
ErrNotGetStorage = errors.New("csrf storage not found data")
ErrNotSetStorage = errors.New("csrf storage not set data")
renanbastos93 marked this conversation as resolved.
Show resolved Hide resolved

errOriginNotFound = errors.New("origin not supplied or is null") // internal error, will not be returned to the user
dummyValue = []byte{'+'}
)

// Handler for CSRF middleware
Expand Down Expand Up @@ -102,11 +105,10 @@
switch c.Method() {
case fiber.MethodGet, fiber.MethodHead, fiber.MethodOptions, fiber.MethodTrace:
cookieToken := c.Cookies(cfg.CookieName)

if cookieToken != "" {
raw := getRawFromStorage(c, cookieToken, cfg, sessionManager, storageManager)

if raw != nil {
// In this case, handling error doesn't make sense because we have validations after the switch.
raw, err := getRawFromStorage(c, cookieToken, cfg, sessionManager, storageManager)
if raw != nil && err == nil {
Copy link
Contributor

@coderabbitai coderabbitai bot May 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle potential errors from getRawFromStorage properly.

The error from getRawFromStorage is checked, but the handling could be more robust. Consider adding specific error handling for different types of errors that could be returned:

  raw, err := getRawFromStorage(c, cookieToken, cfg, sessionManager, storageManager)
  if err != nil {
+     if errors.Is(err, ErrNotGetStorage) {
+         // Handle specific error
+     }
      return cfg.ErrorHandler(c, err)
  }

Committable suggestion was skipped due low confidence.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai it doesn't make sense, can you explain why I need to do that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@renanbastos93 The suggestion to handle specific errors from getRawFromStorage more robustly is aimed at improving the middleware's ability to respond appropriately to different failure scenarios. Currently, the error handling in the switch case for GET, HEAD, OPTIONS, and TRACE methods does not differentiate between the types of errors that could be returned from getRawFromStorage. This function could return errors like ErrNotGetStorage, which specifically indicates issues in retrieving data from storage.

By handling such specific errors distinctly, you can tailor the middleware's response to better suit the nature of the error. For example, if ErrNotGetStorage is encountered, it might be beneficial to log this as a critical error or perform a specific action, because it indicates a failure in the storage backend, which is critical for CSRF protection. This makes the error handling more granular and can help in debugging and maintaining the system more effectively.

Let me know if this clarifies the suggestion or if there's anything else you'd like to discuss!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai I got it I am waiting for the team comment for that too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@renanbastos93, understood! It's great to hear that you're considering the suggestion and discussing it further with your team. If you need any more information or further clarification as you move forward, feel free to reach out. I'm here to help!

token = cookieToken // Token is valid, safe to set it
}
}
Expand Down Expand Up @@ -148,9 +150,8 @@
return cfg.ErrorHandler(c, ErrTokenInvalid)
}

raw := getRawFromStorage(c, extractedToken, cfg, sessionManager, storageManager)

if raw == nil {
raw, err := getRawFromStorage(c, extractedToken, cfg, sessionManager, storageManager)
if err != nil || raw == nil {
renanbastos93 marked this conversation as resolved.
Show resolved Hide resolved
// If token is not in storage, expire the cookie
expireCSRFCookie(c, cfg)
// and return an error
Expand Down Expand Up @@ -209,7 +210,7 @@

// getRawFromStorage returns the raw value from the storage for the given token
// returns nil if the token does not exist, is expired or is invalid
func getRawFromStorage(c fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) []byte {
func getRawFromStorage(c fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) (raw []byte, err error) {

Check failure on line 213 in middleware/csrf/csrf.go

View workflow job for this annotation

GitHub Actions / lint

named return "raw" with type "[]byte" found (nonamedreturns)

Check failure on line 213 in middleware/csrf/csrf.go

View workflow job for this annotation

GitHub Actions / lint

named return "err" with type "error" found (nonamedreturns)
renanbastos93 marked this conversation as resolved.
Show resolved Hide resolved
if cfg.Session != nil {
return sessionManager.getRaw(c, token, dummyValue)
}
Expand All @@ -221,7 +222,7 @@
if cfg.Session != nil {
sessionManager.setRaw(c, token, dummyValue, cfg.Expiration)
} else {
storageManager.setRaw(token, dummyValue, cfg.Expiration)

Check failure on line 225 in middleware/csrf/csrf.go

View workflow job for this annotation

GitHub Actions / lint

unhandled-error: Unhandled error in call to function csrf.storageManager.setRaw (revive)

Check failure on line 225 in middleware/csrf/csrf.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `storageManager.setRaw` is not checked (errcheck)
}
}

Expand All @@ -229,7 +230,7 @@
if cfg.Session != nil {
sessionManager.delRaw(c)
} else {
storageManager.delRaw(token)

Check failure on line 233 in middleware/csrf/csrf.go

View workflow job for this annotation

GitHub Actions / lint

unhandled-error: Unhandled error in call to function csrf.storageManager.delRaw (revive)

Check failure on line 233 in middleware/csrf/csrf.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `storageManager.delRaw` is not checked (errcheck)
}
}

Expand Down
18 changes: 10 additions & 8 deletions middleware/csrf/session_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,22 @@
}

// get token from session
func (m *sessionManager) getRaw(c fiber.Ctx, key string, raw []byte) []byte {
func (m *sessionManager) getRaw(c fiber.Ctx, key string, raw []byte) (rawToken []byte, err error) {

Check failure on line 29 in middleware/csrf/session_manager.go

View workflow job for this annotation

GitHub Actions / lint

named return "rawToken" with type "[]byte" found (nonamedreturns)

Check failure on line 29 in middleware/csrf/session_manager.go

View workflow job for this annotation

GitHub Actions / lint

named return "err" with type "error" found (nonamedreturns)
renanbastos93 marked this conversation as resolved.
Show resolved Hide resolved
sess, err := m.session.Get(c)
if err != nil {
return nil
return nil, err

Check warning on line 32 in middleware/csrf/session_manager.go

View check run for this annotation

Codecov / codecov/patch

middleware/csrf/session_manager.go#L32

Added line #L32 was not covered by tests
}

token, ok := sess.Get(m.key).(Token)
if ok {
if token.Expiration.Before(time.Now()) || key != token.Key || !compareTokens(raw, token.Raw) {
return nil
}
return token.Raw
if !ok {
return nil, ErrTokenInvalid
}

if token.Expiration.Before(time.Now()) || key != token.Key || !compareTokens(raw, token.Raw) {
return nil, ErrTokenInvalid
}

return nil
return token.Raw, nil
}

// set token in session
Expand Down
36 changes: 27 additions & 9 deletions middleware/csrf/storage_manager.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package csrf

import (
"fmt"
"sync"
"time"

Expand Down Expand Up @@ -40,31 +41,48 @@
}

// get raw data from storage or memory
func (m *storageManager) getRaw(key string) []byte {
var raw []byte
func (m *storageManager) getRaw(key string) (raw []byte, err error) {

Check failure on line 44 in middleware/csrf/storage_manager.go

View workflow job for this annotation

GitHub Actions / lint

named return "raw" with type "[]byte" found (nonamedreturns)

Check failure on line 44 in middleware/csrf/storage_manager.go

View workflow job for this annotation

GitHub Actions / lint

named return "err" with type "error" found (nonamedreturns)
renanbastos93 marked this conversation as resolved.
Show resolved Hide resolved
if m.storage != nil {
raw, _ = m.storage.Get(key) //nolint:errcheck // TODO: Do not ignore error
raw, err = m.storage.Get(key)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrNotGetStorage, err.Error())

Check warning on line 48 in middleware/csrf/storage_manager.go

View check run for this annotation

Codecov / codecov/patch

middleware/csrf/storage_manager.go#L46-L48

Added lines #L46 - L48 were not covered by tests
}
} else {
raw, _ = m.memory.Get(key).([]byte) //nolint:errcheck // TODO: Do not ignore error
var ok bool
raw, ok = m.memory.Get(key).([]byte)
if !ok {
return nil, ErrNotGetStorage
}
}
return raw

return raw, nil
}

// set data to storage or memory
func (m *storageManager) setRaw(key string, raw []byte, exp time.Duration) {
func (m *storageManager) setRaw(key string, raw []byte, exp time.Duration) (err error) {
if m.storage != nil {
_ = m.storage.Set(key, raw, exp) //nolint:errcheck // TODO: Do not ignore error
err = m.storage.Set(key, raw, exp)
if err != nil {
return fmt.Errorf("%w: %s", ErrNotSetStorage, err.Error())

Check warning on line 66 in middleware/csrf/storage_manager.go

View check run for this annotation

Codecov / codecov/patch

middleware/csrf/storage_manager.go#L64-L66

Added lines #L64 - L66 were not covered by tests
}
} else {
// the key is crucial in crsf and sometimes a reference to another value which can be reused later(pool/unsafe values concept), so a copy is made here
m.memory.Set(utils.CopyString(key), raw, exp)
}

return nil
}

// delete data from storage or memory
func (m *storageManager) delRaw(key string) {
func (m *storageManager) delRaw(key string) (err error) {
if m.storage != nil {
_ = m.storage.Delete(key) //nolint:errcheck // TODO: Do not ignore error
err = m.storage.Delete(key)
if err != nil {
return fmt.Errorf("%w: %s", ErrNotSetStorage, err.Error())

Check warning on line 81 in middleware/csrf/storage_manager.go

View check run for this annotation

Codecov / codecov/patch

middleware/csrf/storage_manager.go#L79-L81

Added lines #L79 - L81 were not covered by tests
}
} else {
m.memory.Delete(key)
}

return nil
}
Loading