From 27fc1672d45fe2089fdcbc14459e46c30f825104 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Thu, 23 Apr 2015 14:57:07 -0600 Subject: [PATCH] Basic auth middleware --- config/middleware.go | 2 + middleware/basicauth/basicauth.go | 101 ++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 middleware/basicauth/basicauth.go diff --git a/config/middleware.go b/config/middleware.go index 4f2fe547..d144cc6a 100644 --- a/config/middleware.go +++ b/config/middleware.go @@ -2,6 +2,7 @@ package config import ( "github.com/mholt/caddy/middleware" + "github.com/mholt/caddy/middleware/basicauth" "github.com/mholt/caddy/middleware/browse" "github.com/mholt/caddy/middleware/errors" "github.com/mholt/caddy/middleware/extensions" @@ -45,6 +46,7 @@ func init() { register("rewrite", rewrite.New) register("redir", redirect.New) register("ext", extensions.New) + register("basicauth", basicauth.New) register("proxy", proxy.New) register("fastcgi", fastcgi.New) register("websocket", websockets.New) diff --git a/middleware/basicauth/basicauth.go b/middleware/basicauth/basicauth.go new file mode 100644 index 00000000..d81d3a2e --- /dev/null +++ b/middleware/basicauth/basicauth.go @@ -0,0 +1,101 @@ +package basicauth + +import ( + "net/http" + + "github.com/mholt/caddy/middleware" +) + +// New constructs a new BasicAuth middleware instance. +func New(c middleware.Controller) (middleware.Middleware, error) { + rules, err := parse(c) + if err != nil { + return nil, err + } + + basic := BasicAuth{ + Rules: rules, + } + + return func(next middleware.Handler) middleware.Handler { + basic.Next = next + return basic + }, nil +} + +// ServeHTTP implements the middleware.Handler interface. +func (a BasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { + for _, rule := range a.Rules { + for _, res := range rule.Resources { + if !middleware.Path(r.URL.Path).Matches(res) { + continue + } + + // Path matches; parse auth header + username, password, ok := r.BasicAuth() + + // Check credentials + if !ok || username != rule.Username || password != rule.Password { + w.Header().Set("WWW-Authenticate", "Basic") + return http.StatusUnauthorized, nil + } + + // "It's an older code, sir, but it checks out. I was about to clear them." + return a.Next.ServeHTTP(w, r) + } + } + + // Pass-thru when no paths match + return a.Next.ServeHTTP(w, r) +} + +func parse(c middleware.Controller) ([]Rule, error) { + var rules []Rule + + for c.Next() { + var rule Rule + + args := c.RemainingArgs() + + switch len(args) { + case 2: + rule.Username = args[0] + rule.Password = args[1] + for c.NextBlock() { + rule.Resources = append(rule.Resources, c.Val()) + if c.NextArg() { + return rules, c.Err("Expecting only one resource per line (extra '" + c.Val() + "')") + } + } + case 3: + rule.Resources = append(rule.Resources, args[0]) + rule.Username = args[1] + rule.Password = args[2] + default: + return rules, c.ArgErr() + } + + rules = append(rules, rule) + } + + return rules, nil +} + +// BasicAuth is middleware to protect resources with a username and password. +// Note that HTTP Basic Authentication is not secure by itself and should +// not be used to protect important assets without HTTPS. Even then, the +// security of HTTP Basic Auth is disputed. Use discretion when deciding +// what to protect with BasicAuth. +type BasicAuth struct { + Next middleware.Handler + Rules []Rule +} + +// Rule represents a BasicAuth rule. A username and password +// combination protect the associated resources, which are +// file or directory paths. +type Rule struct { + Username string + Password string + Resources []string +}