From 4c98ab6a3a1f41ebaa5360a6a4615cd705a94db0 Mon Sep 17 00:00:00 2001 From: René 'Necoro' Neumann Date: Tue, 13 Feb 2024 22:49:23 +0100 Subject: session handling and login/logout --- go.mod | 2 ++ go.sum | 6 ++++ main.go | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 106 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index b851d68..50c0149 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/go-sql-driver/mysql v1.7.1 github.com/gorilla/handlers v1.5.2 github.com/gorilla/schema v1.2.1 + github.com/gorilla/sessions v1.2.2 github.com/mattn/go-sqlite3 v1.14.22 github.com/simukti/sqldb-logger v0.0.0-20230108155151-646c1a075551 golang.org/x/crypto v0.19.0 @@ -17,6 +18,7 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect diff --git a/go.sum b/go.sum index 6b6bff5..2b48e8d 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -18,6 +20,10 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/schema v1.2.1 h1:tjDxcmdb+siIqkTNoV+qRH2mjYdr2hHe5MKXbp61ziM= github.com/gorilla/schema v1.2.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= diff --git a/main.go b/main.go index 863e1ff..ef6cef3 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,9 @@ package main import ( + "context" "database/sql" + "encoding/gob" "errors" "flag" "fmt" @@ -13,6 +15,8 @@ import ( "github.com/gorilla/handlers" "github.com/gorilla/schema" + "github.com/gorilla/securecookie" + "github.com/gorilla/sessions" "golang.org/x/crypto/bcrypt" "gosten/model" @@ -28,10 +32,15 @@ var ( func init() { flag.StringVar(&host, "h", "localhost", "address to listen on") flag.Uint64Var(&port, "p", 8080, "port to listen on") + + gob.Register(SessionData{}) } var Q *model.Queries var s *schema.Decoder +var sessionStore sessions.Store + +const sessionCookie = "sessionKeks" func main() { flag.Parse() @@ -44,14 +53,17 @@ func main() { Q = model.New(db) s = schema.NewDecoder() + sessionStore = sessions.NewCookieStore(securecookie.GenerateRandomKey(32)) mux := http.NewServeMux() - mux.HandleFunc("/{$}", showTemplate("index", nil)) - mux.HandleFunc("GET /login", showTemplate("login", User{})) + mux.Handle("/{$}", showTemplate("index", nil)) + mux.HandleFunc("GET /login", loginPage) mux.HandleFunc("POST /login", handleLogin) + mux.HandleFunc("GET /logout", handleLogout) - handler := handlers.CombinedLoggingHandler(os.Stderr, mux) + handler := sessionHandler(mux) + handler = handlers.CombinedLoggingHandler(os.Stderr, handler) handler = handlers.ProxyHeaders(handler) address := net.JoinHostPort(host, strconv.FormatUint(port, 10)) @@ -59,9 +71,10 @@ func main() { } type User struct { - Name string `form:"options=required"` - Password string `form:"type=password;options=required"` - Errors []error `form:"-"` + Name string `form:"options=required"` + Password string `form:"type=password;options=required"` + RememberMe bool `form:"type=checkbox;value=y;options=checked"` + Errors []error `form:"-"` } type fieldError struct { @@ -94,6 +107,24 @@ func parseForm[T any](r *http.Request, data *T) { } } +func loginPage(w http.ResponseWriter, r *http.Request) { + s := session(r) + + if !s.s.IsNew && s.Authenticated { + u, err := Q.GetUserById(r.Context(), s.UserID) + if err != nil { + s.Authenticated = false + s.Save(w, r) + } else { + u2 := User{Name: u.Name} + showTemplate("login2", u2).ServeHTTP(w, r) + return + } + } + + showTemplate("login", User{}).ServeHTTP(w, r) +} + func handleLogin(w http.ResponseWriter, r *http.Request) { u := User{} parseForm(r, &u) @@ -120,5 +151,66 @@ func handleLogin(w http.ResponseWriter, r *http.Request) { return } + s := session(r) + if u.RememberMe { + s.MaxAge(86400 * 7) // 1 week + } else { + s.MaxAge(0) + } + + s.UserID = dbUser.ID + s.Authenticated = true + s.Save(w, r) + showTemplate("login2", u).ServeHTTP(w, r) } + +func handleLogout(w http.ResponseWriter, r *http.Request) { + s := session(r) + s.Authenticated = false + s.MaxAge(-1) + s.Save(w, r) +} + +func sessionHandler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + session, err := sessionStore.Get(r, sessionCookie) + if err != nil { + } + + ctx := context.WithValue(r.Context(), "_session", session) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +type Session struct { + *SessionData + s *sessions.Session +} + +type SessionData struct { + UserID int64 + Authenticated bool +} + +func (s *Session) Save(w http.ResponseWriter, r *http.Request) { + s.s.Values["data"] = *s.SessionData + if err := s.s.Save(r, w); err != nil { + log.Panic("Storing session: ", err) + } +} + +func (s *Session) MaxAge(maxAge int) { + s.s.Options.MaxAge = maxAge +} + +func session(r *http.Request) Session { + s := r.Context().Value("_session").(*sessions.Session) + s.Options.HttpOnly = true + + sd, ok := s.Values["data"].(SessionData) + if !ok { + sd = SessionData{} + } + return Session{&sd, s} +} -- cgit v1.2.3-54-g00ecf