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) { |
| 23 | 23 | if status != 200 || !strings.Contains(body, "invite-only") || !strings.Contains(body, `name="key"`) { |
| 24 | 24 | t.Fatalf("register form: %d\n%s", status, body) |
| 25 | 25 | } |
| 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 | } |
| 26 | 31 | |
| 27 | 32 | // Invite issued over the admin path; redeemed through the browser. |
| 28 | 33 | inst.admin(t, "admin", "invite", "--email", "erin@example.test") |
| @@ -76,4 +81,11 @@ func TestWebSignupClosedInstance(t *testing.T) { |
| 76 | 81 | if strings.Contains(body, `href="/register"`) { |
| 77 | 82 | t.Fatal("closed landing advertises signup") |
| 78 | 83 | } |
| 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 | } |
| 79 | 91 | } |
internal/httpd/accounts.go
+12 −9
| @@ -60,22 +60,25 @@ func (s *Server) checkOrigin(h http.HandlerFunc) http.HandlerFunc { |
| 60 | 60 | } |
| 61 | 61 | } |
| 62 | 62 | |
| 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. |
| 65 | func (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 | |
| 63 | 73 | func (s *Server) login(w http.ResponseWriter, r *http.Request) { |
| 64 | 74 | token := r.URL.Query().Get("token") |
| 65 | 75 | 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, "") |
| 70 | 77 | return |
| 71 | 78 | } |
| 72 | 79 | userID, err := s.st.ConsumeLoginToken(store.HashToken(token)) |
| 73 | 80 | 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") |
| 79 | 82 | return |
| 80 | 83 | } |
| 81 | 84 | sessTok, sessHash, err := store.NewToken() |
internal/web/templates/login.html
+9
| @@ -6,4 +6,13 @@ |
| 6 | 6 | with your registered key:</p> |
| 7 | 7 | <pre class="message">ssh git@{{.Host}} web login</pre> |
| 8 | 8 | <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 |
| 11 | public 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 |
| 15 | the terminal:</p> |
| 16 | <pre class="message">ssh git@{{.Host}} register --username you --invite <code></pre> |
| 17 | {{else}}<p>This instance is not accepting new accounts.</p>{{end}} |
| 9 | 18 | {{end}} |