pairflow

package
v0.22.1 Latest Latest
Warning

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

Go to latest
Published: Sep 14, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package pairflow owns the device-pairing mint flow shared by the CLI (`gadak pairing`, GDK-433/450/797) and the desktop app's Devices tab (GDK-1047): endpoint resolution and validation, the scoped mint plus offer encode, the _home routing-token guard, list row shaping, and the two QR encodings (the terminal module matrix and the PNG a phone scans).

One owner so the surfaces cannot drift. The guard that motivates the extraction is ensureHomeRoutingToken: once one device token exists the serve gate takes Bearer only — no loopback bypass — so a mint that skips the home routing token locks the built-in home out of its own passthrough. The CLI carried that guard; before this package the desktop would have had to copy it, and a copy is where a guard goes missing.

Presentation stays with the callers: this package returns values (the offer, the loopback flag, which _home action ran) and prints nothing.

Index

Constants

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

DefaultTTL is 90 days: a device token is revocable, so it does not need a short life, but an unattended one should not outlive a quarter by default either.

Variables

This section is empty.

Functions

func AdvertisedEndpoint

func AdvertisedEndpoint(cfg *config.Config) string

AdvertisedEndpoint turns a live UI-serve listen address into the URL form endpoint validation wants. Discovery is origin.LiveServeFor, the single owner of the serveaddr walk. An explicit endpoint is not normalized: making the user name the scheme is the point.

func DefaultTTLFlag

func DefaultTTLFlag() string

DefaultTTLFlag renders DefaultTTL in the <N><unit> syntax --ttl speaks, so the 90-day default has one owner.

func Dir

func Dir(cfg *config.Config) (string, error)

Dir is the profile directory the token store lives in. Pairing protects both gated surfaces of a home serve — the origin passthrough (builtIn, GDK-433) and the mirror REST a phone companion reads (GDK-797) — so both workspace kinds may mint; what stays closed for a connected workspace is the passthrough itself (origin_rest.go 404s it). A workspace that is itself paired away cannot mint: its home is another machine.

func EndpointFromAdvertise

func EndpointFromAdvertise(addr string) string

EndpointFromAdvertise upgrades the raw bind address the advertise file stores (host:port) to the URL form, leaving an address that already carries a scheme alone.

func GateOpen

func GateOpen(dir string, now time.Time) bool

GateOpen reports whether the serve gate has fallen back open: no active token exists (including _home). The GDK-481 sentence callers print is theirs; this is the one boolean behind it.

func MintHome

func MintHome(dir string, cfg *config.Config, endpoint string, now time.Time) (pairing.Meta, error)

MintHome is `pairing mint --label _home`: routing-token rotation, not a device offer. No offer is produced. Built-in only — a connected workspace writes go straight to its site, and the routing file here would make origin.pairedRemote read this workspace as paired with its own serve.

func PairedLine

func PairedLine(cfg *config.Config, rem *pairing.Remote) string

PairedLine is the self-status sentence a paired workspace gets where it tried to act as a home.

func ParseScopeList added in v0.21.0

func ParseScopeList(s string) ([]string, error)

ParseScopeList parses --scope: one scope, or a comma list like "serve,terminal" (GDK-1498 — one offer, one token per scope). Members are trimmed, validated against the three real scopes, and returned in canonical order, so "terminal,serve" and "serve,terminal" mint the same offer. Duplicates are refused: the offer carries one token per scope, so a repeated name cannot mean two different tokens.

func ParseTTL

func ParseTTL(s string) (time.Duration, error)

ParseTTL accepts one integer with a single unit — "90d", "24h", "30m", "45s". time.ParseDuration rejects "d", and inventing compound syntax ("1d12h") is surface nobody asked for; a clear error beats guessing.

func QRModules

func QRModules(offer string) ([][]bool, error)

QRModules encodes the offer at EC Medium and returns the module matrix, quiet zone included (the library default 4-module border). Medium rather than Lower: pairing happens once per device, and a code that scans from a phone held at an angle beats a dense one; higher than Medium buys almost nothing at this payload size.

func QRPNG

func QRPNG(offer string) ([]byte, error)

QRPNG renders the offer QR as a PNG: white background, black modules, quiet zone included via the module matrix, square. The desktop serves it to the webview as a data URI; the geometry matches what the terminal half-block renderer draws, so both encodings are the same code.

func Revoke

