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
- Variables
- func BuildKeyStores(ctx context.Context, st StateStore, pub ed25519.PublicKey) ([]string, error)
- func Canonical(raw []byte) ([]byte, error)
- func ComparePins(in *Intent, src *Source) error
- func Fingerprints(keys []Key) []string
- func InstanceOrigin(raw string) (string, error)
- func LicenseID(kind, name string) string
- func NormalizeOrigin(raw string, loopback bool) (string, error)
- func PayloadSHA256(payload []byte) (string, error)
- func Prefix(key string) string
- func RequestText(method, path, instanceID, keyFingerprint, timestamp, nonce string, body []byte) string
- func SHA256Hex(b []byte) string
- func Scope(multiTenant bool, providerID int64) string
- func SignRequest(priv ed25519.PrivateKey, method, path, instanceID string, body []byte, ...) map[string]string
- func StorageBuildGate(st StateStore, svc *Service) plugin.BuildGate
- func StorageLicensed(ctx context.Context, st StateStore) ([]string, error)
- func ValidConnectCode(s string) bool
- func ValidKey(s string) bool
- func ValidKeyID(id string) bool
- func ValidToken(t string) bool
- func Verify(env *Envelope, trusted []Key, use string) error
- type Answer
- type AppView
- type Catalog
- type CatalogApp
- type Client
- func (c *Client) Complete(ctx context.Context, origin, token, instanceID, result string) error
- func (c *Client) Intent(ctx context.Context, origin, token string) (*Envelope, error)
- func (c *Client) Keys(ctx context.Context, origin string) (*KeySet, error)
- func (c *Client) VerifyLicense(ctx context.Context, origin string, req LicenseRequest) (*Envelope, error)
- type Conformance
- type ConnectionView
- type Envelope
- type Error
- type Holder
- type Intent
- type IntentBuild
- type Key
- type KeySet
- type KeyView
- type License
- type LicenseRequest
- type Mismatch
- type Options
- type Pending
- type RequestedIntent
- type Service
- func (s *Service) AppLicense(ctx context.Context, app string) *AppView
- func (s *Service) ApplyHolds(ctx context.Context)
- func (s *Service) Approve(ctx context.Context, origin string, fingerprints []string, actorID *int64, ...) (*TrustView, error)
- func (s *Service) Catalog(ctx context.Context, origin string) (*Catalog, error)
- func (s *Service) Check(ctx context.Context, app string, actorID *int64) (*View, error)
- func (s *Service) Configured(origin string) bool
- func (s *Service) Connect(ctx context.Context, origin, code, self string, actorID *int64, ...) (*ConnectionView, error)
- func (s *Service) Connected(ctx context.Context, origin string) bool
- func (s *Service) Connection(ctx context.Context, origin string) (*ConnectionView, error)
- func (s *Service) Disconnect(ctx context.Context, origin string, actorID *int64) (bool, error)
- func (s *Service) Drop(handle string)
- func (s *Service) DropCatalog(origin string)
- func (s *Service) Finish(ctx context.Context, p *Pending, result string, actorID *int64)
- func (s *Service) Forget(ctx context.Context, app string)
- func (s *Service) Hold(origin, token string, in *Intent, userID int64) *Pending
- func (s *Service) HoldFor(origin, token string, in *Intent, userID, requestID int64) *Pending
- func (s *Service) InstalledFrom(ctx context.Context, app string) (string, error)
- func (s *Service) InstanceID(ctx context.Context) (string, error)
- func (s *Service) LicenseOf(ctx context.Context, app string) (*View, error)
- func (s *Service) Licenses(ctx context.Context) []*View
- func (s *Service) ListTrust(ctx context.Context) ([]TrustView, error)
- func (s *Service) LockApp(app string) (unlock func())
- func (s *Service) Loopback() bool
- func (s *Service) Media(ctx context.Context, origin, file string) ([]byte, string, error)
- func (s *Service) Now() time.Time
- func (s *Service) Pending(handle string, userID int64) (*Pending, error)
- func (s *Service) PutBack(p *Pending)
- func (s *Service) ReadIntent(ctx context.Context, origin, token, instance string) (*Intent, error)
- func (s *Service) RememberRequest(ctx context.Context, origin, tokenID string, requestID int64, ...) error
- func (s *Service) Remove(ctx context.Context, origin string, actorID *int64) (bool, error)
- func (s *Service) RequestFor(ctx context.Context, origin, tokenID string) int64
- func (s *Service) RequestIntent(ctx context.Context, origin, app, version, licenseKey string, actorID *int64) (*RequestedIntent, error)
- func (s *Service) Require(ctx context.Context, app, store, key string, actorID *int64) error
- func (s *Service) RequireUndoable(ctx context.Context, app, store, key string, actorID *int64) (*Undo, error)
- func (s *Service) RequireUndoableAs(ctx context.Context, app, storeApp, store, key string, actorID *int64) (*Undo, error)
- func (s *Service) Run(ctx context.Context)
- func (s *Service) SetKey(ctx context.Context, app, key string, actorID *int64) (*View, error)
- func (s *Service) SetView(ctx context.Context, scope string, v ViewSettings, actorID *int64, ...) (*ViewSettings, error)
- func (s *Service) Start(ctx context.Context)
- func (s *Service) StorageInstalledFrom(ctx context.Context, name string) (string, error)
- func (s *Service) Take(handle string, userID int64) (*Pending, error)
- func (s *Service) Tick(ctx context.Context)
- func (s *Service) TrustStatus(ctx context.Context, origin string) string
- func (s *Service) Undo(ctx context.Context, u *Undo)
- func (s *Service) View(ctx context.Context, scope string) (*ViewSettings, error)
- func (s *Service) Views(ctx context.Context) (map[string]*ViewSettings, error)
- type Source
- type StateStore
- type Trust
- type TrustView
- type Undo
- type View
- type ViewSettings
Constants ¶
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.
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).
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.
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`).
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).
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.
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.
const ( SourceAdmin = "admin" // an administrator trusted it on first use SourceConfig = "config" // FILEX_APP_STORE_URLS + FILEX_APP_STORE_KEYS )
Trust sources.
const ( AudienceEveryone = "everyone" AudienceRoles = "roles" AudienceGroups = "groups" )
Audiences: who in the scope sees the screen.
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).
const ScopeDefault = "default"
ScopeDefault is the single-tenant scope (no tenancy, or its main tenant before multi-tenant mode was turned on).
const StoragePrefix = "storage:"
StoragePrefix starts every license id (and Holder name) of a storage plugin; no app name holds a colon.
Variables ¶
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.
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.
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
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 ¶
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 ¶
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 ¶
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 ¶
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
LicenseID is the license row of a store entry of kind called name.
func NormalizeOrigin ¶
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 ¶
PayloadSHA256 is the lower-hex sha256 of the payload's canonical form: what the signature is over.
func Prefix ¶
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 Scope ¶ added in v0.53.0
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
ValidConnectCode reports whether s has the shape of a store's connection code (what a person pastes; nothing else is sent).
func ValidKey ¶
ValidKey reports whether s may be a license key: 4-512 printable ASCII characters, no spaces.
func ValidKeyID ¶
ValidKeyID reports whether id may name a store key.
func ValidToken ¶
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 ¶
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.
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 (*Client) Complete ¶
Complete tells the store how an install link ended: `POST <store>/v1/install/{token}/complete`.
func (*Client) Intent ¶
Intent reads `GET <store>/v1/install/{token}` as an envelope. 404 and 410 are said as such.
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 ¶
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).
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).
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 ¶
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.
type KeySet ¶
type KeySet struct {
Keys []Key `json:"keys"`
}
KeySet is a store's keys.json.
func (*KeySet) TrustedKeys ¶
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).
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 ¶
New builds the service. Configured stores that do not parse are logged and left out, never guessed at.
func (*Service) AppLicense ¶
AppLicense is what the app reads about itself (fx.license.get()).
func (*Service) ApplyHolds ¶
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
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 ¶
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 ¶
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
Connected reports whether this filex holds a key for origin.
func (*Service) Connection ¶ added in v0.53.0
Connection answers origin's connection for the panel ("not connected" when there is none).
func (*Service) Disconnect ¶ added in v0.53.0
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) DropCatalog ¶ added in v0.53.0
DropCatalog forgets origin's catalog (a store no longer trusted).
func (*Service) Finish ¶
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 ¶
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 ¶
Hold keeps a reviewed link under a new handle for the administrator who reviewed it.
func (*Service) HoldFor ¶ added in v0.53.0
HoldFor is Hold for a link an approval asked for: requestID is the plugin request it ends (0: none).
func (*Service) InstalledFrom ¶
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 ¶
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 ¶
LicenseOf is app's license for the panel; nil when app is not a paid app.
func (*Service) ListTrust ¶
ListTrust is every trusted store: configured ones first, then the ones an administrator trusted.
func (*Service) LockApp ¶
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) Media ¶ added in v0.53.0
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 ¶
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) PutBack ¶
PutBack returns a taken review (its install failed), unless it has expired meanwhile.
func (*Service) ReadIntent ¶
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 ¶
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
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 ¶
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 ¶
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) 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 ¶
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
StorageInstalledFrom is InstalledFrom for a storage plugin.
func (*Service) Take ¶
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 ¶
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 ¶
TrustStatus answers whether origin is trusted, for the panel, without asking the store: "config", "admin" or "".
func (*Service) 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.
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.
Source Files
¶
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). |