package handlers // Inspired by node.js' Connect library implementation of the basicAuth middleware. // https://github.com/senchalabs/connect import ( "bytes" "encoding/base64" "fmt" "net/http" "strings" ) // Internal writer that keeps track of the currently authenticated user. type userResponseWriter struct { http.ResponseWriter user interface{} userName string } // Implement the WrapWriter interface. func (this *userResponseWriter) WrappedWriter() http.ResponseWriter { return this.ResponseWriter } // Writes an unauthorized response to the client, specifying the expected authentication // information. func Unauthorized(w http.ResponseWriter, realm string) { w.Header().Set("Www-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm)) w.WriteHeader(http.StatusUnauthorized) w.Write([]byte("Unauthorized")) } // Writes a bad request response to the client, with an optional message. func BadRequest(w http.ResponseWriter, msg string) { w.WriteHeader(http.StatusBadRequest) if msg == "" { msg = "Bad Request" } w.Write([]byte(msg)) } // BasicAuthHandlerFunc is the same as BasicAuthHandler, it is just a convenience // signature that accepts a func(http.ResponseWriter, *http.Request) instead of // a http.Handler interface. It saves the boilerplate http.HandlerFunc() cast. func BasicAuthHandlerFunc(h http.HandlerFunc, authFn func(string, string) (interface{}, bool), realm string) http.HandlerFunc { return BasicAuthHandler(h, authFn, realm) } // Returns a Basic Authentication handler, protecting the wrapped handler from // being accessed if the authentication function is not successful. func BasicAuthHandler(h http.Handler, authFn func(string, string) (interface{}, bool), realm string) http.HandlerFunc { if realm == "" { realm = "Authorization Required" } return func(w http.ResponseWriter, r *http.Request) { // Self-awareness if _, ok := GetUser(w); ok { h.ServeHTTP(w, r) return } authInfo := r.Header.Get("Authorization") if authInfo == "" { // No authorization info, return 401 Unauthorized(w, realm) return } parts := strings.Split(authInfo, " ") if len(parts) != 2 { BadRequest(w, "Bad authorization header") return } scheme := parts[0] creds, err := base64.StdEncoding.DecodeString(parts[1]) if err != nil { BadRequest(w, "Bad credentials encoding") return } index := bytes.Index(creds, []byte(":")) if scheme != "Basic" || index < 0 { BadRequest(w, "Bad authorization header") return } user, pwd := string(creds[:index]), string(creds[index+1:]) udata, ok := authFn(user, pwd) if ok { // Save user data and continue uw := &userResponseWriter{w, udata, user} h.ServeHTTP(uw, r) } else { Unauthorized(w, realm) } } } // Return the currently authenticated user. This is the same data that was returned // by the authentication function passed to BasicAuthHandler. func GetUser(w http.ResponseWriter) (interface{}, bool) { usr, ok := GetResponseWriter(w, func(tst http.ResponseWriter) bool { _, ok := tst.(*userResponseWriter) return ok }) if ok { return usr.(*userResponseWriter).user, true } return nil, false } // Return the currently authenticated user name. This is the user name that was // authenticated for the current request. func GetUserName(w http.ResponseWriter) (string, bool) { usr, ok := GetResponseWriter(w, func(tst http.ResponseWriter) bool { _, ok := tst.(*userResponseWriter) return ok }) if ok { return usr.(*userResponseWriter).userName, true } return "", false }