package routes import ( "crypto/rand" "database/sql" "html/template" "net/http" "net/url" "git.soup.land/soup/shelves/internal/auth" "git.soup.land/soup/shelves/internal/errorsx" "git.soup.land/soup/shelves/internal/forms" "git.soup.land/soup/shelves/internal/httpx" "git.soup.land/soup/shelves/internal/templates/components" ) func settingsRenderView(f settingsForm, e settingsFormErrors) template.HTML { body := components.Form{ Action: "/settings", Fields: []components.Field{ components.Field{ Label: "Display name", Name: "displayName", Placeholder: "Jane Doe", Value: f.displayName, Error: errorsx.String(e.displayName), }, components.Field{ Label: "Password", Type: "password", Placeholder: "Password", Name: "password", Error: errorsx.String(e.password), }, components.Field{ Label: "Confirm password", Placeholder: "Confirm Password", Type: "password", Name: "passwordConfirmation", }, }, }.HTML() return components.Page{Title: "Set up", Body: body}.HTML() } func queryGetOwnerSettings(db *sql.DB) (settingsForm, error) { form := settingsForm{} err := db.QueryRow(`select display_name from owner_settings`).Scan(&form.displayName) if err == sql.ErrNoRows { err = nil } return form, err } func SettingsGet(w http.ResponseWriter, r *http.Request) { ctx := httpx.GetCtx(r) if !ctx.SessionInfo.IsAdmin && !ctx.NeedsOwnerSetup { httpx.LoginRedirect(w, *r.URL) return } form, err := queryGetOwnerSettings(ctx.DB) if err != nil { httpx.InternalServerError(w, err) return } html(w, settingsRenderView(form, settingsFormErrors{})) } type settingsForm struct { password string displayName string } type settingsFormErrors struct { password error displayName error } func parseForm(f *settingsForm, e *settingsFormErrors, vs url.Values, v *forms.Validator) { f.password = vs.Get("password") passwordConfirmation := vs.Get("passwordConfirmation") f.displayName = vs.Get("displayName") e.password = v.MinLength(f.password, 1) if e.password == nil && f.password != passwordConfirmation { e.password = v.Fail("Passwords did not match") } e.displayName = v.MinLength(f.displayName, 1) } func queryUpdateOwnerSettings(db *sql.DB, display_name string, password_salt []byte, password_hash []byte) error { _, err := db.Exec(` insert or replace into owner_settings (display_name, password_salt, password_hash) values (?, ?, ?) `, display_name, password_salt, password_hash) return err } func updateOwnerSettings(db *sql.DB, f settingsForm) error { salt := make([]byte, 32) _, err := rand.Read(salt) if err != nil { return err } hash := auth.HashPassword([]byte(f.password), salt) return queryUpdateOwnerSettings(db, f.displayName, salt, hash) } func SettingsPost(w http.ResponseWriter, r *http.Request) { ctx := httpx.GetCtx(r) if !ctx.SessionInfo.IsAdmin && !ctx.NeedsOwnerSetup { httpx.Unauthorized(w) return } form := settingsForm{} errs := settingsFormErrors{} failed := forms.ParseFormData(r, func(vs url.Values, v *forms.Validator) { parseForm(&form, &errs, vs, v) }) if failed { w.WriteHeader(http.StatusBadRequest) html(w, settingsRenderView(form, errs)) return } err := updateOwnerSettings(ctx.DB, form) if err != nil { httpx.InternalServerError(w, err) } else { httpx.SeeOther(w, "/") } }