summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsanine-a <sanine.not@pm.me>2023-05-10 12:18:57 -0500
committersanine-a <sanine.not@pm.me>2023-05-10 12:18:57 -0500
commitfd994ce631ce7244157f81678574e6bc420d0fcc (patch)
treeaa1bac1a1419dc83588d2895ae54f5c8a3da8283
parentfacfe2b26f1ce8cbc125f7f1192d2e0d65c51ae9 (diff)
implement user logins and gatekeeping
-rw-r--r--db/db.go1
-rw-r--r--db/session.go7
-rw-r--r--db/user.go43
-rw-r--r--phlox/login.go71
-rw-r--r--phlox/main.go36
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(`
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>Login</title>
+ </head>
+ <body>
+ {{ .Body }}
+ </body>
+</html>
+`)
+
+ 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: `
+ <form method="post">
+ <label for="user">Username</label>
+ <input type="text" id="user" name="username">
+ <br>
+ <label for="pass">Password</label>
+ <input type="text" id="pass" name="password">
+ <br>
+ <input type="submit" value="Submit">
+ `,
+ })
}
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