gotify/server · error · errCannotParseToken

cannot parse token

Error message

cannot parse token

What it means

errCannotParseToken is the sentinel error for tokens that cannot even be parsed as enhanced tokens — most commonly a token that does not start with the required 'gtfy' prefix (wrapped with %w so errors.Is matches). Unlike errInvalidToken (signature failure), this is a structural/format failure. The auth middleware special-cases it to return 401 instead of 500.

Source

Thrown at auth/token.go:23

	"crypto/ed25519"
	"crypto/rand"
	"crypto/sha512"
	"encoding/base64"
	"errors"
	"fmt"
	"math/big"
	"strconv"
	"strings"
)

const (
	maxTimestampDiffSeconds = 15 * 60
	randomTokenLength       = 22 // ~2^132 keyspace
)

var (
	errInvalidToken     = errors.New("invalid token")
	errCannotParseToken = errors.New("cannot parse token")
	errNoPrivateKey     = errors.New("no private key")
	tokenCharacters     = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_")
	pluginPrefix        = "P"
	enhancedTokenPrefix = "gtfy"

	randReader = rand.Reader
)

type EnhancedToken struct {
	ident        string
	pubOrPrivKey []byte
	timestamp    int64
	signature    []byte
}

// PublicForm returns the a canonicalized representation of the public key.
func (c *EnhancedToken) PublicForm() string {
	if c.timestamp != 0 || len(c.signature) != 0 {

View on GitHub (pinned to 14bfc25627)

Solutions

  1. Verify the token string starts with 'gtfy' for enhanced tokens; if it starts with 'P' or is a plain random token, use the parser/handler for that token type or regenerate an enhanced token
  2. Generate a new enhanced token from the server and store it without modifications (no quotes, no trailing newline)
  3. Trim whitespace/newlines from the token in env/config before use
  4. Check errors.Is(err, errCannotParseToken) to distinguish format problems from signature problems when debugging

Example fix

// before
raw := "\"gtfyAbC...\"" // includes quotes
user, err := ParseEnhancedToken(raw)
// after
raw := strings.TrimSpace(strings.Trim(os.Getenv("GITEA_TOKEN"), \"\"))
if !strings.HasPrefix(raw, "gtfy") { /* regenerate enhanced token */ }
user, err := ParseEnhancedToken(raw)
Defensive patterns

Strategy: try-catch

Validate before calling

if !strings.HasPrefix(strings.TrimSpace(token), "gtfy") {
    return fmt.Errorf("value is not an enhanced token (must start with 'gtfy')")
}

Type guard

func isEnhancedToken(s string) bool {
    return strings.HasPrefix(strings.TrimSpace(s), "gtfy")
}

Try / catch

user, err := ParseEnhancedToken(token)
if err != nil {
    if errors.Is(err, errCannotParseToken) {
        // format problem: wrong prefix, quotes, whitespace, or plain legacy token
        return nil, fmt.Errorf("malformed token %q; regenerate an enhanced token", summarize(token))
    }
    return nil, err
}

Prevention

When it happens

Trigger: Passing a random-plugin token ('P' prefix), a raw random token, an empty string, or an arbitrary string to ParseEnhancedToken or through auth middleware evaluate() where the token can't be parsed.

Common situations: Using an old-style plain token after the server switched to enhanced (signed) tokens; env var containing whitespace or a placeholder value; client library version mismatch producing a different token format; pasting a token with surrounding quotes.

Related errors


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