From 4b2d729f4cb8a566d26bfb05cfaff8cacce39775 Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Sun, 9 Feb 2025 10:47:26 -0500 Subject: [PATCH] Logout and some event logging on auth --- internal/server/handlers.go | 26 +++++++++++++++++++++++--- internal/server/helpers.go | 31 +++++++++++++++++++++++++++++++ internal/server/routes.go | 2 +- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 0c72f83..9ee098e 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -389,6 +389,7 @@ func handleUserLoginPost(logger *slog.Logger, tc *TemplateCache, sm *scs.Session id, err := userService.Authenticate(form.Email, form.Password) if err != nil { if errors.Is(err, model.ErrInvalidCredentials) { + logAuthFailure(logger, r, form.Email) form.AddNonFieldError("Email or password is incorrect") data := newTemplateData(r, sm) @@ -414,13 +415,32 @@ func handleUserLoginPost(logger *slog.Logger, tc *TemplateCache, sm *scs.Session // Add the ID of the current user to the session, so that they are now "logged in" sm.Put(r.Context(), "authenticatedUserID", id) - + logAuthSuccess(logger, r, form.Email, id) http.Redirect(w, r, "/snippet/create", http.StatusSeeOther) }) } -func handleUserLogoutPost() http.Handler { +func handleUserLogoutPost(logger *slog.Logger, sm *scs.SessionManager) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "Logout the user") + // Use RenewToken on the current session to change the session ID + err := sm.RenewToken(r.Context()) + if err != nil { + serverError(w, r, err) + } + + userId := sm.GetString(r.Context(), "authenticatedUserID") + if userId == "" { + logger.Info("No athenticated user in session") + } else { + logger.Info(fmt.Sprintf("Logging out user: %s", userId)) + } + + // Remove the authenticatedUserID from the session data + sm.Remove(r.Context(), "authenticatedUserID") + + // Add a flash message + sm.Put(r.Context(), "flash", "You've been logged out successfully!") + + http.Redirect(w, r, "/", http.StatusSeeOther) }) } diff --git a/internal/server/helpers.go b/internal/server/helpers.go index 979e3e5..4a8136e 100644 --- a/internal/server/helpers.go +++ b/internal/server/helpers.go @@ -4,6 +4,8 @@ import ( "log/slog" "net/http" "runtime/debug" + "strconv" + "strings" ) // serverError helper writes a log entry at Error level (including the request @@ -29,3 +31,32 @@ func serverError(w http.ResponseWriter, r *http.Request, err error) { func clientError(w http.ResponseWriter, status int) { http.Error(w, http.StatusText(status), status) } + +func getClientIP(r *http.Request) string { + // Check X-Forwarded-For header first (for proxied requests) + forwardedFor := r.Header.Get("X-Forwarded-For") + if forwardedFor != "" { + // Take the first IP in case of multiple proxies + return strings.Split(forwardedFor, ",")[0] + } + + // Fall back to RemoteAddr + return strings.Split(r.RemoteAddr, ":")[0] +} + +func logAuthFailure(logger *slog.Logger, r *http.Request, email string) { + logger.Info("authentication attempt failed", + slog.String("event_type", "authentication_failure"), + slog.String("username", email), + slog.String("ip_address", getClientIP(r)), + slog.String("user_agent", r.Header.Get("User-Agent"))) +} + +func logAuthSuccess(logger *slog.Logger, r *http.Request, email string, userId int) { + logger.Info("successful login", + slog.String("event_type", "authentication_success"), + slog.String("username", email), + slog.String("user_id", strconv.Itoa(userId)), + slog.String("ip_address", getClientIP(r)), + slog.String("user_agent", r.Header.Get("User-Agent"))) +} diff --git a/internal/server/routes.go b/internal/server/routes.go index 00c1c7b..54cd8b1 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -35,7 +35,7 @@ func addRoutes(mux *http.ServeMux, mux.Handle("POST /user/signup", sm.LoadAndSave(handleUserSignupPost(logger, tc, fd, sm, userService))) mux.Handle("GET /user/login", sm.LoadAndSave(handleUserLoginGet(tc, sm))) mux.Handle("POST /user/login", sm.LoadAndSave(handleUserLoginPost(logger, tc, sm, fd, userService))) - mux.Handle("POST /user/logout", sm.LoadAndSave(handleUserLogoutPost())) + mux.Handle("POST /user/logout", sm.LoadAndSave(handleUserLogoutPost(logger, sm))) return mux }