fix(auth): new passwords need at least 8 characters (FLAW-09)
auth.ValidatePassword checks every way a password is set: setup, registration, profile, admin create and reset, reset by mail. Same rule in the browser before sending (lib/password.ts), with our own German message instead of the browser popup. Existing shorter passwords still log in. Profile and admin reset no longer trim the password, which made passwords with surrounding spaces unusable for login. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f8989d0c44
commit
b5f926bcf7
13 files changed
+142
-26
No files matched your search
@@ -111,8 +111,12 @@ func (s *Server) handleAdminCreateUser(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, "Ungültiges JSON")
|
||||
return
|
||||
}
|
||||
if req.Email == "" || req.Password == "" {
|
||||
writeError(w, http.StatusBadRequest, "E-Mail und Passwort erforderlich")
|
||||
if req.Email == "" {
|
||||
writeError(w, http.StatusBadRequest, "E-Mail-Adresse erforderlich")
|
||||
return
|
||||
}
|
||||
if err := auth.ValidatePassword(req.Password); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
// Only full server admin can create another server admin
|
||||
@@ -200,10 +204,14 @@ func (s *Server) handleAdminResetPassword(w http.ResponseWriter, r *http.Request
|
||||
var req struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || strings.TrimSpace(req.Password) == "" {
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Neues Passwort erforderlich")
|
||||
return
|
||||
}
|
||||
if err := auth.ValidatePassword(req.Password); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
hash, err := auth.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Passwort-Hash fehlgeschlagen")
|
||||
|
||||
@@ -90,8 +90,12 @@ func (s *Server) handleFirstBootSetup(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, "Ungültige Anfrage")
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(req.Email) == "" || len(req.Password) < 4 {
|
||||
writeError(w, http.StatusBadRequest, "E-Mail und Passwort (mind. 4 Zeichen) erforderlich")
|
||||
if strings.TrimSpace(req.Email) == "" {
|
||||
writeError(w, http.StatusBadRequest, "E-Mail-Adresse erforderlich")
|
||||
return
|
||||
}
|
||||
if err := auth.ValidatePassword(req.Password); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(req.DisplayName) == "" {
|
||||
@@ -154,8 +158,12 @@ func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, "Ungültige Anfrage")
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(req.Email) == "" || len(req.Password) < 4 {
|
||||
writeError(w, http.StatusBadRequest, "E-Mail und Passwort (mind. 4 Zeichen) erforderlich")
|
||||
if strings.TrimSpace(req.Email) == "" {
|
||||
writeError(w, http.StatusBadRequest, "E-Mail-Adresse erforderlich")
|
||||
return
|
||||
}
|
||||
if err := auth.ValidatePassword(req.Password); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(req.DisplayName) == "" {
|
||||
@@ -415,6 +423,10 @@ func (s *Server) handleUpdateUserSettings(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
}
|
||||
if req.Password != nil && strings.TrimSpace(*req.Password) != "" {
|
||||
if err := auth.ValidatePassword(*req.Password); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
hash, err := auth.HashPassword(*req.Password)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Fehler beim Hashen des Passworts")
|
||||
|
||||
@@ -68,8 +68,8 @@ func (s *Server) handleResetPassword(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, storage.ErrResetTokenInvalid.Error())
|
||||
return
|
||||
}
|
||||
if len(req.Password) < 4 {
|
||||
writeError(w, http.StatusBadRequest, "Das Passwort braucht mindestens 4 Zeichen")
|
||||
if err := auth.ValidatePassword(req.Password); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
hash, err := auth.HashPassword(req.Password)
|
||||
|
||||
@@ -1344,7 +1344,16 @@ func TestFirstBootSetupWithSampleData(t *testing.T) {
|
||||
defer store.Close()
|
||||
srv := NewServer(&config.Config{Port: "8080", DatabasePath: dbPath, JWTSecret: "test-jwt-secret-key"}, store, ws.NewHub(), nil)
|
||||
|
||||
body := []byte(`{"email":"me@example.com","password":"secret","displayName":"Ich","createSampleData":true}`)
|
||||
// Too short passwords are refused (FLAW-09)
|
||||
short := httptest.NewRequest(http.MethodPost, "/api/v1/auth/setup", strings.NewReader(`{"email":"me@example.com","password":"secret"}`))
|
||||
short.Header.Set("Content-Type", "application/json")
|
||||
shortRec := httptest.NewRecorder()
|
||||
srv.Router().ServeHTTP(shortRec, short)
|
||||
if shortRec.Code != http.StatusBadRequest || !strings.Contains(shortRec.Body.String(), "mindestens 8 Zeichen") {
|
||||
t.Fatalf("setup with 6 characters: want 400, got %d %s", shortRec.Code, shortRec.Body.String())
|
||||
}
|
||||
|
||||
body := []byte(`{"email":"me@example.com","password":"secret123","displayName":"Ich","createSampleData":true}`)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/setup", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
Reference in new issue
Block a user