1package archesrv
2
3import (
4 "net/http"
5 "strings"
6 "time"
7)
8
9type setupData struct {
10 User *User
11 RegistrationOpen bool
12 Error string
13}
14
15func (s *forgeServer) handleSetup(w http.ResponseWriter, r *http.Request) {
16 has, _ := s.db.HasAnyUser()
17 if has {
18 http.Redirect(w, r, "/login", http.StatusFound)
19 return
20 }
21 s.render(w, "srv_setup.html", setupData{})
22}
23
24func (s *forgeServer) handleSetupPost(w http.ResponseWriter, r *http.Request) {
25 has, _ := s.db.HasAnyUser()
26 if has {
27 http.Redirect(w, r, "/login", http.StatusFound)
28 return
29 }
30
31 r.ParseForm() //nolint:errcheck
32 username := strings.TrimSpace(r.FormValue("username"))
33 password := r.FormValue("password")
34
35 if username == "" || password == "" {
36 s.render(w, "srv_setup.html", setupData{Error: "username and password required"})
37 return
38 }
39
40 u, err := s.db.CreateUser(username, password, true)
41 if err != nil {
42 s.render(w, "srv_setup.html", setupData{Error: "create user: " + err.Error()})
43 return
44 }
45
46 tok, err := s.db.CreateSession(u.ID)
47 if err != nil {
48 http.Redirect(w, r, "/login", http.StatusFound)
49 return
50 }
51
52 http.SetCookie(w, &http.Cookie{
53 Name: sessionCookie,
54 Value: tok,
55 Path: "/",
56 HttpOnly: true,
57 Expires: time.Now().Add(sessionTTL),
58 })
59 http.Redirect(w, r, "/", http.StatusFound)
60}
61
62type loginData struct {
63 User *User
64 RegistrationOpen bool
65 Error string
66}
67
68func (s *forgeServer) handleLoginPage(w http.ResponseWriter, r *http.Request) {
69 s.render(w, "srv_login.html", loginData{RegistrationOpen: s.cfg.Auth.Registration == "open"})
70}
71
72func (s *forgeServer) handleLoginPost(w http.ResponseWriter, r *http.Request) {
73 r.ParseForm() //nolint:errcheck
74 username := r.FormValue("username")
75 password := r.FormValue("password")
76
77 u, hash, err := s.db.GetUserByName(username)
78 if err != nil || u == nil || !checkPassword(hash, password) {
79 s.render(w, "srv_login.html", loginData{Error: "invalid username or password"})
80 return
81 }
82
83 tok, err := s.db.CreateSession(u.ID)
84 if err != nil {
85 http.Error(w, "session: "+err.Error(), http.StatusInternalServerError)
86 return
87 }
88
89 http.SetCookie(w, &http.Cookie{
90 Name: sessionCookie,
91 Value: tok,
92 Path: "/",
93 HttpOnly: true,
94 Expires: time.Now().Add(sessionTTL),
95 })
96 http.Redirect(w, r, "/", http.StatusFound)
97}
98
99func (s *forgeServer) handleLogout(w http.ResponseWriter, r *http.Request) {
100 if c, err := r.Cookie(sessionCookie); err == nil {
101 s.db.DeleteSession(c.Value) //nolint:errcheck
102 http.SetCookie(w, &http.Cookie{
103 Name: sessionCookie,
104 Value: "",
105 Path: "/",
106 MaxAge: -1,
107 Expires: time.Unix(0, 0),
108 })
109 }
110 http.Redirect(w, r, "/", http.StatusFound)
111}
112
113type registerData struct {
114 User *User
115 RegistrationOpen bool
116 Error string
117 InviteRequired bool
118}
119
120func (s *forgeServer) handleRegisterPage(w http.ResponseWriter, r *http.Request) {
121 s.render(w, "srv_register.html", registerData{InviteRequired: s.cfg.Auth.Registration == "invite"})
122}
123
124func (s *forgeServer) handleRegisterPost(w http.ResponseWriter, r *http.Request) {
125 r.ParseForm() //nolint:errcheck
126 username := strings.TrimSpace(r.FormValue("username"))
127 password := r.FormValue("password")
128 confirm := r.FormValue("confirm")
129 inviteToken := strings.TrimSpace(r.FormValue("invite_token"))
130
131 rd := registerData{InviteRequired: s.cfg.Auth.Registration == "invite"}
132
133 if username == "" || password == "" {
134 rd.Error = "username and password required"
135 s.render(w, "srv_register.html", rd)
136 return
137 }
138 if password != confirm {
139 rd.Error = "passwords do not match"
140 s.render(w, "srv_register.html", rd)
141 return
142 }
143
144 if s.cfg.Auth.Registration == "invite" {
145 if inviteToken == "" {
146 rd.Error = "invite token required"
147 s.render(w, "srv_register.html", rd)
148 return
149 }
150 inv, err := s.db.GetInvite(inviteToken)
151 if err != nil || inv == nil || inv.UsedBy != nil {
152 rd.Error = "invalid or already-used invite token"
153 s.render(w, "srv_register.html", rd)
154 return
155 }
156 }
157
158 u, err := s.db.CreateUser(username, password, false)
159 if err != nil {
160 rd.Error = "could not create account: " + err.Error()
161 s.render(w, "srv_register.html", rd)
162 return
163 }
164
165 if s.cfg.Auth.Registration == "invite" {
166 s.db.UseInvite(inviteToken, u.ID) //nolint:errcheck
167 }
168
169 tok, err := s.db.CreateSession(u.ID)
170 if err != nil {
171 http.Redirect(w, r, "/login", http.StatusFound)
172 return
173 }
174 http.SetCookie(w, &http.Cookie{
175 Name: sessionCookie,
176 Value: tok,
177 Path: "/",
178 HttpOnly: true,
179 Expires: time.Now().Add(sessionTTL),
180 })
181 http.Redirect(w, r, "/", http.StatusFound)
182}
183
184type repoListItem struct {
185 Name string
186 Description string
187 Visibility string
188 CreatedAt string
189 LastCommit string
190}
191
192type indexData struct {
193 User *User
194 Repos []repoListItem
195 RegistrationOpen bool
196}
197
198func (s *forgeServer) handleIndex(w http.ResponseWriter, r *http.Request) {
199 if r.URL.Path != "/" {
200 http.NotFound(w, r)
201 return
202 }
203
204 has, _ := s.db.HasAnyUser()
205 if !has {
206 http.Redirect(w, r, "/setup", http.StatusFound)
207 return
208 }
209
210 user := s.db.currentUser(r)
211 recs, _ := s.db.ListRepos()
212
213 var items []repoListItem
214 for _, rec := range recs {
215 if !s.db.CanRead(&rec, user) {
216 continue
217 }
218
219 item := repoListItem{
220 Name: rec.Name,
221 Description: rec.Description,
222 Visibility: rec.Visibility,
223 CreatedAt: rec.CreatedAt.Format("2006-01-02"),
224 }
225
226 if r, err := openRepo(s.dataDir(), rec.Name); err == nil {
227 if c, _, err2 := r.HeadCommit(); err2 == nil && c.Message != "" {
228 msg := c.Message
229 if idx := strings.IndexByte(msg, '\n'); idx >= 0 {
230 msg = msg[:idx]
231 }
232 item.LastCommit = msg
233 }
234 r.Close()
235 }
236 items = append(items, item)
237 }
238
239 s.render(w, "srv_repo_list.html", indexData{
240 User: user,
241 Repos: items,
242 RegistrationOpen: s.cfg.Auth.Registration == "open",
243 })
244}