package web
import (
"errors"
"net/http"
"net/url"
"strconv"
"strings"
"sourcecraft.dev/bigbes/sr-ht-ecore/chrome"
"sourcecraft.dev/bigbes/sr-ht-ecore/pages"
"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 `#<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 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
}
form, err := pages.FormValues(w, r, 0)
if err != nil {
a.renderKeys(w, r, ac, http.StatusBadRequest, "Malformed form submission.", "")
return
}
if idStr := form.Get("delete_id"); idStr != "" {
a.keysDelete(w, r, ac, idStr)
return
}
a.keysAdd(w, r, ac, form)
}
// keysAdd decodes and registers a dolt public key for the caller.
func (a *app) keysAdd(w http.ResponseWriter, r *http.Request, ac *authContext, form url.Values) {
pubStr := strings.TrimSpace(form.Get("pubkey"))
comment := strings.TrimSpace(form.Get("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.")
}