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

  1. Check DB connectivity and the wrapped cause in logs
  2. Run pending database migrations
  3. Verify the schema used by GetUserByOIDC (index/columns on oidc ID)
  4. 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

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


AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05). Data as JSON: /api/errors/be5f4c825efd99f1. Report an issue: GitHub.