|
|
@@ -0,0 +1,185 @@
|
|
|
+package httpauth
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "crypto/sha256"
|
|
|
+ "crypto/subtle"
|
|
|
+ "encoding/base64"
|
|
|
+ "fmt"
|
|
|
+ "net/http"
|
|
|
+ "strings"
|
|
|
+)
|
|
|
+
|
|
|
+type basicAuth struct {
|
|
|
+ h http.Handler
|
|
|
+ opts AuthOptions
|
|
|
+}
|
|
|
+
|
|
|
+// AuthOptions stores the configuration for HTTP Basic Authentication.
|
|
|
+//
|
|
|
+// A http.Handler may also be passed to UnauthorizedHandler to override the
|
|
|
+// default error handler if you wish to serve a custom template/response.
|
|
|
+type AuthOptions struct {
|
|
|
+ Realm string
|
|
|
+ User string
|
|
|
+ Password string
|
|
|
+ AuthFunc func(string, string, *http.Request) bool
|
|
|
+ UnauthorizedHandler http.Handler
|
|
|
+}
|
|
|
+
|
|
|
+// Satisfies the http.Handler interface for basicAuth.
|
|
|
+func (b basicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
+ // Check if we have a user-provided error handler, else set a default
|
|
|
+ if b.opts.UnauthorizedHandler == nil {
|
|
|
+ b.opts.UnauthorizedHandler = http.HandlerFunc(defaultUnauthorizedHandler)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check that the provided details match
|
|
|
+ if b.authenticate(r) == false {
|
|
|
+ b.requestAuth(w, r)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Call the next handler on success.
|
|
|
+ b.h.ServeHTTP(w, r)
|
|
|
+}
|
|
|
+
|
|
|
+// authenticate retrieves and then validates the user:password combination provided in
|
|
|
+// the request header. Returns 'false' if the user has not successfully authenticated.
|
|
|
+func (b *basicAuth) authenticate(r *http.Request) bool {
|
|
|
+ const basicScheme string = "Basic "
|
|
|
+
|
|
|
+ if r == nil {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ // In simple mode, prevent authentication with empty credentials if User is
|
|
|
+ // not set. Allow empty passwords to support non-password use-cases.
|
|
|
+ if b.opts.AuthFunc == nil && b.opts.User == "" {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ // Confirm the request is sending Basic Authentication credentials.
|
|
|
+ auth := r.Header.Get("Authorization")
|
|
|
+ if !strings.HasPrefix(auth, basicScheme) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get the plain-text username and password from the request.
|
|
|
+ // The first six characters are skipped - e.g. "Basic ".
|
|
|
+ str, err := base64.StdEncoding.DecodeString(auth[len(basicScheme):])
|
|
|
+ if err != nil {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ // Split on the first ":" character only, with any subsequent colons assumed to be part
|
|
|
+ // of the password. Note that the RFC2617 standard does not place any limitations on
|
|
|
+ // allowable characters in the password.
|
|
|
+ creds := bytes.SplitN(str, []byte(":"), 2)
|
|
|
+
|
|
|
+ if len(creds) != 2 {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ givenUser := string(creds[0])
|
|
|
+ givenPass := string(creds[1])
|
|
|
+
|
|
|
+ // Default to Simple mode if no AuthFunc is defined.
|
|
|
+ if b.opts.AuthFunc == nil {
|
|
|
+ b.opts.AuthFunc = b.simpleBasicAuthFunc
|
|
|
+ }
|
|
|
+
|
|
|
+ return b.opts.AuthFunc(givenUser, givenPass, r)
|
|
|
+}
|
|
|
+
|
|
|
+// simpleBasicAuthFunc authenticates the supplied username and password against
|
|
|
+// the User and Password set in the Options struct.
|
|
|
+func (b *basicAuth) simpleBasicAuthFunc(user, pass string, r *http.Request) bool {
|
|
|
+ // Equalize lengths of supplied and required credentials
|
|
|
+ // by hashing them
|
|
|
+ givenUser := sha256.Sum256([]byte(user))
|
|
|
+ givenPass := sha256.Sum256([]byte(pass))
|
|
|
+ requiredUser := sha256.Sum256([]byte(b.opts.User))
|
|
|
+ requiredPass := sha256.Sum256([]byte(b.opts.Password))
|
|
|
+
|
|
|
+ // Compare the supplied credentials to those set in our options
|
|
|
+ if subtle.ConstantTimeCompare(givenUser[:], requiredUser[:]) == 1 &&
|
|
|
+ subtle.ConstantTimeCompare(givenPass[:], requiredPass[:]) == 1 {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+// Require authentication, and serve our error handler otherwise.
|
|
|
+func (b *basicAuth) requestAuth(w http.ResponseWriter, r *http.Request) {
|
|
|
+ w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, b.opts.Realm))
|
|
|
+ b.opts.UnauthorizedHandler.ServeHTTP(w, r)
|
|
|
+}
|
|
|
+
|
|
|
+// defaultUnauthorizedHandler provides a default HTTP 401 Unauthorized response.
|
|
|
+func defaultUnauthorizedHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
+ http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
|
|
+}
|
|
|
+
|
|
|
+// BasicAuth provides HTTP middleware for protecting URIs with HTTP Basic Authentication
|
|
|
+// as per RFC 2617. The server authenticates a user:password combination provided in the
|
|
|
+// "Authorization" HTTP header.
|
|
|
+//
|
|
|
+// Example:
|
|
|
+//
|
|
|
+// package main
|
|
|
+//
|
|
|
+// import(
|
|
|
+// "net/http"
|
|
|
+// "github.com/zenazn/goji"
|
|
|
+// "github.com/goji/httpauth"
|
|
|
+// )
|
|
|
+//
|
|
|
+// func main() {
|
|
|
+// basicOpts := httpauth.AuthOptions{
|
|
|
+// Realm: "Restricted",
|
|
|
+// User: "Dave",
|
|
|
+// Password: "ClearText",
|
|
|
+// }
|
|
|
+//
|
|
|
+// goji.Use(httpauth.BasicAuth(basicOpts), SomeOtherMiddleware)
|
|
|
+// goji.Get("/thing", myHandler)
|
|
|
+// }
|
|
|
+//
|
|
|
+// Note: HTTP Basic Authentication credentials are sent in plain text, and therefore it does
|
|
|
+// not make for a wholly secure authentication mechanism. You should serve your content over
|
|
|
+// HTTPS to mitigate this, noting that "Basic Authentication" is meant to be just that: basic!
|
|
|
+func BasicAuth(o AuthOptions) func(http.Handler) http.Handler {
|
|
|
+ fn := func(h http.Handler) http.Handler {
|
|
|
+ return basicAuth{h, o}
|
|
|
+ }
|
|
|
+ return fn
|
|
|
+}
|
|
|
+
|
|
|
+// SimpleBasicAuth is a convenience wrapper around BasicAuth. It takes a user and password, and
|
|
|
+// returns a pre-configured BasicAuth handler using the "Restricted" realm and a default 401 handler.
|
|
|
+//
|
|
|
+// Example:
|
|
|
+//
|
|
|
+// package main
|
|
|
+//
|
|
|
+// import(
|
|
|
+// "net/http"
|
|
|
+// "github.com/zenazn/goji/web/httpauth"
|
|
|
+// )
|
|
|
+//
|
|
|
+// func main() {
|
|
|
+//
|
|
|
+// goji.Use(httpauth.SimpleBasicAuth("dave", "somepassword"), SomeOtherMiddleware)
|
|
|
+// goji.Get("/thing", myHandler)
|
|
|
+// }
|
|
|
+//
|
|
|
+func SimpleBasicAuth(user, password string) func(http.Handler) http.Handler {
|
|
|
+ opts := AuthOptions{
|
|
|
+ Realm: "Restricted",
|
|
|
+ User: user,
|
|
|
+ Password: password,
|
|
|
+ }
|
|
|
+ return BasicAuth(opts)
|
|
|
+}
|