auth/internal/middleware/basic_auth.go

103 lines
2.1 KiB
Go

package middleware
import (
"encoding/base64"
"net/http"
"strconv"
"strings"
"source.toby3d.me/toby3d/auth/internal/common"
)
type (
BasicAuthConfig struct {
Skipper Skipper
Validator BasicAuthValidator
Realm string
}
BasicAuthValidator func(w http.ResponseWriter, r *http.Request, login, password string) (bool, error)
)
const DefaultRealm string = "Restricted"
const basic string = "basic"
// nolint: gochecknoglobals
var DefaultBasicAuthConfig = BasicAuthConfig{
Skipper: DefaultSkipper,
Realm: DefaultRealm,
}
func BasicAuth(validator BasicAuthValidator) Interceptor {
cfg := DefaultBasicAuthConfig
cfg.Validator = validator
return BasicAuthWithConfig(cfg)
}
func BasicAuthWithConfig(config BasicAuthConfig) Interceptor {
// Defaults
if config.Validator == nil {
panic("middleware: basic-auth middleware requires a validator function")
}
if config.Skipper == nil {
config.Skipper = DefaultBasicAuthConfig.Skipper
}
if config.Realm == "" {
config.Realm = DefaultRealm
}
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if config.Skipper(w, r) {
next(w, r)
return
}
auth := r.Header.Get(common.HeaderAuthorization)
l := len(basic)
if len(auth) > l+1 && strings.EqualFold(string(auth[:l]), basic) {
b, err := base64.StdEncoding.DecodeString(string(auth[l+1:]))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
cred := string(b)
for i := 0; i < len(cred); i++ {
if cred[i] == ':' {
// NOTE(toby3d): verify credentials.
valid, err := config.Validator(w, r, cred[:i], cred[i+1:])
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if valid {
next(w, r)
return
}
break
}
}
}
realm := DefaultRealm
if config.Realm != DefaultRealm {
realm = strconv.Quote(config.Realm)
}
// NOTE(toby3d): require 401 status for login pop-up.
w.Header().Set(common.HeaderWWWAuthenticate, basic+" realm="+realm)
w.WriteHeader(http.StatusUnauthorized)
}
}