Help us shape the future of Go! Take the 2026 Go Developer Survey

access

package
v0.131.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2026 License: MIT Imports: 15 Imported by: 2

Documentation

Overview

Package access is ContentKit's access model: the Actor; the key grammar (<tenant>:content:<id>, <tenant>:content:<id>/<version>, <tenant>:group:<id> and configured tiers); rules stored on content, versions and entities; the Gate deciding versions from one billing read per request, its listing Filter and SQL over content_facets; and the Billing dependency.

Index

Constants

View Source
const (
	// ChannelContentCreate permits creating one's own posts in a channel.
	ChannelContentCreate = "channel:content:create"
	// ChannelContentEditOwn permits editing and publishing one's own channel posts.
	ChannelContentEditOwn = "channel:content:edit-own"
	// ChannelContentWrite permits editing every post and version in the channel.
	ChannelContentWrite = "channel:content:write"
	// ChannelSettingsManage permits changing channel settings and lifecycle.
	ChannelSettingsManage = "channel:settings:manage"
	// ChannelMediaWrite permits uploading and editing the channel's media.
	ChannelMediaWrite = "channel:media:write"
	// ChannelCommentsModerate permits moderating comments on the channel and its posts.
	ChannelCommentsModerate = "channel:comments:moderate"
)

Channel permissions are held in the primary channel's AuthKit group. Metadata associations never carry authority. Hosts compose roles from these capabilities and assign them to a channel group.

View Source
const Anyone = ""

Anyone is the audience of a free row: an anonymous visitor.

View Source
const HeldLimit = 1000

HeldLimit is the keys one billing read returns per keyspace; a viewer holding more gets an incomplete Filter.

View Source
const (

	// MaxKeyBytes bounds a key: <tenant>:content:<id>/<version>.
	MaxKeyBytes = maxTenant + len(":content:") + 36 + 1 + 36
)

Variables

View Source
var ErrNotContentKey = errors.New("access: not a content key")

ErrNotContentKey is a string outside the key grammar.

View Source
var ErrRule = errors.New("access: invalid rule")

ErrRule is a rule outside the grammar or naming an undeclared tier.

View Source
var ErrUnavailable = errors.New("access: entitlements unavailable")

ErrUnavailable wraps a failed billing read. The result it comes with is the fail-closed one: only free rules and claimed tiers open, nothing is offered.

Functions

func AudiencesSQL added in v0.81.0

func AudiencesSQL(alias string, tiers []string) (string, map[string]any, error)

AudiencesSQL is a text[] expression over the content_facets row aliased alias naming who opens it without buying it: Anyone and every one of tiers for a public row or matured public release, the current base and released tier audiences for a restricted row, nobody otherwise. Totals count rows per audience with it. Args are pgx named args prefixed audience_.

func ChannelPermissions added in v0.131.0

func ChannelPermissions() []string

ChannelPermissions lists the capabilities hosts may bundle into channel roles.

func ChannelPrefix added in v0.131.0

func ChannelPrefix(tenant string) string

ChannelPrefix is the keyspace of a tenant's channel keys.

func ContentPrefix added in v0.81.0

func ContentPrefix(tenant string) string

ContentPrefix is the keyspace of a tenant's content and version keys.

func Names added in v0.71.0

func Names(ps ...Permission) []string

Names is ps as strings, as an auth's role catalog declares them.

func PublicSQL added in v0.117.0

func PublicSQL(alias string) string

PublicSQL reports anonymous access from the current rule and mature releases. alias is a trusted SQL identifier, validated by Filter.SQL/AudiencesSQL.

func ReleasedTiersSQL added in v0.117.0

func ReleasedTiersSQL(alias string) string

ReleasedTiersSQL lists the tier audiences that have reached their timestamp.

func ValidTenant added in v0.81.0

func ValidTenant(s string) bool

ValidTenant reports a tenant id usable in keys: [a-z0-9_-]{1,64}.

