implement file-based basic auth
This commit is contained in:
parent
0b4d62892c
commit
679a43789c
102
auth.go
102
auth.go
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -8,9 +9,11 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"crypto/subtle"
|
"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 {
|
type Auth interface {
|
||||||
Validate(wr http.ResponseWriter, req *http.Request) bool
|
Validate(wr http.ResponseWriter, req *http.Request) bool
|
||||||
|
@ -24,8 +27,9 @@ func NewAuth(paramstr string) (Auth, error) {
|
||||||
|
|
||||||
switch strings.ToLower(url.Scheme) {
|
switch strings.ToLower(url.Scheme) {
|
||||||
case "static":
|
case "static":
|
||||||
auth, err := NewStaticAuth(url)
|
return NewStaticAuth(url)
|
||||||
return auth, err
|
case "basicfile":
|
||||||
|
return NewBasicFileAuth(url)
|
||||||
case "none":
|
case "none":
|
||||||
return NoAuth{}, nil
|
return NoAuth{}, nil
|
||||||
default:
|
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 {}
|
type NoAuth struct {}
|
||||||
|
|
||||||
func (_ NoAuth) Validate(wr http.ResponseWriter, req *http.Request) bool {
|
func (_ NoAuth) Validate(wr http.ResponseWriter, req *http.Request) bool {
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,3 +1,5 @@
|
||||||
module github.com/Snawoot/dumbproxy
|
module github.com/Snawoot/dumbproxy
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
|
require golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
|
||||||
|
|
|
@ -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=
|
Loading…
Reference in New Issue