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

pluginreq

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: 17 Imported by: 0

Documentation

Overview

Package pluginreq is the "leave a request" half of installing a plugin.

The rule (owner, 2026-09-28)

An API key — an agent, a script, the CLI — may not install, upgrade, remove or re-permission a plugin. Those need an administrator signed in to the admin panel (handlers.requireSession). What a key CAN do is leave a REQUEST: which plugin, from where, and why. An administrator reads it on the Plugins page and approves or rejects it.

The design principle it protects: "sha256 protects the administrator, the sandbox protects the user". The security boundary for an app is the administrator's permission review; before this, a key could copy the manifest's permission list into `permissions` and pass that review itself.

What a request freezes

A request is resolved when it is made — the install review's dry run — and what the source answered is kept on the row: the manifest (an app's filex-app.json, a storage plugin's filex-storage.json feed), the hash the bytes must have, and the permissions it asks to grant. The administrator approves THAT. Approval fetches the source again and installs only when it still answers those bytes; otherwise the request becomes `superseded`, nothing is installed, and the requester can ask again.

One pending request per source: asking twice answers the first request. A pending request expires after the TTL (14 days by default, FILEX_PLUGIN_REQUEST_TTL_DAYS).

Every step writes an audit row: created, approved, rejected, expired, superseded.