func WithMemo added in v0.66.0

func WithMemo(ctx context.Context) context.Context

WithMemo returns ctx with a request memo: within it a viewer's Filter is read once and Decide answers from it (or from keys already read), so Filter then Decide is one billing read. ContentKit's content and media handlers install it; a host installs it in its own middleware. A ctx that already has one is returned unchanged.

Types

type AccountMembership added in v0.92.0

type AccountMembership struct{ ID string }

AccountMembership identifies an existing account in the target tenant. ID is empty for an actor who can administer the tenant but is not a member.

type Actor

type Actor struct {
	ID string // the subject (auth.Identity.Subject); empty when Anonymous
	// Account records membership in the engine's tenant independently of the
	// source actor. A nonnil empty membership is a verified actor from another
	// tenant; it grants no audience identity or ownership shortcut here.
	// Nil retains the legacy host contract that a person's ID is its account ID.
	Account *AccountMembership
	// Kind is the subject's kind: a person (auth.SubjectUser) or an
	// application (auth.SubjectApplication).
	Kind auth.SubjectKind
	// Invoker is who acts on the subject's behalf; zero when the subject acts
	// for itself.
	Invoker auth.Invoker
	// IP is the client's address (the request's RemoteAddr): the key of an
	// anonymous actor's reactions, votes and limits.
	IP        string
	Anonymous bool
	// Verified is the request's verified credential, kept for that request
	// alone: Can asks it. Nil for an anonymous actor.
	Verified auth.Verified
}

Actor is the caller. ContentKit's routes build it from the host's auth.Authenticator: the Verified's Identity, or anonymous when the request carries no credential.

func (Actor) AccountID added in v0.92.0

func (a Actor) AccountID() string

AccountID is the actor's audience account in the current tenant, if any. A global operator keeps its source ID for audit but has no implicit account in a different tenant.

func (Actor) Can added in v0.71.0

func (a Actor) Can(ctx context.Context, scope auth.Scope, permission string) (bool, error)

Can reports whether the actor holds exactly permission in scope, asking its Verified (auth.PermissionChecker). Anonymous, without a Verified or without the capability it holds nothing. An error never grants.

func (Actor) Member added in v0.92.0

func (a Actor) Member() bool

Member reports a person with an account in the engine's tenant.

func (Actor) Person added in v0.71.0

func (a Actor) Person() bool

Person reports a signed-in person: a subject that is not an application.

type Answer added in v0.66.0

type Answer struct {
	Keys map[string]bool     // every requested key; absent means not held
	Held map[string]HeldKeys // one entry per requested prefix
}

Answer answers a Query.

type Billing added in v0.81.0

type Billing interface {
	// Held answers q for subject in one read. A subject that cannot hold
	// keys (no customer id) answers an empty Answer, not an error.
	Held(ctx context.Context, subject string, q Query) (Answer, error)
}

Billing reads authoritative entitlement grants from the billing service. Products, prices, checkout, subscriptions and grants are managed directly through that service's API; ContentKit does not copy its catalog or queue billing mutations. adapters/openrails supports embedded and HTTP clients.

type Decision added in v0.66.0

type Decision struct {
	Allowed bool
	// Unknown: the billing read failed, so the item is denied and nothing is
	// offered (the viewer may already hold it).
	Unknown bool
	// Sell are the keys whose purchase opens it, the rule's own first (its
	// tiers or entity), then the version's;
	// The host resolves these keys through its billing provider.
	Sell []Key
	// Requires is the key a buyer must hold first (ChannelPPV), nil when held.
	Requires *Key
	// Preview is the part a viewer may read while it is locked.
	Preview *Preview
	// Releases describe upcoming additional audiences.
	Releases []Release
}

Decision is one item's verdict.

type Entitled added in v0.81.0

type Entitled interface {
	Entitlements() []string
}

Entitled is an optional capability of an actor's auth.Verified: the entitlement claims its verified token carries. The Gate answers a tier in kinds.Config.TokenClaims from them without a billing read, so a refund keeps such a tier until the token expires.

