auth

package
v0.5.0-beta.1 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2026 License: PostgreSQL Imports: 12 Imported by: 0

Documentation

Overview

Package auth resolves API credentials and manages cached access tokens per profile and module under ~/.pgedge/cli/cache/.

Index

Constants

View Source
const (
	SourceFlags    = "flags"
	SourceEnv      = "env"
	SourceKeychain = "keychain"
	SourceConfig   = "config"
)

Credential sources ResolveCredentials reports.

View Source
const (
	EnvClientID     = "PGEDGE_CLIENT_ID"
	EnvClientSecret = "PGEDGE_CLIENT_SECRET" //nolint:gosec // G101: a variable name, not a credential
)

The environment pair. Empty counts as unset.

View Source
const RefreshWindow = 5 * time.Minute

RefreshWindow is how close to expiry a cached token may be before callers treat it as stale and exchange credentials again.

Variables

View Source
var ErrNoCredentials = errors.New("no credentials found — use " +
	"--client-id/--client-secret, set PGEDGE_CLIENT_ID and " +
	"PGEDGE_CLIENT_SECRET, or run 'pgedge starfleet auth login'")

ErrNoCredentials is the "nothing configured anywhere" failure.

Functions

func Fingerprint

func Fingerprint(apiURL, clientID, clientSecret string) string

Fingerprint derives a stable, one-way identifier for the connection that minted a cached token: SHA-256 over fingerprintDomain, the resolved API base URL, the client ID and the client secret, with a NUL byte before each input so ("a","bc") and ("ab","c") differ.

apiURL is hashed exactly as resolved, unnormalized, the same string the CLI is about to dial. A false mismatch costs one extra token exchange with that host; a false match hands a live bearer token to a host that never minted it, and normalizing can only produce the second kind. A trailing slash therefore re-exchanges, by design.

The digest is never printed; only whether it matches (MintedBy) is user-facing. Hashing the secret is acceptable in a file that already holds a live bearer token at 0600, and the client ID goes into the same hash rather than beside it in the clear. The API URL is no secret, and both diagnostics print it, but it shares the digest so that "same connection" is one comparison in one place, not two mechanisms that must agree.

Types

type Auth

type Auth struct {
	Profile  string
	Module   string
	CacheDir string // empty => ~/.pgedge/cli/cache

	// Keychain holds the profile's secret when the config file does
	// not; nil means unavailable. ConfigPath is the file the profile
	// lives in, which keys the entry (keychain.Account).
	Keychain   keychain.Store
	ConfigPath string
}

Auth performs credential resolution and token-cache management for one profile+module pair. The token exchange itself belongs to internal/starfleet/conn, which owns the typed /account/v1/oauth/token call.

func (*Auth) ClearToken

func (a *Auth) ClearToken() error

ClearToken removes the cached token (auth logout), including any staging file an interrupted write left behind, because logout promises to remove every on-disk copy of the token. atomicfile.Write cleans up with a deferred Remove, so a signal between its write and rename (Ctrl-C, SIGKILL, an OOM kill) leaves a 0600 file holding a live bearer token under a name no other code path knows. Nothing else in this CLI enumerates the cache directory, so without the sweep that token would sit there until its 24h expiry while logout reported success. The window is tiny; the promise is not conditional.

The sweep matches a literal prefix over os.ReadDir, not filepath.Glob: the profile name is user-controlled and validatePathSegment allows glob characters, so a profile named `*` would match every other profile's staging files.

func (*Auth) KeychainAccount

func (a *Auth) KeychainAccount() string

KeychainAccount is this profile's keychain entry name.

func (*Auth) KeychainSecret

func (a *Auth) KeychainSecret() (string, error)

KeychainSecret reads this profile's secret from the keychain.

func (*Auth) LoadToken

func (a *Auth) LoadToken() (*CachedToken, error)

LoadToken reads the cached token for this profile+module. A missing or unparseable cache returns an error; callers treat that as "no token".

