Tencent/WeKnora · error · errInvalidExternalUserID
%w: %v
Error message
%w: %v
What it means
In internal/middleware/auth.go:587, resolveAPIPrincipal (APIPrincipalModeDirect) wraps errInvalidExternalUserID with the underlying validation failure from validateExternalUserID. The external user id supplied in the direct header is empty, too long, or contains control characters, so no Principal is resolved and the request is rejected.
Source
Thrown at internal/middleware/auth.go:587
}
if tenant == nil || tenantID == 0 {
return fallback, nil
}
cfg := tenant.APIPrincipalConfig
if cfg == nil || cfg.Mode == "" || cfg.Mode == types.APIPrincipalModeTenant {
return fallback, nil
}
switch cfg.Mode {
case types.APIPrincipalModeDirect:
externalUserID := strings.TrimSpace(header.Get(defaultExternalUserIDHeader))
if externalUserID == "" {
if cfg.RequireDirectHeader {
return types.Principal{}, errMissingDirectHeader
}
return fallback, nil
}
if err := validateExternalUserID(externalUserID); err != nil {
return types.Principal{}, fmt.Errorf("%w: %v", errInvalidExternalUserID, err)
}
return types.Principal{
Type: types.PrincipalAPIExternalUser,
ID: strconv.FormatUint(tenantID, 10) + ":" + externalUserID,
}, nil
case types.APIPrincipalModeSignedToken:
externalUserID, err := verifyExternalUserJWT(header.Get(defaultExternalUserTokenHeader), tenantID, cfg.HMACSecret)
if err != nil || externalUserID == "" {
logger.Warnf(ctx, "invalid external user token for tenant=%d: %v", tenantID, err)
return types.Principal{}, fmt.Errorf("%w: %w", errInvalidExternalUserToken, err)
}
if err := validateExternalUserID(externalUserID); err != nil {
return types.Principal{}, fmt.Errorf("%w: %v", errInvalidExternalUserID, err)
}
return types.Principal{
Type: types.PrincipalAPIExternalUser,
ID: strconv.FormatUint(tenantID, 10) + ":" + externalUserID,
}, nilView on GitHub (pinned to 988cbb0330)
Solutions
- Send a non-empty external user id in the direct header with no control characters and within maxExternalUserIDLen.
- Trim whitespace/newlines from the value on the client side before setting the header.
- Check errors.Is(err, errInvalidExternalUserID) and surface the inner message to fix the exact violation (empty / too long / invalid characters).
Example fix
// before (client)
req.Header.Set("X-External-User-ID", userID + "\n") // control char -> rejected
// after
req.Header.Set("X-External-User-ID", strings.TrimSpace(userID)) Defensive patterns
Strategy: try-catch
Validate before calling
id := strings.TrimSpace(extUserID)
if id == "" || len(id) > maxExternalUserIDLen || strings.ContainsFunc(id, func(r rune) bool { return r < 0x20 || r == 0x7f }) {
// fix the header value before sending the request
} Type guard
func validExternalUserID(id string) bool {
id = strings.TrimSpace(id)
if id == "" || len(id) > maxExternalUserIDLen { return false }
for _, r := range id { if r < 0x20 || r == 0x7f { return false } }
return true
} Try / catch
if err != nil {
if errors.Is(err, errInvalidExternalUserID) {
// correct the external user id header and retry once
}
} Prevention
- Trim and length-check external user ids at the client boundary.
- Reject ids containing control characters in your own validation layer.
- Map long provider identifiers to short stable IDs.
When it happens
Trigger: Tenant configured with APIPrincipalModeDirect; request carries the external user id header (defaultExternalUserIDHeader) with a value that is empty after trim, longer than maxExternalUserIDLen, or contains bytes <0x20 or 0x7f.
Common situations: Client sends a padded/newline-terminated user id from a misconfigured header; integration passes an oversized UUID-with-prefix string; id built by concatenating fields picks up a tab or control character.
Related errors
- API key is required for Ollama provider
- API key is required for Baidu provider
- API key is required for Exa provider
- API key is required for Exa provider
- API key is required for Metaso provider
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/cd944ec499fa3dd3.
Report an issue: GitHub.