implement file-based basic auth

This commit is contained in:
Vladislav Yarmak 2020-05-24 21:32:40 +03:00
parent 0b4d62892c
commit 679a43789c
3 changed files with 108 additions and 3 deletions

102
auth.go
View File

@ -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 {

2
go.mod
View File

@ -1,3 +1,5 @@
module github.com/Snawoot/dumbproxy
go 1.13
require golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37

7
go.sum Normal file
View File

@ -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=