type Facet added in v0.81.0

type Facet struct {
	Level     Level
	Tiers     []string
	ChannelID *string
	Releases  []Release
}

Facet is a rule as the content_facets listing projection stores it: access_level, access_tiers, access_group_id and access_releases.

type Filter added in v0.66.0

type Filter struct {
	// contains filtered or unexported fields
}

Filter is one viewer's access as one billing read left it: the configured tiers they hold and their keys in the tenant's content and group keyspaces. It does no I/O: listings push it into SQL (Filter.SQL), loops call Allows. A Filter is immutable and safe for concurrent use.

func (*Filter) Allows added in v0.66.0

func (f *Filter) Allows(it Item) (allowed, known bool)

Allows decides it from the filter alone. known is false when the answer needs a key the filter did not read in full (a truncated keyspace, a failed read); Gate.Settle and Gate.Decide resolve those. Allows and SQL(Hide) agree: the SQL admits exactly the rows Allows allows or does not know, and on an Unknown filter only the allowed ones.

func (*Filter) Anonymous added in v0.66.0

func (f *Filter) Anonymous() bool

Anonymous reports a filter of an anonymous viewer: it holds nothing.

func (*Filter) Channels added in v0.131.0

func (f *Filter) Channels() []string

Channels returns the channel ids whose membership key the viewer holds, sorted.

func (*Filter) Complete added in v0.66.0

func (f *Filter) Complete() bool

Complete reports whether Contents, Versions and Channels are the viewer's whole holdings. An anonymous filter is complete; an unknown one is not.

func (*Filter) Contents added in v0.81.0

func (f *Filter) Contents() []string

Contents returns legacy parent grants for audit and explicit migration. They do not grant version access and must not be used as audience ownership.

func (*Filter) SQL added in v0.66.0

func (f *Filter) SQL(alias string, mode Mode) (string, map[string]any, error)

SQL returns a boolean predicate over the content_facets row aliased alias, with its pgx named args (all prefixed access_). Hide admits the rows the viewer opens: free, a held tier the rule names, a held key of the rule's channel, or an exact held version key; plus, while a keyspace is truncated, its candidates, which Gate.Settle decides. An Unknown filter admits no candidates. Lock admits every row. The text depends only on alias and mode, so one prepared statement serves every viewer. Lock mode retains inaccessible items for a locked preview; Hide mode excludes them.

func (*Filter) Tier added in v0.66.0

func (f *Filter) Tier(name string) bool

Tier reports whether the viewer holds the configured tier name.

func (*Filter) Unknown added in v0.66.0

func (f *Filter) Unknown() bool

Unknown reports a failed billing read: only free rules and claimed tiers open, and nothing is for sale.

func (*Filter) Versions added in v0.81.0

func (f *Filter) Versions() map[string]string

Versions returns the held version keys: version id -> content id.

type Gate added in v0.66.0

type Gate struct {
	// contains filtered or unexported fields
}

Gate decides access to versions from one billing read per viewer and request (see WithMemo).

func NewGate added in v0.66.0

func NewGate(c GateConfig) (*Gate, error)

NewGate validates c.

func (*Gate) Decide added in v0.66.0

func (g *Gate) Decide(ctx context.Context, actor Actor, items []Item) ([]Decision, error)

Decide decides items for actor in order. It answers from the request's memoized Filter and keys where they suffice and reads every other key in one billing read. A failed read denies what it would have decided (Unknown) and returns an error wrapping ErrUnavailable. An item with an invalid id or rule is denied with nothing offered and logged.

func (*Gate) Filter added in v0.66.0

func (g *Gate) Filter(ctx context.Context, actor Actor) (*Filter, error)

Filter reads the viewer's access in one billing read: the configured tiers a token claim does not answer and the tenant's content and entity keyspaces. Within WithMemo it is read once per request. An anonymous viewer costs no read. On a failed read Filter returns the fail-closed filter (Unknown) and an error wrapping ErrUnavailable: serve that filter or answer 503.

func (*Gate) Holds added in v0.81.0

func (g *Gate) Holds(ctx context.Context, actor Actor, keys []Key) ([]bool, error)

Holds reports, per key in order, whether actor holds it: from the request's memoized Filter and keys where they suffice, the rest in one billing read. An anonymous actor holds nothing and costs no read. A failed read answers false for what it would have decided and returns an error wrapping ErrUnavailable. Non-tier keys outside this gate's tenant are refused without a billing read.

func (*Gate) Settle added in v0.66.0

func (g *Gate) Settle(ctx context.Context, actor Actor, f *Filter, items []Item) (keep []bool, short bool, err error)

Settle completes f's verdicts for a hide-mode page: items f decides keep its answer, the rest (candidates of a truncated keyspace) are decided in one exact read. keep[i] reports items[i] open; short reports a dropped item, so the page may hold fewer rows than asked. It costs no read when f decides every item. A failed read drops the undecided items and returns an error wrapping ErrUnavailable.

func (*Gate) Tenant added in v0.81.0

func (g *Gate) Tenant() string

Tenant is the gate's tenant.

func (*Gate) Tiers added in v0.81.0

func (g *Gate) Tiers() []string

Tiers returns the configured tiers, sorted.

type GateConfig added in v0.66.0

type GateConfig struct {
	// Tenant is the tenant of every key: [a-z0-9_-]{1,64}.
	Tenant string
	// Kinds supplies the configured tiers, the only bare keys ContentKit
	// interprets, and the token claims answering them without a billing read.
	Kinds kinds.Config
	// Billing answers held keys; nil holds none for anyone, so only free
	// rules and claimed tiers open.
	Billing Billing
	Logger  *slog.Logger
	// Now is the application clock used for audience-release decisions.
	// Nil uses time.Now, matching ordinary production behavior.
	Now func() time.Time
}

GateConfig configures a Gate from the engine's config.

type HeldKeys added in v0.66.0

type HeldKeys struct {
	Keys      []string
	Truncated bool // more than Query.Limit keys are held
}

HeldKeys is the keys a subject holds under one prefix, in byte order.

type Item added in v0.66.0

type Item struct {
	Content string
	Version string
	// Rule is the version's effective rule (Effective).
	Rule Rule
}

Item is one independently accessible version. Content identifies its catalog grouping; Version is required and is the entitlement boundary.

type Key added in v0.66.0

type Key struct {
	// contains filtered or unexported fields
}

Key is an entitlement key ContentKit gives meaning to. OpenRails stores keys opaquely; construct them here, never by hand. The zero Key is invalid.

func ChannelKey added in v0.131.0

func ChannelKey(tenant, id string) (Key, error)

ChannelKey is the key of channel id.

func ContentKey deprecated added in v0.81.0

func ContentKey(tenant, id string) (Key, error)

ContentKey identifies a legacy parent grant for audit and explicit migration. It never opens a version. New products must use VersionKey for each item.

Deprecated: Use VersionKey for audience access. ContentKey remains for explicitly auditing and migrating old billing records.

func ParseKey added in v0.66.0

func ParseKey(s string) (Key, error)

ParseKey parses a key string. Anything outside the grammar is ErrNotContentKey, a bare string that is not a valid tier name included.

func TierKey added in v0.81.0

func TierKey(name string) (Key, error)

TierKey is a tier's key, its bare name: ^[a-z][a-z0-9_-]{0,62}$.

func VersionKey added in v0.81.0

func VersionKey(tenant, content, version string) (Key, error)

VersionKey is the key of one version of content.

func (Key) Channel added in v0.131.0

func (k Key) Channel() string

Entity is the channel id of an channel key.

func (Key) Content added in v0.81.0

func (k Key) Content() string

Content is the content id of a content or version key.

