package web
import (
"errors"
"net/http"
"strconv"
"strings"
"go.bigb.es/sourcehut-dolt/db"
)
// keysView is the dolt-key management page model.
type keysView struct {
basePage
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{
basePage: a.newBasePage(r, "Dolt keys — "+serviceName),
Keys: keys,
Error: errMsg,
Notice: notice,
}
a.render(w, status, "keys.html", view)
}
// handleKeys renders the dolt-key page: the user's registered keys and the
// add-key form. The page reads a `#<pubkey-base32>` 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 and a same-origin POST are required.
func (a *app) handleKeysPost(w http.ResponseWriter, r *http.Request) {
ac := a.requireLogin(w, r)
if ac == nil {
return
}
if !a.checkSameOrigin(r) {
a.forbidden(w, r, "Cross-origin request rejected.")
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.")
}