package token import ( "encoding/hex" "errors" "time" "aidanwoods.dev/go-paseto" ) const TokenDuration = 7 * 24 * time.Hour const ( claimUserID = "uid" ) // KeyFromHex loads a V4 symmetric key from a 64-char hex string (AUTH_PASETO_KEY). func KeyFromHex(h string) (paseto.V4SymmetricKey, error) { b, err := hex.DecodeString(h) if err != nil { return paseto.V4SymmetricKey{}, err } return paseto.V4SymmetricKeyFromBytes(b) } // IssueToken creates a PASETO v4 local (encrypted) token carrying the // user ID and a jti that maps to a row in the sessions table. func IssueToken(key paseto.V4SymmetricKey, userID, jti string) (string, error) { tok := paseto.NewToken() tok.SetIssuedAt(time.Now()) tok.SetNotBefore(time.Now()) tok.SetExpiration(time.Now().Add(TokenDuration)) tok.SetJti(jti) if err := tok.Set(claimUserID, userID); err != nil { return "", err } return tok.V4Encrypt(key, nil), nil } // Claims holds the verified payload extracted from a token. type Claims struct { UserID string JTI string } // VerifyToken decrypts and validates a PASETO v4 local token. func VerifyToken(key paseto.V4SymmetricKey, raw string) (Claims, error) { parser := paseto.NewParser() parser.AddRule(paseto.NotExpired()) parser.AddRule(paseto.ValidAt(time.Now())) tok, err := parser.ParseV4Local(key, raw, nil) if err != nil { return Claims{}, err } var userID string if err := tok.Get(claimUserID, &userID); err != nil { return Claims{}, errors.New("missing uid claim") } jti, err := tok.GetJti() if err != nil { return Claims{}, errors.New("missing jti claim") } return Claims{UserID: userID, JTI: jti}, nil }