| 1 |
|
package web |
| 2 |
|
|
| 3 |
|
import ( |
| 4 |
|
"errors" |
| 5 |
|
"net/http" |
| 6 |
|
"net/url" |
| 7 |
|
"strconv" |
| 8 |
|
"strings" |
| 9 |
|
|
| 10 |
|
"sourcecraft.dev/bigbes/sr-ht-ecore/chrome" |
| 11 |
|
"sourcecraft.dev/bigbes/sr-ht-ecore/pages" |
| 12 |
|
|
| 13 |
|
"sourcecraft.dev/bigbes/sr-ht-dolt/db" |
| 14 |
|
) |
| 15 |
|
|
| 16 |
|
// keysView is the dolt-key management page model. |
| 17 |
|
type keysView struct { |
| 18 |
|
chrome.Page |
| 19 |
|
Keys []*db.DoltKey |
| 20 |
|
Error string |
| 21 |
|
Notice string |
| 22 |
|
} |
| 23 |
|
|
| 24 |
4 |
func (a *app) renderKeys(w http.ResponseWriter, r *http.Request, ac *authContext, status int, errMsg, notice string) { |
| 25 |
4 |
keys, err := a.cfg.Repos.ListKeysByUser(r.Context(), ac.UserID) |
| 26 |
4 |
if err != nil { |
| 27 |
0 |
http.Error(w, "failed to list keys", http.StatusInternalServerError) |
| 28 |
0 |
return |
| 29 |
0 |
} |
| 30 |
4 |
view := keysView{ |
| 31 |
4 |
Page: a.page(r, "Dolt keys — "+serviceName), |
| 32 |
4 |
Keys: keys, |
| 33 |
4 |
Error: errMsg, |
| 34 |
4 |
Notice: notice, |
| 35 |
4 |
} |
| 36 |
4 |
a.render(w, status, "keys", view) |
| 37 |
|
} |
| 38 |
|
|
| 39 |
|
// handleKeys renders the dolt-key page: the user's registered keys and the |
| 40 |
|
// add-key form. The page reads a `#<pubkey-base32>` URL fragment (which |
| 41 |
|
// `dolt login` appends) into the form via a few lines of inline JS, but works |
| 42 |
|
// without JS too — the user can paste the key the CLI printed. Login required. |
| 43 |
1 |
func (a *app) handleKeys(w http.ResponseWriter, r *http.Request) { |
| 44 |
1 |
ac := a.requireLogin(w, r) |
| 45 |
1 |
if ac == nil { |
| 46 |
0 |
return |
| 47 |
0 |
} |
| 48 |
1 |
a.renderKeys(w, r, ac, http.StatusOK, "", "") |
| 49 |
|
} |
| 50 |
|
|
| 51 |
|
// handleKeysPost adds or deletes a dolt key. A form carrying `delete_id` removes |
| 52 |
|
// that key; otherwise `pubkey` (the base32 string dolt emits) is decoded, |
| 53 |
|
// validated and registered. Login is required; the same-origin check is the |
| 54 |
|
// router's (csrf.Require) and has already run. |
| 55 |
3 |
func (a *app) handleKeysPost(w http.ResponseWriter, r *http.Request) { |
| 56 |
3 |
ac := a.requireLogin(w, r) |
| 57 |
3 |
if ac == nil { |
| 58 |
0 |
return |
| 59 |
0 |
} |
| 60 |
3 |
form, err := pages.FormValues(w, r, 0) |
| 61 |
3 |
if err != nil { |
| 62 |
0 |
a.renderKeys(w, r, ac, http.StatusBadRequest, "Malformed form submission.", "") |
| 63 |
0 |
return |
| 64 |
0 |
} |
| 65 |
|
|
| 66 |
3 |
if idStr := form.Get("delete_id"); idStr != "" { |
| 67 |
1 |
a.keysDelete(w, r, ac, idStr) |
| 68 |
1 |
return |
| 69 |
1 |
} |
| 70 |
2 |
a.keysAdd(w, r, ac, form) |
| 71 |
|
} |
| 72 |
|
|
| 73 |
|
// keysAdd decodes and registers a dolt public key for the caller. |
| 74 |
2 |
func (a *app) keysAdd(w http.ResponseWriter, r *http.Request, ac *authContext, form url.Values) { |
| 75 |
2 |
pubStr := strings.TrimSpace(form.Get("pubkey")) |
| 76 |
2 |
comment := strings.TrimSpace(form.Get("comment")) |
| 77 |
2 |
|
| 78 |
2 |
pubkey, kid, err := decodeDoltPubKey(pubStr) |
| 79 |
2 |
if err != nil { |
| 80 |
1 |
a.renderKeys(w, r, ac, http.StatusBadRequest, "Invalid public key: "+err.Error(), "") |
| 81 |
1 |
return |
| 82 |
1 |
} |
| 83 |
|
|
| 84 |
1 |
if _, err := a.cfg.Repos.InsertKey(r.Context(), ac.UserID, kid, pubkey, comment); err != nil { |
| 85 |
0 |
if errors.Is(err, db.ErrKeyExists) { |
| 86 |
0 |
a.renderKeys(w, r, ac, http.StatusConflict, "That key is already registered.", "") |
| 87 |
0 |
return |
| 88 |
0 |
} |
| 89 |
0 |
http.Error(w, "failed to register key", http.StatusInternalServerError) |
| 90 |
0 |
return |
| 91 |
|
} |
| 92 |
1 |
a.renderKeys(w, r, ac, http.StatusOK, "", "Key added. You can now use `dolt clone`/`push` without --user.") |
| 93 |
|
} |
| 94 |
|
|
| 95 |
|
// keysDelete removes one of the caller's keys, scoped by user id so a user can |
| 96 |
|
// only delete their own keys. |
| 97 |
1 |
func (a *app) keysDelete(w http.ResponseWriter, r *http.Request, ac *authContext, idStr string) { |
| 98 |
1 |
id, err := strconv.Atoi(idStr) |
| 99 |
1 |
if err != nil { |
| 100 |
0 |
a.renderKeys(w, r, ac, http.StatusBadRequest, "Invalid key id.", "") |
| 101 |
0 |
return |
| 102 |
0 |
} |
| 103 |
1 |
if err := a.cfg.Repos.DeleteKey(r.Context(), id, ac.UserID); err != nil { |
| 104 |
0 |
if errors.Is(err, db.ErrNotFound) { |
| 105 |
0 |
a.renderKeys(w, r, ac, http.StatusNotFound, "No such key.", "") |
| 106 |
0 |
return |
| 107 |
0 |
} |
| 108 |
0 |
http.Error(w, "failed to delete key", http.StatusInternalServerError) |
| 109 |
0 |
return |
| 110 |
|
} |
| 111 |
1 |
a.renderKeys(w, r, ac, http.StatusOK, "", "Key deleted.") |
| 112 |
|
} |