diff --git a/go.mod b/go.mod index 854a544..67234f6 100644 --- a/go.mod +++ b/go.mod @@ -10,17 +10,17 @@ require ( github.com/ory/hydra-client-go v1.11.8 github.com/pquerna/otp v1.3.0 go.etcd.io/bbolt v1.3.6 - golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 + golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 k8s.io/klog/v2 v2.70.1 ) require ( github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/golang/protobuf v1.4.3 // indirect - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect - golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 // indirect - golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect - google.golang.org/appengine v1.6.6 // indirect - google.golang.org/protobuf v1.25.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect + golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7 // indirect + golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.28.1 // indirect ) diff --git a/go.sum b/go.sum index 496a724..2336827 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -94,6 +97,7 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -146,6 +150,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c= +golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -204,6 +210,9 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= +golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -211,6 +220,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 h1:D7nTwh4J0i+5mW4Zjzn5omvlr6YBcWywE6KOcatyNxY= golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7 h1:dtndE8FcEta75/4kHF3AbpuWzV6f1LjnLrM4pe2SZrw= +golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -249,6 +260,9 @@ golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U= +golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -326,6 +340,8 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -378,6 +394,10 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/pkg/db/helper.go b/pkg/db/helper.go index c4f73a2..68a129d 100644 --- a/pkg/db/helper.go +++ b/pkg/db/helper.go @@ -7,11 +7,10 @@ import ( "time" ) -func validatePassword(pw string) (b []byte) { +func validatePassword(pw string) []byte { hash := sha256.New() - hash.Write(b) - b = hash.Sum(nil) - return + hash.Write([]byte(pw)) + return hash.Sum(nil) } const timeFmt = "2006-01-02 15:04:05" diff --git a/pkg/server/auth.go b/pkg/server/auth.go new file mode 100644 index 0000000..77dca9a --- /dev/null +++ b/pkg/server/auth.go @@ -0,0 +1,119 @@ +// Copyright (C) 2022 Marius Schellenberger + +package server + +import ( + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "io" + weakrand "math/rand" + "sync" + "time" + + "git.giftfish.de/ston1th/goacc/pkg/db" +) + +type AuthValidator interface { + Login(username, password string) (auth bool) +} + +const cacheSize = 100 + +func init() { + weakrand.Seed(time.Now().UnixNano()) +} + +type DBLoginValidator struct { + db *db.DB +} + +func NewDBLoginValidator(db *db.DB) *DBLoginValidator { + return &DBLoginValidator{db} +} + +func (val *DBLoginValidator) Login(username, password string) bool { + _, err := val.db.Login(username, password) + return err == nil +} + +type cache struct { + mu sync.RWMutex + m map[string]bool +} + +func newCache() *cache { + return &cache{m: make(map[string]bool)} +} + +func (c *cache) get(k string) (v, ok bool) { + c.mu.RLock() + defer c.mu.RUnlock() + v, ok = c.m[k] + return +} + +func (c *cache) set(k string, v bool) { + c.mu.Lock() + defer c.mu.Unlock() + c.makeRoom() + c.m[k] = v +} + +func (c *cache) makeRoom() { + n := len(c.m) + if n < cacheSize { + return + } + del := n / 10 + if del < 1 { + del = 1 + } + for i := 0; i <= del; i++ { + rnd := weakrand.Intn(len(c.m)) + j := 0 + for k := range c.m { + if j == rnd { + delete(c.m, k) + break + } + j++ + } + } +} + +type BasicAuth struct { + mu sync.Mutex + auth AuthValidator + cache *cache + key []byte +} + +func NewBasicAuth(auth AuthValidator) (*BasicAuth, error) { + key := make([]byte, 32) + _, err := io.ReadFull(rand.Reader, key) + return &BasicAuth{ + auth: auth, + cache: newCache(), + key: key, + }, err +} + +func (a *BasicAuth) genkey(username, password string) string { + h := hmac.New(sha256.New, a.key) + h.Write(append([]byte(username), []byte(password)...)) + return hex.EncodeToString(h.Sum(nil)) +} + +func (a *BasicAuth) Auth(username, password string) (auth bool) { + k := a.genkey(username, password) + auth, ok := a.cache.get(k) + if !ok { + a.mu.Lock() + auth = a.auth.Login(username, password) + a.mu.Unlock() + a.cache.set(k, auth) + } + return +} diff --git a/pkg/server/context.go b/pkg/server/context.go index cc07b28..a0d0d9a 100644 --- a/pkg/server/context.go +++ b/pkg/server/context.go @@ -154,28 +154,46 @@ func (c *Context) Error(i interface{}) { func (c *Context) NotFound() { c.Template("notFoundHandler") c.Data = webData{Title: "404"} - c.Status = http.StatusNotFound - c.Response.WriteHeader(http.StatusNotFound) + c.setStatus(http.StatusNotFound) c.Exec() } func (c *Context) Forbidden() { c.Template("forbiddenHandler") c.Data = webData{Title: "403"} - c.Status = http.StatusForbidden - c.Response.WriteHeader(http.StatusForbidden) + c.setStatus(http.StatusForbidden) c.Exec() } +func (c *Context) setStatus(status int) { + c.Status = status + c.Response.WriteHeader(status) +} + +func (c *Context) plainResponse(msg string, status int) { + defer c.accessLog() + c.Status = status + http.Error(c.Response, msg, status) +} + +func (c *Context) PlainUnauthorized() { + c.SetHeader("WWW-Authenticate", `Basic realm="auth/basic", charset="UTF-8"`) + c.plainResponse("unauthorized", http.StatusUnauthorized) +} + +func (c *Context) PlainForbidden() { + c.SetHeader("WWW-Authenticate", `Basic realm="auth/basic", charset="UTF-8"`) + c.plainResponse("forbidden", http.StatusForbidden) +} + +func (c *Context) PlainOK() { + c.plainResponse("ok", http.StatusOK) +} + func (c *Context) Template(name string) { c.T = c.Srv.ts.Get(name) } -func (c *Context) Write(buf []byte) (err error) { - _, err = c.Response.Write(buf) - return -} - func (c *Context) SetHeader(name, value string) { c.Response.Header().Set(name, value) } diff --git a/pkg/server/handler.go b/pkg/server/handler.go index d22a73c..3f3020f 100644 --- a/pkg/server/handler.go +++ b/pkg/server/handler.go @@ -576,3 +576,20 @@ func logoutHandler(ctx *Context) { ctx.SetCookie(nil, 0) ctx.Redirect(root, http.StatusFound) } + +func authBasicHandler(ctx *Context) { + if ctx.Method() != "GET" { + ctx.PlainUnauthorized() + return + } + username, password, ok := ctx.Request.BasicAuth() + if !ok { + ctx.PlainUnauthorized() + return + } + if ctx.Srv.BasicAuth.Auth(username, password) { + ctx.PlainOK() + return + } + ctx.PlainForbidden() +} diff --git a/pkg/server/routes.go b/pkg/server/routes.go index 2c5197b..e2093ba 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -16,6 +16,7 @@ const ( consent = root + "consent" users = root + "users" logout = root + "logout" + auth = root + "auth/" uroot = root + "user/" userNew = uroot + "new" @@ -24,6 +25,8 @@ const ( userTotp = uroot + "totp/{user:[a-zA-Z0-9]+$}" userSessions = uroot + "sessions/{user:[a-zA-Z0-9]+$}" userUnlock = uroot + "unlock/{user:[a-zA-Z0-9]+$}" + + authBasic = auth + "basic" ) var routes = []route{ @@ -115,4 +118,9 @@ var routes = []route{ userUnlockHandler)), []string{"POST"}, }, + { + authBasic, + authBasicHandler, + []string{"GET"}, + }, } diff --git a/pkg/server/server.go b/pkg/server/server.go index 3bf3449..0aad8cd 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -28,9 +28,10 @@ type Server struct { srv *http.Server - API *client.APIClient - DB *db.DB - JWT *jwt.JWT + API *client.APIClient + DB *db.DB + JWT *jwt.JWT + BasicAuth *BasicAuth ts *TemplateStore } @@ -72,6 +73,10 @@ func (s *Server) Start(dblog logr.Logger) (err error) { if err != nil { return errors.New("server: " + err.Error()) } + s.BasicAuth, err = NewBasicAuth(NewDBLoginValidator(s.DB)) + if err != nil { + return errors.New("server: " + err.Error()) + } go func() { time.Sleep(time.Second * 2) s.srv.ListenAndServe() diff --git a/pkg/server/templates/userEdit.html b/pkg/server/templates/userEdit.html index e49afe9..e5b7d0a 100644 --- a/pkg/server/templates/userEdit.html +++ b/pkg/server/templates/userEdit.html @@ -1,7 +1,7 @@ {{define "body"}} {{if .Data}}
-
+
{{.BodyTitle}} @@ -14,21 +14,27 @@
-
- - +
+
+
+ + +
+
+ + +
-
- - +
+
+ + +
+
+ + +
-
- - -
-
- -
{{if .Admin}}
diff --git a/pkg/server/templates/userNew.html b/pkg/server/templates/userNew.html index 0ce5d4f..51ea6e1 100644 --- a/pkg/server/templates/userNew.html +++ b/pkg/server/templates/userNew.html @@ -1,6 +1,6 @@ {{define "body"}}
-
+
{{.BodyTitle}} @@ -8,21 +8,27 @@
-
- - +
+
+
+ + +
+
+ + +
-
- - +
+
+ + +
+
+ + +
-
- - -
-
- -