Documentation
¶
Overview ¶
Package auth implements login against Auth0 using the OAuth device authorization grant, refresh-token storage in the OS keychain, and a per-session TokenSource that refreshes transparently. Endpoints come from build-time variables so no tenant details ship in source.
Index ¶
- Constants
- Variables
- func IsAuthRequired(err error) bool
- func Logout(ctx context.Context, hc *http.Client, cfg *Config, st Store) (bool, error)
- func RevokeRefreshToken(ctx context.Context, hc *http.Client, cfg *Config, refreshToken string) error
- func SleepContext(ctx context.Context, d time.Duration) error
- type AccessToken
- type AuthError
- type Claims
- type Config
- type DeviceCode
- type KeyringStore
- type MemoryStore
- type PollStatus
- type Session
- type SessionSource
- type Store
- type TokenResponse
Constants ¶
const ServiceName = "sweetrpg-cli"
ServiceName is the OS-keychain service all CLI credentials live under.
Variables ¶
var ( Domain string // e.g. "dev-abc123.us.auth0.com" ClientID string Audience string // catalog-api identifier Scopes = "openid profile email offline_access" )
Build-time settings. Empty means auth is not configured; commands that need it fail with a clear message instead of guessing a tenant.
var ( ErrDenied = errors.New("authorization denied") ErrExpired = errors.New("device code expired before authorization completed") ErrPending = errors.New("authorization pending") )
ErrDenied and ErrExpired end the flow; every other error is transport or server trouble surfaced to the caller as-is.
var ErrNotLoggedIn = errors.New("not logged in: run 'sweetrpg auth login'")
ErrNotLoggedIn means no usable stored credentials exist. Callers map it to exit code 3 with a pointer at `auth login`.
Functions ¶
func IsAuthRequired ¶
IsAuthRequired reports whether err should exit 3 with a login hint.
func Logout ¶
Logout revokes the stored refresh token server-side (best effort) and deletes local credentials. Missing credentials are not an error, so logout is idempotent; the bool reports whether credentials existed.
Types ¶
type AccessToken ¶
AccessToken pairs a bearer token with its expiry for session caching.
type AuthError ¶
type AuthError struct{ Msg string }
AuthError marks failures that need `auth login` again. The command layer maps them to exit code 3.
type Claims ¶
Claims are the ID-token fields the CLI cares about. The token arrives directly from Auth0 over TLS in this process, so fields are read without signature verification - verification is Auth0's job here.
func Login ¶
func Login(ctx context.Context, hc *http.Client, cfg *Config, st Store, sleep func(context.Context, time.Duration) error, openURL func(string)) (*Claims, error)
Login performs the full device flow and persists the resulting session. sleep is injectable for tests; production passes SleepContext. openURL, when non-nil, is tried with the verification URL as a convenience.
func ParseIDTokenClaims ¶
ParseIDTokenClaims decodes the payload segment of a JWT. It returns an error for malformed tokens rather than guessing.
type Config ¶
type Config struct {
Domain string
ClientID string
Audience string
Scopes string
// PollIntervalFloor guards against servers reporting interval 0; RFC 8628
// suggests treating anything under 5s as 5s.
PollIntervalFloor time.Duration
}
Config is one resolved tenant + client pairing. Tests build it directly; production resolves it from the vars above via DefaultConfig.
func DefaultConfig ¶
DefaultConfig resolves auth settings from the build-time values only (no env var or config file override). Most callers want ResolveConfig instead; this exists for callers with no config file context.
func ResolveConfig ¶
ResolveConfig resolves the Auth0 tenant by precedence: env var > fileDomain/fileClientID/fileAudience (the config file's authTenant section) > the build-time Domain/ClientID/Audience vars baked in via -ldflags at release time. A release binary ships with the tenant baked in, but an operator can still repoint it via env var or config file without a rebuild - the baked value is a default, not a hardcode. It errors when no source provides a full configuration.
type DeviceCode ¶
type DeviceCode struct {
DeviceCode string `json:"device_code"`
UserCode string `json:"user_code"`
VerificationURI string `json:"verification_uri"`
VerificationURIComplete string `json:"verification_uri_complete"`
ExpiresIn int `json:"expires_in"` // seconds
Interval int `json:"interval"` // seconds
// contains filtered or unexported fields
}
DeviceCode is the start of one authorization attempt.
type KeyringStore ¶
type KeyringStore struct{}
KeyringStore persists sessions in the OS keychain via go-keyring.
func (KeyringStore) Delete ¶
func (KeyringStore) Delete() error
func (KeyringStore) Load ¶
func (KeyringStore) Load() (*Session, error)
func (KeyringStore) Save ¶
func (KeyringStore) Save(s Session) error
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is an in-process Store for tests and for holding a session that could not be persisted (the refuse-to-persist fallback keeps it in memory for the rest of the run only).
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
func (*MemoryStore) Delete ¶
func (m *MemoryStore) Delete() error
func (*MemoryStore) Load ¶
func (m *MemoryStore) Load() (*Session, error)
func (*MemoryStore) Save ¶
func (m *MemoryStore) Save(s Session) error
type PollStatus ¶
type PollStatus int
PollStatus classifies one token-endpoint poll result.
const ( StatusAuthorized PollStatus = iota StatusPending // user has not approved yet - keep polling StatusSlowDown // polling too fast - back off and keep polling )
type Session ¶
type Session struct {
Account string `json:"account"`
Email string `json:"email,omitempty"`
RefreshToken string `json:"refresh_token"`
}
Session is what persists between runs: who logged in and the refresh token that proves it. Access tokens are never persisted - they are short-lived and re-derived from the refresh token.
type SessionSource ¶
type SessionSource struct {
Cfg *Config
HTTP *http.Client
Store Store
// RotateSaveErr records a failed save after refresh-token rotation: the
// current run keeps working, but the next run will need to log in again.
RotateSaveErr error
// contains filtered or unexported fields
}
SessionSource supplies bearer tokens for one logged-in session, refreshing transparently and persisting rotations.
type Store ¶
Store abstracts credential persistence so commands stay testable and so a broken keychain degrades to session-only use instead of plaintext files.
type TokenResponse ¶
type TokenResponse struct {
AccessToken string `json:"access_token"`
IDToken string `json:"id_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
}
TokenResponse is a successful token endpoint reply. RefreshToken may be empty on refresh grants that do not rotate it.
func AwaitAuthorization ¶
func AwaitAuthorization(ctx context.Context, hc *http.Client, cfg *Config, dc *DeviceCode, sleep func(context.Context, time.Duration) error) (*TokenResponse, error)
AwaitAuthorization runs the polling loop until the user approves, denies, the code expires, or ctx is cancelled. sleep is called between polls with the delay to observe; production passes a context-aware sleeper.
func RefreshAccessToken ¶
func RefreshAccessToken(ctx context.Context, hc *http.Client, cfg *Config, refreshToken string) (*TokenResponse, error)
RefreshAccessToken exchanges a refresh token for a fresh token set. When Auth0 rotates the refresh token the new one is in RefreshToken; otherwise that field is empty and the caller keeps the old one.