package handler import ( "context" "net/http" "strings" "time" "projectreshoot/internal/models" "projectreshoot/internal/view/component/form" "projectreshoot/internal/view/page" "git.haelnorr.com/h/golib/hws" "git.haelnorr.com/h/golib/hwsauth" "git.haelnorr.com/h/golib/cookies" "github.com/pkg/errors" "github.com/uptrace/bun" ) // Validates the username matches a user in the database and the password // is correct. Returns the corresponding user func validateLogin( ctx context.Context, tx bun.Tx, r *http.Request, ) (*models.UserBun, error) { formUsername := r.FormValue("username") formPassword := r.FormValue("password") user, err := models.GetUserByUsername(ctx, tx, formUsername) if err != nil { return nil, errors.Wrap(err, "db.GetUserFromUsername") } if user == nil { return nil, errors.New("Username or password incorrect") } err = user.CheckPassword(ctx, tx, formPassword) if err != nil { if !strings.Contains(err.Error(), "Username or password incorrect") { return nil, errors.Wrap(err, "user.CheckPassword") } return nil, errors.New("Username or password incorrect") } return user, nil } // Returns result of the "Remember me?" checkbox as a boolean func checkRememberMe(r *http.Request) bool { rememberMe := r.FormValue("remember-me") if rememberMe == "on" { return true } else { return false } } // Handles an attempted login request. On success will return a HTMX redirect // and on fail will return the login form again, passing the error to the // template for user feedback func LoginRequest( server *hws.Server, auth *hwsauth.Authenticator[*models.UserBun, bun.Tx], db *bun.DB, ) http.Handler { return http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) defer cancel() // Start the transaction tx, err := db.BeginTx(ctx, nil) if err != nil { err := server.ThrowError(w, r, hws.HWSError{ StatusCode: http.StatusServiceUnavailable, Message: "Login failed", Error: err, }) if err != nil { server.ThrowFatal(w, err) } return } r.ParseForm() user, err := validateLogin(ctx, tx, r) if err != nil { tx.Rollback() if err.Error() != "Username or password incorrect" { err := server.ThrowError(w, r, hws.HWSError{ StatusCode: http.StatusInternalServerError, Message: "Login failed", Error: err, }) if err != nil { server.ThrowFatal(w, err) } } else { form.LoginForm("Username or password incorrect").Render(r.Context(), w) } return } rememberMe := checkRememberMe(r) err = auth.Login(w, r, user, rememberMe) if err != nil { tx.Rollback() err := server.ThrowError(w, r, hws.HWSError{ StatusCode: http.StatusInternalServerError, Message: "Login failed", Error: err, }) if err != nil { server.ThrowFatal(w, err) } return } tx.Commit() pageFrom := cookies.CheckPageFrom(w, r) w.Header().Set("HX-Redirect", pageFrom) }, ) } // Handles a request to view the login page. Will attempt to set "pagefrom" // cookie so a successful login can redirect the user to the page they came func LoginPage(trustedHost string) http.Handler { return http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { cookies.SetPageFrom(w, r, trustedHost) page.Login().Render(r.Context(), w) }, ) }