Documentation
¶
Overview ¶
Package auth issues and verifies the HMAC-SHA256 JWTs that gate access to Parsec. There are three token types, distinguished by the typ claim:
- access — short-lived, used to connect over the websocket and to subscribe to listed private channels
- refresh — exchanged at the RefreshToken RPC for a fresh access token
- mgmt — operator token presented as Authorization: Bearer on the management RPC surface
The wire format is the standard JWT compact serialization (base64url(header).base64url(claims).base64url(hmac)), but Parsec uses a fixed header — alg=HS256, typ=JWT — and refuses any other.
No JWT library: the implementation is ~150 lines of stdlib crypto/hmac and crypto/sha256.
Index ¶
- Constants
- Variables
- func GenerateSecret() ([]byte, error)
- func MapErr(err error) error
- func NewSubscribeAuthorizer(v *Verifier) func(ctx context.Context, userID string, ch channels.Name, ...) error
- func NewSubscribeAuthorizerWithLimiter(v *Verifier, limiter ratelimit.Limiter, defaultLimit ratelimit.Limit) func(ctx context.Context, userID string, ch channels.Name, ...) error
- func ReloadInto(path string, ring *KeyRing) error
- func SaveKeyRing(path string, r *KeyRing) error
- func WatchKeyRingFile(ctx context.Context, path string, ring *KeyRing, interval time.Duration, ...)
- type Claims
- type CompositeVerifier
- type FileKeyRingStore
- func (s *FileKeyRingStore) Ensure(ctx context.Context) (*KeyRing, bool, error)
- func (s *FileKeyRingStore) Load(_ context.Context) (*KeyRing, error)
- func (s *FileKeyRingStore) Path() string
- func (s *FileKeyRingStore) Save(_ context.Context, r *KeyRing) error
- func (s *FileKeyRingStore) Watch(ctx context.Context, onChange func(*KeyRing)) error
- type Issuer
- func (i *Issuer) IssueAccess(sub, channel string, refreshExp time.Time, scopes []Scope) (string, time.Time, error)
- func (i *Issuer) IssueAccessForChannels(sub string, chs []string, refreshExp time.Time) (string, time.Time, error)
- func (i *Issuer) IssueMgmt(sub string, ttl time.Duration) (string, time.Time, error)
- func (i *Issuer) IssuePair(sub, channel string, channelTTL time.Duration, scopes []Scope) (PairResult, error)
- func (i *Issuer) IssuePairForChannels(sub string, chs []string, ttl time.Duration, scopes []Scope) (PairResult, error)
- func (i *Issuer) IssuePairWithRateLimit(sub, channel string, channelTTL time.Duration, override ratelimit.Limit) (PairResult, error)
- func (i *Issuer) SetClock(c func() time.Time)
- type Key
- type KeyRing
- func (r *KeyRing) Active() (Key, error)
- func (r *KeyRing) ActiveID() string
- func (r *KeyRing) Add(id string, secret []byte) (Key, error)
- func (r *KeyRing) Generate() (Key, error)
- func (r *KeyRing) Get(id string) (Key, error)
- func (r *KeyRing) List() []Key
- func (r *KeyRing) LoadSnapshot(s Snapshot) error
- func (r *KeyRing) Promote(id string) error
- func (r *KeyRing) Retire(id string) error
- func (r *KeyRing) Snapshot() Snapshot
- type KeyRingStore
- type OIDCConfig
- type OIDCGrant
- type OIDCVerifier
- type PairResult
- type RedisKeyRingStore
- func (s *RedisKeyRingStore) Ensure(ctx context.Context) (*KeyRing, bool, error)
- func (s *RedisKeyRingStore) Load(ctx context.Context) (*KeyRing, error)
- func (s *RedisKeyRingStore) Save(ctx context.Context, r *KeyRing) error
- func (s *RedisKeyRingStore) Watch(ctx context.Context, onChange func(*KeyRing)) error
- func (s *RedisKeyRingStore) WithKeyPrefix(p string) *RedisKeyRingStore
- type Role
- type Scope
- type Signer
- type Snapshot
- type SnapshotKey
- type Type
- type Verb
- type Verifier
Constants ¶
const KeyringFileName = "keyring.json"
KeyringFileName is the conventional file name inside the state dir.
Variables ¶
var ( ErrMalformedToken = errors.New("auth: malformed token") ErrInvalidSignature = errors.New("auth: signature does not verify") ErrUnsupportedAlg = errors.New("auth: unsupported algorithm") ErrExpired = errors.New("auth: token expired") ErrNotYetValid = errors.New("auth: token not yet valid") ErrTypeMismatch = errors.New("auth: token type mismatch") ErrChannelMismatch = errors.New("auth: token does not authorize this channel") )
Sentinel errors. Callers errors.Is against these instead of string-matching.
var AllVerbs = []Verb{VerbSubscribe, VerbPublish, VerbManage}
AllVerbs lists every recognized verb in stable order. Used by the manifest so client SDKs can discover the surface.
Functions ¶
func GenerateSecret ¶
GenerateSecret returns a 32-byte cryptographically random secret suitable for Signer / Verifier. The caller persists it (env var, secrets store); regenerating wipes every previously-issued token.
func MapErr ¶
MapErr translates an auth sentinel into a parsec coded error. Exposed so the library facade and surface code can reuse the mapping.
func NewSubscribeAuthorizer ¶
func NewSubscribeAuthorizer(v *Verifier) func(ctx context.Context, userID string, ch channels.Name, event centrifuge.SubscribeEvent) error
NewSubscribeAuthorizer returns a broker subscribe authorizer that allows any well-formed public channel and verifies an access token for any private channel. The token must list the requested channel in its chs claim.
The returned function matches broker.SubscribeAuthorizer.
func NewSubscribeAuthorizerWithLimiter ¶
func NewSubscribeAuthorizerWithLimiter(v *Verifier, limiter ratelimit.Limiter, defaultLimit ratelimit.Limit) func(ctx context.Context, userID string, ch channels.Name, event centrifuge.SubscribeEvent) error
NewSubscribeAuthorizerWithLimiter is NewSubscribeAuthorizer plus a per-key rate-limit gate that runs BEFORE token verification (so a stream of bad-token attempts cannot exhaust CPU on HMAC verifies).
The key is the userID when set, otherwise the centrifuge client ID (which encodes the connection — best-effort proxy for IP when running behind the centrifuge transport).
Passing limiter == nil or a zero Limit reverts to the no-rate-limit behaviour.
func ReloadInto ¶
ReloadInto re-reads path into the existing ring, swapping its contents atomically. The pointer identity is preserved so any Signer / Verifier already bound to ring picks up the new keys.
func SaveKeyRing ¶
SaveKeyRing writes the ring's snapshot to path, atomically. The file is created with mode 0600; the parent directory is created with mode 0700 if missing.
func WatchKeyRingFile ¶
func WatchKeyRingFile(ctx context.Context, path string, ring *KeyRing, interval time.Duration, onReload func(activeID string), onError func(error))
WatchKeyRingFile polls path's mtime every interval; when it changes, reload is called with the new contents. The function blocks until ctx is canceled. interval == 0 disables polling (returns immediately).
onReload is invoked on every successful reload (after the snapshot swap). Errors during reload are passed to onError; the watcher keeps running so a transient parse failure does not kill the loop.
Types ¶
type Claims ¶
type Claims struct {
// Sub is the subject — user id for client tokens, operator id for mgmt.
Sub string `json:"sub,omitempty"`
// Typ is the Parsec token type discriminator.
Typ Type `json:"typ"`
// Chs is the list of channels this token authorizes. Empty means
// "no client-channel authorization." For mgmt tokens it is ignored.
Chs []string `json:"chs,omitempty"`
// Iat is the issued-at time (Unix seconds).
Iat int64 `json:"iat"`
// Exp is the expiration time (Unix seconds).
Exp int64 `json:"exp"`
// RateLimitOverride, when non-nil, overrides the server's default
// per-subject rate limit for this token. Useful to hand a noisy
// integration a tighter budget without touching server config. The
// claim is private to Parsec (no JWT-RFC equivalent); absence means
// "use the operator-configured default."
RateLimitOverride *ratelimit.Limit `json:"rl,omitempty"`
// Scopes is the pattern-based grant set. Each Scope binds a
// channel-name Pattern to a set of Verbs (subscribe, publish,
// manage). Empty means "no pattern grants" — only the Chs exact
// list applies. A token carrying both Chs and Scopes is authorized
// for the union.
Scopes []Scope `json:"scopes,omitempty"`
}
Claims is the Parsec payload. Fields use JWT-conventional short names so the wire shape is compact.
func (Claims) Authorizes ¶
Authorizes reports whether the holder is granted verb v on the named channel. The check unions two grant sources, with deny-wins precedence applied uniformly:
- Chs (exact match): any name in c.Chs authorizes every verb.
- Scopes (pattern match): each Scope grants (or, when Deny=true, subtracts) its listed verbs on channel names that match its Pattern.
Evaluation order:
- Walk the Scopes for any deny scope whose pattern matches and whose verb list contains v — if found, return false immediately ("deny wins", consistent with AWS IAM and similar systems). Denies override both Chs entries and overlapping allow scopes.
- Walk c.Chs for an exact-name match.
- Walk the Scopes for an allow scope whose pattern matches and whose verb list contains v.
- Otherwise return false.
channel is the raw wire-form channel name; it is parsed once and compared against any applicable Scope. A malformed channel name returns false (fail closed). An empty c.Chs and empty c.Scopes return false for every input.
A Scope with Deny=true is visible in the token like any other claim — operators should not treat deny patterns as confidential channel metadata (see docs/src/channels/acl.md).
type CompositeVerifier ¶
type CompositeVerifier struct {
// HMAC is the parsec-issued JWT verifier. Required.
HMAC *Verifier
// OIDC validates ID tokens from a configured IdP. Optional —
// when nil the composite is HMAC-only.
OIDC *OIDCVerifier
}
CompositeVerifier tries the parsec HMAC verifier first; if that fails AND an OIDC verifier is configured, it falls back to OIDC. A success from either path wins. When both fail, the HMAC error is returned (the HMAC path is the primary verification surface — its error code maps cleanly onto PARSEC_AUTH_*).
A nil OIDC field reduces this to a pass-through over HMAC, so deployments without OIDCConfig accept only HMAC-signed tokens.
func NewCompositeVerifier ¶
func NewCompositeVerifier(hmac *Verifier, oidc *OIDCVerifier) (*CompositeVerifier, error)
NewCompositeVerifier returns a verifier that composes an HMAC verifier with an optional OIDC verifier. hmac must be non-nil (parsec.New always constructs one).
func (*CompositeVerifier) HMACVerify ¶
func (c *CompositeVerifier) HMACVerify(token string, expected Type) (Claims, error)
HMACVerify validates token strictly via the HMAC path. Useful for surface code that must reject OIDC-shaped tokens (e.g. refresh-token RPC, which has no OIDC analog).
func (*CompositeVerifier) OIDCEnabled ¶
func (c *CompositeVerifier) OIDCEnabled() bool
OIDCEnabled reports whether an OIDC verifier is wired.
func (*CompositeVerifier) Verify ¶
func (c *CompositeVerifier) Verify(ctx context.Context, token string, expected Type) (Claims, error)
Verify tries the HMAC verifier first; on any failure, and if OIDC is wired, it tries the OIDC verifier. The first success wins. When both fail, the HMAC error is returned because it is the more specific path (operator-minted tokens dominate inbound traffic on a typical deployment).
Verify is safe for concurrent use. The expected Type is enforced against the HMAC payload; OIDC tokens always synthesize Typ=TypeMgmt and so are only honored when expected is empty or TypeMgmt.
type FileKeyRingStore ¶
type FileKeyRingStore struct {
// contains filtered or unexported fields
}
FileKeyRingStore reads and writes the KeyRing as a JSON file at <state-dir>/keyring.json. Single-node deployments use this; multi-node deployments swap for RedisKeyRingStore.
func NewFileKeyRingStore ¶
func NewFileKeyRingStore(path string, pollEvery time.Duration) *FileKeyRingStore
NewFileKeyRingStore constructs a store at path. pollEvery controls how frequently Watch checks the file's mtime. Pass 0 to disable polling (Watch becomes a no-op).
func (*FileKeyRingStore) Ensure ¶
Ensure satisfies the bootstrap pattern: load or generate a fresh ring, persist, return.
func (*FileKeyRingStore) Load ¶
func (s *FileKeyRingStore) Load(_ context.Context) (*KeyRing, error)
Load reads the file.
func (*FileKeyRingStore) Path ¶
func (s *FileKeyRingStore) Path() string
Path returns the on-disk path.
type Issuer ¶
type Issuer struct {
// AccessTTL is the lifetime of access tokens minted for clients.
// Default 5 minutes. Clamped to [1m, 1h].
AccessTTL time.Duration
// MaxRefreshTTL is the upper bound on refresh tokens. The actual
// refresh TTL is min(channelTTL, MaxRefreshTTL). Default 1h.
MaxRefreshTTL time.Duration
// contains filtered or unexported fields
}
Issuer mints Parsec JWTs with the right TTL bounds. It wraps a Signer with policy: refresh tokens cannot outlive their channel; access tokens cannot outlive their refresh token.
func (*Issuer) IssueAccess ¶
func (i *Issuer) IssueAccess(sub, channel string, refreshExp time.Time, scopes []Scope) (string, time.Time, error)
IssueAccess mints a single access token from a verified refresh. The caller is responsible for verifying the refresh token first; this method trusts its inputs (sub, channel, refreshExp, scopes).
The new access expiry is min(now+AccessTTL, refreshExp) so a refresh can never extend a session past the refresh token's own lifetime.
A nil/empty scopes slice produces a token with no pattern grants — the channel listed in Chs is still authorized for every verb. Each scope's Pattern is validated against the channel grammar before signing.
func (*Issuer) IssueAccessForChannels ¶
func (i *Issuer) IssueAccessForChannels(sub string, chs []string, refreshExp time.Time) (string, time.Time, error)
IssueAccessForChannels mints a fresh access token authorizing chs. The expiry is min(now+AccessTTL, refreshExp).
func (*Issuer) IssueMgmt ¶
IssueMgmt mints an operator token. ttl is clamped to [1h, 7d]; default 24h when zero.
func (*Issuer) IssuePair ¶
func (i *Issuer) IssuePair(sub, channel string, channelTTL time.Duration, scopes []Scope) (PairResult, error)
IssuePair mints an access + refresh token pair for sub on the named channel and stamps the provided scope grants into both tokens. The refresh token is bounded by min(channelTTL, MaxRefreshTTL); the access token by min(AccessTTL, refreshTTL).
A nil/empty scopes slice produces tokens with no pattern grants — the channel listed in Chs is still authorized for every verb.
Each scope's Pattern is validated against the channel grammar before signing; an invalid pattern yields PARSEC_INVALID_ARGUMENT.
func (*Issuer) IssuePairForChannels ¶
func (i *Issuer) IssuePairForChannels(sub string, chs []string, ttl time.Duration, scopes []Scope) (PairResult, error)
IssuePairForChannels mints an access + refresh token pair that authorizes the listed channels (rather than a single channel like IssuePair). The refresh TTL is bounded by min(ttl, MaxRefreshTTL); the access TTL by min(AccessTTL, refresh). Used by the token broker for multi-channel issuance.
func (*Issuer) IssuePairWithRateLimit ¶
func (i *Issuer) IssuePairWithRateLimit(sub, channel string, channelTTL time.Duration, override ratelimit.Limit) (PairResult, error)
IssuePairWithRateLimit is IssuePair with an additional per-token rate limit embedded in the access + refresh claims (rl). When the rate limiter is consulted at publish/subscribe time the override beats the server default for this subject.
Passing a zero Limit (Rate == 0) is the same as IssuePair — no override claim is written.
type Key ¶
type Key struct {
ID string
Secret []byte
Role Role
CreatedAt time.Time
RetiredAt *time.Time
// contains filtered or unexported fields
}
Key is one HMAC secret with rotation metadata. Construct via KeyRing, never directly.
type KeyRing ¶
type KeyRing struct {
// contains filtered or unexported fields
}
KeyRing is the live set of HMAC keys parsec uses to sign and verify tokens. Exactly one key holds RoleActive. The ring is safe for concurrent use.
func EnsureKeyRing ¶
EnsureKeyRing returns a usable KeyRing for path. If the file exists, it is loaded. If not, a fresh ring with one active key is generated and saved. The bool return is true when bootstrap happened.
func LoadKeyRing ¶
LoadKeyRing reads path into a fresh ring. If the file does not exist, returns (nil, os.ErrNotExist) so the caller can decide whether to bootstrap.
func NewKeyRingFromSecret ¶
NewKeyRingFromSecret seeds a ring with one active key whose Secret is the supplied bytes. Convenient for tests that want a stable HMAC across a parsec.New / recreate pair without using a state directory.
func (*KeyRing) Add ¶
Add installs id+secret into the ring. If the ring was empty the new key becomes active; otherwise it joins as verify-only and must be Promoted before it signs.
func (*KeyRing) Generate ¶
Generate creates a fresh 32-byte secret, installs it as a new key in the ring, and returns a copy. If the ring was empty the new key becomes active; otherwise it joins as verify-only.
func (*KeyRing) Get ¶
Get returns a copy of the key with id, or an error if no such key exists OR the key is retired (callers should treat both the same).
func (*KeyRing) List ¶
List returns a snapshot of every key in the ring, sorted by CreatedAt. Retired keys are included so the operator can audit recent deletions.
func (*KeyRing) LoadSnapshot ¶
LoadSnapshot replaces the ring's contents with the snapshot's. Safe to call on a running ring — the swap is atomic from the caller's perspective.
func (*KeyRing) Promote ¶
Promote makes id the active key. The previously-active key (if any) transitions to verify-only. Promoting an unknown or retired key errors.
type KeyRingStore ¶
type KeyRingStore interface {
// Load returns the persisted ring, or (nil, os.ErrNotExist) when the
// store is empty so the caller can decide whether to bootstrap.
Load(ctx context.Context) (*KeyRing, error)
// Save replaces the persisted snapshot with the supplied ring's.
Save(ctx context.Context, r *KeyRing) error
// Watch subscribes to remote modifications. onChange fires with each
// freshly loaded ring snapshot after a remote write. Blocks until ctx
// is canceled or the underlying transport errors fatally. File-backed
// implementations may emit periodically via mtime polling; Redis
// implementations use pub/sub.
Watch(ctx context.Context, onChange func(*KeyRing)) error
}
KeyRingStore is the persistence interface for the HMAC KeyRing. Two implementations ship: a file-backed store (single-node) and a Redis- backed store that lets multiple Parsec nodes share the same keys and observe rotation events without manual SIGHUPs.
type OIDCConfig ¶
type OIDCConfig struct {
// Issuer is the IdP's OpenID issuer URL (e.g.
// "https://accounts.google.com"). NewOIDCVerifier fetches
// <Issuer>/.well-known/openid-configuration to discover the
// JWKS endpoint and supported algorithms.
Issuer string
// Audience is the expected `aud` claim. Set to the parsec
// deployment's client identifier (e.g. "parsec-prod"). If empty
// the verifier rejects every token (defensive — every IdP
// requires an audience).
Audience string
// SubjectClaim names the ID-token claim to copy into the
// synthetic Claims.Sub field. Defaults to "sub" when empty.
// Operators frequently set this to "email" so the access log
// records the operator's address.
SubjectClaim string
// ScopesClaim names the ID-token claim that carries the
// operator's group memberships. Defaults to "groups" when
// empty. The claim is expected to be a JSON array of strings.
ScopesClaim string
// Grants maps IdP group names onto parsec scope patterns. When
// an incoming token's ScopesClaim contains a group listed here,
// the matching grant's Scope + Verbs is added to the synthetic
// Claims.Scopes set. Multiple grants may match a single token.
Grants []OIDCGrant
// Clock overrides time.Now for verification, primarily for
// tests. nil = time.Now.
Clock func() time.Time
}
OIDCConfig captures the operator-supplied configuration for an OIDC identity provider. Empty Issuer means "OIDC is disabled" — the composite verifier short-circuits past it.
type OIDCGrant ¶
type OIDCGrant struct {
// IfGroup is the literal group name to match against the
// token's ScopesClaim list. Comparison is byte-exact — no
// wildcards, no case folding.
IfGroup string
// Scope is the channel-name pattern to grant (parsec scope
// grammar — see channels.ParsePattern).
Scope string
// Verbs is the set of actions the matching operator may take
// on channels covered by Scope.
Verbs []Verb
}
OIDCGrant maps one IdP group name onto one parsec scope. When the incoming ID token's group list contains IfGroup, the synthetic Claims gains a Scope with Pattern=Scope and Verbs=Verbs.
type OIDCVerifier ¶
type OIDCVerifier struct {
// contains filtered or unexported fields
}
OIDCVerifier wraps go-oidc's provider + id-token verifier behind a parsec-friendly Verify call that returns synthetic auth.Claims.
One OIDCVerifier is constructed per parsec.New (issuer discovery happens at boot, NOT per request). The underlying *oidc.IDTokenVerifier is safe for concurrent use, so a single OIDCVerifier services every inbound request.
func NewOIDCVerifier ¶
func NewOIDCVerifier(ctx context.Context, cfg OIDCConfig) (*OIDCVerifier, error)
NewOIDCVerifier discovers the issuer's OpenID configuration and returns a verifier ready to validate ID tokens. The ctx is used for the discovery roundtrip only — once NewOIDCVerifier returns, the verifier is decoupled from ctx and can outlive it.
Returns an error when cfg.Issuer is empty (callers should construct no verifier in that case), when discovery fails, or when cfg.Audience is empty.
func (*OIDCVerifier) Audience ¶
func (v *OIDCVerifier) Audience() string
Audience reports the configured audience. Exposed for the manifest and tests.
func (*OIDCVerifier) Issuer ¶
func (v *OIDCVerifier) Issuer() string
Issuer reports the configured issuer URL. Exposed for the manifest.
func (*OIDCVerifier) Verify ¶
Verify validates token against the configured IdP (signature, exp, iat, aud, iss) and translates the result into a synthetic mgmt Claims. Returns one of the auth sentinel errors on failure so the composite verifier and the HTTP middleware can map to PARSEC_AUTH_*.
The returned Claims always carries Typ=TypeMgmt. OIDC IdPs do not issue refresh / access tokens in the parsec sense; clients hit `parsec login oidc` once per session and present the resulting ID token as a mgmt bearer.
type PairResult ¶
type PairResult struct {
AccessToken string
RefreshToken string
AccessExpires time.Time
RefreshExpires time.Time
}
PairResult is what IssuePair returns: both tokens and their absolute expirations.
type RedisKeyRingStore ¶
type RedisKeyRingStore struct {
// contains filtered or unexported fields
}
RedisKeyRingStore persists the KeyRing as a JSON blob in Redis with a monotonic version counter. Save uses WATCH/MULTI to detect concurrent writes; Watch listens on a pub/sub channel for cross-node fanout of rotation events.
func NewRedisKeyRingStore ¶
func NewRedisKeyRingStore(client redis.UniversalClient) *RedisKeyRingStore
NewRedisKeyRingStore constructs a store backed by client. The default key prefix is "parsec"; the on-disk layout becomes:
<prefix>:keyring string (JSON snapshot) <prefix>:keyring:version integer (monotonic) <prefix>:keyring:events pub/sub channel (version-number payloads)
func (*RedisKeyRingStore) Ensure ¶
Ensure returns a ring, bootstrapping if empty. Mirrors the file store bootstrap pattern; the resulting ring is persisted on bootstrap.
func (*RedisKeyRingStore) Load ¶
func (s *RedisKeyRingStore) Load(ctx context.Context) (*KeyRing, error)
Load reads the persisted snapshot. Returns (nil, os.ErrNotExist) when the key does not exist so the caller can bootstrap.
func (*RedisKeyRingStore) Save ¶
func (s *RedisKeyRingStore) Save(ctx context.Context, r *KeyRing) error
Save persists ring with optimistic concurrency. Uses WATCH on the version key so concurrent writes from two nodes are detected and the loser must reload + retry. Bumps the version and publishes it on the pub/sub channel so other nodes can react.
func (*RedisKeyRingStore) Watch ¶
func (s *RedisKeyRingStore) Watch(ctx context.Context, onChange func(*KeyRing)) error
Watch subscribes to the events channel and re-loads + fires onChange on every version bump it sees from another node. Own writes are observable too — callers may dedupe by comparing the resulting snapshot. Blocks until ctx is canceled.
func (*RedisKeyRingStore) WithKeyPrefix ¶
func (s *RedisKeyRingStore) WithKeyPrefix(p string) *RedisKeyRingStore
WithKeyPrefix overrides the namespace.
type Role ¶
type Role string
Role is a key's lifecycle position in a KeyRing.
const ( // RoleActive is the signing key. Exactly one key in a ring holds this // role at a time. New tokens are minted with this key's kid. RoleActive Role = "active" // RoleVerifyOnly accepts existing tokens but does not sign new ones. // A key transitions to verify-only when superseded by Promote, or when // added via Add (which never installs as active). RoleVerifyOnly Role = "verify-only" // RoleRetired is a deleted key. Retired keys are dropped from the next // snapshot and stop verifying immediately. RoleRetired Role = "retired" )
type Scope ¶
type Scope struct {
Pattern string `json:"pat"`
Verbs []Verb `json:"v"`
Deny bool `json:"deny,omitempty"`
// contains filtered or unexported fields
}
Scope is one entry in a Claims.Scopes set: a channel pattern plus the verbs the holder is granted on names that match.
The Pattern field stays as raw text on the wire so the token shape matches the spec; Compiled() lazily parses and caches the structured Pattern for the matcher's hot path.
A scope with Deny=true inverts the meaning: a matching (channel, verb) pair is REMOVED from the grant set with "deny wins" precedence. See Claims.Authorizes for the full evaluation order. The Deny field is omitted from the wire when false so allow-only tokens stay compact.
func (*Scope) AllowsVerb ¶
AllowsVerb is a cheap check that only inspects the verb list, used by callers that have already matched the pattern.
func (*Scope) Authorizes ¶
Authorizes reports whether this scope grants verb v on the channel name. A malformed Pattern fails closed (returns false, never panics).
func (*Scope) Compiled ¶
Compiled returns the parsed channels.Pattern, caching it on first call. Errors are sticky: a Scope with a malformed Pattern returns the same error every time without re-parsing.
func (*Scope) IsDeny ¶
IsDeny reports whether the scope is a negative grant. Convenience accessor so callers don't need to reach for the field directly when iterating a Claims.Scopes slice.
func (*Scope) MatchesVerb ¶
MatchesVerb is the shared "pattern matches AND verb is listed" check. Used by both the allow-pass and the deny-pass in Claims.Authorizes so the precedence logic stays in one place. Returns false on any compile / match / verb miss (fail closed).
type Signer ¶
type Signer struct {
// contains filtered or unexported fields
}
Signer produces signed Parsec JWTs from Claims using a KeyRing's active key.
type Snapshot ¶
type Snapshot struct {
FormatVersion string `json:"format_version"`
ActiveKeyID string `json:"active_key_id"`
Keys []SnapshotKey `json:"keys"`
}
Snapshot is the serializable view of a KeyRing.
type SnapshotKey ¶
type SnapshotKey struct {
ID string `json:"id"`
SecretHex string `json:"secret_hex"`
Role Role `json:"role"`
CreatedAt string `json:"created_at"`
RetiredAt string `json:"retired_at,omitempty"`
}
SnapshotKey is one persisted key entry.
type Type ¶
type Type string
Type is the JWT typ-extension claim Parsec uses to disambiguate token purpose. The standard JWT typ header stays "JWT"; the discriminator lives in the payload's "typ" field.
type Verb ¶
type Verb string
Verb is the typed action a Scope grants on a channel pattern. The wire values are the lowercase strings — they appear inside JWT payloads and the manifest, so changing them is a wire break.
const ( // VerbSubscribe authorizes a subscribe over the websocket / SSE // surface. This is the verb the broker's subscribe authorizer // checks. VerbSubscribe Verb = "subscribe" // VerbPublish authorizes a client-side publish (server-side // publishes via the management RPC still go through the mgmt // bearer). VerbPublish Verb = "publish" // VerbManage authorizes per-channel management RPCs called with // an access token instead of a mgmt token — delegated administration. VerbManage Verb = "manage" )
type Verifier ¶
type Verifier struct {
Clock func() time.Time
Leeway time.Duration
// OnVerify, when non-nil, is invoked once per Verify call with the
// token type that was checked (or the empty string when the token
// failed to parse before the typ claim could be read), the expected
// type (may be empty), and the resulting error (nil on success).
// Used by the metrics layer to record verifications by type+result
// without coupling the auth package to prometheus.
OnVerify func(parsed Type, expected Type, err error)
// contains filtered or unexported fields
}
Verifier validates Parsec JWTs against a KeyRing. The ring is followed by reference, so a reload that swaps the ring's contents takes effect on the next call.
func NewVerifier ¶
NewVerifier returns a Verifier bound to ring.