{"record":{"id":"6d9bd0467aaa8c78","repo":"amir20/dozzle","slug":"invalid-credentials","errorCode":null,"errorMessage":"invalid credentials","messagePattern":"invalid credentials","errorType":"error_code","errorClass":"ErrInvalidCredentials","httpStatus":401,"severity":"warning","filePath":"internal/auth/simple.go","lineNumber":26,"sourceCode":"\t\"net/http\"\n\t\"slices\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-chi/jwtauth/v5\"\n\t\"github.com/rs/zerolog/log\"\n)\n\ntype simpleAuthContext struct {\n\tUserDatabase UserDatabase\n\ttokenAuth    *jwtauth.JWTAuth\n\tttl          time.Duration\n\t// UserDatabase.Find reloads users.yml in place, and the middleware now calls it\n\t// on every request, so the reload has to be serialized.\n\tmu sync.Mutex\n}\n\nvar ErrInvalidCredentials = errors.New(\"invalid credentials\")\n\nfunc NewSimpleAuth(userDatabase UserDatabase, ttl time.Duration) *simpleAuthContext {\n\t// Hash the users in a stable order. Ranging over the map directly makes the\n\t// digest depend on Go's randomized map iteration order, so any users.yml with\n\t// more than one user derives a different signing key on every start and\n\t// silently invalidates every session on restart.\n\th := sha256.New()\n\tfor _, username := range slices.Sorted(maps.Keys(userDatabase.Users)) {\n\t\tuser := userDatabase.Users[username]\n\t\th.Write([]byte(user.Password))\n\t\th.Write([]byte(user.RolesConfigured))\n\t}\n\n\ttokenAuth := jwtauth.New(\"HS256\", h.Sum(nil), nil)\n\n\treturn &simpleAuthContext{\n\t\tUserDatabase: userDatabase,\n\t\ttokenAuth:    tokenAuth,","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/amir20/dozzle/blob/d9463cbe21874e44ab79db6fa63e746ca7d22928/internal/auth/simple.go#L8-L44","documentation":"ErrInvalidCredentials is the sentinel error returned by SimpleAuth.CreateToken in internal/auth/simple.go when the username cannot be found in the user database or the bcrypt-style password comparison fails. The HTTP layer maps it to a failed login attempt.","triggerScenarios":"CreateToken(username, password) is called (login endpoint) and find(username) misses, or CompareHashAndPassword(user.Password, password) returns false.","commonSituations":"users.yml doesn't exist or has no such user; the password is wrong; users.yml was edited but the entry was malformed/removed; client sends an email-style username while the file has a plain name (or vice versa); YAML indentation mistakes produce empty/partial user records.","solutions":["Double-check the username and password against ./data/users.yml (usernames are case-sensitive keys)","Regenerate the password entry with a properly hashed value matching the expected hash format","Verify users.yml parses as valid YAML with correct indentation for each user entry","Confirm the auth provider is `simple` mode and the file path configured is the one actually loaded"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// before calling login\nconst users, _ := loadUsersYAML(path) // verify file parses and username key exists\nif _, ok := users[username]; !ok {\n    return fmt.Errorf(\"user %q not present in users.yml\", username)\n}","typeGuard":null,"tryCatchPattern":"token, err := auth.CreateToken(username, password)\nif errors.Is(err, auth.ErrInvalidCredentials) {\n    http.Error(w, \"invalid username or password\", http.StatusUnauthorized)\n    return\n}","preventionTips":["Keep users.yml valid YAML with one correctly indented entry per user","Verify the username key case exactly matches the file","Regenerate password hashes with the supported hashing scheme after manual edits","Check errors.Is(err, ErrInvalidCredentials) rather than string comparison"],"tags":["authentication","go","credentials","yaml-config"],"backgroundTag":"user-not-found","analyzedSha":"d9463cbe21874e44ab79db6fa63e746ca7d22928","analyzedAt":"2026-09-07T10:08:55.855Z","contentChangedAt":"2026-09-07T10:08:55.855Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}