Documentation
¶
Overview ¶
Package auth implements pvtr's OIDC device-grant login and credential storage for authenticated publishing to grc.store. It mirrors grcli's flow (ADR-0028): a device-authorization grant against the hub-advertised OIDC issuer + client, with tokens cached in a 0600 file under the XDG data dir. The consumer (install) path stays anonymous and does not touch this package.
Index ¶
- Variables
- func BearerToken(ctx context.Context, issuer, clientID string) (string, error)
- func Login(ctx context.Context, issuer, clientID string, promptOut io.Writer) (string, error)
- func Logout(issuer string) error
- func SigningIDToken(ctx context.Context, promptOut io.Writer) (string, error)
- type Credentials
- type DeviceAuthorization
- type OIDCMetadata
- type Store
Constants ¶
This section is empty.
Variables ¶
var ( ErrAccessDenied = errors.New("authorization denied by the user") ErrExpiredDeviceCode = errors.New("device code expired before authorization completed; run `pvtr login` again") )
User-facing sentinel errors from the device flow.
var ErrNoCredentials = errors.New("no stored credentials for this issuer (run `pvtr login`)")
ErrNoCredentials is returned by Get when no entry exists for the issuer.
Functions ¶
func BearerToken ¶
BearerToken resolves an OIDC bearer to authenticate registry/hub writes. Resolution order (highest first):
- PVTR_TOKEN env — an explicit token (CI trusted-publishing's GHA-OIDC token, or a manually minted one). No store interaction.
- The device-grant store for the given issuer, refreshing if near expiry.
Returns ErrNoCredentials when neither is available — callers map that to "run `pvtr login` (or set PVTR_TOKEN in CI)".
func Login ¶
Login runs the device-authorization grant against the issuer and stores the resulting credentials. promptOut receives the user-facing "open this URL, enter this code" message. It returns the canonical issuer it logged into.
func SigningIDToken ¶
SigningIDToken returns an OIDC ID token suitable for PUBLIC-GOOD Fulcio — the identity the keyless plugin signature is minted under. This is DISTINCT from BearerToken (the grc.store registry/hub login): Fulcio only trusts public OIDC issuers (GitHub Actions, GitLab, Google, the interactive sigstore Dex), not the grc.store Keycloak.
Resolution order (highest first):
- SIGSTORE_ID_TOKEN env — an explicit OIDC token with audience "sigstore". The headless escape hatch for any CI that can mint one (e.g. GitLab CI's `id_tokens`), checked first so an explicit override wins over ambient detection — mirroring how PVTR_TOKEN overrides the bearer.
- GitHub Actions ambient OIDC — mints an aud=sigstore token from the runner's token service (a SEPARATE request from the hub bearer).
- Interactive sigstore browser sign-in — a SECOND browser auth on top of `pvtr login`, inherent to keyless signing. Skipped with an actionable error when stdin is not a TTY, so CI fails fast instead of hanging on a flow no one can complete.
promptOut receives any interactive instructions.
Types ¶
type Credentials ¶
type Credentials struct {
Issuer string `json:"issuer"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"`
TokenType string `json:"token_type,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
}
Credentials is one issuer's saved tokens. Issuer doubles as the store key.
func PollForToken ¶
func PollForToken(ctx context.Context, meta *OIDCMetadata, clientID string, da *DeviceAuthorization) (*Credentials, error)
PollForToken blocks polling the token endpoint until the device flow completes, returning the issued Credentials.
func (*Credentials) Expired ¶
func (c *Credentials) Expired() bool
Expired reports whether the access token is at/near expiry (within the renewal window), so callers refresh before a push rather than mid-flight.
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"`
}
DeviceAuthorization is RFC 8628 §3.2's device authorization response.
func StartDeviceFlow ¶
func StartDeviceFlow(ctx context.Context, meta *OIDCMetadata, clientID string) (*DeviceAuthorization, error)
StartDeviceFlow calls the device_authorization_endpoint. The caller displays user_code + verification_uri, then hands the result to PollForToken.
type OIDCMetadata ¶
type OIDCMetadata struct {
Issuer string `json:"issuer"`
DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
}
OIDCMetadata is the subset of the issuer's OpenID Connect discovery document pvtr's device-grant flow needs.
func FetchOIDCMetadata ¶
func FetchOIDCMetadata(ctx context.Context, issuerURL string) (*OIDCMetadata, error)
FetchOIDCMetadata loads <issuer>/.well-known/openid-configuration. issuerURL is the hub-advertised oidc_issuer (NOT the hub URL).
type Store ¶
type Store struct {
Path string
}
Store is pvtr's on-disk credential cache: a single JSON file at ${XDG_DATA_HOME:-~/.local/share}/pvtr/credentials.json, 0600, one entry per issuer. Same posture as grcli/gh/flyctl. Kept separate from grcli's store so the two tools don't fight over one file.
func NewDefaultStore ¶
NewDefaultStore returns a Store at the standard XDG path.
func (*Store) Get ¶
func (s *Store) Get(issuer string) (*Credentials, error)
Get returns the credentials saved for issuer, or ErrNoCredentials.
func (*Store) Put ¶
func (s *Store) Put(creds *Credentials) error
Put writes creds.Issuer → creds, replacing any existing entry. The file is rewritten atomically (temp + rename) with 0600 perms. NOTE: intentionally does NOT use utils.WriteFileAtomic — credentials require os.CreateTemp (kernel-assigned unique name) + os.File.Chmod(0600) before any write, so the 0600 mode is set before secret bytes land on disk. The shared helper's WriteFile+rename sequence cannot provide that ordering guarantee.