func Revoke(dir, selector string, now time.Time) ([]pairing.Meta, error)

Revoke is the store call the CLI and desktop share: selector is an exact label or a hash prefix of at least 8 hex characters (what Rows prints). A label selector revokes every live token carrying it — a device is one label (GDK-1498) — so the plural return; a hash prefix still selects exactly one. The _home refusal and ambiguity wording live in internal/pairing — callers classify by that error, they do not rewrite it.

Types

type HomeRoutingAction

type HomeRoutingAction string

HomeRoutingAction is what a mint did about the _home routing token.

const (
	// HomeRoutingNone: the existing credential is valid, or the gate is
	// off, or the workspace is connected (no routing token by design).
	HomeRoutingNone HomeRoutingAction = ""
	// HomeRoutingMinted: this mint created the first _home credential.
	HomeRoutingMinted HomeRoutingAction = "minted"
	// HomeRoutingReissued: the stored credential was stale or revoked
	// while the gate was on (GDK-450 recovery).
	HomeRoutingReissued HomeRoutingAction = "reissued"
)

type LoopbackEndpointError added in v0.20.0

type LoopbackEndpointError struct{ Endpoint string }

LoopbackEndpointError is the GDK-1266 refusal: no --endpoint was given and the live serve's own address is loopback, so no remote device could use the offer. Callers add their own prescription (the CLI: a completed command); the endpoint is here so they can name it.

func (*LoopbackEndpointError) Error added in v0.20.0

func (e *LoopbackEndpointError) Error() string

type MintResult

type MintResult struct {
	Offer, Label, Scope, Endpoint, ExpiresAt string
	Meta                                     pairing.Meta
	// Scopes is every scope the offer carries and Metas the matching
	// store rows, in the offer's order (GDK-1498 multi-scope mint). A
	// single-scope mint has one entry each and keeps using the scalar
	// fields above — the readers that predate multi-scope keep working.
	Scopes []string
	Metas  []pairing.Meta
	// LoopbackWarning says the explicit endpoint is a loopback address —
	// only a device on this machine can reach it. The caller renders its
	// own copy. (A discovered loopback endpoint is refused instead:
	// LoopbackEndpointError, GDK-1266.)
	LoopbackWarning bool
	// HomeRouting names what ensureHomeRoutingToken did, so the CLI can
	// print its stderr note and the desktop can stay quiet.
	HomeRouting HomeRoutingAction
}

MintResult is everything a mint produced. The offer is a credential: it exists here and in the caller output, never in a log or an error.

func MintDevice

func MintDevice(dir string, cfg *config.Config, label, scope, ttl, endpoint string, now time.Time) (MintResult, error)

MintDevice is the device-mint flow: validate, resolve the endpoint, mint the scoped token, encode the offer, and — for a built-in home — keep the _home routing token valid. Callers pre-validate their own input surface (flags, form fields); this function re-validates because it is the structural owner, not a trusted callee.

endpoint may be empty: a live serve for cfg's profile is then discovered via origin.LiveServeFor. ttl may be empty: DefaultTTL applies. On a failure after the token was minted (the routing-token step is the only one) the result still carries the offer: the plaintext exists exactly once, in the caller output, or a minted token would be stranded unrecoverable.

func MintDeviceMulti added in v0.21.0

func MintDeviceMulti(dir string, cfg *config.Config, label string, scopes []string, ttl, endpoint string, now time.Time) (MintResult, error)

MintDeviceMulti is the multi-scope device-mint flow (GDK-1498): one offer, one token per scope, one device. A single-scope list delegates to MintDevice so a single-scope offer stays the byte-identical v1 line every existing pairer was promised (GDK-799). scopes is re-validated here — canonicalized and duplicate-free — because this function is the structural owner, not a trusted callee.

type Row

type Row struct {
	Hash     string `json:"hash"`
	Label    string `json:"label"`
	Scope    string `json:"scope"`
	Created  string `json:"created"`
	Expires  string `json:"expires"`
	LastUsed string `json:"last_used"`
	State    string `json:"state"`
}

Row is one pairing-list row. JSON tags are the table columns — never the plaintext token, never the full hash (only the 8-char prefix the list already showed).

func Rows

func Rows(dir string, now time.Time) ([]Row, error)

Rows shapes every stored token for listing: the _home row reads as local-routing (not a device scope), and state distinguishes revoked and expired from active.

Jump to

Keyboard shortcuts

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