Commit 5ec1488c authored by bensinober's avatar bensinober
Browse files

Add server mutex lock and implement user update for minutes api

parent ba5a0667
......@@ -5,7 +5,6 @@
## Server
[ ] Rewrite auth against koha from SIP2 to something else
[ ] Use minutes from db instead of in-memory server, optionally add api for minutes for later server rewrite
[ ] Only use db for persistence, remove in-memory server clients
[ ] Simple ui for server updates
......
......@@ -9,6 +9,8 @@ import (
"log"
"net/http"
"net/url"
"strconv"
"sync"
"time"
_ "github.com/go-sql-driver/mysql"
......@@ -44,6 +46,7 @@ type server struct {
db *DB
startTime time.Time
clients map[string]*client.Client // in-memory logged in clients
mu sync.Mutex
}
func newServer(conn *sql.DB) *server {
......@@ -83,8 +86,10 @@ func (s *server) clientHandler(w http.ResponseWriter, r *http.Request) {
}
if c.User != nil {
c.User.UpdateSeen() // for keep-alive
c.User.UpdateSeen() // for keep-alive
s.mu.Lock()
s.clients[cname] = c // Update in memory client
s.mu.Unlock()
}
b, err := json.Marshal(c)
if err != nil {
......@@ -107,7 +112,9 @@ func (s *server) clientHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, fmt.Sprintf("Client not found in db, mac: %s, ip: %s", mac, ip), http.StatusNotFound)
return
}
s.mu.Lock()
s.clients[c.Name] = c // TODO: in memory client list, unneccessary ?
s.mu.Unlock()
b, err := json.Marshal(c)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
......@@ -144,8 +151,11 @@ func (s *server) logInHandler(w http.ResponseWriter, r *http.Request) {
u := &client.User{Type: "AnonymousUser", Minutes: &mins}
u.UpdateSeen()
c.SetUser(u)
c.Client = &c.Id
s.mu.Lock()
s.clients[cname] = c // TODO: in memory client list, unneccessary ?
s.db.UpdateUser(c)
s.mu.Unlock()
s.db.UpdateUser(u)
b, err := json.Marshal(c)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
......@@ -193,19 +203,39 @@ func (s *server) logInHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Insert or Update user in db based on login response
// Insert or Update user response based on login response
u, err := s.db.UpsertUser(c, authUser)
if err != nil {
log.Println(err)
http.Error(w, "Failed decoding auth response.", http.StatusInternalServerError)
return
}
u.Authenticated = authUser.Authenticated
// client specific auth tests
switch {
case !authUser.Authenticated:
u.Message = "Feil lånenummer/brukernavn eller PIN/passord"
case *u.Minutes <= 0:
u.Message = "Beklager, du har brukt opp kvoten din for i dag!"
case u.Age < *c.Options.AgeL:
case u.Age > *c.Options.AgeH:
u.Message = fmt.Sprintf("Denne maskinen er kun for de mellom %d og %d", c.Options.AgeL, c.Options.AgeH)
default:
u.Authenticated = authUser.Authenticated
}
u.Minutes = authUser.Minutes
u.Client = &c.Id
u.UpdateSeen()
c.SetUser(u)
s.db.UpdateUser(c)
s.clients[cname] = c // TODO: in memory client list, unneccessary ?
// dont actually add user if not allowed use
if u.Authenticated {
s.db.UpdateUser(u)
s.mu.Lock()
s.clients[cname] = c // TODO: in memory client list, unneccessary ?
s.mu.Unlock()
}
b, err := json.Marshal(c)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
......@@ -231,16 +261,66 @@ func (s *server) logOutHandler(w http.ResponseWriter, r *http.Request) {
return
}
log.Printf("POST /api/logout from client: %s", cname)
c.User.Client = nil
if _, err := s.db.RemoveClientActiveUser(c.User.Id); err != nil {
http.Error(w, "Failed removing client active user.", http.StatusInternalServerError)
return
}
c.SetUser(nil)
s.mu.Lock()
s.clients[cname] = c // TODO: in memory client list, unneccessary ?
s.mu.Unlock()
w.Write([]byte("OK"))
}
// POST /api/users/update : update existing user
func (s *server) updateUserHandler(w http.ResponseWriter, r *http.Request) {
username := r.FormValue("username")
name := r.FormValue("name")
minutes := r.FormValue("minutes")
if username == "" {
http.Error(w, "Missing parameter username!", http.StatusBadRequest)
}
u, err := s.db.GetUserByUsername(username)
if u.Id == 0 {
http.Error(w, "User not found", http.StatusNotFound)
return
}
if err != nil {
http.Error(w, "Failed getting user.", http.StatusInternalServerError)
return
}
log.Printf("POST /api/users/update for username: %s, minutes: %s", username, minutes)
if name != "" {
u.Name = &name
}
if minutes != "" {
if m, err := strconv.Atoi(minutes); err == nil {
u.Minutes = &m
}
}
s.db.UpdateUser(u)
if u.Client != nil {
c, err := s.db.GetClient(u.Client)
if err != nil {
http.Error(w, "Failed getting user.", http.StatusInternalServerError)
return
}
c.User = u
s.mu.Lock()
s.clients[c.Name] = c // TODO: in memory client list, unneccessary ?
s.mu.Unlock()
}
b, err := json.Marshal(u)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(b)
}
// POST /api/queue : add item to queue
func (s *server) queueHandler(w http.ResponseWriter, r *http.Request) {
var q client.Queue
......@@ -319,7 +399,9 @@ func (s *server) addItemToClientQueue(q client.Queue) error {
q.Time = time.Now()
q.Uuid = uuid.New()
c.Queue = append(c.Queue, q)
s.mu.Lock()
s.clients[q.Client] = c
s.mu.Unlock()
} else {
return errors.New("No matching client")
......@@ -333,7 +415,9 @@ func (s *server) updateClientQueueItem(q client.Queue) error {
for i, cq := range c.Queue {
if cq.Uuid == q.Uuid {
q.Time = time.Now()
s.mu.Lock()
s.clients[q.Client].Queue[i] = q
s.mu.Unlock()
}
}
} else {
......@@ -360,7 +444,7 @@ func (s *server) DecreaseTime() {
if c.User != nil {
if c.User.SeenLastMinute() {
*c.User.Minutes--
s.db.UpdateUser(c)
s.db.UpdateUser(c.User)
} else {
if _, err := s.db.RemoveClientActiveUser(c.User.Id); err != nil {
log.Println("Failed force log out of user", c.User.Id)
......@@ -368,7 +452,9 @@ func (s *server) DecreaseTime() {
log.Println("Force logout of inactive user:", c.User.Username)
c.SetUser(nil)
}
s.mu.Lock()
s.clients[name] = c
s.mu.Unlock()
}
}
}
......@@ -396,9 +482,13 @@ func main() {
go s.ticker()
mux := http.NewServeMux()
mux.HandleFunc("/.status", s.statusHandler)
mux.HandleFunc("/api/clients", s.clientHandler) // client start / status endpoint
mux.HandleFunc("/api/login", s.logInHandler) // client logon
mux.HandleFunc("/api/logout", s.logOutHandler) // client logoff
// Client API
mux.HandleFunc("/api/clients", s.clientHandler) // client start / status endpoint
mux.HandleFunc("/api/login", s.logInHandler) // client logon
mux.HandleFunc("/api/logout", s.logOutHandler) // client logoff
// User API
mux.HandleFunc("/api/user/update", s.updateUserHandler) // user update (username,name,minutes)
// Queue API
mux.HandleFunc("/api/queue", s.queueHandler) // server initiated commands
mux.HandleFunc("/api/updatequeue", s.queueResponseHandler) // Response to server initiated commands
......
......@@ -60,32 +60,17 @@ func Login(w fyne.Window, st *Status, hostAPI, name string, extraMinutes, agel,
return
}
cl, err := authenticate(hostAPI, name, username.Text, password.Text)
user := cl.User
// Unknown error gives general failed login
if err != nil {
log.Println("authentication API call failed: ", err)
errDiv.Text = "Feil lånenummer/brukernavn eller PIN/passord"
errDiv.Refresh()
return
}
if !user.Authenticated {
errDiv.Text = user.Message
errDiv.Refresh()
return
}
uMins := *user.Minutes
if uMins+extraMinutes <= 0 && user.Type != "G" {
log.Println(extraMinutes)
errDiv.Text = "Beklager, du har brukt opp kvoten din for i dag!"
errDiv.Refresh()
return
}
if user.Type == "G" && uMins <= 0 {
errDiv.Text = "Beklager, du har brukt opp kvoten din for i dag!"
errDiv.Refresh()
return
}
if user.Age < agel || user.Age > ageh {
errDiv.Text = fmt.Sprintf("Denne maskinen er kun for de mellom %d og %d", agel, ageh)
// Display error
if !cl.User.Authenticated {
errDiv.Text = cl.User.Message
errDiv.Refresh()
return
}
......@@ -97,7 +82,7 @@ func Login(w fyne.Window, st *Status, hostAPI, name string, extraMinutes, agel,
password.Refresh()
w.SetFullScreen(false)
w.Hide()
st.SetMinutes(uMins)
st.SetMinutes(*cl.User.Minutes)
st.ShowStatusWindow()
return
}
......
......@@ -3,6 +3,7 @@ package window
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"image/color"
"log"
......@@ -44,10 +45,12 @@ func NewStatus(w, m fyne.Window, api string, c *client.Client, mins int) *Status
func (st *Status) logout() error {
resp, err := http.Get(fmt.Sprintf("%s/api/logout?client=%s", st.hostApi, st.client.Name))
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.New("Failed logging out")
}
return nil
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment