Documentation
¶
Overview ¶
Package credentials is the ONE on-disk store of control-plane access tokens, shared by every CLI that talks to a Reliant Labs control plane — `forge login` and `reliant auth login` both write it, and every command of both reads it.
── ONE FILE, KEYED BY ENDPOINT, NO "CURRENT" ─────────────────────────
The file maps a NORMALIZED endpoint URL, then a client id, to the credential that client holds for that endpoint:
{
"version": 1,
"credentials": {
"https://admin.reliantapi.com": {
"forge-cli": {"token": "rlat_…", "scopes": ["deploy:write", …], …}
},
"http://127.0.0.1:8090": {
"forge-cli": {"token": "rlat_…", …},
"reliant-cli": {"token": "rlat_…", …}
}
}
}
WHY THE CLIENT LEVEL. The endpoint is the server a token is PRESENTED to. Two CLIs can present tokens to the same origin with different scopes: in a dev stack one admin-server serves both forge's deploy API and reliant's proxied API. Keyed by endpoint alone, each CLI's login would silently replace the other's token with one that lacks its scopes. A client id is not a "current" selection; each CLI always reads its own.
It answers exactly one question — "what credential does client C hold for endpoint X" — and deliberately never "which endpoint should I use". WHICH endpoint is always decided by the caller from something explicit and reviewable (an env's KCL declaration for forge, --server / RELIANT_SERVER for reliant). A stored "current" entry would make which server a command hits depend on what a previous command did, which is the stateful-context model both CLIs have deleted.
── WHERE IT LIVES ────────────────────────────────────────────────────
Path resolves as:
$FORGE_HOME/credentials.json when FORGE_HOME is set $XDG_CONFIG_HOME/forge/credentials.json when XDG_CONFIG_HOME is set ~/.config/forge/credentials.json otherwise, on every OS
~/.config rather than os.UserConfigDir: on macOS that is ~/Library/ Application Support, a path with a space in it that users cannot type and that no other CLI tool uses. A file two CLIs share — and that a human is told to inspect or delete — belongs where CLI config conventionally lives. FORGE_HOME is honoured first so a test or a sandboxed CI job can redirect the file without touching a developer's real credentials.
The CALLER reads those variables and passes them in as Dirs: forge/pkg never reads the ambient environment (internal/pkgguard). Every CLI that shares the file builds Dirs the same way, from the same three variables, which is what makes it one file.
The directory is named for forge because forge OWNS the format: this package is its only reader and writer, and reliant imports it rather than re-implementing it. A second implementation of the same file is how two tools end up disagreeing about its shape.
── WHAT IS STORED ────────────────────────────────────────────────────
The token is opaque: nothing here parses it. Scopes, expiry and client id are the issuer's answer at login time, stored for DISPLAY and for the "expired, log in again" hint — never for an authorization decision, which only the server can make.
Index ¶
Constants ¶
const FileName = "credentials.json"
FileName is the credentials file's base name.
Variables ¶
var ErrNotFound = errors.New("credentials: no stored credential for this endpoint")
ErrNotFound reports that the file holds no credential for an endpoint and client.
Functions ¶
func Normalize ¶
Normalize turns an endpoint URL into its store key: lower-cased scheme and host, the port only when it is not the scheme's default, and nothing else — no path, no query, no trailing slash.
Two spellings of one server must land on one entry, or a user who logged in to "https://Admin.example.com/" is told they are not logged in to "https://admin.example.com". The path is dropped because a credential is issued by an ORIGIN; two paths on one origin are one issuer.
func Remove ¶
Remove is the one-call delete: drop (endpoint, client) from the file at path. Reports whether an entry existed. Every other entry is untouched.
func Save ¶
Save writes f to path ATOMICALLY with mode 0600 in a 0700 directory.
Atomic (temp file + rename in the same directory) because two CLIs write this file and a torn write would lose every endpoint's credential, not just the one being updated. The modes matter because every value is a bearer credential: a world-readable file is a leak on any shared machine.
func Store ¶
func Store(path, endpoint, client string, c Credential) error
Store is the one-call write: put c for (endpoint, client) into the file at path and save it, preserving every other entry.
Types ¶
type Credential ¶
type Credential struct {
// Token is the bearer credential. Opaque; never logged.
Token string `json:"token"`
// TokenPrefix is the issuer's display prefix (e.g. "rlat_A3f9Kd2p") —
// safe to print, and how a human matches this entry to a token listed
// in the web UI.
TokenPrefix string `json:"token_prefix,omitempty"`
// Scopes the issuer granted, as it reported them.
Scopes []string `json:"scopes,omitempty"`
// ExpiresAt is when the issuer said the token expires; nil when it did
// not say. Informational — the server decides.
ExpiresAt *time.Time `json:"expires_at,omitempty"`
// CreatedAt is when this entry was written.
CreatedAt time.Time `json:"created_at"`
// Issuer is the authorization server that minted it, when that differs
// from the endpoint it is presented to (reliant logs in at the control
// plane and presents the token to its API server). Informational.
Issuer string `json:"issuer,omitempty"`
}
Credential is what one endpoint's login produced.
func Lookup ¶
func Lookup(path, endpoint, client string) (Credential, error)
Lookup is the one-call read: client's credential for endpoint in the file at path, or ErrNotFound.
type Dirs ¶
Dirs are the inputs to the file's location: the values of $FORGE_HOME and $XDG_CONFIG_HOME, and the user's home directory. A caller fills them from its environment; see the package doc for the precedence.
type File ¶
type File struct {
Version int `json:"version"`
// Credentials is endpoint → client id → credential.
Credentials map[string]map[string]Credential `json:"credentials"`
}
File is the whole store.
func Load ¶
Load reads the file at path. A missing file is an empty store, not an error: "never logged in" is the ordinary first state.
func (*File) Delete ¶
Delete removes client's credential for endpoint and reports whether there was one. An endpoint left with no clients is dropped.