mirror of
https://github.com/foomo/keel.git
synced 2025-10-16 12:35:34 +00:00
refactor: split up auth middlewares
This commit is contained in:
parent
ef9575a329
commit
a66a86aa58
@ -1,88 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/foomo/keel/log"
|
||||
)
|
||||
|
||||
func BearerAuth(bearerToken string) Middleware {
|
||||
bearerPrefix := "Bearer "
|
||||
return func(l *zap.Logger, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.HasPrefix(bearerToken, bearerPrefix) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
if _, err := w.Write([]byte("malformed token")); err != nil {
|
||||
log.WithError(l, err).Error("failed to write http response")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
authHeader := strings.Replace(bearerToken, bearerPrefix, "", 1)
|
||||
if subtle.ConstantTimeCompare([]byte(authHeader), []byte(bearerToken)) == 1 {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
if _, err := w.Write([]byte("Unauthorized")); err != nil {
|
||||
log.WithError(l, err).Error("failed to write http response")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BasicAuth hashes the password when called and returns a middleware.
|
||||
// NOTE: The error handling only takes place on incomming http requests.
|
||||
// Therefore (and because of security) it is advised to hash the password
|
||||
// beforehand and use BasicAuthBcryptHash.
|
||||
func BasicAuth(user, password string) Middleware {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return func(l *zap.Logger, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l.Error("unable to create password hash", zap.Error(err))
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return BasicAuthBcryptHash(user, string(hashedPassword))
|
||||
}
|
||||
|
||||
// BasicAuthBcryptHash uses a plain text user name an a bcrypt salted hash of
|
||||
// the password in order to authenticate the incomming http request.
|
||||
func BasicAuthBcryptHash(user, hashedPassword string) Middleware {
|
||||
return func(l *zap.Logger, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// basic auth from request header
|
||||
u, p, ok := r.BasicAuth()
|
||||
if !ok || len(strings.TrimSpace(u)) < 1 || len(strings.TrimSpace(p)) < 1 {
|
||||
unauthorised(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Compare the username and password hash with the ones in the request
|
||||
userMatch := (subtle.ConstantTimeCompare([]byte(u), []byte(user)) == 1)
|
||||
errP := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(p))
|
||||
if !userMatch || errP != nil {
|
||||
unauthorised(w)
|
||||
return
|
||||
}
|
||||
|
||||
// If required, Context could be updated to include authentication
|
||||
// related data so that it could be used in consequent steps.
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func unauthorised(w http.ResponseWriter) {
|
||||
w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}
|
||||
74
net/http/middleware/basicauth.go
Normal file
74
net/http/middleware/basicauth.go
Normal file
@ -0,0 +1,74 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
httputils "github.com/foomo/keel/utils/net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
BasicAuthOptions struct {
|
||||
Realm string
|
||||
}
|
||||
BasicAuthOption func(*BasicAuthOptions)
|
||||
)
|
||||
|
||||
// GetDefaultBasicAuthOptions returns the default options
|
||||
func GetDefaultBasicAuthOptions() BasicAuthOptions {
|
||||
return BasicAuthOptions{
|
||||
Realm: "Restricted",
|
||||
}
|
||||
}
|
||||
|
||||
// BasicAuthWithRealm middleware option
|
||||
func BasicAuthWithRealm(v string) BasicAuthOption {
|
||||
return func(o *BasicAuthOptions) {
|
||||
o.Realm = v
|
||||
}
|
||||
}
|
||||
|
||||
// BasicAuth middleware
|
||||
func BasicAuth(username string, passwordHash []byte, opts ...BasicAuthOption) Middleware {
|
||||
options := GetDefaultBasicAuthOptions()
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(&options)
|
||||
}
|
||||
}
|
||||
return BasicAuthWithOptions(username, passwordHash, options)
|
||||
}
|
||||
|
||||
// BasicAuthWithOptions middleware
|
||||
func BasicAuthWithOptions(username string, passwordHash []byte, opts BasicAuthOptions) Middleware {
|
||||
return func(l *zap.Logger, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// basic auth from request header
|
||||
u, p, ok := r.BasicAuth()
|
||||
if !ok || len(strings.TrimSpace(u)) < 1 || len(strings.TrimSpace(p)) < 1 {
|
||||
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%s", opts.Realm))
|
||||
httputils.UnauthorizedServerError(l, w, r, errors.New("missing basic auth credentials"))
|
||||
return
|
||||
}
|
||||
|
||||
// Compare the username and password hash with the ones in the request
|
||||
userMatch := subtle.ConstantTimeCompare([]byte(u), []byte(username)) == 1
|
||||
errP := bcrypt.CompareHashAndPassword(passwordHash, []byte(p))
|
||||
if !userMatch || errP != nil {
|
||||
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%s", opts.Realm))
|
||||
httputils.UnauthorizedServerError(l, w, r, errors.New("invalid basic auth credentials"))
|
||||
return
|
||||
}
|
||||
|
||||
// If required, Context could be updated to include authentication
|
||||
// related data so that it could be used in consequent steps.
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
63
net/http/middleware/tokenauth.go
Normal file
63
net/http/middleware/tokenauth.go
Normal file
@ -0,0 +1,63 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
|
||||
httputils "github.com/foomo/keel/utils/net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
TokenAuthOptions struct {
|
||||
// TokenProvider function to retrieve the token
|
||||
TokenProvider TokenProvider
|
||||
}
|
||||
TokenAuthOption func(*TokenAuthOptions)
|
||||
)
|
||||
|
||||
// GetDefaultTokenAuthOptions returns the default options
|
||||
func GetDefaultTokenAuthOptions() TokenAuthOptions {
|
||||
return TokenAuthOptions{
|
||||
TokenProvider: HeaderTokenProvider(),
|
||||
}
|
||||
}
|
||||
|
||||
// TokenAuthWithTokenProvider middleware option
|
||||
func TokenAuthWithTokenProvider(v TokenProvider) TokenAuthOption {
|
||||
return func(o *TokenAuthOptions) {
|
||||
o.TokenProvider = v
|
||||
}
|
||||
}
|
||||
|
||||
// TokenAuth middleware
|
||||
func TokenAuth(token string, opts ...TokenAuthOption) Middleware {
|
||||
options := GetDefaultTokenAuthOptions()
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(&options)
|
||||
}
|
||||
}
|
||||
return TokenAuthWithOptions(token, options)
|
||||
}
|
||||
|
||||
// TokenAuthWithOptions middleware
|
||||
func TokenAuthWithOptions(token string, opts TokenAuthOptions) Middleware {
|
||||
return func(l *zap.Logger, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if value, err := opts.TokenProvider(r); err != nil {
|
||||
httputils.UnauthorizedServerError(l, w, r, errors.Wrap(err, "failed to retrieve token"))
|
||||
return
|
||||
} else if value == "" {
|
||||
httputils.UnauthorizedServerError(l, w, r, errors.New("missing token"))
|
||||
return
|
||||
} else if subtle.ConstantTimeCompare([]byte(value), []byte(token)) != 1 {
|
||||
httputils.UnauthorizedServerError(l, w, r, errors.New("invalid token"))
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
9
utils/net/http/basicauth.go
Normal file
9
utils/net/http/basicauth.go
Normal file
@ -0,0 +1,9 @@
|
||||
package httputils
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func HashBasicAuthPassword(v []byte) ([]byte, error) {
|
||||
return bcrypt.GenerateFromPassword(v, bcrypt.DefaultCost)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user