8000 ♻️ v3: fix!: ContextKey collisions by sixcolors · Pull Request #2781 · gofiber/fiber · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
8000

♻️ v3: fix!: ContextKey collisions #2781

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

Merged
merged 3 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 5 additions & 1 deletion ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ const (
// maxParams defines the maximum number of parameters per route.
const maxParams = 30

// The contextKey type is unexported to prevent collisions with context keys defined in
// other packages.
type contextKey int

// userContextKey define the key name for storing context.Context in *fasthttp.RequestCtx
const userContextKey = "__local_user_context__"
const userContextKey contextKey = 0 // __local_user_context__

type DefaultCtx struct {
app *App // Reference to *App
Expand Down
14 changes: 12 additions & 2 deletions docs/api/ctx.md
Original file line number Diff line number Diff line change
Expand Up @@ -895,13 +895,23 @@ func (c *Ctx) Locals(key interface{}, value ...interface{}) interface{}
```

```go title="Example"

// key is an unexported type for keys defined in this package.
// This prevents collisions with keys defined in other packages.
type key int

// userKey is the key for user.User values in Contexts. It is
// unexported; clients use user.NewContext and user.FromContext
// instead of using this key directly.
var userKey key

app.Use(func(c *fiber.Ctx) error {
c.Locals("user", "admin")
c.Locals(userKey, "admin")
return c.Next()
})

app.Get("/admin", func(c *fiber.Ctx) error {
if c.Locals("user") == "admin" {
if c.Locals(userKey) == "admin" {
return c.Status(fiber.StatusOK).SendString("Welcome, admin!")
}
return c.SendStatus(fiber.StatusForbidden)
Expand Down
19 changes: 13 additions & 6 deletions docs/api/middleware/basicauth.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Basic Authentication middleware for [Fiber](https://github.com/gofiber/fiber) th

```go
func New(config Config) fiber.Handler
func UsernameFromContext(c *fiber.Ctx) string
func PasswordFromContext(c *fiber.Ctx) string
```

## Examples
Expand Down Expand Up @@ -53,11 +55,20 @@ app.Use(basicauth.New(basicauth.Config{
Unauthorized: func(c *fiber.Ctx) error {
return c.SendFile("./unauthorized.html")
},
ContextUsername: "_user",
ContextPassword: "_pass",
}))
```

Getting the username and password

```go
func handler(c *fiber.Ctx) error {
username := basicauth.UsernameFromContext(c)
password := basicauth.PasswordFromContext(c)
log.Printf("Username: %s Password: %s", username, password)
return c.SendString("Hello, " + username)
}
```

## Config

| Property | Type | Description | Default |
Expand All @@ -67,8 +78,6 @@ app.Use(basicauth.New(basicauth.Config{
| Realm | `string` | Realm is a string to define the realm attribute of BasicAuth. The realm identifies the system to authenticate against and can be used by clients to save credentials. | `"Restricted"` |
| Authorizer | `func(string, string) bool` | Authorizer defines a function to check the credentials. It will be called with a username and password and is expected to return true or false to indicate approval. | `nil` |
| Unauthorized | `fiber.Handler` | Unauthorized defines the response body for unauthorized responses. | `nil` |
| ContextUsername | `string` | ContextUsername is the key to store the username in Locals. | `"username"` |
| ContextPassword | `string` | ContextPassword is the key to store the password in Locals. | `"password"` |

## Default Config

Expand All @@ -79,7 +88,5 @@ var ConfigDefault = Config{
Realm: "Restricted",
Authorizer: nil,
Unauthorized: nil,
ContextUsername: "username",
ContextPassword: "password",
}
```
52 changes: 40 additions & 12 deletions docs/api/middleware/csrf.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This middleware can be used with or without a user session and offers two token

## Token Generation

CSRF tokens are generated on 'safe' requests and when the existing token has expired or hasn't been set yet. If `SingleUseToken` is `true`, a new token is generated after each use. Retrieve the CSRF token using `c.Locals(contextKey)`, where `contextKey` is defined in the configuration.
CSRF tokens are generated on 'safe' requests and when the existing token has expired or hasn't been set yet. If `SingleUseToken` is `true`, a new token is generated after each use. Retrieve the CSRF token using `csrf.TokenFromContext(c)`.

## Security Considerations

Expand Down Expand Up @@ -82,7 +82,8 @@ Using `SingleUseToken` comes with usability trade-offs and is not enabled by def
When the authorization status changes, the CSRF token MUST be deleted, and a new one generated. This can be done by calling `handler.DeleteToken(c)`.

```go
if handler, ok := app.AcquireCtx(ctx).Locals(ConfigDefault.HandlerContextKey).(*CSRFHandler); ok {
handler := csrf.HandlerFromContext(ctx)
if handler != nil {
if err := handler.DeleteToken(app.AcquireCtx(ctx)); err != nil {
// handle error
}
Expand All @@ -101,6 +102,10 @@ It's important to note that the token is sent as a header on every request. If y

```go
func New(config ...Config) fiber.Handler
func TokenFromContext(c *fiber.Ctx) string
func HandlerFromContext(c *fiber.Ctx) *Handler

func (h *Handler) DeleteToken(c *fiber.Ctx) error
```

## Examples
Expand Down Expand Up @@ -135,6 +140,36 @@ app.Use(csrf.New(csrf.Config{
KeyLookup will be ignored if Extractor is explicitly set.
:::

Getting the CSRF token in a handler:

```go

```go
func handler(c *fiber.Ctx) error {
handler := csrf.HandlerFromContext(c)
token := csrf.TokenFromContext(c)
if handler == nil {
panic("csrf middleware handler not registered")
}
cfg := handler.Config
if cfg == nil {
panic("csrf middleware handler has no config")
}
if !strings.Contains(cfg.KeyLookup, ":") {
panic("invalid KeyLookup format")
}
formKey := strings.Split(cfg.KeyLookup, ":")[1]

tmpl := fmt.Sprintf(`<form action="/post" method="POST">
<input type="hidden" name="%s" value="%s">
<input type="text" name="message">
<input type="submit" value="Submit">
</form>`, formKey, token)
c.Set("Content-Type", "text/html")
return c.SendString(tmpl)
}
```

## Config

| Property | Type | Description | Default |
Expand All @@ -152,15 +187,10 @@ KeyLookup will be ignored if Extractor is explicitly set.
| SingleUseToken | `bool` | SingleUseToken indicates if the CSRF token be destroyed and a new one generated on each use. (See TokenLifecycle) | false |
| Storage | `fiber.Storage` | Store is used to store the state of the middleware. | `nil` |
| Session | `*session.Store` | Session is used to store the state of the middleware. Overrides Storage if set. | `nil` |
| SessionKey | `string` | SessionKey is the key used to store the token in the session. | "fiber.csrf.token" |
| ContextKey | `string` | Context key to store the generated CSRF token into the context. If left empty, the token will not be stored in the context. | "" |
| SessionKey | `string` | SessionKey is the key used to store the token in the session. | "csrfToken" |
| KeyGenerator | `func() string` | KeyGenerator creates a new CSRF token. | utils.UUID |
| CookieExpires | `time.Duration` (Deprecated) | Deprecated: Please use Expiration. | 0 |
| Cookie | `*fiber.Cookie` (Deprecated) | Deprecated: Please use Cookie* related fields. | `nil` |
| TokenLookup | `string` (Deprecated) | Deprecated: Please use KeyLookup. | "" |
| ErrorHandler | `fiber.ErrorHandler` | ErrorHandler is executed when an error is returned from fiber.Handler. | DefaultErrorHandler |
| Extractor | `func(*fiber.Ctx) (string, error)` | Extractor returns the CSRF token. If set, this will be used in place of an Extractor based on KeyLookup. | Extractor based on KeyLookup |
| HandlerContextKey | `string` | HandlerContextKey is used to store the CSRF Handler into context. | "fiber.csrf.handler" |

### Default Config

Expand All @@ -173,8 +203,7 @@ var ConfigDefault = Config{
KeyGenerator: utils.UUIDv4,
ErrorHandler: defaultErrorHandler,
Extractor: CsrfFromHeader(HeaderName),
SessionKey: "fiber.csrf.token",
HandlerContextKey: "fiber.csrf.handler",
SessionKey: "csrfToken",
}
```

Expand All @@ -194,8 +223,7 @@ var ConfigDefault = Config{
ErrorHandler: defaultErrorHandler,
Extractor: CsrfFromHeader(HeaderName),
Session: session.Store,
SessionKey: "fiber.csrf.token",
HandlerContextKey: "fiber.csrf.handler",
SessionKey: "csrfToken",
}
```

Expand Down
1 change: 1 addition & 0 deletions docs/api/middleware/earlydata.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Safe HTTP methods — `GET`, `HEAD`, `OPTIONS` and `TRACE` — should not modify

```go
func New(config ...Config) fiber.Handler
func IsEarlyData(c fiber.Ctx) bool
```

## Examples
Expand Down
2 changes: 2 additions & 0 deletions docs/api/middleware/idempotency.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Refer to https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-ke

```go
func New(config ...Config) fiber.Handler
func IsFromCache(c fiber.Ctx) bool
func WasPutToCache(c fiber.Ctx) bool
```

## Examples
Expand Down
17 changes: 8 additions & 9 deletions docs/api/middleware/keyauth.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Key auth middleware provides a key based authentication.

```go
func New(config ...Config) fiber.Handler
func TokenFromContext(c fiber.Ctx) string
```

## Examples
Expand Down Expand Up @@ -213,15 +214,14 @@ curl --header "Authorization: Bearer my-super-secret-key" http://localhost:3000

## Config

| Property | Type | Description | Default |
|:---------------|:-----------------------------------------|:-----------------------------------------------------------------------------------------------------|:------------------------------|
| Next | `func(*fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` |
| SuccessHandler | `fiber.Handler` | SuccessHandler defines a function which is executed for a valid key. | `nil` |
| ErrorHandler | `fiber.ErrorHandler` | ErrorHandler defines a function which is executed for an invalid key. | `401 Invalid or expired key` |
| Property | Type | Description | Default |
|:---------------|:-----------------------------------------|:-------------------------------------------------------------------------------------------------------|:------------------------------|
| Next | `func(*fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` |
| SuccessHandler | `fiber.Handler` | SuccessHandler defines a function which is executed for a valid key. | `nil` |
| ErrorHandler | `fiber.ErrorHandler` | ErrorHandler defines a function which is executed for an invalid key. | `401 Invalid or expired key` |
| KeyLookup | `string` | KeyLookup is a string in the form of "`<source>:<name>`" that is used to extract key from the request. | "header:Authorization" |
| AuthScheme | `string` | AuthScheme to be used in the Authorization header. | "Bearer" |
| Validator | `func(*fiber.Ctx, string) (bool, error)` | Validator is a function to validate the key. | A function for key validation |
| ContextKey | `string` | Context key to store the bearer token from the token into context. | "token" |
| AuthScheme | `string` | AuthScheme to be used in the Authorization header. | "Bearer" |
| Validator | `func(*fiber.Ctx, string) (bool, error)` | Validator is a function to validate the key. | A function for key validation |

## Default Config

Expand All @@ -238,6 +238,5 @@ var ConfigDefault = Config{
},
KeyLookup: "header:" + fiber.HeaderAuthorization,
AuthScheme: "Bearer",
ContextKey: "token",
}
```
13 changes: 11 additions & 2 deletions docs/api/middleware/requestid.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ RequestID middleware for [Fiber](https://github.com/gofiber/fiber) that adds an

```go
func New(config ...Config) fiber.Handler
func FromContext(c *fiber.Ctx) string
```

## Examples
Expand Down Expand Up @@ -38,14 +39,23 @@ app.Use(requestid.New(requestid.Config{
}))
```

Getting the request ID

```go
func handler(c *fiber.Ctx) error {
id := requestid.FromContext(c)
log.Printf("Request ID: %s", id)
return c.SendString("Hello, World!")
}
```

## Config

| Property | Type | Description | Default |
|:-----------|:------------------------|:--------------------------------------------------------------------------------------------------|:---------------|
| Next | `func(*fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` |
| Header | `string` | Header is the header key where to get/set the unique request ID. | "X-Request-ID" |
| Generator | `func() string` | Generator defines a function to generate the unique identifier. | utils.UUID |
| ContextKey | `interface{}` | ContextKey defines the key used when storing the request ID in the locals for a specific request. | "requestid" |

## Default Config
The default config uses a fast UUID generator which will expose the number of
Expand All @@ -57,6 +67,5 @@ var ConfigDefault = Config{
Next: nil,
Header: fiber.HeaderXRequestID,
Generator: utils.UUID,
ContextKey: "requestid",
}
```
34 changes: 32 additions & 2 deletions middleware/basicauth/basicauth.go
A505
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ import (
"github.com/gofiber/utils/v2"
)

// The contextKey type is unexported to prevent collisions with context keys defined in
// other packages.
type contextKey int

// The keys for the values in context
const (
usernameKey contextKey = iota
passwordKey
)

// New creates a new middleware handler
func New(config Config) fiber.Handler {
// Set default config
Expand Down Expand Up @@ -49,12 +59,32 @@ func New(config Config) fiber.Handler {
password := creds[index+1:]

if cfg.Authorizer(username, password) {
c.Locals(cfg.ContextUsername, username)
c.Locals(cfg.ContextPassword, password)
c.Locals(usernameKey, username)
c.Locals(passwordKey, password)
return c.Next()
}

// Authentication failed
return cfg.Unauthorized(c)
}
}

// UsernameFromContext returns the username found in the context
// returns an empty string if the username does not exist
func UsernameFromContext(c fiber.Ctx) string {
username, ok := c.Locals(usernameKey).(string)
if !ok {
return ""
}
return username
}

// PasswordFromContext returns the password found in the context
// returns an empty string if the password does not exist
func PasswordFromContext(c fiber.Ctx) string {
password, ok := c.Locals(passwordKey).(string)
if !ok {
return ""
}
return password
}
4 changes: 2 additions & 2 deletions middleware/basicauth/basicauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ func Test_Middleware_BasicAuth(t *testing.T) {
}))

app.Get("/testauth", func(c fiber.Ctx) error {
username := c.Locals("username").(string) //nolint:errcheck, forcetypeassert // not needed
password := c.Locals("password").(string) //nolint:errcheck, forcetypeassert // not needed
username := UsernameFromContext(c)
password := PasswordFromContext(c)

return c.SendString(username + password)
})
Expand Down
Loading
0