gotify/server · error
database error: %w
Error message
database error: %w
What it means
After validating the issuer and subject, resolveUser looks up an existing user by the composite oidcID via `a.DB.GetUserByOIDC`. Any error returned by the database layer is wrapped as 'database error: %w' and surfaced as 500, distinguishing persistence failures from 'user not found'.
Source
Thrown at api/oidc.go:445
issuer := idToken.GetIssuer()
if issuer == "" {
return nil, http.StatusInternalServerError, errors.New("issuer claim was empty")
}
if _, err := url.Parse(issuer); err != nil {
return nil, http.StatusInternalServerError, fmt.Errorf("issuer url %q is not a valid url: %w", issuer, err)
}
if strings.Contains(issuer, "#") {
return nil, http.StatusInternalServerError, fmt.Errorf("issuer url %q may not contain a fragment", issuer)
}
subject := info.GetSubject()
if subject == "" {
return nil, http.StatusInternalServerError, errors.New("subject claim was empty")
}
oidcID := issuer + "#" + subject
user, err := a.DB.GetUserByOIDC(oidcID)
if err != nil {
return nil, http.StatusInternalServerError, fmt.Errorf("database error: %w", err)
}
hasAdminGroup, status, err := a.resolvePermission(idToken.Claims, info.Claims)
if err != nil {
log.Err(err).Str("oidc_id", oidcID).Interface("idTokenClaims", idToken.Claims).Interface("userinfoClaims", info.Claims).Msg("OIDC: resolve permission")
return nil, status, err
}
if user != nil {
if len(a.GroupsAdmin) > 0 && user.Admin != hasAdminGroup {
user.Admin = hasAdminGroup
if err := a.DB.UpdateUser(user); err != nil {
return nil, http.StatusInternalServerError, fmt.Errorf("database error: %w", err)
}
log.Warn().Str("oidc_id", oidcID).Str("username", user.Name).Bool("admin", user.Admin).Msg("OIDC change permission")
}
return user, 0, nil
}View on GitHub (pinned to 14bfc25627)
Solutions
- Check DB connectivity and the wrapped cause in logs
- Run pending database migrations
- Verify the schema used by GetUserByOIDC (index/columns on oidc ID)
- Restart/reconnect the database and retry login
Defensive patterns
Strategy: try-catch
Validate before calling
// health-check the DB before the login flow
const healthy = await db.ping().then(() => true).catch(() => false);
if (!healthy) throw new Error('database unavailable'); Try / catch
try {
user = await db.getUserByOIDC(oidcID);
} catch (err) {
if (isConnectionError(err)) return retryWithBackoff(err);
if (isMissingTable(err)) return runMigrations();
throw err;
} Prevention
- Run migrations as part of deployment
- Monitor DB connectivity and pool saturation
- Distinguish not-found (nil user) from real DB errors in the repository layer
When it happens
Trigger: GetUserByOIDC returns a non-nil error during the external token login flow — e.g. the users table is missing/corrupted, the DB connection is down, or the query fails for schema reasons.
Common situations: Database not migrated (missing oidc column/table); DB temporarily unreachable; connection pool exhausted; migration version mismatch after upgrade.
Understand the failure class
Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.
Related errors
- failed to bind user to OIDC identity: %w
- failed to create user: %w
- failed to create client: %v
- database error: %v
- failed to elevate session: %v
AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05).
Data as JSON: /api/errors/be5f4c825efd99f1.
Report an issue: GitHub.