internal/httpd/account.go

70dc0648f931f6f7112c6b71b0be485eeae4077f
gitbay/internal/httpd/account.go history · blame · raw

185 lines · 5152 bytes

  1package httpd
  2
  3import (
  4	"encoding/json"
  5	"net/http"
  6	"net/url"
  7	"strings"
  8
  9	"gitbay.org/gitbay/internal/control"
 10	"gitbay.org/gitbay/internal/store"
 11)
 12
 13// accountKey is one SSH key as the settings page shows it: enough to
 14// recognise which key this is without printing the whole blob.
 15type accountKey struct {
 16	Fingerprint string
 17	Algo        string
 18	Scope       string
 19}
 20
 21type accountPGP struct {
 22	Fingerprint string
 23	UIDs        []string
 24	Expired     bool
 25	Revoked     bool
 26}
 27
 28// accountForm renders the account's own settings: keys, addresses, and the
 29// commands for everything that stays on SSH.
 30func (s *Server) accountForm(w http.ResponseWriter, r *http.Request, u store.User) {
 31	var keys []accountKey
 32	if list, err := s.st.ListSSHKeys(u.ID); err == nil {
 33		for _, k := range list {
 34			keys = append(keys, accountKey{Fingerprint: k.Fingerprint, Algo: k.Algo, Scope: k.Scope})
 35		}
 36	}
 37	var pgp []accountPGP
 38	if list, err := s.st.ListPGPKeys(u.ID); err == nil {
 39		for _, k := range list {
 40			var uids []string
 41			json.Unmarshal([]byte(k.UIDsJSON), &uids)
 42			pgp = append(pgp, accountPGP{
 43				Fingerprint: k.Fingerprint, UIDs: uids,
 44				Expired: k.ExpiresAt != nil, Revoked: k.RevokedAt != nil,
 45			})
 46		}
 47	}
 48	emails, _ := s.st.ListEmails(u.ID)
 49
 50	var profile control.ProfileOut
 51	s.runControlInto(u, []string{"profile", "show"}, &profile)
 52
 53	s.render(w, "account.html", struct {
 54		basePage
 55		Tab       string // marks the rail's Settings row as current
 56		Keys      []accountKey
 57		PGP       []accountPGP
 58		Emails    []store.Email
 59		Profile   control.ProfileOut
 60		LinksText string
 61		Host      string
 62		Notice    string
 63		Message   string
 64	}{s.baseFor(u), "account", keys, pgp, emails, profile, profileLinksText(profile.Links), s.cfg.SiteHost(),
 65		s.takeFlash(w, r), r.URL.Query().Get("m")})
 66}
 67
 68// profileLinksText turns a profile's links into the form the textarea
 69// shows and reads back: one per line, "label|url" when there is a label
 70// and the bare url otherwise.
 71func profileLinksText(links []store.ProfileLink) string {
 72	lines := make([]string, len(links))
 73	for i, l := range links {
 74		if l.Label != "" {
 75			lines[i] = l.Label + "|" + l.URL
 76		} else {
 77			lines[i] = l.URL
 78		}
 79	}
 80	return strings.Join(lines, "\n")
 81}
 82
 83// profileLinkArgs turns the textarea back into the --link values profile
 84// set expects: one per non-blank line, or a single empty one to clear the
 85// list when the field was emptied.
 86func profileLinkArgs(raw string) []string {
 87	var links []string
 88	for _, line := range strings.Split(raw, "\n") {
 89		if line = strings.TrimSpace(line); line != "" {
 90			links = append(links, line)
 91		}
 92	}
 93	if links == nil {
 94		return []string{""}
 95	}
 96	return links
 97}
 98
 99// accountSubmit routes the account forms to their commands. Keys,
100// addresses and the profile are the whole surface — no secret is accepted
101// over the web.
102func (s *Server) accountSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
103	back := func(msg, note string) {
104		q := ""
105		if note != "" {
106			q = "?m=" + url.QueryEscape(note)
107		}
108		s.setFlash(w, msg)
109		http.Redirect(w, r, "/settings"+q, http.StatusSeeOther)
110	}
111
112	switch r.FormValue("field") {
113	case "key-add":
114		body := strings.TrimSpace(r.FormValue("key"))
115		if body == "" {
116			back("paste a public key in authorized_keys format", "")
117			return
118		}
119		argv := []string{"keys", "add"}
120		if scope := r.FormValue("scope"); scope == "git" {
121			argv = append(argv, "--scope", "git")
122		}
123		if msg, ok := s.runControlStdin(u, argv, body+"\n"); !ok {
124			back(msg, "")
125			return
126		}
127		back("", "key registered")
128	case "key-remove":
129		if _, msg, ok := s.runControl(u, []string{"keys", "remove", r.FormValue("fingerprint")}); !ok {
130			back(msg, "")
131			return
132		}
133		back("", "key removed")
134	case "pgp-add":
135		body := strings.TrimSpace(r.FormValue("key"))
136		if body == "" {
137			back("paste an armored OpenPGP public key", "")
138			return
139		}
140		if msg, ok := s.runControlStdin(u, []string{"pgp", "add"}, body+"\n"); !ok {
141			back(msg, "")
142			return
143		}
144		back("", "PGP key registered")
145	case "pgp-remove":
146		if _, msg, ok := s.runControl(u, []string{"pgp", "remove", r.FormValue("fingerprint")}); !ok {
147			back(msg, "")
148			return
149		}
150		back("", "PGP key removed")
151	case "email-add":
152		if _, msg, ok := s.runControl(u, []string{"email", "add", strings.TrimSpace(r.FormValue("address"))}); !ok {
153			back(msg, "")
154			return
155		}
156		back("", "check that inbox for a verification code")
157	case "email-verify":
158		if _, msg, ok := s.runControl(u, []string{"email", "verify", strings.TrimSpace(r.FormValue("code"))}); !ok {
159			back(msg, "")
160			return
161		}
162		back("", "address verified")
163	case "profile":
164		format := r.FormValue("format")
165		if format != "org" {
166			format = "md"
167		}
168		argv := []string{"profile", "set",
169			"--description", r.FormValue("description"),
170			"--website", r.FormValue("website"),
171			"--about-format", format,
172			"--file", "-",
173		}
174		for _, link := range profileLinkArgs(r.FormValue("links")) {
175			argv = append(argv, "--link", link)
176		}
177		if msg, ok := s.runControlStdin(u, argv, r.FormValue("about")); !ok {
178			back(msg, "")
179			return
180		}
181		back("", "profile updated")
182	default:
183		back("unknown form", "")
184	}
185}