gotify/server · error · errInvalidToken

invalid token

Error message

invalid token

What it means

errInvalidToken is the sentinel error (errors.Is-able) returned by ParseEnhancedToken when an enhanced token ('gtfy'-prefixed, signed) fails cryptographic verification — e.g. the ed25519 signature over the timestamp/payload does not verify against the server's public key. TestNewComplexToken also surfaces it when a crafted token fails verification.

Source

Thrown at auth/token.go:22

	"crypto"
	"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 {

View on GitHub (pinned to 14bfc25627)

Solutions

  1. Request a fresh token from the server (login / token generation) and use it verbatim — no truncation or reformatting
  2. Ensure the token was issued by the same server/key you are validating against; do not share tokens across instances
  3. Check client clock skew: keep it well under 15 minutes or sync with NTP
  4. If you hand-build tokens, re-sign them with the correct ed25519 private key and the SHA-512 options the server uses

Example fix

// before: reconstructed token string
token := 'gtfy' + payloadPart + guessedSig
user, err := auth.ParseEnhancedToken(token)
// after: use the token exactly as issued by the server
token := os.Getenv("GITEA_TOKEN")
user, err := auth.ParseEnhancedToken(token)
// handle with errors.Is(err, auth.ErrInvalidToken)
Defensive patterns

Strategy: try-catch

Validate before calling

// structural + provenance sanity checks before parsing
if (!token || !token.startsWith('gtfy')) throw new Error('not an enhanced token');
if (token !== tokenIssuedByThisServer) throw new Error('token from a different server/key');

Type guard

function isEnhancedToken(t) {
  return typeof t === 'string' && t.startsWith('gtfy') && t.length > 4;
}

Try / catch

user, err := ParseEnhancedToken(token)
if err != nil {
    if errors.Is(err, errInvalidToken) {
        // signature verification failed: token tampered, wrong key, or stale
        return nil, fmt.Errorf("token rejected; request a fresh token from the server")
    }
    return nil, err
}

Prevention

When it happens

Trigger: Parsing a token whose signature was mutated/truncated (see token_test.go:44), a token signed by a different key than the server expects, a token replayed outside maxTimestampDiffSeconds (15 min) or with a corrupted timestamp, or a token whose payload was tampered with.

Common situations: Copying tokens between environments (staging token on production server); truncating the token in a shell variable or config; manually editing a token; clock skew beyond 15 minutes between issuance and validation after token regen.

Understand the failure class

Related errors


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