web: the login page tells a new visitor how to register !123
merged
merged by cmc on 2026-08-30 16:22 UTC
· krz/gitbay:login-register-hint into main
3 files changed, +33 −9
Layout: unified · split
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}} |