package app import ( "errors" md "farga/app/middleware" db "farga/db" "farga/db/types" "farga/lib" "farga/templating" "net/http" "strings" "github.com/google/uuid" "github.com/gorilla/mux" ) type RegisterForm struct { Name string `form:"name" validate:"required,min=2,max=40"` Handle string `form:"handle" validate:"required,min=3,max=30"` Email string `form:"email" validate:"required,email"` Password string `form:"password" validate:"required,min=8"` InviteToken string `form:"invite-token"` } type LoginForm struct { Email string `form:"email" validate:"required,email"` Password string `form:"password" validate:"required,min=8"` } func (a *App) Login(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": templating.Render(w, r, "base", "login", map[string]any{ "AllowSignups": a.Config.AllowSignups, }) case "POST": form, _ := md.Form[LoginForm](r) user, err := a.Store.LoginUser(form.Email, form.Password) if err != nil && (errors.Is(err, db.UserNotFound) || errors.Is(err, db.IncorrectPassword)) { // user not found or incorrect password http.Redirect(w, r, "/auth/login?err=InvalidCredentials", http.StatusSeeOther) return } session, err := a.Store.NewSession(&user.ID) if err != nil { http.Error(w, "Something went wrong while creating your session.", http.StatusInternalServerError) return } lib.SetCookie(w, "session", session.ID.String()) http.Redirect(w, r, "/?msg=AuthSuccess", http.StatusSeeOther) } } func (a *App) Register(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) switch r.Method { case "GET": inviteToken := vars["token"] // if signups are disallowed, but the user has an inviteToken if inviteToken != "" { invite, ok := a.Store.ValidateInviteToken(inviteToken) if ok { // allow user to register templating.Render(w, r, "base", "register", map[string]any{ "HandleRegex": lib.HandleRegex.String(), "Invite": invite, }) } else { // token is invvalid/expired templating.Render(w, r, "base", "invite/invalid_token") } } else if a.Config.AllowSignups { // if signups are allowed templating.Render(w, r, "base", "register", map[string]any{ "HandleRegex": lib.HandleRegex.String(), }) } else { // if neither, just reject them. templating.Render(w, r, "base", "invite_only") } case "POST": form, ok := md.Form[RegisterForm](r) if !ok { http.Error(w, "Internal error.", http.StatusInternalServerError) return } if !a.Config.AllowSignups && form.InviteToken == "" { http.Redirect(w, r, "/auth/register?err=Forbidden", http.StatusSeeOther) return } var invite types.Invite if form.InviteToken != "" { invite, ok = a.Store.ValidateInviteToken(form.InviteToken) if !ok { templating.Render(w, r, "base", "invite/invalid_token") return } } if !lib.ValidateHandle(form.Handle) { http.Redirect(w, r, "?err=UserInvalidHandle", http.StatusSeeOther) return } if !lib.ValidateUsername(form.Name) { http.Redirect(w, r, "?err=UserInvalidHandle", http.StatusSeeOther) return } password, err := lib.GenerateHash(form.Password) if err != nil { http.Error(w, "Something went wrong while generating your password's hash. Please try again!", 500) return } user := types.User{ Name: strings.TrimSpace(form.Name), Handle: strings.TrimSpace(form.Handle), Email: strings.TrimSpace(form.Email), PasswordHash: password, Config: types.UserConfig{ GitDefaultBranch: lib.GitDefaultBranch, }, } // set to admin if invite token says so. if invite.ID != uuid.Nil && invite.Role == uint(types.RoleAdmin) { user.Role = types.RoleAdmin } else { user.Role = types.RoleUser } if form.InviteToken != "" { err = a.Store.NewUserWithInvite(&user, form.InviteToken) } else { err = a.Store.NewUser(&user) } if err != nil { if errors.Is(err, db.ErrInvalidInvite) { templating.Render(w, r, "base", "invite/invalid_token") return } constrainedField := strings.Replace(err.Error(), "UNIQUE constraint failed: users.", "That ", 1) + " has already been claimed." http.Error(w, constrainedField, 500) return } session, err := a.Store.NewSession(&user.ID) if err != nil { http.Error(w, "Something went wrong while creating your session.", http.StatusInternalServerError) return } lib.SetCookie(w, "session", session.ID.String()) http.Redirect(w, r, "/?msg=AuthSuccess", http.StatusSeeOther) } } func (a *App) Logout(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": templating.Render(w, r, "base", "logout") case "POST": session := r.Context().Value(lib.SessionKey).(string) err := a.Store.DeleteSession(session) if err != nil { http.Error(w, "Something went wrong while creating your session.", http.StatusInternalServerError) return } lib.DeleteCookie(w, "session") http.Redirect(w, r, "/auth/login?msg=LogoutSuccess", http.StatusFound) } }