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

credentials

package
v1.0.14 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

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.

DirsFromEnv (env.go) reads those three inputs from the process — the one place this package touches the environment, exempted from internal/pkgguard by name. Every CLI that shares the file calls it rather than reading the variables itself, which is what makes it one file. Dirs stays a plain value for callers (tests, a sandbox) that must place the file explicitly.

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

View Source
const (
	// EnvForgeHome redirects the file to $FORGE_HOME/credentials.json. It wins
	// over everything else, so a test or a sandboxed CI job can move the file
	// without touching a developer's real credentials.
	EnvForgeHome = "FORGE_HOME"
	// EnvXDGConfigHome places the file at $XDG_CONFIG_HOME/forge/credentials.json.
	EnvXDGConfigHome = "XDG_CONFIG_HOME"
)

Environment variable names of the contract. Exported so docs and doctor checks refer to one spelling.

View Source
const FileName = "credentials.json"

FileName is the credentials file's base name.

Variables

View Source
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

func Normalize(endpoint string) (string, error)

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 PathFromEnv added in v1.0.0

func PathFromEnv() (string, error)

PathFromEnv is DirsFromEnv().Path(): where the credentials file lives for this process.

func Remove

func Remove(path, endpoint, client string) (bool, error)

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 RemoveRevoked added in v1.0.0

func RemoveRevoked(path, endpoint, client, revokedToken string) (bool, error)

RemoveRevoked removes only the exact session the caller revoked. A newer login or a different namespace is never erased by an older logout.

func Save

func Save(path string, f *File) error

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.

func Update added in v1.0.0

func Update(path string, mutate func(*File) error) error

Update serializes a credential-file read/modify/write transaction across all participating processes. The callback must not perform network operations.

Types

type Claimable added in v1.0.0

type Claimable struct {
	// ClaimURL is the link a human opens to take the deploy over.
	ClaimURL string `json:"claim_url"`
	// OrgID is the anonymous user's personal org.
	OrgID string `json:"org_id,omitempty"`
	// URL is the deploy's live address, as last observed. Empty until the
	// first deploy has gone live.
	URL string `json:"url,omitempty"`
	// ExpiresAt is when the deploy goes offline; PurgeAfter when it is
	// deleted (and when the token stops being accepted).
	ExpiresAt  time.Time `json:"expires_at"`
	PurgeAfter time.Time `json:"purge_after"`
}

Claimable is the anonymous deploy a claimable-deploy credential belongs to. Everything here is what `forge env status --claimable` reprints, so it is stored rather than re-fetched: the agent that holds it has no account to ask the control plane with.

type Credential

type Credential struct {
	// Kind distinguishes human identities from delegated capabilities.
	Kind string `json:"kind,omitempty"`
	// 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"`
	// AccountID and AccountEmail name the account the login signed in as,
	// as the issuer reported them (the token reply's account_id and
	// account_email extension fields; AccountID is the issuer's stable
	// external id for the person). Empty when the issuer did not say — an
	// older issuer, or a token pasted by hand. Informational: they let a
	// client show who it is signed in as, and tell two logins of one
	// person from logins of two people, without a network call. The server
	// decides who the token acts as.
	AccountID    string `json:"account_id,omitempty"`
	AccountEmail string `json:"account_email,omitempty"`
	// Claimable is set only on an entry stored under a claimable-deploy client
	// id (forge-cli-claimable…): the anonymous deploy this token belongs to.
	// It sits beside the token because the claim link is as sensitive as the
	// token is (whoever opens it first owns the deploy), and the file is
	// already 0600. Nil on every ordinary login.
	Claimable *Claimable `json:"claimable,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.

func (Credential) Expired

func (c Credential) Expired(now time.Time) bool

Expired reports whether the issuer-reported expiry has passed.

type Dirs

type Dirs struct {
	ForgeHome     string
	XDGConfigHome string
	Home          string
}

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.

func DirsFromEnv added in v1.0.0

func DirsFromEnv() Dirs

DirsFromEnv returns the Dirs this process's environment names: $FORGE_HOME, $XDG_CONFIG_HOME and the user's home directory. It is how `forge login` locates the file, so a consumer that calls it shares the file with forge by construction.

An unknown home directory leaves Dirs.Home empty; Path then fails only when neither variable is set either, which is the one case with nowhere to put the file.

func (Dirs) Path

func (d Dirs) Path() (string, error)

Path returns where the credentials file lives for these Dirs.

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

func Load(path string) (*File, error)

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

func (f *File) Delete(endpoint, client string) (bool, error)

Delete removes client's credential for endpoint and reports whether there was one. An endpoint left with no clients is dropped.

func (*File) Endpoints

func (f *File) Endpoints() []string

Endpoints lists the stored endpoints, sorted.

func (*File) Get

func (f *File) Get(endpoint, client string) (Credential, error)

Get returns the credential client holds for endpoint, or ErrNotFound.

func (*File) Put

func (f *File) Put(endpoint, client string, c Credential) error

Put stores c as client's credential for endpoint, replacing any previous one for that pair and leaving every other pair untouched.

Jump to

Keyboard shortcuts

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