Files
fiber/middleware/basicauth/basicauth.go
zeekayandHanzo Dev f60e254acb module: rename to github.com/zap-proto/fiber/v3
Fork of gofiber/fiber v3 based on the v3.2.0 tag. Rewrites all self-imports
github.com/gofiber/fiber/v3 -> github.com/zap-proto/fiber/v3. Upstream
gofiber/schema and gofiber/utils/v2 dependencies are left untouched.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-10 12:49:35 -07:00

133 lines
3.1 KiB
Go

package basicauth
import (
"encoding/base64"
"errors"
"strings"
"unicode"
"unicode/utf8"
"github.com/zap-proto/fiber/v3"
"github.com/gofiber/utils/v2"
"golang.org/x/text/unicode/norm"
)
// The contextKey type is unexported to prevent collisions with context keys defined in
// other packages.
type contextKey int
// The key for the username value stored in the context
const (
usernameKey contextKey = iota
)
const basicScheme = "Basic"
// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
// Set default config
cfg := configDefault(config...)
var cerr base64.CorruptInputError
// Return new handler
return func(c fiber.Ctx) error {
// Don't execute middleware if Next returns true
if cfg.Next != nil && cfg.Next(c) {
return c.Next()
}
// Get authorization header and ensure it matches the Basic scheme
rawAuth := c.Get(fiber.HeaderAuthorization)
if rawAuth == "" {
return cfg.Unauthorized(c)
}
if len(rawAuth) > cfg.HeaderLimit {
return c.SendStatus(fiber.StatusRequestHeaderFieldsTooLarge)
}
if containsInvalidHeaderChars(rawAuth) {
return cfg.BadRequest(c)
}
auth := utils.TrimSpace(rawAuth)
if auth == "" {
return cfg.Unauthorized(c)
}
if len(auth) < len(basicScheme) || !utils.EqualFold(auth[:len(basicScheme)], basicScheme) {
return cfg.Unauthorized(c)
}
rest := auth[len(basicScheme):]
if len(rest) < 2 || rest[0] != ' ' || rest[1] == ' ' {
return cfg.BadRequest(c)
}
rest = rest[1:]
if strings.IndexFunc(rest, unicode.IsSpace) != -1 {
return cfg.BadRequest(c)
}
// Decode the header contents
raw, err := base64.StdEncoding.DecodeString(rest)
if err != nil {
if errors.As(err, &cerr) {
raw, err = base64.RawStdEncoding.DecodeString(rest)
}
if err != nil {
return cfg.BadRequest(c)
}
}
if !utf8.Valid(raw) {
return cfg.BadRequest(c)
}
if !norm.NFC.IsNormal(raw) {
raw = norm.NFC.Bytes(raw)
}
// Get the credentials
var creds string
if c.App().Config().Immutable {
creds = string(raw)
} else {
creds = utils.UnsafeString(raw)
}
// Check if the credentials are in the correct form
// which is "username:password".
username, password, found := strings.Cut(creds, ":")
if !found {
return cfg.BadRequest(c)
}
if containsCTL(username) || containsCTL(password) {
return cfg.BadRequest(c)
}
if cfg.Authorizer(username, password, c) {
fiber.StoreInContext(c, usernameKey, username)
return c.Next()
}
// Authentication failed
return cfg.Unauthorized(c)
}
}
func containsCTL(s string) bool {
return strings.IndexFunc(s, unicode.IsControl) != -1
}
func containsInvalidHeaderChars(s string) bool {
return strings.IndexFunc(s, func(r rune) bool {
return (r < 0x20 && r != '\t') || r == 0x7F || r >= 0x80
}) != -1
}
// UsernameFromContext returns the username found in the context.
// It accepts fiber.CustomCtx, fiber.Ctx, *fasthttp.RequestCtx, and context.Context.
// It returns an empty string if the username does not exist.
func UsernameFromContext(ctx any) string {
if username, ok := fiber.ValueFromContext[string](ctx, usernameKey); ok {
return username
}
return ""
}