internal/httpd/account.go
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}