package handlers import ( "net/http" "sneak.berlin/go/webhooker/internal/database" ) // HandleLoginPage returns a handler for the login page (GET) func (h *Handlers) HandleLoginPage() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Check if already logged in sess, err := h.session.Get(r) if err == nil && h.session.IsAuthenticated(sess) { http.Redirect(w, r, "/", http.StatusSeeOther) return } // Render login page data := map[string]any{ tmplKeyError: "", } h.renderTemplate(w, r, "login.html", data) } } // HandleLoginSubmit handles the login form submission (POST) func (h *Handlers) HandleLoginSubmit() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // The body size cap is enforced by the MaxBodySize // middleware, which runs before CSRF parses the form. err := r.ParseForm() if err != nil { h.log.Error("failed to parse form", "error", err) http.Error(w, "Bad request", http.StatusBadRequest) return } username := r.FormValue("username") password := r.FormValue("password") // Validate input if username == "" || password == "" { h.renderLoginError( w, r, "Username and password are required", http.StatusBadRequest, ) return } user, err := h.authenticateUser( w, r, username, password, ) if err != nil { return } err = h.createAuthenticatedSession(w, r, user) if err != nil { return } h.log.Info( "user logged in", "username", username, "user_id", user.ID, ) // Redirect to home page http.Redirect(w, r, "/", http.StatusSeeOther) } } // renderLoginError renders the login page with an error message. func (h *Handlers) renderLoginError( w http.ResponseWriter, r *http.Request, msg string, status int, ) { data := map[string]any{ tmplKeyError: msg, } w.WriteHeader(status) h.renderTemplate(w, r, "login.html", data) } // authenticateUser looks up and verifies a user's credentials. // On failure it writes an HTTP response and returns an error. func (h *Handlers) authenticateUser( w http.ResponseWriter, r *http.Request, username, password string, ) (database.User, error) { var user database.User err := h.db.DB().Where( "username = ?", username, ).First(&user).Error if err != nil { h.log.Debug("user not found", "username", username) h.renderLoginError( w, r, "Invalid username or password", http.StatusUnauthorized, ) return user, err } valid, err := database.VerifyPassword(password, user.Password) if err != nil { h.log.Error("failed to verify password", "error", err) http.Error( w, "Internal server error", http.StatusInternalServerError, ) return user, err } if !valid { h.log.Debug("invalid password", "username", username) h.renderLoginError( w, r, "Invalid username or password", http.StatusUnauthorized, ) return user, errInvalidPassword } return user, nil } // createAuthenticatedSession regenerates the session and stores // user info. On failure it writes an HTTP response and returns // an error. func (h *Handlers) createAuthenticatedSession( w http.ResponseWriter, r *http.Request, user database.User, ) error { oldSess, err := h.session.Get(r) if err != nil { h.log.Error("failed to get session", "error", err) http.Error( w, "Internal server error", http.StatusInternalServerError, ) return err } sess, err := h.session.Regenerate(r, w, oldSess) if err != nil { h.log.Error( "failed to regenerate session", "error", err, ) http.Error( w, "Internal server error", http.StatusInternalServerError, ) return err } h.session.SetUser(sess, user.ID, user.Username) err = h.session.Save(r, w, sess) if err != nil { h.log.Error("failed to save session", "error", err) http.Error( w, "Internal server error", http.StatusInternalServerError, ) return err } return nil } // HandleLogout handles user logout func (h *Handlers) HandleLogout() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { sess, err := h.session.Get(r) if err != nil { h.log.Error("failed to get session", "error", err) http.Redirect( w, r, "/pages/login", http.StatusSeeOther, ) return } // Destroy session h.session.Destroy(sess) // Save the destroyed session err = h.session.Save(r, w, sess) if err != nil { h.log.Error( "failed to save destroyed session", "error", err, ) } // Redirect to login page http.Redirect(w, r, "/pages/login", http.StatusSeeOther) } }