From fd994ce631ce7244157f81678574e6bc420d0fcc Mon Sep 17 00:00:00 2001 From: sanine-a Date: Wed, 10 May 2023 12:18:57 -0500 Subject: implement user logins and gatekeeping --- db/db.go | 1 + db/session.go | 7 ++++++ db/user.go | 43 +++++++++-------------------------- phlox/login.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- phlox/main.go | 36 +++++++++++++++++++++++++---- 5 files changed, 117 insertions(+), 41 deletions(-) diff --git a/db/db.go b/db/db.go index d9b4578..c58f6ad 100644 --- a/db/db.go +++ b/db/db.go @@ -56,6 +56,7 @@ type Model interface { DeleteSession(session Session) error TouchSession(session Session) error CheckSession(session Session) (bool, error) + CheckSessionId(id string) (bool, error) CleanSessions(maxIdle time.Duration) error AllSessions() ([]Session, error) diff --git a/db/session.go b/db/session.go index 8590d2f..da081f9 100644 --- a/db/session.go +++ b/db/session.go @@ -90,6 +90,13 @@ func (p *Phlox) CheckSession(session Session) (bool, error) { } + +func (p *Phlox) CheckSessionId(id string) (bool, error) { + session := Session{ Id: id } + return p.CheckSession(session) +} + + func (p *Phlox) TouchSession(session Session) error { now := time.Now().UTC().Format(time.RFC3339) _, err := p.db.Exec( diff --git a/db/user.go b/db/user.go index 27e6f89..1aff73f 100644 --- a/db/user.go +++ b/db/user.go @@ -1,7 +1,7 @@ package db import ( - "golang.org/x/crypto/bcrypt" + "golang.org/x/crypto/argon2" "crypto/rand" "encoding/base64" "database/sql" @@ -23,21 +23,8 @@ func getNextUserId(db *sql.DB) (int, error) { } -func saltPassword(password string, salt []byte) []byte { - salted := []byte(password) - salted = append(salted, salt...) - return salted -} - - -func hashPassword(password string, salt []byte) ([]byte, error) { - salted := saltPassword(password, salt) - hash, err := bcrypt.GenerateFromPassword(salted, bcrypt.DefaultCost) - if err != nil { - return []byte{}, err - } - - return hash, nil +func hashPassword(password string, salt []byte) []byte { + return argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32) } @@ -55,10 +42,7 @@ func (p *Phlox) CreateUser(username, password string) (User, error) { return user, err } - hash, err := hashPassword(password, salt) - if err != nil { - return user, err - } + hash := hashPassword(password, salt) hash64 := base64.StdEncoding.EncodeToString(hash) salt64 := base64.StdEncoding.EncodeToString(salt) @@ -86,13 +70,10 @@ func (p *Phlox) DeleteUser(user User) error { func (p *Phlox) SetPassword(user User, password string) error { - hash, err := hashPassword(password, user.Salt) - if err != nil { - return err - } + hash := hashPassword(password, user.Salt) hash64 := base64.StdEncoding.EncodeToString(hash) - _, err = p.db.Exec("update users set passwordhash=? where userid=?;", hash64, user.Id) + _, err := p.db.Exec("update users set passwordhash=? where userid=?;", hash64, user.Id) return err } @@ -135,15 +116,11 @@ func (p *Phlox) AuthenticateUser(username, password string) (bool, User, error) return false, User{}, err } - salted := saltPassword(password, user.Salt) - err = bcrypt.CompareHashAndPassword(user.PasswordHash, salted) - if err != nil { - // bad password - return false, User{}, nil - } else { - // success! - return true, user, nil + hash := hashPassword(password, user.Salt) + for i, v := range user.PasswordHash { + if v != hash[i] { return false, user, nil; } } + return true, user, nil } diff --git a/phlox/login.go b/phlox/login.go index 27decf4..9dd82f1 100644 --- a/phlox/login.go +++ b/phlox/login.go @@ -2,12 +2,16 @@ package main import ( "fmt" + "strings" "net/http" + "text/template" + log "github.com/sirupsen/logrus" db "sanine.net/git/phlox/db" ) func LoginUser(username, password string) (bool, db.Session, error) { + p := &P auth, user, err := p.AuthenticateUser(username, password) if err != nil { return false, db.Session{}, err @@ -27,7 +31,8 @@ func LoginUser(username, password string) (bool, db.Session, error) { func LoginPostHandler(w http.ResponseWriter, r *http.Request) { r.ParseForm() username := r.Form.Get("username") - password := r.Form.Get("password") + password := strings.TrimSpace(r.Form.Get("password")) + log.Infof("username: %v\tpassword: '%v'", username, password) redirect := r.Form.Get("redirect") auth, session, err := LoginUser(username, password) @@ -36,21 +41,79 @@ func LoginPostHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "an error was encountered when processing the request") log.Error(err) + return } - if !auth { + if auth == false { // not allowed! w.WriteHeader(http.StatusUnauthorized) fmt.Fprintf(w, "bad username or password") log.Errorf("failed login for %v", username) + return } http.SetCookie(w, &http.Cookie{ Name: "phlox-session-id", Value: session.Id, SameSite: http.SameSiteLaxMode, + }) + + w.Header().Add("Location", redirect) + w.WriteHeader(http.StatusTemporaryRedirect) +} + + +var page *template.Template + +type Page struct { + Title string + Body string +} + + +func InitLogin() { + var err error + page, err = template.New("").Parse(` + + + + + + Login + + + {{ .Body }} + + +`) + + if err != nil { + log.Fatal(err) } - http.Header.Add("Location", redirect) - http.WriteHeader(http.StatusTemporaryRedirect) + http.HandleFunc("/login", func (w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + LoginPostHandler(w, r) + } else { + LoginGetHandler(w, r) + } + }) +} + + +func LoginGetHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + page.Execute(w, Page{ + Title: "Login", + Body: ` +
+ + +
+ + +
+ + `, + }) } diff --git a/phlox/main.go b/phlox/main.go index 3f80724..f2946c8 100644 --- a/phlox/main.go +++ b/phlox/main.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "strings" + "errors" "flag" "net" "net/http" @@ -13,15 +14,15 @@ import ( ) -var p *db.Phlox +var P db.Phlox func main() { + p := &P var dbfile string flag.StringVar(&dbfile, "db", "/etc/phlox/phlox.conf", "path to the configuration db") flag.Parse() - p := &db.Phlox{} err := p.Open(dbfile) if err != nil { log.Fatal(err) @@ -42,6 +43,8 @@ func main() { configureEndpoint(endpoint.Path, endpoint.Address) } + InitLogin() + log.Infof("serving on %v", addr) log.Fatal(http.ListenAndServe(addr, nil)) } @@ -73,8 +76,32 @@ func configureEndpoint(path, address string) { func proxy(w http.ResponseWriter, req *http.Request, end Endpoint) { + p := &P + cookie, err := req.Cookie("phlox-session-id") + if errors.Is(err, http.ErrNoCookie) { + // not logged in + w.Header().Set("Location", "/login") + w.WriteHeader(http.StatusTemporaryRedirect) + return + } + + // check cookie + check, err := p.CheckSessionId(cookie.Value) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "internal server error") + return + } + if !check { + w.WriteHeader(http.StatusUnauthorized) + fmt.Fprintf(w, "unauthorized") + return + } + response := proxyRequest(w, req, end) - proxyResponse(w, response) + if response != nil { + proxyResponse(w, response) + } } @@ -99,7 +126,8 @@ func proxyRequest(w http.ResponseWriter, req *http.Request, end Endpoint) *http if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "%v", err) - log.Fatal(err) + log.Error(err) + return nil } return response -- cgit v1.2.1