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

appstore

package
v0.56.0 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 29 Imported by: 0

Documentation

Overview

Package appstore is filex's side of an app store (docs/APP-PLUGINS.md → Installing from a store, Paid apps; docs/APP-PLUGINS-API.md → The store contract): the stores an administrator trusts, the install links a store hands out, and the licenses of paid apps, which the store issues, keeps and is asked about.

What it never does is install anything by itself. A link opens filex's own install review, filled in; the administrator reads it and presses Install, exactly as for a repository they typed (the SHA-256 protects the administrator, the sandbox protects the user).

Index

Constants

View Source
const (
	CodeBadStore         = "store_invalid"            // the store address is not an origin filex talks to
	CodeStoreRefused     = "store_not_allowed"        // FILEX_APP_STORE_URLS is set and does not list it (an allow list: no trust on first use)
	CodeTrustRequired    = "store_trust_required"     // not trusted yet: the administrator is asked
	CodeKeyChanged       = "store_key_changed"        // trusted, but its keys changed: asked again
	CodeKeyNotConfigured = "store_key_not_configured" // a configured store signed with a key FILEX_APP_STORE_KEYS lacks
	CodeUnreachable      = "store_unreachable"        // the store could not be reached
	CodeBadAnswer        = "store_bad_answer"         // the store answered something filex cannot read
	CodeSignature        = "store_signature_invalid"
	CodeIntentUnknown    = "intent_unknown"        // 404
	CodeIntentGone       = "intent_gone"           // 410: used or expired at the store
	CodeIntentExpired    = "intent_expired"        // its expires_at has passed
	CodeIntentUsed       = "intent_used"           // this filex already used it
	CodeIntentInvalid    = "intent_invalid"        // fields missing or not for this store
	CodeWrongInstance    = "intent_wrong_instance" // made for another filex (filex_origin)
	CodePinMismatch      = "intent_pin_mismatch"
	CodeVersionRollback  = "intent_version_rollback" // older than (or the same as) what is installed
	CodeSourceChanged    = "store_source_changed"    // the app installed came from another store or repository: remove it first
	CodeIntentNotFound   = "intent_session_unknown"  // no reviewed intent under that handle
	CodeLicenseKey       = "license_key_invalid"
	// The embedded store (#162, instance.go, catalog.go).
	CodeNotConnected   = "store_not_connected"        // this filex is not connected to the store: an administrator connects it with a code
	CodeConnectRefused = "store_connection_refused"   // the store refused this filex's signed request: connect it again
	CodeConnectCode    = "store_connect_code_invalid" // the connection code is unknown, used, expired or for another filex
	CodeStoreRefusal   = "store_refused"              // the store refused what was asked (detail.store_error says why)
	CodeIndexInvalid   = "store_index_invalid"        // the store's signed index does not verify, has expired or is not one filex reads
	CodeMediaInvalid   = "store_media_invalid"        // an icon the catalog does not name, or bytes that are not it

	// A storage plugin's link (#215).
	CodeNoBuild           = "intent_no_build"           // the store pinned no build for this server's platform
	CodeIncompatible      = "intent_incompatible"       // the feed's filex range leaves this filex out
	CodeSignatureRequired = "intent_signature_required" // this filex runs only signed plugins and no signature of the build verifies
	CodePluginsOff        = "plugins_disabled"          // storage plugins are off here (FILEX_PLUGINS_DISABLED)
)

Error codes the HTTP layer answers with and the panel switches on.

View Source
const (
	HeaderInstance  = "Fapps-Instance"
	HeaderKey       = "Fapps-Key"
	HeaderTimestamp = "Fapps-Timestamp"
	HeaderNonce     = "Fapps-Nonce"
	HeaderSignature = "Fapps-Signature"
)

