extension

package
v0.17.0 Latest Latest
Warning

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

Go to latest
Published: Aug 28, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package extension lets a single definition of a custom authorization parameter — including a Rich Authorization Requests (RFC 9396) detail type — be shared between client and server, so its wire name, cardinality, encoding, size limit, sensitivity and validation rules are implemented exactly once instead of twice with subtly different rules.

A Definition is created once (typically as a package-level var) and then used by client code to set a value on an outgoing request (extension.Set(&req.Extensions, Definition, value)) and by server code, after registering the same Definition in a Registry, to read the validated value back out through a typed accessor (extension.Get(validated.Extensions, Definition)) — never a generic map[string]any, which would allow name collisions and invalid type assertions. Set and Get are package-level generic functions rather than methods on Values — Go does not allow a method to introduce its own type parameter beyond its receiver's, so a generic method shaped exactly like Values.Set[T](...) cannot be expressed. A RAR detail type additionally bounds the number of detail objects, bytes per object, total bytes, JSON depth, and duplicate/unknown JSON members.

Any parameter without a registered Definition is rejected by default; there is no production option to silently preserve unknown fields. extension has no dependency on client or server and must stay that way — wiring a Registry into server's authorization-parameter validation and into client's outgoing request construction is the integration step that depends on this package, not the other way around.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUnregisteredParameter indicates an authorization parameter had
	// no matching registered Definition — the default-reject behavior
	// this package requires (see doc.go).
	ErrUnregisteredParameter = errors.New("extension: unregistered parameter")

	// ErrSourceNotAllowed indicates a parameter arrived somewhere its
	// Definition's AllowedSources does not permit (e.g. a
	// SourceRequestObject-only value submitted as a plain parameter).
	ErrSourceNotAllowed = errors.New("extension: parameter is not permitted from this source")

	// ErrValueTooLarge indicates an encoded value exceeded its
	// Definition's MaxBytes.
	ErrValueTooLarge = errors.New("extension: value exceeds the configured size limit")

	// ErrDuplicateDefinition indicates two Definitions (or RARDefinitions)
	// were registered under the same wire name (or RAR type).
	ErrDuplicateDefinition = errors.New("extension: duplicate definition")

	// ErrCardinalityMismatch indicates a Definition's declared
	// Cardinality does not match its Go type T (e.g. Cardinality: Single
	// with a slice T, or vice versa).
	ErrCardinalityMismatch = errors.New("extension: cardinality does not match the definition's type")

	// ErrDuplicateMember indicates a RAR detail object had the same
	// top-level JSON member name more than once.
	ErrDuplicateMember = errors.New("extension: duplicate JSON member")

	// ErrRARTooLarge indicates an authorization_details array exceeded
	// its RARRegistry's MaxTotalBytes.
	ErrRARTooLarge = errors.New("extension: authorization_details exceeds the configured total size limit")

	// ErrRARTooDeep indicates an authorization_details array (or one of
	// its objects) exceeded its RARRegistry's MaxDepth.
	ErrRARTooDeep = errors.New("extension: authorization_details exceeds the configured nesting depth limit")

	// ErrRARUnregisteredType indicates a detail object's "type" member
	// had no matching registered RARDefinition.
	ErrRARUnregisteredType = errors.New("extension: unregistered authorization_details type")

	// ErrRARTooManyObjects indicates more detail objects of one type
	// appeared than that type's RARDefinition.MaxObjects permits.
	ErrRARTooManyObjects = errors.New("extension: too many authorization_details objects of this type")

	// ErrRARObjectTooLarge indicates one detail object exceeded its
	// RARDefinition.MaxBytesPerObject.
	ErrRARObjectTooLarge = errors.New("extension: authorization_details object exceeds the configured size limit")
)

Functions

func AsParameters

func AsParameters(values Values, defs ...Registered) map[string]json.RawMessage

AsParameters returns values re-encoded as a map[string]json.RawMessage keyed by wire name — the shape AccessTokenParams.Parameters and IDTokenParams.Parameters (internal/token) expect — restricted to the given Definitions whose ReturnInTokenClaims is true. A caller passes the same Definitions it registered so this package never has to guess which values are claim-eligible from the raw map alone.

func Get

func Get[T any](values Values, def Definition[T]) (T, bool)

Get decodes the value stored under def's wire name, if any. It returns false if no value was set, and false (not an error) if the stored raw value cannot be decoded into T — that should not happen for a Values produced by this package's own Set or Registry.Parse, since both validate against T themselves before storing, but Get stays defensive rather than panicking on an externally-assembled Values.

func Set

func Set[T any](values *Values, def Definition[T], v T) error

Set encodes v under def's wire name into values. It fails if v does not satisfy def.Validate (when set), or if the encoded size exceeds def.MaxBytes.

func Snapshot

func Snapshot(values Values) map[string]json.RawMessage

Snapshot returns a copy of every value in values, still raw JSON, keyed by wire name — for a caller (e.g. client, embedding an outgoing request object) that needs to enumerate an arbitrary, caller-populated Values without knowing each entry's Definition ahead of time. It is the one place this package hands back an untyped map, and only ever raw JSON bytes Set has already validated — never a live reference to Values' internal storage.

Types

type Cardinality

type Cardinality uint8

Cardinality is a closed set describing whether a Definition's Go type represents one value or a list of values on the wire.

const (

	// Single means the Definition's T is one value.
	Single Cardinality

	// Multiple means T is a slice — the wire value is a JSON array.
	Multiple
)

type Definition

