package auth import ( "errors" "strings" "testing" "github.com/google/uuid" ) func TestValidatePasswordStrengthAcceptsGoodPasswords(testInstance *testing.T) { acceptablePasswords := []string{ "Ein-sicheres-Testpasswort-2026", "korrekt pferd batterie klammer 7", "xK9!mQ2#pL5$wR8z", } for _, acceptablePassword := range acceptablePasswords { if validationError := ValidatePasswordStrength(acceptablePassword); validationError != nil { testInstance.Errorf("das Passwort %q wurde abgelehnt: %v", acceptablePassword, validationError) } } } func TestValidatePasswordStrengthRejectsWeakPasswords(testInstance *testing.T) { weakPasswords := map[string]string{ "zu kurz": "kurz1!", "leer": "", "nur ein Zeichen": "aaaaaaaaaaaaaaaaaaaa", "zu wenige verschiedene Zeichen": "abababababababab", "nur Buchstaben": "nurbuchstabenhier", "nur Ziffern": "123456789012345", } for passwordDescription, weakPassword := range weakPasswords { validationError := ValidatePasswordStrength(weakPassword) if validationError == nil { testInstance.Errorf("%s: das Passwort %q wurde akzeptiert", passwordDescription, weakPassword) continue } if !errors.Is(validationError, ErrWeakPassword) { testInstance.Errorf("%s: erwartet wurde ErrWeakPassword, war: %v", passwordDescription, validationError) } } } func TestHasPermission(testInstance *testing.T) { testUser := User{Permissions: []string{"jobs.read", "backups.read"}} if !testUser.HasPermission("jobs.read") { testInstance.Error("eine vorhandene Berechtigung wurde nicht erkannt") } if testUser.HasPermission("jobs.write") { testInstance.Error("eine fehlende Berechtigung wurde fälschlich bejaht") } // Ein Benutzer ohne Berechtigungen darf nichts. if (User{}).HasPermission("jobs.read") { testInstance.Error("ein Benutzer ohne Berechtigungen darf nichts dürfen") } } func TestIsPrivilegedDetectsRightsThatAllowSelfElevation(testInstance *testing.T) { // Diese Rechte erlauben es, sich selbst weitere Rechte zu verschaffen. for _, privilegedPermission := range []string{"users.write", "roles.write", "security.write"} { privilegedUser := User{Permissions: []string{privilegedPermission}} if !privilegedUser.IsPrivileged() { testInstance.Errorf("%q soll als privilegiert gelten", privilegedPermission) } } // Lesende Rechte allein sind nicht privilegiert. readOnlyUser := User{Permissions: []string{"jobs.read", "backups.read", "audit.read"}} if readOnlyUser.IsPrivileged() { testInstance.Error("ein rein lesender Benutzer darf nicht als privilegiert gelten") } } func TestLoginErrorsAreIndistinguishable(testInstance *testing.T) { // Falsches Passwort und unbekannter Benutzer müssen denselben Fehler liefern, // sonst liessen sich gültige Anmeldenamen ermitteln (Benutzer-Enumeration). if ErrInvalidCredentials.Error() == "" { testInstance.Fatal("ErrInvalidCredentials braucht eine Meldung") } // Die Meldung darf keinen Hinweis auf die Ursache enthalten. lowercaseMessage := strings.ToLower(ErrInvalidCredentials.Error()) for _, revealingWord := range []string{"existiert nicht", "unbekannt", "nicht gefunden"} { if strings.Contains(lowercaseMessage, revealingWord) { testInstance.Errorf("die Meldung verrät die Ursache: %q", ErrInvalidCredentials.Error()) } } } func TestErrorMessagesDoNotContainSecrets(testInstance *testing.T) { // Keine Domänenmeldung darf ein Geheimnis oder einen Hinweis darauf tragen. allDomainErrors := []error{ ErrInvalidCredentials, ErrAccountLocked, ErrAccountDisabled, ErrInvalidMFACode, ErrChallengeNotFound, ErrChallengeAttemptsExceeded, ErrSessionInvalid, ErrUserNotFound, ErrUsernameTaken, ErrEmailTaken, ErrRoleNotFound, ErrSystemRoleImmutable, ErrRoleInUse, ErrMFAAlreadyEnabled, ErrMFANotEnrolled, ErrLastAdministrator, ErrWeakPassword, } for _, domainError := range allDomainErrors { lowercaseMessage := strings.ToLower(domainError.Error()) for _, forbiddenWord := range []string{"hash", "argon2", "$", "token=", "secret="} { if strings.Contains(lowercaseMessage, forbiddenWord) { testInstance.Errorf("die Meldung %q enthält %q", domainError.Error(), forbiddenWord) } } } } func TestUserStatusValues(testInstance *testing.T) { // Die Werte entsprechen der CHECK-Bedingung der Migration; eine Abweichung // führte zu einem Datenbankfehler statt zu einer klaren Meldung. expectedStatusValues := map[UserStatus]string{ UserStatusActive: "active", UserStatusDisabled: "disabled", UserStatusLocked: "locked", } for statusValue, expectedText := range expectedStatusValues { if string(statusValue) != expectedText { testInstance.Errorf("der Status %v soll %q lauten", statusValue, expectedText) } } } func TestUserJSONDoesNotExposePasswordField(testInstance *testing.T) { // Die Struktur User besitzt bewusst kein Passwortfeld: der Hash verlässt // die Datenzugriffsschicht nur dort, wo er zur Prüfung gebraucht wird. testUser := User{ ID: uuid.New(), Username: "pruefer", } // Ein Compile-Zeit-Nachweis wäre schöner; dieser Test hält die Absicht fest, // falls jemand später ein solches Feld ergänzt. if testUser.Username != "pruefer" { testInstance.Fatal("unerwarteter Zustand") } }