package web import ( "errors" "net/http" "strconv" "strings" "sourcecraft.dev/bigbes/sr-ht-ecore/chrome" "sourcecraft.dev/bigbes/sr-ht-dolt/db" ) // keysView is the dolt-key management page model. type keysView struct { chrome.Page Keys []*db.DoltKey Error string Notice string } func (a *app) renderKeys(w http.ResponseWriter, r *http.Request, ac *authContext, status int, errMsg, notice string) { keys, err := a.cfg.Repos.ListKeysByUser(r.Context(), ac.UserID) if err != nil { http.Error(w, "failed to list keys", http.StatusInternalServerError) return } view := keysView{ Page: a.page(r, "Dolt keys — "+serviceName), Keys: keys, Error: errMsg, Notice: notice, } a.render(w, status, "keys", view) } // handleKeys renders the dolt-key page: the user's registered keys and the // add-key form. The page reads a `#` URL fragment (which // `dolt login` appends) into the form via a few lines of inline JS, but works // without JS too — the user can paste the key the CLI printed. Login required. func (a *app) handleKeys(w http.ResponseWriter, r *http.Request) { ac := a.requireLogin(w, r) if ac == nil { return } a.renderKeys(w, r, ac, http.StatusOK, "", "") } // handleKeysPost adds or deletes a dolt key. A form carrying `delete_id` removes // that key; otherwise `pubkey` (the base32 string dolt emits) is decoded, // validated and registered. Login is required; the same-origin check is the // router's (csrf.Require) and has already run. func (a *app) handleKeysPost(w http.ResponseWriter, r *http.Request) { ac := a.requireLogin(w, r) if ac == nil { return } if err := r.ParseForm(); err != nil { a.renderKeys(w, r, ac, http.StatusBadRequest, "Malformed form submission.", "") return } if idStr := r.PostFormValue("delete_id"); idStr != "" { a.keysDelete(w, r, ac, idStr) return } a.keysAdd(w, r, ac) } // keysAdd decodes and registers a dolt public key for the caller. func (a *app) keysAdd(w http.ResponseWriter, r *http.Request, ac *authContext) { pubStr := strings.TrimSpace(r.PostFormValue("pubkey")) comment := strings.TrimSpace(r.PostFormValue("comment")) pubkey, kid, err := decodeDoltPubKey(pubStr) if err != nil { a.renderKeys(w, r, ac, http.StatusBadRequest, "Invalid public key: "+err.Error(), "") return } if _, err := a.cfg.Repos.InsertKey(r.Context(), ac.UserID, kid, pubkey, comment); err != nil { if errors.Is(err, db.ErrKeyExists) { a.renderKeys(w, r, ac, http.StatusConflict, "That key is already registered.", "") return } http.Error(w, "failed to register key", http.StatusInternalServerError) return } a.renderKeys(w, r, ac, http.StatusOK, "", "Key added. You can now use `dolt clone`/`push` without --user.") } // keysDelete removes one of the caller's keys, scoped by user id so a user can // only delete their own keys. func (a *app) keysDelete(w http.ResponseWriter, r *http.Request, ac *authContext, idStr string) { id, err := strconv.Atoi(idStr) if err != nil { a.renderKeys(w, r, ac, http.StatusBadRequest, "Invalid key id.", "") return } if err := a.cfg.Repos.DeleteKey(r.Context(), id, ac.UserID); err != nil { if errors.Is(err, db.ErrNotFound) { a.renderKeys(w, r, ac, http.StatusNotFound, "No such key.", "") return } http.Error(w, "failed to delete key", http.StatusInternalServerError) return } a.renderKeys(w, r, ac, http.StatusOK, "", "Key deleted.") }