type Definition[T any] struct {
	// Name is the wire parameter name.
	Name string

	// Cardinality is whether the wire value is a single value or a JSON
	// array — checked against T's actual Go kind when the Definition is
	// registered (see Registered), so a mismatched declaration is caught
	// at startup rather than silently ignored.
	Cardinality Cardinality

	// AllowedSources is where this parameter may legitimately appear on
	// an authorization request. A value arriving anywhere else is
	// rejected with ErrSourceNotAllowed.
	AllowedSources Source

	// MaxBytes bounds the size of the parameter's encoded JSON value.
	// Required — there is no implicit default; zero rejects every value.
	MaxBytes int

	// Sensitive marks a value that must never be copied into a log line
	// or error message — a caller reading it back via Get is expected to
	// apply the same care it would to a fapi.Secret.
	Sensitive bool

	// ReturnInTokenClaims, if true, means a validated value should be
	// copied into the token claims an authorization server issues
	// (AccessTokenParams.Parameters / IDTokenParams.Parameters) — a
	// caller's decision, not something this package does on its own.
	ReturnInTokenClaims bool

	// Validate, if non-nil, applies extra semantic checks beyond T's JSON
	// shape (e.g. a string pattern, a numeric range, a business rule). A
	// Definition with no Validate accepts any value that unmarshals into
	// T without unknown fields.
	Validate func(T) error
}

Definition captures the complete wire contract for one custom authorization parameter, defined exactly once and shared between client and server — see ARCHITECTURE.md design rules 10-11.

type RARDefinition

type RARDefinition[T any] struct {
	// Type is the detail object's "type" member (RFC 9396 §2).
	Type string

	// MaxObjects bounds how many objects of this type may appear in one
	// authorization_details array.
	MaxObjects int

	// MaxBytesPerObject bounds the size of one detail object's raw JSON.
	MaxBytesPerObject int

	// Validate, if non-nil, applies extra semantic checks beyond T's JSON
	// shape.
	Validate func(T) error
}

RARDefinition captures the wire contract for one Rich Authorization Requests (RFC 9396) detail type: the "type" discriminator value, the Go type its type-specific fields decode into, and per-object bounds. Registered alongside plain Definitions in the same package, but kept in its own RARRegistry — an authorization_details array is validated as a whole (total size, nesting depth), not parameter-by-parameter.

type RARDetail

type RARDetail[T any] struct {
	Type   string
	Fields T
}

RARDetail is one validated authorization_details object: its type and decoded type-specific fields.

func RARGet

func RARGet[T any](values RARValues, def RARDefinition[T]) ([]RARDetail[T], error)

RARGet decodes every validated object of def's type.

type RARRegistry

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

RARRegistry is an immutable set of registered RARDefinitions, plus the bounds that apply to the authorization_details array as a whole.

func NewRARRegistry

func NewRARRegistry(maxTotalBytes, maxDepth int, defs ...registeredRAR) (*RARRegistry, error)

NewRARRegistry validates and indexes defs. maxTotalBytes bounds the entire authorization_details array's raw size; maxDepth bounds its JSON nesting depth (an object or array literal one level deep counts as depth 1). Both are required — a zero value rejects every request rather than silently permitting unbounded nesting or size.

func (*RARRegistry) Parse

func (r *RARRegistry) Parse(raw json.RawMessage) (RARValues, error)

Parse validates raw — the authorization_details parameter's raw JSON array — against the registry: total size and nesting depth bounds, then per object: a registered type, no duplicate top-level JSON members, its own size and per-type object-count bounds, and strict decoding against its RARDefinition's type.

type RARValues

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

RARValues holds validated authorization_details objects, grouped by type. Like Values, it has no public accessor of its own — RARGet is the only way to read validated objects back out, typed.

type Registered

type Registered interface {
	// contains filtered or unexported methods
}

Registered is a type-erased handle to a Definition[T], for holding a heterogeneous collection of registered definitions in a Registry. Every concrete Definition[T] implements it; nothing else can, since its methods are unexported to this package — the same closed-set pattern used for every other sum type in this module.

type Registry

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

Registry is an immutable set of registered Definitions, built once via NewRegistry (typically at server startup) and reused to validate every incoming request against the same rules.

func NewRegistry

func NewRegistry(defs ...Registered) (*Registry, error)

NewRegistry validates and indexes defs. It fails if two Definitions share a wire name, or if any Definition's declared Cardinality does not match its Go type.

func (*Registry) Definitions

func (r *Registry) Definitions() []Registered

Definitions returns every Definition r was built with, for use with AsParameters.

func (*Registry) Parse

func (r *Registry) Parse(params map[string]json.RawMessage, core map[string]struct{}, source Source) (Values, error)

Parse validates params against the registry: every name not present in core (the caller's own set of standard protocol parameter names, already handled elsewhere) must have a registered Definition — a name with neither is rejected with ErrUnregisteredParameter, the default-reject behavior this package requires. source identifies where params came from (a plain parameter or a signed request object), checked against each matching Definition's AllowedSources.

type Source

type Source uint8

Source is a bitmask of where a Definition's value may legitimately originate on an authorization request. A Definition must permit at least one.

const (
	// SourcePlainParameter permits the value to appear as a plain,
	// unprotected top-level PAR/authorization parameter.
	SourcePlainParameter Source = 1 << iota

	// SourceRequestObject permits the value to appear inside a signed
	// request object — required for any Definition whose value must be
	// integrity-protected rather than accepted as a bare, unsigned
	// parameter.
	SourceRequestObject
)

func (Source) Allows

func (s Source) Allows(one Source) bool

Allows reports whether s permits one.

type Values

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

Values holds validated extension values, keyed internally by wire name. It has no public fields and no generic accessor of its own — Set and Get are the only way to write or read a value, and both take the same Definition a value was validated against, so a caller can never mismatch a stored value's type against its wire name the way a map[string]any would allow.

Jump to

Keyboard shortcuts

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