func (Key) Kind added in v0.66.0

func (k Key) Kind() KeyKind

Kind returns what the key grants; 0 for the zero Key.

func (Key) MarshalText added in v0.66.0

func (k Key) MarshalText() ([]byte, error)

MarshalText refuses the zero Key.

func (Key) String added in v0.66.0

func (k Key) String() string

String is the key as OpenRails stores it; "" for the zero Key.

func (Key) Tenant added in v0.81.0

func (k Key) Tenant() string

Tenant is the tenant of a content, version or channel key.

func (Key) Tier added in v0.66.0

func (k Key) Tier() string

Tier is the name of a tier key.

func (*Key) UnmarshalText added in v0.66.0

func (k *Key) UnmarshalText(b []byte) error

UnmarshalText parses with ParseKey.

func (Key) Version added in v0.81.0

func (k Key) Version() string

Version is the version id of a version key.

type KeyKind added in v0.66.0

type KeyKind uint8

KeyKind says what an entitlement key grants.

const (
	KeyContent KeyKind = iota + 1 // <tenant>:content:<id>: legacy parent grant; audit/migration only
	KeyVersion                    // <tenant>:content:<id>/<version>: one version
	KeyChannel                    // <tenant>:group:<id>: a channel membership named by an explicit rule
	KeyTier                       // <tier>: a tier from config, such as premium
)

type Level added in v0.66.0

type Level string

Level is a rule's access level. The stored group/group_ppv spellings remain stable encodings for channel access, including already recorded releases.

const (
	Public       Level = "public"    // anyone
	TierLevel    Level = "tier"      // holders of any of Rule.Tiers
	ChannelLevel Level = "group"     // holders of Rule.ChannelID's key: members-only content
	PPV          Level = "ppv"       // buyers of this version
	ChannelPPV   Level = "group_ppv" // buyers; buying needs Rule.ChannelID's key first
)

type Mode added in v0.81.0

type Mode string

Mode is a listing's access mode.

const (
	// Hide lists only what the viewer opens (a purchased library).
	Hide Mode = "hide"
	// Lock lists everything; Decide marks what stays locked.
	Lock Mode = "lock"
)

type Permission added in v0.71.0

type Permission string

Permission is a ContentKit application permission: a full root:<resource>:<action> name, held in the root scope of the host's auth (auth.Root) and checked there by the Verified's Can. ContentKit names them; a host declares them in its auth's roles from these constants (AuthKit: rbac.Root.Declare) and never spells one out.

const (
	// ContentCreate: a signed-in person may create their own content outside
	// a channel. Channel posts require that channel's create or write grant.
	ContentCreate Permission = "root:content:create"
	// ContentEditOwn: edit and publish content created by this account outside
	// a channel. Channel posts need the channel edit-own or broad write grant.
	ContentEditOwn Permission = "root:content:edit-own"
	// ContentWrite: create, edit, publish, delete and restore any content
	// item, and see everything.
	ContentWrite Permission = "root:content:write"
	// ContentViewRestricted: see every content item, whatever its state,
	// listing, site scope or access, and the site's content statistics.
	ContentViewRestricted Permission = "root:content:view-restricted"
	// PollsWrite: create, edit and delete polls, their options and images,
	// and see scheduled and inactive ones.
	PollsWrite Permission = "root:polls:write"
	// CommentsModerate: edit, delete and restore anyone's comments, and list
	// every comment.
	CommentsModerate Permission = "root:comments:moderate"
	// CommentsBan: ban users from commenting anywhere in the tenant.
	CommentsBan Permission = "root:comments:ban"
	// ModerationReview: list and resolve held comments and content.
	ModerationReview Permission = "root:moderation:review"
	// EntitiesWrite: create, edit, merge and delete entities, and set content's
	// memberships.
	EntitiesWrite Permission = "root:entities:write"
	// ChannelsWrite: manage channel identity, lifecycle and configuration.
	ChannelsWrite Permission = "root:channels:write"
	// NotificationsPublish: publish notices to users' inboxes (a host service).
	NotificationsPublish Permission = "root:notifications:publish"
	// ImportWrite: bulk-import content, entities, interactions and signals (a
	// legacy importer).
	ImportWrite Permission = "root:import:write"
	// AvatarsWrite: upload, edit and remove anyone's avatar on the avatar
	// primary, exempt from limits: staff, and another site forwarding its
	// people's (AvatarsConfig.Key).
	AvatarsWrite Permission = "root:avatars:write"
)