func (*Auth) ResolveCredentials

func (a *Auth) ResolveCredentials(ap *config.StarfleetProfile,
	flagID, flagSecret string) (*Credentials, string, error)

ResolveCredentials returns credentials from the highest-priority source: flags > environment > profile, and names the source.

A half-supplied flag or env pair is a usage error, not a fallback: falling through would let a mistyped ID authenticate as whoever the config file names. A half-filled config section is stale state, so it falls through to "no credentials found" instead.

The profile's secret comes from the config file when it holds one, else from the keychain; login writes one or the other, never both.

func (*Auth) SaveToken

func (a *Auth) SaveToken(tok *CachedToken) error

SaveToken writes the token to the profile+module cache file with 0600 permissions. It refuses a token with no Fingerprint, so no caller, including a future one, can write an unbound cache file; conn.Login, the only caller, always stamps one first.

type CachedToken

type CachedToken struct {
	AccessToken string    `json:"access_token"`
	ExpiresAt   time.Time `json:"expires_at"`
	// Fingerprint binds the token to the API base URL, client ID and
	// client secret that minted it (see Fingerprint). A cache file older
	// than the field, or one using the older name credential_fingerprint,
	// decodes to "", which MintedBy treats as a mismatch, costing one
	// silent re-exchange.
	// The name says binding because the digest covers the URL too.
	Fingerprint string `json:"binding_fingerprint,omitempty"`
}

CachedToken is the on-disk token cache format.

func (*CachedToken) MintedBy

func (t *CachedToken) MintedBy(apiURL, clientID, clientSecret string) bool

MintedBy reports whether t was minted for the given API base URL by the given credential pair. An empty Fingerprint (a cache file written before the field existed, or before it covered the URL) never matches: the safe answer to unknown provenance is no. The URL is part of the question because a cached token is a bearer credential for one endpoint's auth server; matching on the credential alone would let `--api-url otherhost` send a live token to a host that never issued it.

Plain == is deliberate: both operands are local, one from this 0600 cache file and the other from the resolved credential (flags, env, keychain or the 0600 config file), so there is no remote timing channel, and anyone placed to time this can already read both inputs.

type Credentials

type Credentials struct {
	ClientID     string
	ClientSecret string
}

Credentials holds a resolved client ID and secret.

func EnvCredentials

func EnvCredentials() (*Credentials, error)

EnvCredentials resolves the PGEDGE_CLIENT_ID/PGEDGE_CLIENT_SECRET pair with FlagCredentials' rules: both, neither, or a *PartialFlagPairError naming the missing variable, so a typo in a pipeline cannot fall through to a stored identity.

func FlagCredentials

func FlagCredentials(flagID, flagSecret string) (*Credentials, error)

FlagCredentials resolves the --client-id/--client-secret pair on its own, ahead of any other source. It returns credentials when both are supplied, a *PartialFlagPairError when exactly one is, and (nil, nil) when neither is, which is not a failure and is for the caller to interpret.

Every resource command takes the whole chain through ResolveCredentials. `starfleet auth login` takes only this link: it writes the config, so falling through would answer "who are you signing in as?" from ambient state, skip the prompt for anyone already configured, and re-save the credentials being replaced. Sharing this function keeps the two from disagreeing about the same input.

type PartialFlagPairError

type PartialFlagPairError struct {
	// Given is the flag the operator supplied, Missing its counterpart.
	Given, Missing string
}

PartialFlagPairError reports that exactly one half of the flag or environment credential pair was supplied. It is a type so that `starfleet doctor` and `starfleet auth status` can tell a half pair from no credentials at all (errors.As, through conn.IsUsageError): the fixes differ, and a working pair in the config file is ignored while an incomplete override stands. The message text is user-facing and expected to change, so it is not what they match on.

func (*PartialFlagPairError) Error

func (e *PartialFlagPairError) Error() string

Jump to

Keyboard shortcuts

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