Skip to content

Commit

Permalink
PR #1 feat: Basic Auth using bcrypt & bearer rework
Browse files Browse the repository at this point in the history
Merge pull request #1 from botastic/main
@franklinkim this is how we do it :P
  • Loading branch information
loeffert authored Aug 17, 2021
2 parents 9e2fe9d + 7d677ad commit c321e7d
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 18 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ require (
go.opentelemetry.io/otel/sdk v0.20.0
go.opentelemetry.io/otel/trace v0.20.0
go.uber.org/zap v1.16.0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
)
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand Down
64 changes: 46 additions & 18 deletions net/http/middleware/auth.go
Original file line number Diff line number Diff line change
@@ -1,55 +1,83 @@
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) {
authHeader := strings.Split(r.Header.Get("Authorization"), "Bearer ")
if len(authHeader) != 2 {
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")
}
} else {
if authHeader[1] == bearerToken {
next.ServeHTTP(w, r)
} else {
w.WriteHeader(http.StatusUnauthorized)
if _, err := w.Write([]byte("Unauthorized")); 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(rw http.ResponseWriter, rq *http.Request) {
u, p, ok := rq.BasicAuth()
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(rw)
unauthorised(w)
return
}

// This is a dummy check for credentials.
if u != user || p != password {
unauthorised(rw)
// 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(rw, rq)
next.ServeHTTP(w, r)
})
}
}
Expand Down

0 comments on commit c321e7d

Please sign in to comment.