package handlers import ( "context" "encoding/json" "git.markbailey.dev/cervers/ptpp/lib/database" "git.markbailey.dev/cervers/ptpp/lib/database/dto" "net/http" "time" "git.markbailey.dev/cervers/ptpp/lib/logger" "git.markbailey.dev/cervers/ptpp/lib/session" "git.markbailey.dev/cervers/ptpp/util" "git.markbailey.dev/cervers/ptpp/view/user" ) type UserHandler struct { logger logger.ILogger session session.IManager db database.IDB } func NewUserHandler(l logger.ILogger, s session.IManager, db database.IDB) *UserHandler { return &UserHandler{ logger: l, session: s, db: db, } } func (u *UserHandler) Populate(w http.ResponseWriter, r *http.Request) error { existingOrg, err := u.db.Repo().FindOrganizationByName("CerbervsSoft") if existingOrg != nil && err == nil { return util.Redirect(w, r, "/signup", http.StatusSeeOther, false) } authToken, err := util.CreateTokenForUser("CerbervsSoft") if err != nil { return err } organization := dto.Organization{ Name: "CerbervsSoft", OwnerName: "Mark Bailey", OwnerEmail: "email@provider.com", OwnerPhone: "+11111111111", Authorized: 1, AuthToken: authToken, CreatedAt: time.Now(), } _, err = u.db.Repo().CreateOrganization(organization) if err != nil { return u.logDBError(err) } return util.Redirect(w, r, "/signup", http.StatusSeeOther, false) } type UserSignInForm struct { Username string `json:"username"` Password string `json:"password"` } func (u *UserHandler) SignIn(w http.ResponseWriter, r *http.Request) error { sess := u.session.SessionStart(w, r) if r.Method == http.MethodGet { username, ok := r.Context().Value("username").(string) if ok { if username == "" { return u.failWithFormError(w, r, "Invalid Username or Password.") } else { foundUser, err := u.db.Repo().FindUserByUsername(username) if foundUser == nil || err != nil { return u.failWithFormError(w, r, "Invalid Username or Password.") } if foundUser.Admin == 1 { return util.Redirect(w, r, "/admin/", http.StatusSeeOther, false) } else { return util.Redirect(w, r, "/", http.StatusSeeOther, false) } } } formError, ok := sess.Get("formError").(string) if !ok { formError = "" } if err := user.SignIn(formError).Render(context.Background(), w); err != nil { u.logger.Error(u.logger.Wrap(err, "Error rendering sign in form")) return err } return nil } fd := &UserSignInForm{} jsonDecoder := json.NewDecoder(r.Body) if err := jsonDecoder.Decode(&fd); err != nil { u.logger.Error(u.logger.Wrap(err, "Error decoding JSON")) return err } foundUser, err := u.db.Repo().FindUserByUsername(fd.Username) if foundUser == nil || err != nil { return util.Redirect(w, r, "/signin", http.StatusSeeOther, false) } authenticated, err := util.CheckPassword(fd.Password, foundUser.Password) if err != nil || !authenticated { return u.failWithFormError(w, r, "Invalid Username or Password.") } err = sess.Set("username", foundUser.Username) if err != nil { return err } token, err := util.CreateTokenForUser(foundUser.Username) if err != nil { u.logger.Error(u.logger.Wrap(err, "Error creating token")) return err } err = sess.Set("token", token) if err != nil { return err } authToken, err := util.CreateTokenForUser(foundUser.Username) if err != nil { u.logger.Error(err) return err } _, err = u.db.Repo().CreateHeartbeat(&dto.Heartbeat{ User: foundUser.Identifier, CreatedAt: time.Now(), IpAddr: r.RemoteAddr, AuthToken: authToken, }) if err != nil { return u.logDBError(err) } jwtToken, err := util.CreateTokenForUser(fd.Username) if err != nil { return err } http.SetCookie(w, &http.Cookie{ Name: "token", Value: jwtToken, Expires: time.Now().Add(time.Hour * 1), Path: "/", }) err = sess.Delete("formError") if err != nil { return err } u.logger.Info(foundUser.Username + " logged in") if foundUser.Admin == 1 { return util.Redirect(w, r, "/admin/", http.StatusSeeOther, false) } else { return util.Redirect(w, r, "/", http.StatusSeeOther, false) } } type UserSignUpForm struct { Name string `json:"name"` Email string `json:"email"` Username string `json:"username"` Password string `json:"password"` PasswordConfirmation string `json:"password_confirmation"` } func (u *UserHandler) SignUp(w http.ResponseWriter, r *http.Request) error { handlerSess := u.session.SessionStart(w, r) uname, ok := handlerSess.Get("username").(string) if !ok { uname = "" } if uname != "" { return util.Redirect(w, r, "/", http.StatusSeeOther, false) } if r.Method == http.MethodGet { if err := user.SignUpForm().Render(context.Background(), w); err != nil { u.logger.Error(u.logger.Wrap(err, "Error rendering sign up form")) return err } return nil } fd := &UserSignUpForm{} jsonDecoder := json.NewDecoder(r.Body) if err := jsonDecoder.Decode(&fd); err == nil { if fd.Password != fd.PasswordConfirmation { if _, err := w.Write([]byte("Passwords don't match")); err != nil { u.logger.Error(u.logger.Wrap(err, "Error writing response")) return err } return nil } foundUser, err := u.db.Repo().FindUserByUsername(fd.Username) if foundUser != nil { if _, err := w.Write([]byte("Invalid username. Please try another")); err != nil { u.logger.Error(u.logger.Wrap(err, "Error writing response")) } return nil } token, err := util.CreateTokenForUser(fd.Username) if err != nil { u.logger.Error(u.logger.Wrap(err, "Error creating token")) return err } password, err := util.HashPassword(fd.PasswordConfirmation) if err != nil { u.logger.Error(u.logger.Wrap(err, "Error hashing password")) return err } org, err := u.db.Repo().FindOrganizationByName("CerbervsSoft") if org == nil || err != nil { return u.logDBError(err) } newUser, err := u.db.Repo().CreateUser(dto.User{ Username: fd.Username, Password: password, Name: fd.Name, Email: fd.Email, AuthToken: token, Authorized: 1, Admin: 1, Organization: org.Identifier, }) if err != nil { return u.logDBError(err) } _, err = u.db.Repo().CreateHeartbeat(&dto.Heartbeat{ User: newUser.Identifier, CreatedAt: time.Now(), IpAddr: r.RemoteAddr, AuthToken: token, }) if err != nil { return u.logDBError(err) } jwtToken, err := util.CreateTokenForUser(fd.Username) if err != nil { return err } http.SetCookie(w, &http.Cookie{ Name: "token", Value: jwtToken, Expires: time.Now().Add(time.Hour * 1), Path: "/", }) return util.Redirect(w, r, "/signin", http.StatusSeeOther, false) } return nil } func (u *UserHandler) SignOut(w http.ResponseWriter, r *http.Request) error { sess := u.session.SessionStart(w, r) err := sess.Delete("username") if err != nil { return err } err = sess.Delete("token") if err != nil { return err } http.SetCookie(w, &http.Cookie{ Name: "token", Value: "", Expires: time.Now().Add(-time.Hour), Path: "/", }) return util.Redirect(w, r, "/", http.StatusSeeOther, true) } func (u *UserHandler) logDBError(err error) error { u.db.Error(err) return err } func (u *UserHandler) failWithFormError(w http.ResponseWriter, r *http.Request, formError string) error { sess := u.session.SessionStart(w, r) err := sess.Set("formError", formError) if err != nil { return err } return util.Redirect(w, r, r.URL.Path, http.StatusSeeOther, false) }