From db871f328af64a210b4f1c79193f329ef9f6abdb Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Sun, 5 Mar 2023 23:19:03 +0100 Subject: [PATCH] Add simple sessions --- go.mod | 1 + go.sum | 2 + pkg/server/api.go | 7 +++- pkg/server/server.go | 6 +++ pkg/server/session.go | 91 +++++++++++++++++++++++++++++++++++++++-- pkg/server/websocket.go | 8 +++- 6 files changed, 109 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index a4575a0..f25b83d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module nfsense.net/nfsense go 1.19 require ( + github.com/google/uuid v1.3.0 go4.org/netipx v0.0.0-20230125063823-8449b0a6169f golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 nhooyr.io/websocket v1.8.7 diff --git a/go.sum b/go.sum index 826b7c0..d9ac8a3 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,8 @@ github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgj github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= diff --git a/pkg/server/api.go b/pkg/server/api.go index 87d40a9..4986853 100644 --- a/pkg/server/api.go +++ b/pkg/server/api.go @@ -11,6 +11,11 @@ import ( ) func HandleAPI(w http.ResponseWriter, r *http.Request) { + _, s := GetSession(r) + if s == nil { + w.WriteHeader(http.StatusUnauthorized) + return + } defer func() { if r := recover(); r != nil { slog.Error("Recovered Panic Handling HTTP API Request", fmt.Errorf("%v", r), "stack", debug.Stack()) @@ -18,7 +23,7 @@ func HandleAPI(w http.ResponseWriter, r *http.Request) { return } }() - ctx, cancel := context.WithTimeout(r.Context(), time.Second*10) + ctx, cancel := context.WithTimeout(context.WithValue(r.Context(), SessionKey, s), time.Second*10) defer cancel() err := apiHandler.HandleRequest(ctx, r.Body, w) diff --git a/pkg/server/server.go b/pkg/server/server.go index f24734e..51b99fe 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -15,6 +15,7 @@ import ( var server http.Server var mux = http.NewServeMux() var apiHandler *jsonrpc.Handler +var stopCleanup chan struct{} func StartWebserver(conf *definitions.Config, _apiHandler *jsonrpc.Handler) { server.Addr = ":8080" @@ -29,6 +30,10 @@ func StartWebserver(conf *definitions.Config, _apiHandler *jsonrpc.Handler) { mux.HandleFunc("/ws/api", HandleWebsocketAPI) mux.HandleFunc("/", HandleWebinterface) + stopCleanup = make(chan struct{}) + + go CleanupSessions(stopCleanup) + go func() { if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { slog.Error("Webserver error", err) @@ -38,6 +43,7 @@ func StartWebserver(conf *definitions.Config, _apiHandler *jsonrpc.Handler) { } func ShutdownWebserver(ctx context.Context) error { + stopCleanup <- struct{}{} err := server.Shutdown(ctx) if err != nil { return fmt.Errorf("Shutting down: %w", err) diff --git a/pkg/server/session.go b/pkg/server/session.go index f1288b4..af2c94a 100644 --- a/pkg/server/session.go +++ b/pkg/server/session.go @@ -1,15 +1,98 @@ package server -import "net/http" +import ( + "net/http" + "sync" + "time" + + "github.com/google/uuid" +) + +type SessionKeyType string + +const SessionKey SessionKeyType = "session" + +type Session struct { + Username string + Expires time.Time +} + +var sessionsSync sync.Mutex +var sessions map[string]*Session = map[string]*Session{} + +func GetSession(r *http.Request) (string, *Session) { + c, err := r.Cookie("session") + if err != nil { + return "", nil + } + s, ok := sessions[c.Value] + if ok { + return c.Value, s + } + return "", nil +} + +func GenerateSession(w http.ResponseWriter, username string) { + id := uuid.New().String() + expires := time.Now().Add(time.Minute * 5) + sessionsSync.Lock() + defer sessionsSync.Unlock() + sessions[id] = &Session{ + Username: username, + Expires: expires, + } + http.SetCookie(w, &http.Cookie{Name: "session", HttpOnly: true, SameSite: http.SameSiteStrictMode, Value: id, Expires: expires}) +} + +func CleanupSessions(stop chan struct{}) { + tick := time.NewTicker(time.Minute) + for { + select { + case <-tick.C: + ids := []string{} + sessionsSync.Lock() + for id, s := range sessions { + if time.Now().After(s.Expires) { + ids = append(ids, id) + } + } + for _, id := range ids { + delete(sessions, id) + } + sessionsSync.Unlock() + case <-stop: + return + } + } +} func HandleLogin(w http.ResponseWriter, r *http.Request) { - + username := r.PostFormValue("username") + password := r.PostFormValue("password") + if username == "admin" && password == "12345" { + GenerateSession(w, username) + w.WriteHeader(http.StatusOK) + http.Redirect(w, r, "/", http.StatusFound) + } + w.WriteHeader(http.StatusUnauthorized) } func HandleLogout(w http.ResponseWriter, r *http.Request) { - + http.SetCookie(w, &http.Cookie{Name: "session", Value: "", Expires: time.Now()}) + w.WriteHeader(http.StatusOK) } func HandleSession(w http.ResponseWriter, r *http.Request) { - + id, s := GetSession(r) + if s == nil { + w.WriteHeader(http.StatusUnauthorized) + return + } + sessionsSync.Lock() + defer sessionsSync.Unlock() + if s != nil { + s.Expires = time.Now().Add(time.Minute * 5) + } + http.SetCookie(w, &http.Cookie{Name: "session", HttpOnly: true, SameSite: http.SameSiteStrictMode, Value: id, Expires: s.Expires}) + w.WriteHeader(http.StatusOK) } diff --git a/pkg/server/websocket.go b/pkg/server/websocket.go index 0f3d48f..92e60cb 100644 --- a/pkg/server/websocket.go +++ b/pkg/server/websocket.go @@ -13,7 +13,13 @@ import ( ) func HandleWebsocketAPI(w http.ResponseWriter, r *http.Request) { - ctx, cancel := context.WithCancel(r.Context()) + _, s := GetSession(r) + if s == nil { + w.WriteHeader(http.StatusUnauthorized) + return + } + + ctx, cancel := context.WithCancel(context.WithValue(r.Context(), SessionKey, s)) defer cancel() c, err := websocket.Accept(w, r, nil) if err != nil {