arche / internal/archesrv/handlers_auth.go

commit 154431fd
  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}