From 8da42184eb26bbd35618d81e47bcd23b6ce21adb Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Thu, 19 May 2022 21:35:45 -0600 Subject: [PATCH] Implement basic auth middleware --- srv/src/api/auth.go | 72 ++++++++++++++++++++++++++++++++++++++++ srv/src/api/auth_test.go | 21 ++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 srv/src/api/auth.go create mode 100644 srv/src/api/auth_test.go diff --git a/srv/src/api/auth.go b/srv/src/api/auth.go new file mode 100644 index 0000000..e668d7b --- /dev/null +++ b/srv/src/api/auth.go @@ -0,0 +1,72 @@ +package api + +import ( + "net/http" + + "golang.org/x/crypto/bcrypt" +) + +// NewPasswordHash returns the hash of the given plaintext password, for use +// with Auther. +func NewPasswordHash(plaintext string) string { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(plaintext), 12) + if err != nil { + panic(err) + } + return string(hashedPassword) +} + +// Auther determines who can do what. +type Auther interface { + Allowed(username, password string) bool +} + +type auther struct { + users map[string]string +} + +// NewAuther initializes and returns an Auther will which allow the given +// username and password hash combinations. Password hashes must have been +// created using NewPasswordHash. +func NewAuther(users map[string]string) Auther { + return &auther{users: users} +} + +func (a *auther) Allowed(username, password string) bool { + + hashedPassword, ok := a.users[username] + if !ok { + return false + } + + err := bcrypt.CompareHashAndPassword( + []byte(hashedPassword), []byte(password), + ) + + return err == nil +} + +func authMiddleware(auther Auther, h http.Handler) http.Handler { + + respondUnauthorized := func(rw http.ResponseWriter) { + rw.Header().Set("WWW-Authenticate", `Basic realm="NOPE"`) + rw.WriteHeader(http.StatusUnauthorized) + } + + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + + username, password, ok := r.BasicAuth() + + if !ok { + respondUnauthorized(rw) + return + } + + if !auther.Allowed(username, password) { + respondUnauthorized(rw) + return + } + + h.ServeHTTP(rw, r) + }) +} diff --git a/srv/src/api/auth_test.go b/srv/src/api/auth_test.go new file mode 100644 index 0000000..cdf83ef --- /dev/null +++ b/srv/src/api/auth_test.go @@ -0,0 +1,21 @@ +package api + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAuther(t *testing.T) { + + password := "foo" + hashedPassword := NewPasswordHash(password) + + auther := NewAuther(map[string]string{ + "FOO": hashedPassword, + }) + + assert.False(t, auther.Allowed("BAR", password)) + assert.False(t, auther.Allowed("FOO", "bar")) + assert.True(t, auther.Allowed("FOO", password)) +}