A request from the embedded store (#162)

A person who sees the store screen (internal/appstore view.go) leaves a request for an app of a trusted store's signed catalog: CreateStore, source kind "store". What the catalog said is frozen the same way - the version, its pins, the permissions - but nothing is fetched or installed from it: an approval asks the store for a FRESH install link (appstore.RequestIntent) and the administrator installs through the store-install review like any link (handlers/plugin_requests.go). That install closes the request (CompleteStore); Approve itself refuses a store request.

Index

Constants

View Source
const (
	SourceGitHub     = "github"
	SourceURL        = "url"
	SourceFeed       = "source"
	SourceFromSource = "from_source"
	// SourceStore: an app of a trusted store's catalog, asked for from the
	// embedded store screen (CreateStore).
	SourceStore = "store"
)

Source kinds (plugin_requests.source_kind).

View Source
const (
	AuditActionPluginRequestCreate    = "plugin_request.create"
	AuditActionPluginRequestApprove   = "plugin_request.approve"
	AuditActionPluginRequestReject    = "plugin_request.reject"
	AuditActionPluginRequestExpire    = "plugin_request.expire"
	AuditActionPluginRequestSupersede = "plugin_request.supersede"
)

The audit actions a request writes. Constants, so the admin panel's audit labels are held to them (web/tests/lib/auditLabel.test.ts reads them).

View Source
const DefaultTTL = 14 * 24 * time.Hour

DefaultTTL is how long a request waits for a decision.

View Source
const KindStorageEntry = "storage"

KindStorageEntry is a store entry's kind for a storage plugin (appstore.KindStorage, without the import).

View Source
const MaxPendingStorePerPerson = 10

MaxPendingStorePerPerson bounds the store requests one person may have waiting at once.

Variables

This section is empty.

Functions

func StoreSourceOf added in v0.53.0

func StoreSourceOf(r *model.PluginRequest) (store, app, version string, ok bool)

StoreSourceOf reads a store request's store, app and version.

Types

type Actor

type Actor struct {
	UserID     *int64
	Name       string
	TokenID    *int64
	TokenLabel string
	IP         string
}

Actor is who is asking or deciding.

type CreateInput

type CreateInput struct {
	Kind string `json:"kind"`
	Op   string `json:"op"`
	// Name: a storage plugin's name (install), or the installed plugin an
	// upgrade replaces (either kind; PluginID works too).
	Name     string `json:"name"`
	PluginID int64  `json:"plugin_id"`
	Reason   string `json:"reason"`
	Source
}

CreateInput is one request as the caller asks for it.

type Error

type Error struct {
	Status  int
	Code    string
	Message string
	// Say names the sentence when one code is refused for several reasons
	// (not_found: no app, no storage plugin, no request).
	Say string
	// Params fill the sentence ({name}, {version}).
	Params map[string]string
}

Error is a refusal the HTTP layer answers: its code, and the sentence the reader is told - `server.plugin_request.<Say>` (Say empty: the code) filled with Params, in the reader's language (handlers/plugin_requests.go). Message is the English detail, for a log and for a code without a sentence.

func (*Error) Error

func (e *Error) Error() string

type Options

type Options struct {
	Store db.Store
	// Apps is nil when app plugins are off; Plugins nil when storage
	// plugins are off. A request of that kind is then refused (503).
	Apps    *wasmplugin.Registry
	Plugins *plugin.Manager
	// Notify tells the administrators a request is waiting (nil: nobody).
	Notify notify.Service
	TTL    time.Duration
	Now    func() time.Time
	Log    *slog.Logger
}

Options wire a Service.

type Service

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

Service keeps the requests.

func New

func New(o Options) *Service

New builds a Service.

func (*Service) Approve

func (s *Service) Approve(ctx context.Context, id int64, who Actor, lang string) (*model.PluginRequest, any, error)

Approve installs what the request froze, and only that. It answers the closed request and the installed plugin's status; *Superseded when the source changed (the request is closed as superseded); any other failure leaves the request pending, with the refusal kept on it.

func (*Service) ApproveWith added in v0.50.0

func (s *Service) ApproveWith(ctx context.Context, id int64, who Actor, lang string, detail map[string]any) (*model.PluginRequest, any, error)

ApproveWith is Approve with what else the administrator decided on the way (0.50: the File types choices), written into the approval's audit row.

func (*Service) CompleteStore added in v0.53.0

func (s *Service) CompleteStore(ctx context.Context, id int64, who Actor, result any) (*model.PluginRequest, bool, error)

CompleteStore closes a store request as approved: its app was installed through the store's review from a link the approval asked for. result is what the install answered. A request that is no longer pending is left as it is (decided meanwhile) and answered with ok=false.

func (*Service) Create

func (s *Service) Create(ctx context.Context, in CreateInput, who Actor) (*model.PluginRequest, bool, error)

Create resolves a request's source (the dry run), freezes what it answered and records the request — or, when one is already pending for the same source, answers that one (created=false) and records nothing.

func (*Service) CreateStore added in v0.53.0

func (s *Service) CreateStore(ctx context.Context, e StoreEntry, reason string, who Actor) (*model.PluginRequest, bool, error)

CreateStore records a person's request for an app of a store's catalog, or answers the one already waiting for it (created=false). An app that is installed is asked for as an upgrade, and only to a newer version.

func (*Service) ExpireDue

func (s *Service) ExpireDue(ctx context.Context) int

ExpireDue closes every pending request past its expiry and answers how many. It runs before every read and write here, and hourly (StartSweeper), so a request is never shown as waiting after its time.

func (*Service) Get

func (s *Service) Get(ctx context.Context, id int64) (*model.PluginRequest, error)

Get answers one request.

func (*Service) List

func (s *Service) List(ctx context.Context, status string) ([]*model.PluginRequest, error)

List answers the requests in one state ("" = every state), newest first.

func (*Service) Mine added in v0.53.0

func (s *Service) Mine(ctx context.Context, userID int64) ([]*model.PluginRequest, error)

Mine answers the requests a person left from the store screen, newest first.

func (*Service) Reject

func (s *Service) Reject(ctx context.Context, id int64, who Actor, note string) (*model.PluginRequest, error)

Reject closes a request without installing anything; note is optional.

func (*Service) StartSweeper

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

StartSweeper expires overdue requests every hour until ctx ends.

func (*Service) TTL

func (s *Service) TTL() time.Duration

TTL is how long a request waits.

type Source

type Source struct {
	// An app from a GitHub repository (+ ref; default main, then master).
	GitHubRepo string `json:"github_repo,omitempty"`
	Ref        string `json:"ref,omitempty"`
	// An app from explicit addresses (the module's, the manifest's), or a
	// storage plugin's binary by address. SHA256 pins the bytes.
	URL         string `json:"url,omitempty"`
	ManifestURL string `json:"manifest_url,omitempty"`
	SHA256      string `json:"sha256,omitempty"`
	Signature   string `json:"signature,omitempty"`
	// A storage plugin's source: `owner/name` or a filex-storage.json address.
	Source string `json:"source,omitempty"`
	// An upgrade from the installed plugin's own source (the default for an
	// upgrade that names no other).
	FromSource bool `json:"from_source,omitempty"`
	// An app of a trusted store's catalog (SourceStore): the store's origin,
	// the app's name there and the version the catalog offered.
	Store        string `json:"store,omitempty"`
	StoreApp     string `json:"store_app,omitempty"`
	StoreVersion string `json:"store_version,omitempty"`
}

Source is where a request's plugin comes from — the install endpoints' own shapes, flattened into the request body.

func SourceOf

func SourceOf(r *model.PluginRequest) Source

SourceOf reads a request's source.

type StoreEntry added in v0.53.0

type StoreEntry struct {
	Store          string
	App            string
	Kind           string
	Version        string
	Label          map[string]string
	Summary        map[string]string
	Publisher      string
	Repo           string
	Permissions    []string
	ManifestSHA256 string
	WasmSHA256     string
	UISHA256       string
	// Builds are a storage plugin's pinned builds, platform → sha256 (#215).
	Builds map[string]string
}

StoreEntry is what a store's signed catalog said about the app a person asks for (appstore.CatalogApp, without the import).

type Superseded

type Superseded struct{ Why string }

Superseded is Approve's answer when the source no longer serves the bytes the request froze: the request is closed as superseded, nothing installed.

func (*Superseded) Error

func (e *Superseded) Error() string

Jump to

Keyboard shortcuts

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