gotify/server · error · errCannotParseToken
%w: base64 decode failed: %w
Error message
%w: base64 decode failed: %w
What it means
The key field of an enhanced token is base64 RawURL-encoded. If base64.RawURLEncoding.DecodeString fails on that segment, ParseEnhancedToken returns errCannotParseToken wrapped with "base64 decode failed" and the underlying decode error. It indicates the token's key segment was corrupted or never valid base64url.
Source
Thrown at auth/token.go:124
// ParseEnhancedToken parses a string into an EnhancedToken.
func ParseEnhancedToken(token string) (*EnhancedToken, error) {
token, found := strings.CutPrefix(token, enhancedTokenPrefix)
if !found {
return nil, fmt.Errorf("%w: token must start with %s", errCannotParseToken, enhancedTokenPrefix)
}
// count number of dots, one dot -> ident then private key, three dots -> ident, public key, challenge then signature
fields := strings.SplitN(token, ".", 4)
if len(fields) != 2 && len(fields) != 4 {
return nil, fmt.Errorf("%w: token must have 2 or 4 fields separated by dots", errCannotParseToken)
}
ident := fields[0]
pkOrPubkeyB64 := fields[1]
pkOrPubkeyBytesLen := base64.RawURLEncoding.DecodedLen(len(pkOrPubkeyB64))
pkOrPubkey, err := base64.RawURLEncoding.DecodeString(pkOrPubkeyB64)
if err != nil {
return nil, fmt.Errorf("%w: base64 decode failed: %w", errCannotParseToken, err)
}
if len(fields) == 2 {
if pkOrPubkeyBytesLen != ed25519.SeedSize {
return nil, fmt.Errorf("%w: private key must be %d bytes", errCannotParseToken, ed25519.SeedSize)
}
return &EnhancedToken{
ident: ident,
pubOrPrivKey: pkOrPubkey,
}, nil
}
if pkOrPubkeyBytesLen != ed25519.PublicKeySize {
return nil, fmt.Errorf("%w: public key must be %d bytes", errCannotParseToken, ed25519.PublicKeySize)
}
timestampStr := fields[2]
timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
if err != nil {
return nil, fmt.Errorf("%w: timestamp must be an integer: %w", errCannotParseToken, err)
}View on GitHub (pinned to 14bfc25627)
Solutions
- Re-copy the token verbatim from its source without transformation or line-wrapping.
- Ensure the key segment uses Raw URL-safe base64 (no '=' padding, '-'/'_' alphabet).
- Verify the stored token wasn't URL-encoded/HTML-escaped and decode if needed.
- Regenerate the token via the API if the original is unrecoverable.
Example fix
// before: padded standard base64 tok := "gtfyident.aGVsbG8=" // after: raw url-safe base64 tok := "gtfyident.aGVsbG8"
Defensive patterns
Strategy: validation
Validate before calling
seg := strings.Split(strings.TrimPrefix(tok, "gtfy"), ".")[1]
if _, err := base64.RawURLEncoding.DecodeString(seg); err != nil {
return fmt.Errorf("token key segment is not raw url-safe base64: %w", err)
} Type guard
func isRawURLBase64(s string) bool {
_, err := base64.RawURLEncoding.DecodeString(s)
return err == nil
} Try / catch
if _, err := auth.ParseEnhancedToken(raw); err != nil {
if strings.Contains(err.Error(), "base64 decode failed") {
return fmt.Errorf("token corrupted in transit/storage: %w", err)
}
} Prevention
- Keep tokens out of contexts that re-encode characters (URLs, HTML, Markdown).
- Use raw URL-safe base64 (no padding) when constructing key segments.
- Verify token integrity after retrieval from storage or env vars.
When it happens
Trigger: Parsing a token whose second dot-field contains characters outside the base64url alphabet (+, /, = padding, whitespace) or an impossible length — usually from copy/paste corruption, URL-encoding, or a truncated key.
Common situations: Tokens pasted through Markdown/HTML that re-encoded characters; shells mangling '_'/'-'; storing tokens in systems that wrap lines; manually re-encoding with standard base64 (with padding).
Related errors
- %w: token must start with %s
- %w: token must have 2 or 4 fields separated by dots
- %w: private key must be %d bytes
- %w: public key must be %d bytes
- %w: signature must be %d bytes
AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05).
Data as JSON: /api/errors/5146f11afadc3715.
Report an issue: GitHub.