From 679a43789cb3f8787d081e60cf663e5a8b15f220 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sun, 24 May 2020 21:32:40 +0300 Subject: [PATCH] implement file-based basic auth --- auth.go | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- go.mod | 2 ++ go.sum | 7 ++++ 3 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 go.sum diff --git a/auth.go b/auth.go index db9f4cd..aeacc61 100644 --- a/auth.go +++ b/auth.go @@ -1,6 +1,7 @@ package main import ( + "os" "net/http" "net/url" "errors" @@ -8,9 +9,11 @@ import ( "strconv" "encoding/base64" "crypto/subtle" + "golang.org/x/crypto/bcrypt" + "bufio" ) -const AUTH_REQUIRED_MSG = "Proxy authentication required." +const AUTH_REQUIRED_MSG = "Proxy authentication required.\n" type Auth interface { Validate(wr http.ResponseWriter, req *http.Request) bool @@ -24,8 +27,9 @@ func NewAuth(paramstr string) (Auth, error) { switch strings.ToLower(url.Scheme) { case "static": - auth, err := NewStaticAuth(url) - return auth, err + return NewStaticAuth(url) + case "basicfile": + return NewBasicFileAuth(url) case "none": return NoAuth{}, nil default: @@ -97,6 +101,98 @@ func (auth *StaticAuth) Validate(wr http.ResponseWriter, req *http.Request) bool } } +type BasicAuth struct { + users map[string][]byte + hiddenDomain string +} + +func NewBasicFileAuth(param_url *url.URL) (*BasicAuth, error) { + filename := param_url.Path + values, err := url.ParseQuery(param_url.RawQuery) + if err != nil { + return nil, err + } + + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + users := make(map[string][]byte) + for scanner.Scan() { + line := scanner.Text() + trimmed := strings.TrimSpace(line) + if trimmed == "" || strings.HasPrefix(trimmed, "#") { + continue + } + pair := strings.SplitN(line, ":", 2) + if len(pair) != 2 { + return nil, errors.New("Malformed login and password line") + } + login := pair[0] + password := pair[1] + users[login] = []byte(password) + } + if err := scanner.Err(); err != nil { + return nil, err + } + if len(users) == 0 { + return nil, errors.New("No password lines were read from file") + } + return &BasicAuth{ + users: users, + hiddenDomain: strings.ToLower(values.Get("hidden_domain")), + }, nil +} + +func (auth *BasicAuth) Validate(wr http.ResponseWriter, req *http.Request) bool { + hdr := req.Header.Get("Proxy-Authorization") + if hdr == "" { + requireBasicAuth(wr, req, auth.hiddenDomain) + return false + } + hdr_parts := strings.SplitN(hdr, " ", 2) + if len(hdr_parts) != 2 || strings.ToLower(hdr_parts[0]) != "basic" { + requireBasicAuth(wr, req, auth.hiddenDomain) + return false + } + + token := hdr_parts[1] + data, err := base64.StdEncoding.DecodeString(token) + if err != nil { + requireBasicAuth(wr, req, auth.hiddenDomain) + return false + } + + pair := strings.SplitN(string(data), ":", 2) + if len(pair) != 2 { + requireBasicAuth(wr, req, auth.hiddenDomain) + return false + } + + login := pair[0] + password := pair[1] + + hashedPassword, ok := auth.users[login] + if !ok { + requireBasicAuth(wr, req, auth.hiddenDomain) + return false + } + + if bcrypt.CompareHashAndPassword(hashedPassword, []byte(password)) == nil { + if auth.hiddenDomain != "" && + (req.Host == auth.hiddenDomain || req.URL.Host == auth.hiddenDomain) { + http.Error(wr, "Browser auth triggered!", http.StatusGone) + return false + } else { + return true + } + } + return false +} + type NoAuth struct {} func (_ NoAuth) Validate(wr http.ResponseWriter, req *http.Request) bool { diff --git a/go.mod b/go.mod index bf15fb6..ea86cc3 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/Snawoot/dumbproxy go 1.13 + +require golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6760904 --- /dev/null +++ b/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=