The headers a connected filex signs its requests with (the store's contract).

View Source
const (
	KindApp          = "app"
	KindLanguagePack = "language_pack"
	// KindStorage is a storage plugin (#215): installed "from its source",
	// held to the link's pins (handlers/app_store_storage.go).
	KindStorage = "storage"
)

Intent kinds a link may stand for.

View Source
const (
	ResultValid          = "valid"
	ResultInvalid        = "invalid"
	ResultRevoked        = "revoked"
	ResultExpired        = "expired"
	ResultSeatsExhausted = "seats_exhausted"
	ResultWrongApp       = "wrong_app"
)

License results a store signs (`POST <store>/v1/licenses/verify`).

View Source
const (
	StatusFree         = "free"          // not a paid app
	StatusValid        = "valid"         // the store said valid at its last check
	StatusGrace        = "grace"         // valid, but the store could not be asked since; holds until grace_until
	StatusGraceExpired = "grace_expired" // the store could not be asked, and grace_until has passed
	StatusMissing      = "missing"       // a paid app with no key
	StatusUnverified   = "unverified"    // a key, never confirmed by the store
)

License statuses filex reports (the store's results, plus what filex decides by itself).

View Source
const (
	UseIndex    = "index"
	UseArtifact = "artifact"
	UseLicense  = "license"
)

Key uses: what a store key may sign. A key signs for ONE use, and a signature is accepted only from a key of the use the answer needs: an install link is signed with an `index` key, a license answer with a `license` key. One key leaking does not open the other's door.

View Source
const (
	KeyActive  = "active"
	KeyNext    = "next"
	KeyRetired = "retired"
)

Key statuses in a store's keys.json. A key signs while it is `active`; a `next` key is published before it signs (rotation), a `retired` one is kept to identify old signatures and signs nothing new.

View Source
const (
	SourceAdmin  = "admin"  // an administrator trusted it on first use
	SourceConfig = "config" // FILEX_APP_STORE_URLS + FILEX_APP_STORE_KEYS
)

Trust sources.

View Source
const (
	AudienceEveryone = "everyone"
	AudienceRoles    = "roles"
	AudienceGroups   = "groups"
)

Audiences: who in the scope sees the screen.

View Source
const HoldReasonStoreOff = "license: not checked - the app store is off on this server"

HoldReasonStoreOff is why a storage plugin under a store's license is held on a server whose app store is off: nobody can check the license, so the plugin does not run (server.go holds it before any plugin starts).

View Source
const ScopeDefault = "default"

ScopeDefault is the single-tenant scope (no tenancy, or its main tenant before multi-tenant mode was turned on).

View Source
const StoragePrefix = "storage:"

StoragePrefix starts every license id (and Holder name) of a storage plugin; no app name holds a colon.

Variables

View Source
var (
	ErrUnknownKey       = errors.New("signed by a key this store was not trusted with")
	ErrWrongKeyUse      = errors.New("signed by a key of another use")
	ErrKeyNotActive     = errors.New("signed by a key that is not active")
	ErrSignatureInvalid = errors.New("signature does not verify")
	ErrMalformed        = errors.New("not a signed answer")
)

Signature errors, matched by the callers to pick an error code.

View Source
var ErrNoSecretKey = errors.New("a license key is kept encrypted, and this installation has no FILEX_SECRET_KEY")

ErrNoSecretKey: a license key is kept sealed, which needs FILEX_SECRET_KEY.

View Source
var ErrNotCanonicalizable = errors.New("not canonical JSON material")

ErrNotCanonicalizable is answered for JSON the canonical form cannot carry: a duplicated key, invalid UTF-8, trailing data.

Functions

func BuildKeyStores added in v0.55.0

func BuildKeyStores(ctx context.Context, st StateStore, pub ed25519.PublicKey) ([]string, error)

BuildKeyStores answers the stores whose artifact key pub is, as filex remembers them (sorted; none for a publisher's key). It reads the rows directly, so it answers with the app store off too.

func Canonical

func Canonical(raw []byte) ([]byte, error)

Canonical is the one byte form a store signs: the JSON value with every object's keys in sorted order (by their UTF-8 bytes; every key of the contract is ASCII), no whitespace, UTF-8 text written as itself, and only `"`, `\` and the control characters escaped - `\b \f \n \r \t` by name, the others as `\u00xx` in lower-case hex. That is what JavaScript's JSON.stringify writes for a string, so a store written in any language that sorts keys produces the same bytes. A number is kept exactly as it was written.

⚠ Not encoding/json's Marshal: it escapes `<`, `>`, `&`, U+2028 and U+2029, which a store that does not would not have signed, and it cannot keep a number's spelling. A duplicated key is refused rather than resolved: two readers that keep different copies would read two different documents under one signature.

func ComparePins

func ComparePins(in *Intent, src *Source) error

ComparePins holds the source to the link: the same app and version, the same manifest bytes, module and interface hashes, and the same declared permissions. Every difference is listed; any one refuses the install.

func Fingerprints

func Fingerprints(keys []Key) []string

Fingerprints is the sorted fingerprint list of keys: what an administrator's approval names, so the keys they saw are the keys pinned.

func InstanceOrigin

func InstanceOrigin(raw string) (string, error)

InstanceOrigin is the one spelling of a filex's own address that a link's filex_origin is compared in: scheme and host in lower case, the default port dropped, no path, no trailing slash. A configured public URL with a base path (https://example.com/filex) is its origin (https://example.com).

func LicenseID added in v0.55.0

func LicenseID(kind, name string) string

LicenseID is the license row of a store entry of kind called name.

func NormalizeOrigin

func NormalizeOrigin(raw string, loopback bool) (string, error)

NormalizeOrigin turns what a link or an administrator names into the one spelling a store is known by - `https://host[:port]`, lower-case, no path, no default port. Plain http is accepted for this machine only and only with loopback (FILEX_PLUGIN_LOOPBACK_SOURCES), the rule every plugin download follows.

func PayloadSHA256

func PayloadSHA256(payload []byte) (string, error)

PayloadSHA256 is the lower-hex sha256 of the payload's canonical form: what the signature is over.

func Prefix

func Prefix(key string) string

Prefix is what of a key the screens, the API and the audit log show: the first six characters at most (a third of a short key), then an ellipsis.

func RequestText added in v0.53.0

func RequestText(method, path, instanceID, keyFingerprint, timestamp, nonce string, body []byte) string

RequestText is what a connected filex signs for one request: the scheme, the method in upper case, the path as sent (no query), the store's instance id, the key's fingerprint, the timestamp (Unix seconds), the nonce, and the lower-hex sha256 of the body (of no bytes when there is none), one per line. ⚠ The store computes the same text (fapps internal/install RequestText); testdata/instance-request-vector.json holds both to one vector made with Node's crypto.

func SHA256Hex

func SHA256Hex(b []byte) string

SHA256Hex is the lower-hex sha256 of b (what a link's pins are written in).

func Scope added in v0.53.0

func Scope(multiTenant bool, providerID int64) string

Scope is the key of a tenant's settings: ScopeDefault without tenancy, "t<provider id>" with it.

func SignRequest added in v0.53.0

func SignRequest(priv ed25519.PrivateKey, method, path, instanceID string, body []byte, at time.Time) map[string]string

SignRequest answers the request's five headers, signed with priv over the lower-hex sha256 of RequestText (the one signature rule of a store).

func StorageBuildGate added in v0.55.0

func StorageBuildGate(st StateStore, svc *Service) plugin.BuildGate

StorageBuildGate is the plugin.BuildGate of this filex (server.go): a build signed by a key that is no store's goes on; one a store signed is taken through that store's own link, or - any other way (an upload, an address, a source, another store's link) - only when the store still lists that version of that plugin and the plugin itself is not revoked. A store's signature in the old sha256-only form is never taken: a store signs the build's name, version and platform. svc nil (the app store off): the store cannot be asked, so its signature is refused outside its link.

func StorageLicensed added in v0.55.0

func StorageLicensed(ctx context.Context, st StateStore) ([]string, error)

StorageLicensed answers the storage plugins a store's license names - the `license:storage:<name>` rows, whatever their status - read straight from the rows, so it answers with the app store off too.

func ValidConnectCode added in v0.53.0

func ValidConnectCode(s string) bool

ValidConnectCode reports whether s has the shape of a store's connection code (what a person pastes; nothing else is sent).

func ValidKey

func ValidKey(s string) bool

ValidKey reports whether s may be a license key: 4-512 printable ASCII characters, no spaces.

func ValidKeyID

func ValidKeyID(id string) bool

ValidKeyID reports whether id may name a store key.

func ValidToken

func ValidToken(t string) bool

ValidToken reports whether an install link's token may be put in a path: the URL-safe characters a store's random token is made of.

func Verify

func Verify(env *Envelope, trusted []Key, use string) error

Verify checks an envelope against the keys a store was trusted with: the key named by key_id must be one of them, of the use the answer needs, and signing; the signature must verify over the canonical payload. Only that one key is tried - a signature from another trusted key of another use is still refused.

Types

type Answer

type Answer struct {
	Result       string     `json:"result"`
	App          string     `json:"app"`
	Licensee     string     `json:"licensee,omitempty"`
	Seats        *int       `json:"seats,omitempty"`
	SeatsUsed    *int       `json:"seats_used,omitempty"`
	ValidUntil   *time.Time `json:"valid_until,omitempty"`
	UpdatesUntil *time.Time `json:"updates_until,omitempty"`
	InstanceID   string     `json:"instance_id"`
	CheckedAt    time.Time  `json:"checked_at"`
	NextCheckBy  time.Time  `json:"next_check_by"`
	GraceUntil   time.Time  `json:"grace_until"`
}

Answer is a license check's signed payload.

type AppView

type AppView struct {
	Status       string     `json:"status"`
	ValidUntil   *time.Time `json:"valid_until,omitempty"`
	UpdatesUntil *time.Time `json:"updates_until,omitempty"`
}

AppView is a license as the app itself reads it (fx.license.get()): the status and its dates - nothing about the key, the store or who holds it. ⚠ Every user who may run apps reads it, a tenant's included, and any token with that right: the licensee (a person's or a company's name) is the administrator's to see (View), not theirs.

type Catalog added in v0.53.0

type Catalog struct {
	Store     string       `json:"store"`
	Serial    int64        `json:"serial"`
	FetchedAt time.Time    `json:"fetched_at"`
	Stale     bool         `json:"stale"`
	Apps      []CatalogApp `json:"apps"`
	// contains filtered or unexported fields
}

Catalog is a store's catalog at one serial.

func (*Catalog) App added in v0.53.0

func (c *Catalog) App(name string) (CatalogApp, bool)

App answers the catalog's entry for name.

type CatalogApp added in v0.53.0

type CatalogApp struct {
	Name              string            `json:"name"`
	Kind              string            `json:"kind"`
	Label             map[string]string `json:"label"`
	Summary           map[string]string `json:"summary,omitempty"`
	Publisher         string            `json:"publisher"`
	PublisherVerified bool              `json:"publisher_verified,omitempty"`
	PublisherOfficial bool              `json:"publisher_official,omitempty"`
	Categories        []string          `json:"categories"`
	Repo              string            `json:"repo"`
	Version           string            `json:"version"`
	PublishedAt       string            `json:"published_at,omitempty"`
	FilexRange        string            `json:"filex_range"`
	Permissions       []string          `json:"permissions"`
	Icon              string            `json:"icon,omitempty"`
	// A storage plugin (Kind == KindStorage, #215): the platforms the store
	// pinned a build for, and its plugin validator's run.
	Platforms   []string     `json:"platforms,omitempty"`
	Conformance *Conformance `json:"conformance,omitempty"`
	// The pins of that version, frozen with a request made from it (never
	// sent to the browser). Builds: a storage plugin's, platform → sha256.
	ManifestSHA256 string            `json:"-"`
	WasmSHA256     string            `json:"-"`
	UISHA256       string            `json:"-"`
	Builds         map[string]string `json:"-"`
}

CatalogApp is one app of a store's catalog as filex's own screen draws it: the newest version that is not yanked, what it asks for, its icon (a file name Media serves). Revoked apps and apps with no version left are not in a catalog.

type Client

type Client struct {
	HTTP  *http.Client
	Guard *netguard.Policy
	// UserAgent names this filex.
	UserAgent string
}

Client talks to stores: one guarded HTTP client (netguard: public addresses only, judged after DNS), small size ceilings, JSON only - and no redirect followed (netguard.APIClient): a license check carries the key, a completion the instance id, and a redirect must not carry them to another host. (The app's own files - manifest, module, interface, from a GitHub release that redirects to its asset host - are downloaded by wasmplugin's DownloadClient, which follows redirects and sends nothing.)

func NewClient

func NewClient(policy netguard.Policy, userAgent string) *Client

NewClient builds the guarded, non-redirecting client under policy.

func (*Client) Complete

func (c *Client) Complete(ctx context.Context, origin, token, instanceID, result string) error

Complete tells the store how an install link ended: `POST <store>/v1/install/{token}/complete`.

func (*Client) Intent

func (c *Client) Intent(ctx context.Context, origin, token string) (*Envelope, error)

Intent reads `GET <store>/v1/install/{token}` as an envelope. 404 and 410 are said as such.

func (*Client) Keys

func (c *Client) Keys(ctx context.Context, origin string) (*KeySet, error)

Keys reads `<store>/v1/keys.json`, validated.

func (*Client) VerifyLicense

func (c *Client) VerifyLicense(ctx context.Context, origin string, req LicenseRequest) (*Envelope, error)

VerifyLicense asks `POST <store>/v1/licenses/verify` and answers the envelope.

type Conformance added in v0.55.0

type Conformance struct {
	Platform     string   `json:"platform"`
	Filex        string   `json:"filex"`
	Verified     bool     `json:"verified"`
	Passed       int      `json:"passed"`
	Failed       int      `json:"failed"`
	Skipped      int      `json:"skipped"`
	Driver       string   `json:"driver,omitempty"`
	Capabilities []string `json:"capabilities"`
}

Conformance is the store's plugin validator's run of a storage plugin build: filex's own conformance probes against the plugin's selftest area.

type ConnectionView added in v0.53.0

type ConnectionView struct {
	Store           string    `json:"store"`
	Connected       bool      `json:"connected"`
	InstanceID      string    `json:"instance_id,omitempty"`
	KeyFingerprint  string    `json:"key_fingerprint,omitempty"`
	ConnectedAt     time.Time `json:"connected_at,omitempty"`
	ConnectedByName string    `json:"connected_by_name,omitempty"`
}

ConnectionView is a connection as the panel shows it: never the key.

type Envelope

type Envelope struct {
	Payload   json.RawMessage `json:"payload"`
	KeyID     string          `json:"key_id"`
	Signature string          `json:"signature"`
}

Envelope is a signed answer: `{"payload": {...}, "key_id": "...", "signature": "<hex>"}`. signature = ed25519 over the lower-hex sha256 of Canonical(payload) - the rule plugin.VerifyDetached applies to a module.

func ParseEnvelope

func ParseEnvelope(raw []byte) (*Envelope, error)

ParseEnvelope reads a signed answer, refusing one without its three parts.

type Error

type Error struct {
	Code    string         `json:"error"`
	Message string         `json:"message"`
	Detail  map[string]any `json:"detail,omitempty"`
}

Error is a refusal with a code; Detail carries what the panel draws (the fingerprints of a store to trust, the pins that differ).

func AsError

func AsError(err error) (*Error, bool)

AsError unwraps an *Error.

func (*Error) Error

func (e *Error) Error() string

type Holder

type Holder interface {
	SetLicenseHold(app, reason string)
}

Holder is what a license decides about an installed app: held (it does not run, and says why) or not. wasmplugin.Registry is the one in service.

type Intent

type Intent struct {
	Store          string    `json:"store"`
	TokenID        string    `json:"token_id"`
	App            string    `json:"app"`
	Kind           string    `json:"kind"`
	Version        string    `json:"version"`
	Repo           string    `json:"repo"`
	Ref            string    `json:"ref"`
	Commit         string    `json:"commit"`
	ManifestSHA256 string    `json:"manifest_sha256"`
	WasmSHA256     string    `json:"wasm_sha256,omitempty"`
	UISHA256       string    `json:"ui_sha256,omitempty"`
	Permissions    []string  `json:"permissions"`
	FilexRange     string    `json:"filex_range"`
	Paid           bool      `json:"paid"`
	LicenseKey     string    `json:"license_key,omitempty"`
	ExpiresAt      time.Time `json:"expires_at"`
	// FilexOrigin is the filex the link was made for (the store signs it):
	// another filex refuses the link (CodeWrongInstance). That stops a link
	// opened by mistake on the wrong filex, and a link sent to an
	// administrator of another filex to get an app installed there. It is
	// no protection against the administrator of the filex receiving it:
	// without FILEX_PUBLIC_URL the filex's own origin is the request's Host
	// (and X-Forwarded-Proto), which that person controls.
	FilexOrigin string `json:"filex_origin"`

	// A storage plugin's link (Kind == KindStorage, #215): the release's feed
	// the store reviewed (its sha256 is ManifestSHA256), every build by
	// platform with the store's artifact signature over the build (its name,
	// version, platform and sha256 - plugin.VerifyBuild), and what
	// the store's plugin validator measured. Absent for an app.
	FeedURL     string                 `json:"feed_url,omitempty"`
	Binaries    map[string]IntentBuild `json:"binaries,omitempty"`
	Conformance *Conformance           `json:"conformance,omitempty"`
}

Intent is what an install link stands for, as the store signed it (`GET <store>/v1/install/{token}`, signed with an `index` key).

func (*Intent) IsStorage added in v0.55.0

func (in *Intent) IsStorage() bool

IsStorage reports whether the link stands for a storage plugin.

func (*Intent) LicenseID added in v0.55.0

func (in *Intent) LicenseID() string

LicenseID is the row a link's license is kept under: the app's name, and for a storage plugin "storage:<name>" - so a storage plugin and an app of the same name, from two stores, never share one license.

type IntentBuild added in v0.55.0

type IntentBuild struct {
	URL    string `json:"url"`
	SHA256 string `json:"sha256"`
	Size   int64  `json:"size,omitempty"`
	// Sig is the store's artifact key's signature over the build's text -
	// the link's app and version, this platform, SHA256 (plugin.BuildClaim):
	// what FILEX_PLUGIN_TRUSTED_KEYS checks when it lists that key.
	Sig string `json:"sig"`
}

IntentBuild is one platform's build of a storage plugin, as a link pins it.

type Key

type Key struct {
	ID      string `json:"id"`
	Use     string `json:"use"`
	Ed25519 string `json:"ed25519"`
	Status  string `json:"status,omitempty"`
}

Key is one key of a store's keys.json (`{"keys": [...]}`), and what a trusted store keeps of the keys an administrator saw.

func (Key) Fingerprint

func (k Key) Fingerprint() string

Fingerprint is what an administrator compares with what the store publishes: the lower-hex sha256 of the 32 raw key bytes. "" when the material does not parse.

func (Key) PublicKey

func (k Key) PublicKey() (ed25519.PublicKey, error)

PublicKey parses the key's material (hex or base64, 32 bytes).

type KeySet

type KeySet struct {
	Keys []Key `json:"keys"`
}

KeySet is a store's keys.json.

func (*KeySet) TrustedKeys

func (s *KeySet) TrustedKeys() []Key

TrustedKeys is the part of a keys.json filex pins when a store is trusted: the keys that sign what filex reads (install links, license answers) and may sign now or soon. A retired key is not pinned; an artifact key is a module's business (FILEX_PLUGIN_TRUSTED_KEYS), not this package's - it is only remembered as the store's, to narrow what a storage build it signed may install by (buildkeys.go).

func (*KeySet) Validate

func (s *KeySet) Validate() error

Validate checks a keys.json: every key has an id, a known use and material that parses; no id twice. A store whose list does not hold together is refused whole rather than read in part.

type KeyView

type KeyView struct {
	ID          string `json:"id"`
	Use         string `json:"use"`
	Status      string `json:"status,omitempty"`
	Fingerprint string `json:"fingerprint"`
}

KeyView is a key as the panel shows it.

type License

type License struct {
	// App is the row's id: the app's name, or "storage:<name>" for a storage
	// plugin (LicenseID).
	App string `json:"app"`
	// StoreApp is what the store calls the entry when it is not App (a
	// storage plugin's name, without the prefix): what a check names.
	StoreApp  string `json:"store_app,omitempty"`
	Store     string `json:"store"`
	KeySealed string `json:"key_sealed,omitempty"`
	KeyPrefix string `json:"key_prefix,omitempty"`
	// Last is the store's last signed answer (any result); LastValid the
	// last one that said valid. Kept whole: the signature travels with them.
	Last      *Envelope `json:"last,omitempty"`
	LastValid *Envelope `json:"last_valid,omitempty"`
	// LastAttemptAt / LastError: the last check, when it did not end in a
	// signed answer (the store unreachable, a bad answer). Cleared by one
	// that did.
	LastAttemptAt time.Time `json:"last_attempt_at,omitempty"`
	LastSuccessAt time.Time `json:"last_success_at,omitempty"`
	LastError     string    `json:"last_error,omitempty"`
	LastErrorCode string    `json:"last_error_code,omitempty"`
	// Status is the last status decided, for telling a change (audit).
	Status    string    `json:"status,omitempty"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

License is a paid app's license as filex keeps it (app_store_state `license:<app>`): the key sealed, its prefix for the screens, and the store's last signed answers - the evidence a status is judged from.

type LicenseRequest

type LicenseRequest struct {
	Key          string `json:"key"`
	App          string `json:"app"`
	InstanceID   string `json:"instance_id"`
	FilexVersion string `json:"filex_version,omitempty"`
}

LicenseRequest is the body of a license check.

type Mismatch

type Mismatch struct {
	Field  string `json:"field"`
	Link   string `json:"link"`
	Source string `json:"source"`
}

Mismatch is one pin the source does not keep.

type Options

type Options struct {
	Store  StateStore
	Client *Client
	// Box seals license keys (the installation's FILEX_SECRET_KEY). Without a
	// key a license cannot be kept, and a paid app is refused at install.
	Box *secretbox.Box
	// Loopback admits a store on this machine over plain http
	// (FILEX_PLUGIN_LOOPBACK_SOURCES): development and tests.
	Loopback bool
	// ConfigStores (FILEX_APP_STORE_URLS) are stores the operator trusts by
	// configuration; ConfigKeys (FILEX_APP_STORE_KEYS) the keys they sign
	// with. A configured store is never put to an administrator: its keys are
	// these, and a key that is not among them is refused.
	ConfigStores []string
	ConfigKeys   []string
	// ConfigStoresSet: FILEX_APP_STORE_URLS was given a value, also one that
	// names no store - the allow list is in force either way.
	ConfigStoresSet bool
	FilexVersion    string
	Holder          Holder
	Log             *slog.Logger
	// Now is the wall clock and Mono the time elapsed since start on a
	// monotonic clock - for tests; zero values are the real clocks.
	Now  func() time.Time
	Mono func() time.Duration
}

Options configure a Service.

type Pending

type Pending struct {
	Handle  string
	Origin  string
	Token   string
	Intent  *Intent
	UserID  int64
	Created time.Time
	// RequestID is the plugin request (pluginreq) the link was asked for by
	// an approval in the embedded store (RequestFor); 0 for a magic link.
	RequestID int64
	// LegacySignature: the storage plugin's build was taken with a
	// signature in the old sha256-only form (plugin.VerifyBuild; accepted
	// with a warning while plugin.LegacySignatureWindow runs). Finish says so
	// in the audit row.
	LegacySignature bool
}

Pending is a link that passed every check and whose review the administrator is reading. The browser holds its Handle, never the link's license key.

type RequestedIntent added in v0.53.0

type RequestedIntent struct {
	Store     string    `json:"store"`
	Token     string    `json:"token"`
	TokenID   string    `json:"token_id"`
	App       string    `json:"app"`
	Version   string    `json:"version"`
	ExpiresAt time.Time `json:"expires_at"`
}

RequestedIntent is a fresh install link a connected store made for this filex: read it with ReadIntent like any other.

type Service

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

Service is the store side of filex: trust, install links, licenses.

func New

func New(o Options) *Service

New builds the service. Configured stores that do not parse are logged and left out, never guessed at.

func (*Service) AppLicense

func (s *Service) AppLicense(ctx context.Context, app string) *AppView

AppLicense is what the app reads about itself (fx.license.get()).

func (*Service) ApplyHolds

func (s *Service) ApplyHolds(ctx context.Context)

ApplyHolds re-judges every license (time moves; a grace ends) and keeps the clock's reading.

func (*Service) Approve

func (s *Service) Approve(ctx context.Context, origin string, fingerprints []string, actorID *int64, actorName string) (*TrustView, error)

Approve trusts origin with the keys it publishes NOW, provided they are the keys the administrator was shown (fingerprints, as Fingerprints spells them): a store whose keys changed while the page was open is asked about again, never trusted with keys nobody saw.

A configured store is not the administrator's to trust or distrust.

func (*Service) Catalog added in v0.53.0

func (s *Service) Catalog(ctx context.Context, origin string) (*Catalog, error)

Catalog answers origin's catalog: from the cache while it is fresh, otherwise read again and verified. A store that cannot be reached answers the last catalog that verified, Stale; without one, the error.

func (*Service) Check

func (s *Service) Check(ctx context.Context, app string, actorID *int64) (*View, error)

Check asks the store about app's license now and answers the result. actorID is the administrator who pressed "Verify now" (nil: the daily check).

func (*Service) Configured

func (s *Service) Configured(origin string) bool

Configured reports whether origin is a store trusted by configuration.

func (*Service) Connect added in v0.53.0

func (s *Service) Connect(ctx context.Context, origin, code, self string, actorID *int64, actorName string) (*ConnectionView, error)

Connect binds a new key of this filex to the store instance a connection code was made for. self is this filex's own origin (InstanceOrigin): the store must name it in its signed answer, as an install link must. The store must be trusted already (its index key checks the answer).

func (*Service) Connected added in v0.53.0

func (s *Service) Connected(ctx context.Context, origin string) bool

Connected reports whether this filex holds a key for origin.

func (*Service) Connection added in v0.53.0

func (s *Service) Connection(ctx context.Context, origin string) (*ConnectionView, error)

Connection answers origin's connection for the panel ("not connected" when there is none).

func (*Service) Disconnect added in v0.53.0

func (s *Service) Disconnect(ctx context.Context, origin string, actorID *int64) (bool, error)

Disconnect forgets this filex's key for origin and tells the store to forget it too (best effort: a store that cannot be told keeps a key nobody holds any more).

func (*Service) Drop

func (s *Service) Drop(handle string)

Drop forgets a reviewed link.

func (*Service) DropCatalog added in v0.53.0

func (s *Service) DropCatalog(origin string)

DropCatalog forgets origin's catalog (a store no longer trusted).

func (*Service) Finish

func (s *Service) Finish(ctx context.Context, p *Pending, result string, actorID *int64)

Finish ends a reviewed link: the store is told (installed / cancelled) and the link is recorded as used here. A store that cannot be told is logged; the install stands.

func (*Service) Forget

func (s *Service) Forget(ctx context.Context, app string)

Forget removes what filex keeps about an app installed from a store (the app was removed): its license - and the hold goes with it - and where it came from.

func (*Service) Hold

func (s *Service) Hold(origin, token string, in *Intent, userID int64) *Pending

Hold keeps a reviewed link under a new handle for the administrator who reviewed it.

func (*Service) HoldFor added in v0.53.0

func (s *Service) HoldFor(origin, token string, in *Intent, userID, requestID int64) *Pending

HoldFor is Hold for a link an approval asked for: requestID is the plugin request it ends (0: none).

func (*Service) InstalledFrom

func (s *Service) InstalledFrom(ctx context.Context, app string) (string, error)

InstalledFrom answers the store an installed app came from: the one its install link was signed by, or the one its license is with; "" when it was not installed from a store.

func (*Service) InstanceID

func (s *Service) InstanceID(ctx context.Context) (string, error)

InstanceID is this installation's opaque id, made once: what a store counts a license's seats by. Random, not derived from anything about the server or its people.

func (*Service) LicenseOf

func (s *Service) LicenseOf(ctx context.Context, app string) (*View, error)

LicenseOf is app's license for the panel; nil when app is not a paid app.

func (*Service) Licenses

func (s *Service) Licenses(ctx context.Context) []*View

Licenses is every paid app's license, for the panel's warning band.

func (*Service) ListTrust

func (s *Service) ListTrust(ctx context.Context) ([]TrustView, error)

ListTrust is every trusted store: configured ones first, then the ones an administrator trusted.

func (*Service) LockApp

func (s *Service) LockApp(app string) (unlock func())

LockApp serialises the store installs of one app name (the license, the install, the record of where it came from): answers the unlock.

func (*Service) Loopback

func (s *Service) Loopback() bool

Loopback reports whether plain-http loopback stores are admitted.

func (*Service) Media added in v0.53.0

func (s *Service) Media(ctx context.Context, origin, file string) ([]byte, string, error)

Media answers an icon of origin's catalog: only a file the catalog names, fetched from the store's own media path, and only bytes whose sha256 is the file's name and that are the picture its extension says.

func (*Service) Now

func (s *Service) Now() time.Time

Now is the latest time filex has proof of, across every store (clock.go): for the panel and the log. A license is judged by its own store's proof.

func (*Service) Pending

func (s *Service) Pending(handle string, userID int64) (*Pending, error)

Pending answers the reviewed link under handle for this administrator.

func (*Service) PutBack

func (s *Service) PutBack(p *Pending)

PutBack returns a taken review (its install failed), unless it has expired meanwhile.

func (*Service) ReadIntent

func (s *Service) ReadIntent(ctx context.Context, origin, token, instance string) (*Intent, error)

ReadIntent fetches and checks an install link: the store must be trusted (or an administrator is asked, CodeTrustRequired / CodeKeyChanged), the answer signed by one of its `index` keys, the payload this store's, made for this filex (instance, its own origin: InstanceOrigin), naming its commit and pinning its manifest, not expired and not used here before.

func (*Service) RememberRequest added in v0.53.0

func (s *Service) RememberRequest(ctx context.Context, origin, tokenID string, requestID int64, expires time.Time) error

RememberRequest records that the link token_id of origin was asked for the plugin request id: the review that reads it carries the request, and the install that ends it closes the request (handlers/app_store_intent.go).

func (*Service) Remove

func (s *Service) Remove(ctx context.Context, origin string, actorID *int64) (bool, error)

Remove stops trusting a store an administrator trusted. Its install links are refused from then on, and the licenses it issued are not checked against it any more (they hold until their grace ends).

func (*Service) RequestFor added in v0.53.0

func (s *Service) RequestFor(ctx context.Context, origin, tokenID string) int64

RequestFor answers the plugin request a link was asked for (0: none).

func (*Service) RequestIntent added in v0.53.0

func (s *Service) RequestIntent(ctx context.Context, origin, app, version, licenseKey string, actorID *int64) (*RequestedIntent, error)

RequestIntent asks a connected store for a fresh install link for app (version "" = the store's newest; licenseKey "" = none: the administrator may still give one at the install). The link is for this filex's address (the store holds it to the address the connection was made for). actorID is the approving administrator (the audit row).

func (*Service) Require

func (s *Service) Require(ctx context.Context, app, store, key string, actorID *int64) error

Require makes app a paid app licensed by store, with key ("" = none yet). Called at a paid install BEFORE the app is installed: the hold is in place before the app could run, and lifted by a valid answer.

func (*Service) RequireUndoable

func (s *Service) RequireUndoable(ctx context.Context, app, store, key string, actorID *int64) (*Undo, error)

RequireUndoable is Require, answering what to Undo when the install it prepared fails.

func (*Service) RequireUndoableAs added in v0.55.0

func (s *Service) RequireUndoableAs(ctx context.Context, app, storeApp, store, key string, actorID *int64) (*Undo, error)

RequireUndoableAs is RequireUndoable for the license row id (LicenseID) of an entry the store calls storeApp: a storage plugin's row is "storage:<name>", its checks name <name>.

func (*Service) Run

func (s *Service) Run(ctx context.Context)

Run is the license loop: a round a minute after start, then hourly. On the way out (ctx done: a clean shutdown) it keeps the proven time once more, marked clean, so the next start owes no restart allowance.

func (*Service) SetKey

func (s *Service) SetKey(ctx context.Context, app, key string, actorID *int64) (*View, error)

SetKey replaces a paid app's key and asks the store about it at once.

func (*Service) SetView added in v0.53.0

func (s *Service) SetView(ctx context.Context, scope string, v ViewSettings, actorID *int64, actorName string) (*ViewSettings, error)

SetView saves a scope's settings (Normalize first) and writes the audit row.

func (*Service) Start

func (s *Service) Start(ctx context.Context)

Start loads what the service keeps between runs (every store's proven time) and applies every paid app's hold before anything is served. After a run that did not stop cleanly, the proven times move on by the restart allowance first (clock.go); ApplyHolds keeps that at once, marked not clean, so a run killed before its first round still owes it.

func (*Service) StorageInstalledFrom added in v0.55.0

func (s *Service) StorageInstalledFrom(ctx context.Context, name string) (string, error)

StorageInstalledFrom is InstalledFrom for a storage plugin.

func (*Service) Take

func (s *Service) Take(handle string, userID int64) (*Pending, error)

Take answers the reviewed link under handle for this administrator AND removes it, in one step: of two installs of one review at once (a double click, or on purpose) only one gets it; the other is told the review is not open (CodeIntentNotFound). An install that fails before anything is installed puts it back (PutBack), so the administrator can try again.

func (*Service) Tick

func (s *Service) Tick(ctx context.Context)

Tick is one round of the license loop: ask the store about every license that is due, then re-judge them all.

func (*Service) TrustStatus

func (s *Service) TrustStatus(ctx context.Context, origin string) string

TrustStatus answers whether origin is trusted, for the panel, without asking the store: "config", "admin" or "".

func (*Service) Undo

func (s *Service) Undo(ctx context.Context, u *Undo)

Undo takes back what RequireUndoable did for an install that failed: the row it CREATED is removed (and its hold lifted) - that row only, never one another install created: of two installs of one app, the loser must not forget the winner's license - and a row it CHANGED (a paid upgrade's new key) is put back as it was, key, answers and hold.

func (*Service) View added in v0.53.0

func (s *Service) View(ctx context.Context, scope string) (*ViewSettings, error)

View answers a scope's settings, the stores narrowed to those still trusted (nil settings: never set, the screen is off).

func (*Service) Views added in v0.53.0

func (s *Service) Views(ctx context.Context) (map[string]*ViewSettings, error)

Views answers every scope's settings (the panel's list in multi-tenant mode).

type Source

type Source struct {
	ManifestBytes []byte
	Name          string
	Version       string
	Kind          string
	// DeclaredPermissions is the manifest's own `permissions` list (not the
	// ones filex derives from its interface).
	DeclaredPermissions []string
	WasmSHA256          string // the manifest's wasm.sha256 (the module filex fetched hashes to it)
	UISHA256            string // the manifest's ui.bundle.sha256
}

Source is what filex fetched for a link from the app's repository: the manifest's bytes and what it says.

type StateStore

type StateStore interface {
	GetAppStoreState(ctx context.Context, key string) (string, bool, error)
	PutAppStoreState(ctx context.Context, key, value string) error
	DeleteAppStoreState(ctx context.Context, key string) (bool, error)
	ListAppStoreState(ctx context.Context, prefix string) (map[string]string, error)
	InsertAuditEntry(ctx context.Context, e *model.AuditEntry) error
}

StateStore is the part of db.Store this package uses: its own rows (app_store_state, migration 00081) and the audit log.

type Trust

type Trust struct {
	Origin         string    `json:"origin"`
	Keys           []Key     `json:"keys"`
	Source         string    `json:"source"`
	ApprovedBy     *int64    `json:"approved_by,omitempty"`
	ApprovedByName string    `json:"approved_by_name,omitempty"`
	ApprovedAt     time.Time `json:"approved_at"`
	// UpdatedAt: when the key list last narrowed by itself (a key retired or
	// gone at the store) or a status moved (next → active).
	UpdatedAt time.Time `json:"updated_at,omitempty"`
}

Trust is a store filex accepts install links and license answers from, and the keys it accepts them signed with - the keys the administrator SAW when they trusted it (or the operator configured). A key the store publishes later is not among them until somebody approves it again.

type TrustView

type TrustView struct {
	Origin         string    `json:"origin"`
	Source         string    `json:"source"`
	Keys           []KeyView `json:"keys"`
	ApprovedByName string    `json:"approved_by_name,omitempty"`
	ApprovedAt     time.Time `json:"approved_at,omitempty"`
}

TrustView is a trusted store as the panel lists it.

type Undo

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

Undo is what Require changed, for an install that then failed (Service.Undo).

type View

type View struct {
	// App is the license's id (LicenseID): an app's name, or
	// "storage:<name>"; Name is the entry's own name and Kind "storage" for a
	// storage plugin ("" for an app), so a screen never reads the id.
	App           string     `json:"app"`
	Name          string     `json:"name,omitempty"`
	Kind          string     `json:"kind,omitempty"`
	Required      bool       `json:"required"`
	Status        string     `json:"status"`
	Held          bool       `json:"held"`
	Store         string     `json:"store,omitempty"`
	KeyPrefix     string     `json:"key_prefix,omitempty"`
	Licensee      string     `json:"licensee,omitempty"`
	Seats         *int       `json:"seats,omitempty"`
	SeatsUsed     *int       `json:"seats_used,omitempty"`
	ValidUntil    *time.Time `json:"valid_until,omitempty"`
	UpdatesUntil  *time.Time `json:"updates_until,omitempty"`
	CheckedAt     *time.Time `json:"checked_at,omitempty"`
	NextCheckBy   *time.Time `json:"next_check_by,omitempty"`
	GraceUntil    *time.Time `json:"grace_until,omitempty"`
	LastAttemptAt *time.Time `json:"last_attempt_at,omitempty"`
	LastErrorCode string     `json:"last_error_code,omitempty"`
	LastError     string     `json:"last_error,omitempty"`
	// StoreTrusted: false when the store that issued the license is no
	// longer trusted here (it cannot be asked any more).
	StoreTrusted bool `json:"store_trusted"`
}

View is a license as the administrator's panel shows it. The key itself is never in it - its prefix only.

type ViewSettings added in v0.53.0

type ViewSettings struct {
	Enabled bool `json:"enabled"`
	// Stores are the trusted stores it shows, in this order; a store no
	// longer trusted is left out when it is read.
	Stores   []string `json:"stores"`
	Audience string   `json:"audience"`
	// Roles (AudienceRoles) are built-in role names; Groups (AudienceGroups)
	// group ids. A person sees the screen when they hold one of the roles or
	// are in one of the groups.
	Roles         []string  `json:"roles"`
	Groups        []int64   `json:"groups"`
	UpdatedAt     time.Time `json:"updated_at,omitempty"`
	UpdatedByName string    `json:"updated_by_name,omitempty"`
}

ViewSettings is one scope's embedded store screen.

func (*ViewSettings) Allows added in v0.53.0

func (v *ViewSettings) Allows(role string, groups []int64) bool

Allows reports whether a person with role and these groups sees the screen.

func (*ViewSettings) Normalize added in v0.53.0

func (v *ViewSettings) Normalize(trusted []string) error

Normalize cleans settings an administrator sent: known roles only, no repeats, an audience it understands. stores are the origins trusted now.

Directories

Path Synopsis
Package storetest is a fake app store for tests: the `/v1/` paths a filex reads (docs/APP-PLUGINS-API.md → The store contract), signing exactly as a store must - ed25519 over the lower-hex sha256 of the canonical payload - and every answer adjustable mid-test (a tampered signature, a 410, a key that changes, a store that cannot be reached).
Package storetest is a fake app store for tests: the `/v1/` paths a filex reads (docs/APP-PLUGINS-API.md → The store contract), signing exactly as a store must - ed25519 over the lower-hex sha256 of the canonical payload - and every answer adjustable mid-test (a tampered signature, a 410, a key that changes, a store that cannot be reached).

Jump to

Keyboard shortcuts

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