Review pass on the Lightwell redesign: layout, copy and behavior fixes
Dialog buttons are Cancel then the primary action at the bottom right, and each page has one primary action. List pages no longer repeat their title. Dates have the month written out. Fields in a row line up, hints sit under fields instead of inside labels, and long certificate errors are clamped. Organizations show whether local access is on and name the action; setting a user's password uses a dialog instead of a browser prompt. Server errors for sign-in and ranges are reworded with a next step, and the stray scrollbar inside tables is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d74e6c1e72
commit
c585ddbf63
9 files changed
+130
-108
No files matched your search
+11
-11
@@ -66,7 +66,7 @@ func (s *Server) withUser(fn func(http.ResponseWriter, *http.Request, *principal
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
p := s.principalFrom(r)
|
||||
if p == nil || p.User.ID == "" {
|
||||
fail(w, 401, errors.New("not logged in"))
|
||||
fail(w, 401, errors.New("Sign in to continue."))
|
||||
return
|
||||
}
|
||||
fn(w, r, p)
|
||||
@@ -96,7 +96,7 @@ func (s *Server) withOrg(needAdmin bool, fn func(http.ResponseWriter, *http.Requ
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
p := s.principalFrom(r)
|
||||
if p == nil {
|
||||
fail(w, 401, errors.New("not logged in"))
|
||||
fail(w, 401, errors.New("Sign in to continue."))
|
||||
return
|
||||
}
|
||||
id := r.PathValue("org")
|
||||
@@ -169,7 +169,7 @@ func (s *Server) createFirstAdmin(email, name, password, orgName string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if len(s.st.Users) != 0 {
|
||||
return errors.New("setup already done")
|
||||
return errors.New("Hammerhead is already set up. Sign in instead.")
|
||||
}
|
||||
u := &User{ID: newID(), Email: email, Name: name, PassHash: hash, ServerAdmin: true, Created: time.Now()}
|
||||
s.st.Users = append(s.st.Users, u)
|
||||
@@ -240,7 +240,7 @@ func (s *Server) login(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
email := strings.ToLower(strings.TrimSpace(in.Email))
|
||||
if !s.allow("login|"+clientIP(r)+"|"+email, 8, 5*time.Minute) {
|
||||
fail(w, 429, errors.New("too many attempts, wait a few minutes"))
|
||||
fail(w, 429, errors.New("Too many attempts. Wait a few minutes and try again."))
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
@@ -254,7 +254,7 @@ func (s *Server) login(w http.ResponseWriter, r *http.Request) {
|
||||
if u == nil || !good || u.PassHash == "" {
|
||||
s.audit("", email, "login.failed", clientIP(r))
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
fail(w, 401, errors.New("wrong email or password"))
|
||||
fail(w, 401, errors.New("That email and password do not match. Check them and try again."))
|
||||
return
|
||||
}
|
||||
if u.TOTPEnabled {
|
||||
@@ -276,7 +276,7 @@ func (s *Server) loginTOTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !s.allow("totp|"+clientIP(r), 10, 5*time.Minute) {
|
||||
fail(w, 429, errors.New("too many attempts, wait a few minutes"))
|
||||
fail(w, 429, errors.New("Too many attempts. Wait a few minutes and try again."))
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
@@ -307,7 +307,7 @@ func (s *Server) loginTOTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
s.mu.Unlock()
|
||||
if !good {
|
||||
fail(w, 401, errors.New("wrong code"))
|
||||
fail(w, 401, errors.New("That code is not right. Check your authenticator and try again."))
|
||||
return
|
||||
}
|
||||
s.startSession(w, r, u.ID)
|
||||
@@ -413,7 +413,7 @@ func (s *Server) changePassword(w http.ResponseWriter, r *http.Request, p *princ
|
||||
return
|
||||
}
|
||||
if p.User.PassHash != "" && bcrypt.CompareHashAndPassword([]byte(p.User.PassHash), []byte(in.Current)) != nil {
|
||||
fail(w, 400, errors.New("current password is wrong"))
|
||||
fail(w, 400, errors.New("Your current password is not right. Check it and try again."))
|
||||
return
|
||||
}
|
||||
hash, err := hashPassword(in.New)
|
||||
@@ -464,7 +464,7 @@ func (s *Server) totpEnable(w http.ResponseWriter, r *http.Request, p *principal
|
||||
defer s.mu.Unlock()
|
||||
u := s.findUser(p.User.ID)
|
||||
if u.TOTPSecret == "" || !totpOK(u.TOTPSecret, strings.TrimSpace(in.Code)) {
|
||||
fail(w, 400, errors.New("that code doesn't match; check your authenticator's clock"))
|
||||
fail(w, 400, errors.New("That code does not match. Check that your authenticator's clock is right and try again."))
|
||||
return
|
||||
}
|
||||
codes, hashes := make([]string, 8), make([]string, 8)
|
||||
@@ -483,14 +483,14 @@ func (s *Server) totpDisable(w http.ResponseWriter, r *http.Request, p *principa
|
||||
return
|
||||
}
|
||||
if p.User.PassHash != "" && bcrypt.CompareHashAndPassword([]byte(p.User.PassHash), []byte(in.Password)) != nil {
|
||||
fail(w, 400, errors.New("password is wrong"))
|
||||
fail(w, 400, errors.New("That password is not right. Check it and try again."))
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
u := s.findUser(p.User.ID)
|
||||
if !totpOK(u.TOTPSecret, strings.TrimSpace(in.Code)) {
|
||||
fail(w, 400, errors.New("code is wrong"))
|
||||
fail(w, 400, errors.New("That code is not right. Check your authenticator and try again."))
|
||||
return
|
||||
}
|
||||
u.TOTPEnabled, u.TOTPSecret, u.BackupCodes = false, "", nil
|
||||
|
||||
Reference in new issue
Block a user