Documentation
¶
Overview ¶
Package token mints and verifies the HS256 JWTs that the team SPA, the /v1/team/account API and the /v1/team/transactor data plane all share.
The wire format is byte-compatible with the platform `jwt-simple` HS256 tokens (foundations/core/packages/token/src/token.ts): header {"typ":"JWT","alg":"HS256"}, a COMPACT JSON payload whose keys appear in the fixed order {extra, account, workspace, sub, exp, nbf} with undefined/empty keys omitted, base64url WITHOUT padding, and a raw HMAC-SHA256 signature over `header.payload`.
account and workspace, when present, MUST be UUIDs — the upstream `generateToken` throws otherwise and the transactor client refuses a workspace token that is missing either, so we enforce the same invariant at mint time.
The secret is the shared `SERVER_SECRET` (synced from KMS in production). One secret, one wire format — every team surface signs and verifies through this package. Ported VERBATIM from github.com/hanzoai/team-go/pkg/token.
Index ¶
Constants ¶
const DefaultSecret = "secret"
DefaultSecret mirrors the upstream `getSecret()` fallback. Production always injects SERVER_SECRET (synced from KMS); the literal exists only so dev parity with the upstream pods (which also default to "secret") holds. It is NEVER a production secret — the Mount path fails onto the KMS-synced env value.
Variables ¶
var ErrExpired = errors.New("token: expired")
ErrExpired is returned by Decode(verify=true) when the token's `exp` has passed.
var ErrMalformed = errors.New("token: malformed")
ErrMalformed is returned when a token is not three base64url segments.
var ErrNotYetValid = errors.New("token: not yet valid")
ErrNotYetValid is returned by Decode(verify=true) when the token's `nbf` is in the future.
var ErrSignature = errors.New("token: signature mismatch")
ErrSignature is returned when HMAC verification fails.
Functions ¶
func Generate ¶
func Generate(account, workspace string, extra map[string]any, exp int64, secret string) (string, error)
Generate mints an HS256 token for account (always) and workspace (optional — pass "" for an account/login token that is not yet scoped to a workspace). extra is folded in as the leading `extra` claim when non-empty (e.g. {"org":"acme"}). exp is the expiry as a unix-second timestamp — pass 0 to mint a token WITHOUT an `exp` claim (the wire-golden / non-expiring case). Both ids are validated as UUIDs.
Types ¶
type Token ¶
type Token struct {
Extra map[string]any `json:"extra,omitempty"`
Account string `json:"account"`
Workspace string `json:"workspace,omitempty"`
Sub string `json:"sub,omitempty"`
Exp int64 `json:"exp,omitempty"`
Nbf int64 `json:"nbf,omitempty"`
}
Token is the decoded payload. Field order is the jwt-simple emit order so marshalling reproduces the upstream byte layout; `omitempty` drops the keys jwt-simple leaves `undefined`. account carries no omitempty: it is always present (the upstream payload always includes `account`).
func Decode ¶
Decode verifies (when verify is true) and parses an HS256 token. When verify is true it enforces the temporal claims: a token whose `exp` has passed is rejected (ErrExpired) and a token whose `nbf` is in the future is rejected (ErrNotYetValid). A missing claim (0) is not enforced. This closes the permanent-replay window on a captured token (the transactor token rides in the URL path — log-prone — so a bounded lifetime is the mitigation until the front moves it off the URL, #60).