gotify/server · error
invalid credentials
Error message
invalid credentials
What it means
Returned by Login after the Basic credentials were parsed but the lookup failed: either no user with that name exists (a.DB.GetUserByName returned nil) or password.ComparePassword does not match the stored hash. The handler deliberately returns a single 401 'invalid credentials' for both cases to avoid leaking which usernames exist.
Source
Thrown at api/session.go:79
func (a *SessionAPI) Login(ctx *gin.Context) {
if !a.LocalAuthEnabled {
ctx.AbortWithError(403, errors.New("local authentication is disabled"))
return
}
name, pass, ok := ctx.Request.BasicAuth()
if !ok {
ctx.AbortWithError(401, errors.New("basic auth required"))
return
}
user, err := a.DB.GetUserByName(name)
if err != nil {
ctx.AbortWithError(500, err)
return
}
if user == nil || !password.ComparePassword(user.Pass, []byte(pass)) {
ctx.AbortWithError(401, errors.New("invalid credentials"))
return
}
clientParams := ClientParams{}
if err := ctx.Bind(&clientParams); err != nil {
return
}
elevatedUntil := time.Now().Add(model.DefaultElevationDuration)
tokenPublic, tokenPrivate := generateClientToken()
client := model.Client{
Name: clientParams.Name,
Token: tokenPublic,
UserID: user.ID,
ElevatedUntil: &elevatedUntil,
ExpiresAfterInactivitySeconds: auth.CookieMaxAge,
}
if success := successOrAbort(ctx, 500, a.DB.CreateClient(&client)); !success {View on GitHub (pinned to 14bfc25627)
Solutions
- Re-enter the username/password carefully; test the same credentials with curl -u
- Verify the user exists in the DB (users table / GetUserByName) and reset the password if needed (admin UpdatePassword endpoint or CLI)
- Confirm you are connected to the environment/database you expect
- If hashes were migrated, re-hash/re-set passwords to match the current password.ComparePassword scheme
Example fix
// before curl -X POST https://host/api/session -u 'jane:passwrod' // after curl -X POST https://host/api/session -u 'jane:correct-password'
Defensive patterns
Strategy: validation
Validate before calling
if (!username || !password) {
throw new Error('username and password required before calling login');
}
// optionally pre-check user existence via admin API if available Type guard
function hasCredentials(c) {
return typeof c === 'object' && typeof c.username === 'string' && c.username.length > 0 && typeof c.password === 'string' && c.password.length > 0;
} Try / catch
try {
await api.login(username, password);
} catch (e) {
if (e.status === 401) { showLoginFormError('Invalid username or password'); }
else { throw e; }
} Prevention
- Surface a single generic 'invalid credentials' message; do not probe usernames
- Verify environment/database when credentials that 'should work' fail
- After hash-scheme migrations, force password resets for migrated accounts
- Test credentials out-of-band with curl -u before blaming the client
When it happens
Trigger: POST to the session/login endpoint with valid Basic header syntax but a wrong password; a username that does not exist in the database; a user created before a password-hashing scheme change so ComparePassword can no longer validate the stored hash; empty password bytes.
Common situations: Typo in username or password; password changed or reset elsewhere; database pointing at the wrong environment (staging user vs prod); migrating users without rehashing passwords; LDAP/external auth expected but local DB auth is what runs.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- basic auth required
- token exchange failed: %w
- local authentication is disabled
- no client auth provided
- you are not allowed to access this api
AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05).
Data as JSON: /api/errors/f11d2d5bada5b75b.
Report an issue: GitHub.