Documentation
¶
Overview ¶
Package clientconfig manages the local state of the Backyard CLI and MCP server (SPEC §13): the per-user config file with named profiles ($XDG_CONFIG_HOME/backyard/config.json, mode 0600), environment overrides, the per-project .backyard.json file, gateway URL resolution, token expiry awareness and the OAuth device-flow helpers used to obtain and refresh IdP-issued tokens.
The package performs no terminal I/O; callers decide how to prompt.
Index ¶
- Constants
- Variables
- func DefaultHTTPClient() *http.Client
- func DefaultPath() (string, error)
- func GatewayURL(server, gateway string) string
- func HubURL(info *deploy.Info, server string) string
- func IsLocalHost(host string) bool
- func NormalizeURL(raw string) (string, error)
- func RefreshProfile(ctx context.Context, hc *http.Client, p *Profile, now time.Time, ...) (bool, error)
- func SanitizeSiteName(s string) string
- func SaveProject(dir string, p Project) error
- func SiteNameFromDir(dir string) string
- func SiteURL(info *deploy.Info, server, site string) string
- func TokenExpiry(token string) (time.Time, bool)
- func ValidMethod(m string) bool
- func ValidProfileName(name string) bool
- type Device
- type DeviceAuthorization
- type DeviceFlow
- type Discovery
- type File
- type JWTClaims
- type OAuthError
- type Overrides
- type Profile
- type Project
- type ProjectFile
- type Resolved
- type Session
- type SessionOptions
- type Source
- type TokenResponse
- type User
Constants ¶
const ( EnvServer = "BACKYARD_SERVER" EnvGateway = "BACKYARD_GATEWAY" EnvToken = "BACKYARD_TOKEN" EnvProfile = "BACKYARD_PROFILE" EnvDeployMethod = "BACKYARD_DEPLOY_METHOD" )
Environment variables that override profile settings.
const ( LoginToken = "token" LoginBrowser = "browser" LoginDevice = "device" LoginAnonymous = "anonymous" )
Login methods recorded in Profile.Login.
const ( MethodAuto = "auto" MethodAPI = "api" MethodS3 = "s3" MethodGCS = "gcs" MethodAzure = "azure" MethodRsync = "rsync" )
Deploy methods (SPEC §13).
const DefaultProfile = "default"
DefaultProfile is the profile used when none is selected.
const ExpiryWarning = 24 * time.Hour
ExpiryWarning is how long before a (non-refreshable) stored token expires that sessions start warning about it.
const GatewayPath = deploy.EmbeddedPath
GatewayPath is where the server embeds the deploy gateway.
const ProjectFileName = ".backyard.json"
ProjectFileName is the per-project settings file.
const ( // RefreshSkew is how long before expiry a refreshable token is renewed. RefreshSkew = 2 * time.Minute )
Variables ¶
var ErrDeviceDenied = errors.New("the device login was denied")
ErrDeviceDenied is returned when the user denied the device login.
var ErrDeviceExpired = errors.New("the device code expired before the login was approved")
ErrDeviceExpired is returned when the user did not approve the device code in time.
var ErrNoServer = errors.New("no Backyard server configured: run `backyard login --server https://backyard.example.com` (or set BACKYARD_SERVER)")
ErrNoServer is returned when no server or gateway is configured.
var Methods = []string{MethodAuto, MethodAPI, MethodS3, MethodGCS, MethodAzure, MethodRsync}
Methods lists every valid deploy method, "auto" first.
Functions ¶
func DefaultHTTPClient ¶
DefaultHTTPClient returns the HTTP client used for IdP calls when none is supplied: 30 s overall timeout.
func DefaultPath ¶
DefaultPath returns the config file location: $XDG_CONFIG_HOME/backyard/config.json, falling back to ~/.config/backyard/config.json (%AppData%\backyard\config.json on Windows).
func GatewayURL ¶
GatewayURL returns gateway if set, else server + "/__backyard/api/deploy" ("" when both are empty).
func IsLocalHost ¶
IsLocalHost reports whether host (optionally with a port) is localhost, a *.localhost name or a loopback IP.
func NormalizeURL ¶
NormalizeURL validates an http(s) base URL, adding a scheme when missing (http:// for localhost and loopback addresses, https:// otherwise) and dropping the trailing slash, query and fragment.
func RefreshProfile ¶
func RefreshProfile(ctx context.Context, hc *http.Client, p *Profile, now time.Time, skew time.Duration) (bool, error)
RefreshProfile renews p's token in place when it is refreshable and expires within skew of now. It reports whether p changed (and so should be saved).
func SanitizeSiteName ¶
SanitizeSiteName turns an arbitrary string into a DNS-label site name, or "" when nothing usable remains.
func SaveProject ¶
SaveProject writes p to dir/.backyard.json (mode 0644).
func SiteNameFromDir ¶
SiteNameFromDir derives a valid site name from a directory's base name: lowercased, runs of characters outside [a-z0-9] replaced by "-", trimmed to 63 characters. It returns "" when nothing usable remains.
func SiteURL ¶
SiteURL returns the public URL of site for a server described by info. In host mode that is <scheme>://<site>.<base_domain>[:<port>]/ with the scheme and port of the public URL (base_domain may differ from the public URL's host; without one the public URL's host is used), in path mode <public_url>/s/<site>/. server is used when info lacks a public URL.
func TokenExpiry ¶
TokenExpiry returns the "exp" of a JWT token (ok=false for opaque tokens or JWTs without exp).
func ValidMethod ¶
ValidMethod reports whether m is a known deploy method ("" counts as auto).
func ValidProfileName ¶
ValidProfileName reports whether name is usable as a profile name.
Types ¶
type Device ¶
type Device struct {
Issuer string `json:"issuer"`
ClientID string `json:"client_id"`
Scopes []string `json:"scopes,omitempty"`
TokenEndpoint string `json:"token_endpoint,omitempty"`
}
Device describes the IdP used for a device-flow login.
type DeviceAuthorization ¶
type DeviceAuthorization struct {
DeviceCode string `json:"device_code"`
UserCode string `json:"user_code"`
VerificationURI string `json:"verification_uri"`
VerificationURIComplete string `json:"verification_uri_complete,omitempty"`
ExpiresIn int `json:"expires_in"`
Interval int `json:"interval,omitempty"`
}
DeviceAuthorization is an RFC 8628 §3.2 device authorization response.
func (*DeviceAuthorization) UnmarshalJSON ¶
func (d *DeviceAuthorization) UnmarshalJSON(b []byte) error
UnmarshalJSON accepts the "verification_url" spelling used by some IdPs.
type DeviceFlow ¶
type DeviceFlow struct {
// HTTP is the client for IdP calls (default DefaultHTTPClient).
HTTP *http.Client
// ClientID is the public client id (info.auth.device.client_id).
ClientID string
// Scopes requested (info.auth.device.scopes).
Scopes []string
// Discovery holds the IdP endpoints (see Discover).
Discovery *Discovery
// PollUnit is the length of one interval second (default time.Second);
// tests shorten it.
PollUnit time.Duration
}
DeviceFlow performs the OAuth 2.0 device authorization grant (RFC 8628).
func (*DeviceFlow) Poll ¶
func (f *DeviceFlow) Poll(ctx context.Context, da *DeviceAuthorization) (*TokenResponse, error)
Poll polls the token endpoint until the user approves, denies or the code expires, honouring the interval and slow_down responses.
func (*DeviceFlow) Start ¶
func (f *DeviceFlow) Start(ctx context.Context) (*DeviceAuthorization, error)
Start requests a device and user code.
type Discovery ¶
type Discovery struct {
Issuer string `json:"issuer"`
DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
}
Discovery is the subset of OpenID Connect discovery metadata the device flow needs.
type File ¶
type File struct {
// Current is the active profile name.
Current string `json:"current"`
// Profiles by name.
Profiles map[string]*Profile `json:"profiles"`
}
File is the on-disk CLI configuration document.
func (*File) ActiveName ¶
ActiveName returns the profile selected by flag, else $BACKYARD_PROFILE, else the file's current profile, else "default".
func (*File) Delete ¶
Delete removes the named profile; the current selection is cleared when it pointed at it.
func (*File) Resolve ¶
Resolve merges flags > environment > profile into the effective settings. URLs are normalized; invalid URLs yield an error naming their source.
type JWTClaims ¶
type JWTClaims struct {
Exp int64 `json:"exp"`
Iss string `json:"iss"`
Sub string `json:"sub"`
Email string `json:"email"`
Aud json.RawMessage `json:"aud"`
Azp string `json:"azp"`
Cid string `json:"cid"`
}
JWTClaims is the subset of unverified JWT claims the CLI inspects.
func ParseJWTUnverified ¶
ParseJWTUnverified decodes the claims of a compact JWT without verifying it. It is only used for local decisions (expiry display, token choice); servers always verify. ok is false for anything that is not a JWT.
type OAuthError ¶
OAuthError is an error response from an OAuth endpoint.
func (*OAuthError) Error ¶
func (e *OAuthError) Error() string
type Overrides ¶
Overrides are explicit settings (command-line flags) that take precedence over the environment and the stored profile.
type Profile ¶
type Profile struct {
// Server is the hub URL, e.g. https://backyard.acme.com.
Server string `json:"server,omitempty"`
// Gateway is an explicit deploy gateway URL (standalone gateway, or the
// proxy-exempt path); empty means Server + "/__backyard/api/deploy".
Gateway string `json:"gateway,omitempty"`
// Token is the bearer token (Backyard API token, static token or IdP JWT).
Token string `json:"token,omitempty"`
// TokenExpiresAt is when Token expires (zero when unknown or never).
TokenExpiresAt time.Time `json:"token_expires_at,omitzero"`
// Method is the preferred deploy method (auto|api|s3|gcs|azure|rsync).
Method string `json:"method,omitempty"`
// RefreshToken is the IdP refresh token of a device-flow login.
RefreshToken string `json:"refresh_token,omitempty"`
// Device holds what is needed to refresh a device-flow token.
Device *Device `json:"device,omitempty"`
// Login records how the credentials were obtained (token, browser,
// device or anonymous).
Login string `json:"login,omitempty"`
// User is the identity behind the credentials at login time.
User *User `json:"user,omitempty"`
// Info caches the gateway's /info document from the last successful
// contact so direct-sync deploys work while the gateway is unreachable.
Info *deploy.Info `json:"info,omitempty"`
}
Profile is one Backyard server the CLI talks to, with its credentials.
func (*Profile) CanRefresh ¶
CanRefresh reports whether the profile holds a device-flow refresh token.
func (*Profile) ClearCredentials ¶
func (p *Profile) ClearCredentials()
ClearCredentials removes every secret and identity from the profile while keeping its server settings.
func (*Profile) GatewayURL ¶
GatewayURL returns the profile's effective gateway URL.
func (*Profile) TokenExpired ¶
TokenExpired reports whether the profile's token has a known expiry that is not after now.
type Project ¶
type Project struct {
// Site is the site name to deploy to.
Site string `json:"site,omitempty"`
// Dir is the folder to deploy, relative to the project file (default ".").
Dir string `json:"dir,omitempty"`
// Method is the preferred deploy method (default auto).
Method string `json:"method,omitempty"`
// Ignore lists extra gitignore-style patterns excluded from deploys.
Ignore []string `json:"ignore,omitempty"`
}
Project is the content of .backyard.json.
type ProjectFile ¶
type ProjectFile struct {
Project
// Path is the absolute path of the .backyard.json file.
Path string
}
ProjectFile is a project file found on disk.
func FindProject ¶
func FindProject(start string) (*ProjectFile, error)
FindProject searches start and its parents for .backyard.json. It returns (nil, nil) when none is found. Unreadable directories are skipped.
func LoadProject ¶
func LoadProject(path string) (*ProjectFile, error)
LoadProject reads a .backyard.json file.
func (*ProjectFile) DeployDir ¶
func (p *ProjectFile) DeployDir() (string, error)
DeployDir returns the absolute folder to deploy: Dir resolved against the folder holding the project file (Root). Dir must name a folder inside Root: an absolute path, a ".." that leaves Root or a symlink to a folder outside it is an error naming the file. A .backyard.json that arrived with a cloned repository or a pulled site therefore cannot point a plain `backyard deploy` at an unrelated local folder.
func (*ProjectFile) Root ¶
func (p *ProjectFile) Root() string
Root returns the directory containing the project file.
type Resolved ¶
type Resolved struct {
// ProfileName is the selected profile (it may not exist yet).
ProfileName string
// Profile is a copy of the stored profile (never nil).
Profile *Profile
// Exists reports whether the profile is stored in the file.
Exists bool
Server string
Gateway string // effective gateway URL ("" when no server is known)
Token string
Method string
ServerSource Source
TokenSource Source
}
Resolved is the effective client configuration for one invocation.
func (*Resolved) CachedInfo ¶
CachedInfo returns the server info cached in the profile, or nil when this invocation targets another server or gateway than the profile's: the cache describes the profile's server only (its storage, URLs and methods), and applying it to another one would, for example, point a direct-sync deploy at the wrong bucket.
func (*Resolved) UsesProfileServer ¶
UsesProfileServer reports whether the effective server and gateway are the stored profile's own, i.e. no flag or environment variable pointed this invocation at another server or gateway.
type Session ¶
type Session struct {
*Resolved
// Path is the config file path.
Path string
// File is the loaded config file.
File *File
// Warnings collects non-fatal problems worth showing the user (expired
// tokens, failed refreshes).
Warnings []string
// contains filtered or unexported fields
}
Session is the effective client state for one command: the config file, the resolved settings and helpers to persist profile changes.
func OpenSession ¶
func OpenSession(ctx context.Context, o SessionOptions) (*Session, error)
OpenSession loads the config, resolves flags/env/profile and, when the stored device-flow token is about to expire, refreshes and persists it.
func (*Session) CacheInfo ¶
CacheInfo stores info in the profile (best effort) when the session uses the stored profile's own server and gateway (see CachedInfo).
func (*Session) Client ¶
Client returns a deploy gateway client authenticated with the session's token. hc replaces the client's HTTP client when non-nil.
func (*Session) RequireGateway ¶
RequireGateway returns ErrNoServer when no gateway URL is known.
func (*Session) UpdateProfile ¶
UpdateProfile re-reads the config file, applies mutate to the selected profile (created when missing) and saves it, so concurrent CLI processes do not clobber unrelated changes. The session's copy is updated too.
type SessionOptions ¶
type SessionOptions struct {
// ConfigPath is the config file (default DefaultPath()).
ConfigPath string
// Overrides are flag values.
Overrides Overrides
// HTTP is used for IdP token refreshes (default DefaultHTTPClient()).
HTTP *http.Client
// Now returns the current time (default time.Now).
Now func() time.Time
// Logger receives debug/warn logs (default: discard).
Logger *slog.Logger
}
SessionOptions configure OpenSession.
type TokenResponse ¶
type TokenResponse struct {
AccessToken string `json:"access_token"`
IDToken string `json:"id_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
TokenType string `json:"token_type,omitempty"`
ExpiresIn int64 `json:"expires_in,omitempty"`
}
TokenResponse is an OAuth 2.0 token endpoint response.
func Refresh ¶
func Refresh(ctx context.Context, hc *http.Client, tokenEndpoint, clientID, refreshToken string) (*TokenResponse, error)
Refresh exchanges a refresh token at tokenEndpoint.
func (*TokenResponse) Credential ¶
Credential picks the bearer token to present to Backyard and its expiry. The access token is used when it is a JWT issued for clientID (aud, azp or cid); otherwise the ID token (whose audience is the client) when present; otherwise the access token as is.