-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathbasicauth.go
More file actions
147 lines (126 loc) · 3.65 KB
/
basicauth.go
File metadata and controls
147 lines (126 loc) · 3.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package basicauth
import (
"encoding/base64"
"errors"
"strings"
"sync"
"unicode"
"unicode/utf8"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/log"
"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"
// registerExtractor ensures the log context extractor for the authenticated
// username is registered exactly once.
var registerExtractor sync.Once
// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
// Set default config
cfg := configDefault(config...)
// Register a log context extractor so that log.WithContext(c) automatically
// includes the authenticated username when basicauth middleware is in use.
registerExtractor.Do(func() {
log.RegisterContextExtractor(func(ctx any) (string, any, bool) {
username := UsernameFromContext(ctx)
return "username", username, username != ""
})
})
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 ""
}