func Permissions added in v0.71.0

func Permissions() []Permission

Permissions is every application permission; hosts choose which roles hold them.

func (Permission) String added in v0.71.0

func (p Permission) String() string

type Preview added in v0.81.0

type Preview = kinds.Preview

Preview is the free part of locked content (kinds.Preview).

type Query added in v0.66.0

type Query struct {
	Keys     []string // exact keys
	Prefixes []string // each ending in ':'
	Limit    int      // keys per prefix; 0 is HeldLimit
}

Query is one entitlement read: exact keys and keyspace prefixes.

type Release added in v0.117.0

type Release struct {
	At        time.Time `json:"at"`
	Level     Level     `json:"level"`
	Tiers     []string  `json:"tiers,omitempty"`
	ChannelID string    `json:"group_id,omitempty"`
}

Release grants an additional audience access at At. It never removes rights granted by the base rule or an earlier release. At is bounded by the content and version publication dates. Tier names have no implicit rank.

type Rule added in v0.66.0

type Rule struct {
	Level     Level    `json:"level"`
	Tiers     []string `json:"tiers,omitempty"`    // TierLevel: any of these configured tiers
	ChannelID string   `json:"group_id,omitempty"` // Stable stored/wire encoding for ChannelLevel and ChannelPPV.
	// Preview is the part a viewer who does not open it may still read.
	Preview *Preview `json:"preview,omitempty"`
	// Releases add audiences at their release time; earlier rights remain.
	Releases []Release `json:"releases,omitempty"`
}

Rule is stored as JSON on content and its versions. Channel-owned content keeps its editable rule on the overall item; publication snapshots each version's effective rule. A version also opens to holders of its exact version key. Metadata associations never grant access.

func Effective added in v0.81.0

func Effective(content Rule, override *Rule) Rule

Effective is a version's rule: its override, else its content's.

func (Rule) AudienceTiers added in v0.117.0

func (r Rule) AudienceTiers(now time.Time) []string

AudienceTiers is the union of tier audiences released at now. Public access is reported separately by Free; entity/PPV rights require entitlement checks.

func (Rule) Facet added in v0.81.0

func (r Rule) Facet() Facet

Facet projects r onto content_facets' access columns.

func (Rule) Free added in v0.81.0

func (r Rule) Free(now time.Time) bool

Free reports whether r opens to anyone at now.

func (Rule) Previewed added in v0.86.0

func (r Rule) Previewed(k kinds.Kind) Rule

Previewed is r with its content kind's locked preview when r locks and names none of its own.

func (*Rule) UnmarshalJSON added in v0.81.0

func (r *Rule) UnmarshalJSON(b []byte) error

UnmarshalJSON refuses unknown fields; Validate checks the rest.

func (Rule) Validate added in v0.81.0

func (r Rule) Validate(cfg kinds.Config) error

Validate checks r against cfg: a known level with exactly its own fields, configured tiers, a valid channel id, and options only on a rule that locks.

type TenureReader added in v0.89.0

type TenureReader interface {
	Tenure(ctx context.Context, subject string, keys []string, at time.Time) (map[string]int64, error)
}

TenureReader optionally extends Billing with elapsed access to its entitlement keys. Tenure answers seconds per requested key as of at, merging overlapping product-access windows and excluding future time. An unavailable history returns an error, not an invented zero. Adapters may implement it to populate the reader's account statistics without a host-specific endpoint.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL