Tencent/WeKnora · error

empty external user id

Error message

empty external user id

What it means

validateExternalUserID performs post-JWT sanity checks on the external user identifier extracted from the token. It rejects an empty (after trimming) ID because a principal without an ID cannot be persisted or authorized. This guards against tokens whose sub passed a whitespace-only trim earlier or IDs supplied through alternate lookup paths.

Source

Thrown at internal/middleware/auth.go:662

	}
	if nbf, nbfErr := claims.GetNotBefore(); nbfErr == nil && nbf != nil && time.Now().Before(nbf.Time) {
		return "", errors.New("token not yet valid")
	}
	if got := principalTenantIDFromClaims(claims); got != tenantID {
		return "", fmt.Errorf("workspace mismatch: got %d want %d", got, tenantID)
	}
	sub, _ := claims["sub"].(string)
	sub = strings.TrimSpace(sub)
	if sub == "" {
		return "", errors.New("missing subject")
	}
	return sub, nil
}

func validateExternalUserID(id string) error {
	id = strings.TrimSpace(id)
	if id == "" {
		return errors.New("empty external user id")
	}
	if len(id) > maxExternalUserIDLen {
		return fmt.Errorf("external user id too long (max %d)", maxExternalUserIDLen)
	}
	for _, r := range id {
		if r < 0x20 || r == 0x7f {
			return errors.New("external user id contains invalid characters")
		}
	}
	return nil
}

func apiPrincipalAuthErrorMessage(err error) string {
	switch {
	case errors.Is(err, errMissingDirectHeader):
		return "Unauthorized: missing external user id header"
	case errors.Is(err, errInvalidExternalUserID):
		return "Unauthorized: invalid external user id"

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Ensure the upstream token/parameter actually carries the external user ID before calling the API.
  2. If the ID comes from a request field, add client-side/earlier middleware validation for non-empty values.
  3. Check the external user provisioning flow: the account must be created with a real external identifier.
  4. If calling validateExternalUserID directly in tests/tools, pass a real trimmed ID string.

Example fix

// before
if err := validateExternalUserID(c.GetHeader("X-External-User-Id")); err != nil { ... }
// after: check presence first
extID := strings.TrimSpace(c.GetHeader("X-External-User-Id"))
if extID == "" {
    c.AbortWithStatusJSON(400, gin.H{"error": "external user id header required"})
    return
}
if err := validateExternalUserID(extID); err != nil { ... }
Defensive patterns

Strategy: validation

Validate before calling

func preValidateExternalUserID(id string) error {
    if strings.TrimSpace(id) == "" {
        return errors.New("external user id required")
    }
    return nil
}
// call before issuing the API request

Type guard

func hasExternalUserID(v string) bool { return strings.TrimSpace(v) != "" }

Prevention

When it happens

Trigger: resolveAPIPrincipal calling validateExternalUserID with an ID that is empty after TrimSpace — e.g. sub claim containing only spaces, or an empty ID passed when resolving a principal by explicit external user ID.

Common situations: IdP that issues blank sub values; callers extracting the ID from a header/query param that was not sent; data migration bugs where user records lack the external ID field.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/c3585b9b4b2c72ec. Report an issue: GitHub.