contract

package
v1.8.2 Latest Latest
Warning

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

Go to latest
Published: Nov 5, 2026 License: MIT Imports: 30 Imported by: 0

Documentation

Overview

Package contract wires Trove into the Forge dashboard's contract path. It registers the `trove` contributor and answers its intents from the live Trove stores and their drivers, never from the extension's metadata store, which normal operation does not write.

Index

Constants

View Source
const (
	OpDownload = "download"
	OpPreview  = "preview"
	OpUpload   = "upload"
)

Ticket operations. A ticket authorises exactly one of these against one object, so a download ticket cannot be replayed as an upload.

View Source
const (
	DownloadTicketTTL = 60 * time.Second
	UploadTicketTTL   = 15 * time.Minute
)

Ticket lifetimes. The browser follows a download or preview link at once. An upload of the largest allowed file on a slow link takes a while.

View Source
const ContributorName = "trove"

ContributorName is the join key with packages/plugin-trove's `extension` field and matches the extension's name. A mismatch hides the React plugin with no error anywhere.

View Source
const SingleStoreName = "default"

SingleStoreName is the name the only store answers to in single-store mode.

View Source
const TicketHeader = "X-Trove-Ticket"

TicketHeader is the request header that carries an upload ticket. Upload tickets never ride in the URL: forge's tracing records every request's query string, and an upload ticket lives 15 minutes and may allow an overwrite.

Variables

View Source
var (
	// ErrTicketInvalid reports a ticket that is malformed or whose
	// signature does not match.
	ErrTicketInvalid = errors.New("trove/contract: ticket is invalid")

	// ErrTicketExpired reports a correctly signed ticket past its expiry.
	ErrTicketExpired = errors.New("trove/contract: ticket has expired")
)

Functions

func Register

func Register(
	d *dispatcher.Dispatcher,
	reg contract.Registry,
	wreg contract.WardenRegistry,
	deps Deps,
) error

Register loads and validates the embedded manifest, registers the `trove` contributor with reg, and binds every handler. A handler bound to an intent the manifest does not declare is a build mistake and fails here rather than at the first request.

Types

type Content

type Content struct {
	// Signer mints and checks tickets. Required.
	Signer *Signer

	// Path is where the content route is mounted, for example
	// "/dashboard/trove/content".
	Path string

	// MaxUploadBytes is the largest upload a ticket will allow. Every
	// Trove middleware buffers a whole object in memory, which is why this
	// is small by default.
	MaxUploadBytes int64

	// PerProcessSecret is true when Signer's key was generated at start
	// rather than configured, so a ticket verifies only in this process.
	PerProcessSecret bool
}

Content is what the content intents and the content route share: the ticket signer, where the route is mounted, and the upload cap.

func (*Content) Handler

func (c *Content) Handler(stores *Stores, logger forge.Logger) http.Handler

Handler serves object content for tickets minted by the content intents. It authorises by ticket signature alone, which is what lets a plain download link work, and it never serves content inline: an uploaded HTML or SVG file would otherwise run on the dashboard's origin as the operator.

func (*Content) URL

func (c *Content) URL(token string) string

URL is the content route with token attached. Only download and preview tickets travel this way: they are read-only and live 60 seconds, and a plain <a download> link cannot send a header.

type Deps

type Deps struct {
	// Stores resolves each intent's `store` field. Required.
	Stores *Stores

	// Content mints the tickets the content intents return. Required.
	Content *Content

	// Logger receives an Error entry for every error mapped to
	// CodeInternal. Optional.
	Logger forge.Logger
}

Deps bundles what the handlers need.

type Flags

type Flags struct {
	Encryption  bool
	Compression bool
	CAS         bool
}

Flags records which protections a store's configuration asked for. It is what an operator configured, not what is applied: system.status compares the two.

type Signer

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

Signer mints and checks tickets with one HMAC-SHA256 key.

func NewRandomSigner

func NewRandomSigner() (*Signer, error)

NewRandomSigner returns a Signer over a fresh random key. Its tickets verify only in this process, so a deployment with more than one instance needs a configured key instead.

func NewSigner

func NewSigner(key []byte) (*Signer, error)

NewSigner returns a Signer over key, which must be at least 32 bytes.

func (*Signer) Issue

func (s *Signer) Issue(t Ticket, ttl time.Duration) (string, time.Time, error)

Issue signs t with an expiry ttl from now, and returns the token and that expiry. Expiry is kept to the second, because that is what the token carries.

func (*Signer) Verify

func (s *Signer) Verify(token string) (Ticket, error)

Verify checks token's signature and expiry and returns the ticket it carries. A ticket is expired from its expiry second onwards.

type Store

type Store struct {
	Name       string
	Trove      *trove.Trove
	Configured Flags
}

Store is one named Trove the dashboard can address.

type Stores

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

Stores resolves the optional `store` field every intent takes.

func NewSingleStore

func NewSingleStore(t *trove.Trove, configured Flags) *Stores

NewSingleStore wraps the one Trove of a single-store deployment. It answers to "" and to SingleStoreName.

func NewStores

func NewStores(defaultName string, list []Store) (*Stores, error)

NewStores wraps the named Troves of a multi-store deployment, in the order given. An empty defaultName makes the first store the default.

func (*Stores) All

func (s *Stores) All() []Store

All returns every store in declared order.

func (*Stores) DefaultName

func (s *Stores) DefaultName() string

DefaultName is the store an absent `store` field resolves to.

func (*Stores) Multi

func (s *Stores) Multi() bool

Multi reports whether this deployment runs more than one named store.

func (*Stores) Resolve

func (s *Stores) Resolve(name string) (*Store, error)

Resolve maps a request's `store` field to a store. Empty means the default. A value that is blank once trimmed is a bad request: it was sent and cannot be used, and treating it as absent would answer with the default store's objects. An unknown name is not found, never the default.

type Ticket

type Ticket struct {
	Store       string `json:"s"`
	Bucket      string `json:"b"`
	Key         string `json:"k"`
	Op          string `json:"o"`
	Expires     int64  `json:"e"`
	Subject     string `json:"sub,omitempty"`
	Size        int64  `json:"n,omitempty"`
	ContentType string `json:"ct,omitempty"`
	Overwrite   bool   `json:"ow,omitempty"`
	Limit       int64  `json:"l,omitempty"`
}

Ticket says what one content request may do. A contract intent signs it and the content route checks it, so the route needs nothing from the dashboard's session. Field names are short because the ticket travels in a URL.

Jump to

Keyboard shortcuts

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