Documentation
¶
Overview ¶
Package tokenbroker is the policy point for issuing Parsec / Centrifugo connection tokens. A subscriber asks the broker for a token scoped to a list of channels; the broker consults a pluggable Authorizer to decide which channels the requesting principal is allowed to access, then mints a JWT that authorizes the granted subset.
The broker is built on top of the auth.Issuer primitive — it does NOT reimplement signing. What it adds is:
- An HTTP surface (/parsec/token, /parsec/revoke, /parsec/token/delegate) that user-facing clients hit before opening their websocket.
- An Authorizer interface so each Parsec deployment can plug its own ACL model (per-team, role-based, public-everything-private- to-tenant, etc.).
- A revocation list so a mis-issued token can be invalidated before its natural expiry.
- Delegated issuance so a backend service can mint a token "on behalf of" a user, with both identities captured in the audit log.
The broker depends on but does not own the schema registry — channel validity is the registry's concern, and channel-level ACL is the Authorizer's concern. Token signing belongs to auth.
Index ¶
- Variables
- type AuditEntry
- type AuditLogger
- type AuditLoggerFunc
- type AuthDecision
- type Authenticator
- type AuthenticatorFunc
- type Authorizer
- type AuthorizerFunc
- type Broker
- func (b *Broker) Delegate(ctx context.Context, bearer string, req DelegateRequest) (IssueResponse, error)
- func (b *Broker) Handler() http.Handler
- func (b *Broker) IsRevoked(ctx context.Context, tokenID, userID string, issuedAt time.Time) (bool, error)
- func (b *Broker) Issue(ctx context.Context, bearer string, req IssueRequest) (IssueResponse, error)
- func (b *Broker) Revoke(ctx context.Context, bearer string, req RevokeRequest) error
- type DelegateRequest
- type DeniedChannel
- type IssueRequest
- type IssueResponse
- type MemoryRevocations
- func (m *MemoryRevocations) IsRevoked(_ context.Context, tokenID string) (bool, error)
- func (m *MemoryRevocations) IsUserRevoked(_ context.Context, userID string, issuedAt time.Time) (bool, error)
- func (m *MemoryRevocations) Revoke(_ context.Context, tokenID, userID, reason string) error
- func (m *MemoryRevocations) RevokeAllForUser(_ context.Context, userID string) error
- type Options
- type RevocationStore
- type RevokeRequest
- type RoleAuthorizer
- type UserID
Constants ¶
This section is empty.
Variables ¶
var ErrUnauthenticated = errors.New("tokenbroker: unauthenticated")
ErrUnauthenticated is returned by Authenticator when the bearer cannot be resolved to a UserID.
Functions ¶
This section is empty.
Types ¶
type AuditEntry ¶
type AuditEntry struct {
TokenID string
IssuedTo UserID
Delegator UserID // service identity for delegated issuance; empty otherwise
Channels []string
ExpiresAt time.Time
IssuedAt time.Time
RemoteAddr string
}
AuditEntry captures the identity, channels, and lifetime of an issued token. Both the requester and the on-behalf-of identity are recorded for delegated issuance so audit trails capture both.
type AuditLogger ¶
type AuditLogger interface {
Audit(ctx context.Context, entry AuditEntry)
}
AuditLogger receives one structured record per issued token. Pure observation — no return value affects issuance.
type AuditLoggerFunc ¶
type AuditLoggerFunc func(ctx context.Context, entry AuditEntry)
AuditLoggerFunc is the function form of AuditLogger.
func (AuditLoggerFunc) Audit ¶
func (f AuditLoggerFunc) Audit(ctx context.Context, e AuditEntry)
Audit implements AuditLogger.
type AuthDecision ¶
type AuthDecision struct {
Granted []string `json:"granted"`
Denied []DeniedChannel `json:"denied,omitempty"`
}
AuthDecision is the Authorizer's answer.
type Authenticator ¶
Authenticator resolves the caller's identity from a request bearer. The broker delegates user authentication entirely — Parsec deployments already have an auth system; the broker just consumes its tokens.
type AuthenticatorFunc ¶
AuthenticatorFunc is the function form of Authenticator.
func (AuthenticatorFunc) Authenticate ¶
Authenticate implements Authenticator.
type Authorizer ¶
type Authorizer interface {
Authorize(ctx context.Context, user UserID, channels []string) AuthDecision
}
Authorizer decides which channels a user may access. The broker calls Authorize on each /parsec/token request.
var AllowAll Authorizer = AuthorizerFunc(func(_ context.Context, _ UserID, ch []string) AuthDecision { out := make([]string, len(ch)) copy(out, ch) return AuthDecision{Granted: out} })
AllowAll is the trivial authorizer that grants every requested channel. Useful in dev / single-tenant deployments; production deployments should plug in their own.
type AuthorizerFunc ¶
type AuthorizerFunc func(ctx context.Context, user UserID, channels []string) AuthDecision
AuthorizerFunc is the function form of Authorizer.
func (AuthorizerFunc) Authorize ¶
func (f AuthorizerFunc) Authorize(ctx context.Context, user UserID, channels []string) AuthDecision
Authorize implements Authorizer.
type Broker ¶
type Broker struct {
// contains filtered or unexported fields
}
Broker is the running token broker.
func New ¶
New constructs a Broker. opts.Issuer, opts.Authorizer, and opts.Authenticator are required; opts.Revocations defaults to MemoryRevocations.
func (*Broker) Delegate ¶
func (b *Broker) Delegate(ctx context.Context, bearer string, req DelegateRequest) (IssueResponse, error)
Delegate mints a token on behalf of req.OnBehalfOf. The bearer is the service's token. The DelegateAuthorizer (if configured) decides whether the service may act for the target user.
func (*Broker) Handler ¶
Handler returns an http.Handler that mounts the broker's three routes. The handler is path-prefix agnostic — mount it under whatever prefix the deployment prefers (the canonical mount is "/parsec").
Routes (relative to the mount):
POST /token — IssueRequest -> IssueResponse
POST /token/delegate — DelegateRequest -> IssueResponse
POST /revoke — RevokeRequest -> { "ok": true }
All routes require Authorization: Bearer <user-token>. The bearer is passed verbatim to the Authenticator; the broker itself does no signature verification.
func (*Broker) IsRevoked ¶
func (b *Broker) IsRevoked(ctx context.Context, tokenID, userID string, issuedAt time.Time) (bool, error)
IsRevoked is the Centrifugo-side check hook: the connect/subscribe pipeline can consult this when a token presents itself. Token-level revocation is checked by ID; user-level revocation is checked against the issuedAt timestamp.
func (*Broker) Issue ¶
func (b *Broker) Issue(ctx context.Context, bearer string, req IssueRequest) (IssueResponse, error)
Issue is the programmatic entry point — invoked by the HTTP handler for /parsec/token. The bearer is the requester's user-auth token; the broker resolves it to a UserID via the Authenticator.
type DelegateRequest ¶
type DelegateRequest struct {
OnBehalfOf string `json:"on_behalf_of"`
Channels []string `json:"channels"`
LifetimeSeconds int `json:"lifetime_seconds,omitempty"`
}
DelegateRequest is the body of POST /parsec/token/delegate.
type DeniedChannel ¶
DeniedChannel pairs a rejected channel with a human-readable reason. The reason is surfaced in the API response so clients can show a useful error to the end user.
type IssueRequest ¶
type IssueRequest struct {
Channels []string `json:"channels"`
TTL time.Duration `json:"ttl,omitempty"`
}
IssueRequest is the inbound shape of POST /parsec/token.
type IssueResponse ¶
type IssueResponse struct {
Token string `json:"token"`
TokenID string `json:"token_id"`
ExpiresAt time.Time `json:"expires_at"`
ChannelsGranted []string `json:"channels_granted"`
ChannelsDenied []DeniedChannel `json:"channels_denied,omitempty"`
}
IssueResponse is what the broker sends back.
type MemoryRevocations ¶
type MemoryRevocations struct {
// contains filtered or unexported fields
}
MemoryRevocations is the default in-memory revocation store.
func NewMemoryRevocations ¶
func NewMemoryRevocations() *MemoryRevocations
NewMemoryRevocations constructs an empty store.
func (*MemoryRevocations) IsUserRevoked ¶
func (m *MemoryRevocations) IsUserRevoked(_ context.Context, userID string, issuedAt time.Time) (bool, error)
IsUserRevoked reports whether userID has a blanket revocation issued at-or-after issuedAt.
func (*MemoryRevocations) Revoke ¶
func (m *MemoryRevocations) Revoke(_ context.Context, tokenID, userID, reason string) error
Revoke marks tokenID as invalid.
func (*MemoryRevocations) RevokeAllForUser ¶
func (m *MemoryRevocations) RevokeAllForUser(_ context.Context, userID string) error
RevokeAllForUser invalidates every token issued to userID before now.
type Options ¶
type Options struct {
Issuer *auth.Issuer
Authorizer Authorizer
Authenticator Authenticator
Revocations RevocationStore
// DefaultTTL is the access-token TTL when the caller did not
// specify one. Zero falls back to auth.Issuer's own AccessTTL.
DefaultTTL time.Duration
// AuditLog, when non-nil, receives one entry per successful issuance.
AuditLog AuditLogger
// DelegateAuthorizer, when non-nil, gates /parsec/token/delegate.
// The service identity is the bearer's UserID; the function returns
// nil if the service is allowed to act on behalf of the target user.
DelegateAuthorizer func(ctx context.Context, service UserID, onBehalfOf UserID) error
}
Options configures a Broker.
type RevocationStore ¶
type RevocationStore interface {
Revoke(ctx context.Context, tokenID, userID, reason string) error
IsRevoked(ctx context.Context, tokenID string) (bool, error)
RevokeAllForUser(ctx context.Context, userID string) error
IsUserRevoked(ctx context.Context, userID string, issuedAt time.Time) (bool, error)
}
RevocationStore tracks revoked token IDs. The default is in-memory; production deployments should plug in Redis or a database.
type RevokeRequest ¶
type RevokeRequest struct {
TokenID string `json:"token_id,omitempty"`
UserID string `json:"user_id,omitempty"`
Reason string `json:"reason,omitempty"`
}
RevokeRequest is the body of POST /parsec/revoke.
type RoleAuthorizer ¶
type RoleAuthorizer struct {
UserRoles func(ctx context.Context, user UserID) []string
RolePatterns map[string][]string
}
RoleAuthorizer is a small built-in Authorizer that grants channels based on a role->pattern map. Each user has zero or more roles; each role has zero or more channel patterns that match the channels the role may access.
func (*RoleAuthorizer) Authorize ¶
func (r *RoleAuthorizer) Authorize(ctx context.Context, user UserID, channels []string) AuthDecision
Authorize implements Authorizer. A channel is granted when at least one of the user's roles owns a pattern matching the channel.