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 Canonical(raw []byte) ([]byte, error)
- func ComparePins(in *Intent, src *Source) error
- func Fingerprints(keys []Key) []string
- func InstanceOrigin(raw string) (string, error)
- func NormalizeOrigin(raw string, loopback bool) (string, error)
- func PayloadSHA256(payload []byte) (string, error)
- func Prefix(key string) string
- func SHA256Hex(b []byte) string
- 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 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 Envelope
- type Error
- type Holder
- type Intent
- type Key
- type KeySet
- type KeyView
- type License
- type LicenseRequest
- type Mismatch
- type Options
- type Pending
- 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) Check(ctx context.Context, app string, actorID *int64) (*View, error)
- func (s *Service) Configured(origin string) bool
- func (s *Service) Drop(handle 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) 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) 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) Remove(ctx context.Context, origin string, actorID *int64) (bool, 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) Run(ctx context.Context)
- func (s *Service) SetKey(ctx context.Context, app, key string, actorID *int64) (*View, error)
- func (s *Service) Start(ctx context.Context)
- 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)
- type Source
- type StateStore
- type Trust
- type TrustView
- type Undo
- type View
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" )
Error codes the HTTP layer answers with and the panel switches on.
const ( KindApp = "app" KindLanguagePack = "language_pack" )
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.
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 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 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 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 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 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"`
}
Intent is what an install link stands for, as the store signed it (`GET <store>/v1/install/{token}`, signed with an `index` key).
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.
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 string `json:"app"`
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
}
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 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) 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) 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) 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) 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) 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) 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) 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) 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) 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 string `json:"app"`
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.
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). |