A CLI-first git forge.

cli forge git self-hosted

https://gitbay.org

Commit 541f0d571c

541f0d571cd6d178441c0967df41462886b98696

parent: 5a73145992

Verified · cmc ci/build: success

cmc <hello@cleberg.net> · 2026-08-30T16:09:44Z

web: the login page tells a new visitor how to register

/login explained how to mint a session over SSH and stopped there, so a
visitor without an account had nowhere to go from the page they land on.
The landing page carried the only signup link, and requireUser redirects
past it.

The page now branches on registration.mode: open and invite link to
/register and print the equivalent ssh register line, closed says the
instance is not accepting new accounts. Closed needs its own copy rather
than an omission, since /register 404s there.
e2e/websignup_test.go +12
@@ -23,6 +23,11 @@ func TestWebSignup(t *testing.T) {
2323 if status != 200 || !strings.Contains(body, "invite-only") || !strings.Contains(body, `name="key"`) {
2424 t.Fatalf("register form: %d\n%s", status, body)
2525 }
26 // The login page tells a brand-new visitor how to get an account.
27 status, body = inst.get(t, "/login")
28 if status != 200 || !strings.Contains(body, `href="/register"`) || !strings.Contains(body, "invite-only") {
29 t.Fatalf("login signup hint: %d\n%s", status, body)
30 }
2631
2732 // Invite issued over the admin path; redeemed through the browser.
2833 inst.admin(t, "admin", "invite", "--email", "erin@example.test")
@@ -76,4 +81,11 @@ func TestWebSignupClosedInstance(t *testing.T) {
7681 if strings.Contains(body, `href="/register"`) {
7782 t.Fatal("closed landing advertises signup")
7883 }
84 _, body = inst.get(t, "/login")
85 if strings.Contains(body, `href="/register"`) {
86 t.Fatal("closed login page advertises signup")
87 }
88 if !strings.Contains(body, "not accepting new accounts") {
89 t.Fatalf("closed login page says nothing about accounts:\n%s", body)
90 }
7991 }
internal/httpd/accounts.go +12 −9
@@ -60,22 +60,25 @@ func (s *Server) checkOrigin(h http.HandlerFunc) http.HandlerFunc {
6060 }
6161 }
6262
63// renderLogin draws the login page. Mode carries the registration mode so
64// the page can tell a brand-new visitor how to get an account.
65func (s *Server) renderLogin(w http.ResponseWriter, errMsg string) {
66 s.render(w, "login.html", struct {
67 basePage
68 Mode string // closed | invite | open
69 Error string
70 }{basePage{Site: s.siteName(), Host: s.cfg.SiteHost()}, s.cfg.Registration.Mode, errMsg})
71}
72
6373 func (s *Server) login(w http.ResponseWriter, r *http.Request) {
6474 token := r.URL.Query().Get("token")
6575 if token == "" {
66 s.render(w, "login.html", struct {
67 basePage
68 Error string
69 }{basePage{Site: s.siteName(), Host: s.cfg.SiteHost()}, ""})
76 s.renderLogin(w, "")
7077 return
7178 }
7279 userID, err := s.st.ConsumeLoginToken(store.HashToken(token))
7380 if err != nil {
74 s.render(w, "login.html", struct {
75 basePage
76 Error string
77 }{basePage{Site: s.siteName(), Host: s.cfg.SiteHost()},
78 "that login link is invalid, expired, or already used — mint a new one"})
81 s.renderLogin(w, "that login link is invalid, expired, or already used — mint a new one")
7982 return
8083 }
8184 sessTok, sessHash, err := store.NewToken()
internal/web/templates/login.html +9
@@ -6,4 +6,13 @@
66 with your registered key:</p>
77 <pre class="message">ssh git@{{.Host}} web login</pre>
88 <p>then open the printed URL within five minutes.</p>
9<h2>New here?</h2>
10{{if eq .Mode "open"}}<p><a href="/register">Create an account</a> — pick a username, paste your SSH
11public key, verify your email. Or from the terminal:</p>
12<pre class="message">ssh git@{{.Host}} register --username you --email you@example.org</pre>
13{{else if eq .Mode "invite"}}<p>This instance is invite-only. With a code from an admin,
14<a href="/register">create an account</a> — pick a username, paste your SSH public key. Or from
15the terminal:</p>
16<pre class="message">ssh git@{{.Host}} register --username you --invite &lt;code&gt;</pre>
17{{else}}<p>This instance is not accepting new accounts.</p>{{end}}
918 {{end}}