package web import ( "errors" "net/http" "strings" "gardomatic.kleiax.de/lib/client" "github.com/julienschmidt/httprouter" ) type accountForm struct { CSRFToken string `form:"csrf_token"` Name string `form:"name"` Color string `form:"color"` Email string `form:"email"` CurrentPassword string `form:"current_password"` NewPassword string `form:"new_password"` NewPasswordConfirmation string `form:"new_password_confirmation"` Errors map[string]string Message string } func (app *application) account(w http.ResponseWriter, r *http.Request) { user, _ := userFromContext(r.Context()) app.renderAccount(w, r, accountForm{Name: user.Name, Color: user.Color, Email: user.Email, Errors: map[string]string{}}, http.StatusOK) } func (app *application) accountSessionDeletePost(w http.ResponseWriter, r *http.Request) { id := httprouter.ParamsFromContext(r.Context()).ByName("sessionID") response, err := client.FromContext(r.Context()).DeleteAccountSession(r.Context(), id) if err != nil { app.handleAPIError(w, r, err) return } client.ForwardCookies(w, response) http.Redirect(w, r, webPath("account")+gardenQuerySuffix(r), http.StatusSeeOther) } func (app *application) accountProfilePost(w http.ResponseWriter, r *http.Request) { var form accountForm if err := app.decodePostForm(r, &form); err != nil { app.clientError(w, http.StatusBadRequest) return } form.Name = strings.TrimSpace(form.Name) form.Color = strings.TrimSpace(form.Color) form.Errors = map[string]string{} if form.Name == "" { form.Errors["name"] = "Ein Name ist erforderlich." } if len(form.Errors) == 0 { user, _, err := client.FromContext(r.Context()).UpdateAccountProfile(r.Context(), form.Name, form.Color) if err == nil { form.Name, form.Color, form.Email, form.Message = user.Name, user.Color, user.Email, "Profil gespeichert." app.renderAccount(w, r, form, http.StatusOK) return } app.copyAccountError(&form, err) if len(form.Errors) == 0 { app.handleAPIError(w, r, err) return } } app.renderAccount(w, r, form, http.StatusUnprocessableEntity) } func (app *application) accountPasswordPost(w http.ResponseWriter, r *http.Request) { var form accountForm if err := app.decodePostForm(r, &form); err != nil { app.clientError(w, http.StatusBadRequest) return } user, _ := userFromContext(r.Context()) form.Name, form.Color, form.Email, form.Errors = user.Name, user.Color, user.Email, map[string]string{} if form.NewPassword != form.NewPasswordConfirmation { form.Errors["new_password_confirmation"] = "Die Passwörter stimmen nicht überein." } if len(form.NewPassword) < 8 { form.Errors["new_password"] = "Mindestens 8 Zeichen erforderlich." } if len(form.Errors) == 0 { _, err := client.FromContext(r.Context()).UpdateAccountPassword(r.Context(), form.CurrentPassword, form.NewPassword) if err == nil { form.CurrentPassword, form.NewPassword, form.NewPasswordConfirmation = "", "", "" form.Message = "Passwort geändert." app.renderAccount(w, r, form, http.StatusOK) return } app.copyAccountError(&form, err) if len(form.Errors) == 0 { app.handleAPIError(w, r, err) return } } app.renderAccount(w, r, form, http.StatusUnprocessableEntity) } func (app *application) accountEmailPost(w http.ResponseWriter, r *http.Request) { var form accountForm if err := app.decodePostForm(r, &form); err != nil { app.clientError(w, http.StatusBadRequest) return } user, _ := userFromContext(r.Context()) form.Name, form.Color, form.Errors = user.Name, user.Color, map[string]string{} form.Email = strings.TrimSpace(form.Email) if form.Email == "" { form.Errors["email"] = "Eine E-Mail-Adresse ist erforderlich." } if len(form.Errors) == 0 { _, err := client.FromContext(r.Context()).RequestAccountEmailChange(r.Context(), form.Email, form.CurrentPassword) if err == nil { form.CurrentPassword = "" form.Message = "Bestätigungslink wurde an die neue Adresse gesendet." app.renderAccount(w, r, form, http.StatusOK) return } app.copyAccountError(&form, err) if len(form.Errors) == 0 { app.handleAPIError(w, r, err) return } } app.renderAccount(w, r, form, http.StatusUnprocessableEntity) } func (app *application) accountEmailConfirm(w http.ResponseWriter, r *http.Request) { data := app.newTemplateData(r) data.Form = acceptInviteForm{Token: r.URL.Query().Get("token")} app.render(w, http.StatusOK, "email_confirm.tmpl", data) } func (app *application) accountEmailConfirmPost(w http.ResponseWriter, r *http.Request) { var form acceptInviteForm if err := app.decodePostForm(r, &form); err != nil { app.clientError(w, http.StatusBadRequest) return } if _, _, err := client.FromContext(r.Context()).ConfirmAccountEmail(r.Context(), form.Token); err != nil { app.handleAPIError(w, r, err) return } http.Redirect(w, r, webPath("account"), http.StatusSeeOther) } func (app *application) renderAccount(w http.ResponseWriter, r *http.Request, form accountForm, status int) { data := app.newTemplateData(r) data.Form = form if user, ok := userFromContext(r.Context()); ok { data.AccountProtected = app.config.DemoAccountEmail != "" && strings.EqualFold(strings.TrimSpace(user.Email), app.config.DemoAccountEmail) } if !app.loadOptionalGarden(w, r, data) { return } if !data.AccountProtected { sessions, _, err := client.FromContext(r.Context()).AccountSessions(r.Context()) if err != nil { app.handleAPIError(w, r, err) return } data.AccountSessions = sessions } app.render(w, status, "account.tmpl", data) } func (app *application) copyAccountError(form *accountForm, err error) { var apiError *client.APIError if errors.As(err, &apiError) { if apiError.StatusCode == http.StatusUnprocessableEntity { form.Errors = apiError.Validation } if apiError.StatusCode == http.StatusUnauthorized { form.Errors["current_password"] = "Das aktuelle Passwort ist falsch." } } }