Documentation
¶
Overview ¶
Package cliauth stores Oura credentials out-of-band from agent context.
Primary store is the OS keyring (macOS Keychain, Windows Credential Manager, libsecret/Secret Service on Linux) via zalando/go-keyring. Where no keyring is available (headless Linux, CI), it falls back to an AES-256-GCM encrypted file under the user config dir, keyed by a random key stored alongside with 0600 permissions — obfuscation-at-rest, clearly weaker than a real keyring, and reported as such by `oura doctor`.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNotFound = errors.New("no stored credentials")
ErrNotFound is returned when no credentials are stored.
Functions ¶
func ConfigDir ¶
func ConfigDir() string
ConfigDir returns the ouracli config directory (~/.config/ouracli or the platform equivalent), honoring OURA_CONFIG_DIR for tests and containers.
func ResolveToken ¶
func ResolveToken() (token, source string)
ResolveToken returns the access token to use, and where it came from. Precedence: OURA_TOKEN env > stored credentials. Returns source "env", "keyring", "encrypted-file", or "" when nothing is available. OAuth refresh, when needed, is handled by the caller via Load().
Types ¶
type AuthorizeRejectedError ¶
AuthorizeRejectedError reports that Oura's authorize endpoint rejected the request outright (HTTP 400) before any user interaction — in practice this means the redirect URI is not registered on the OAuth app. Detected by the preflight probe so callers can fail fast with exact remediation instead of waiting for a browser callback that will never arrive.
func (*AuthorizeRejectedError) Error ¶
func (e *AuthorizeRejectedError) Error() string
type Credentials ¶
type Credentials struct {
Method Method `json:"method"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"`
Expiry time.Time `json:"expiry,omitempty"`
Scopes []string `json:"scopes,omitempty"`
ClientID string `json:"client_id,omitempty"`
ClientSecret string `json:"client_secret,omitempty"`
SavedAt time.Time `json:"saved_at"`
}
Credentials is the JSON blob stored in the keyring.
func LoginOAuth ¶
func LoginOAuth(ctx context.Context, clientID, clientSecret string, scopes []string, port int, openBrowser func(url string) error) (Credentials, error)
LoginOAuth runs the loopback authorization-code flow. It binds a tiny HTTP server on 127.0.0.1:port, opens the browser to Oura's consent screen (and always prints the URL to stderr so headless users can paste it), waits for the redirect back to /callback, validates the state parameter, and exchanges the returned code for tokens. The granted scope set (which may be a subset of what was requested, since users can untick scopes) and the derived expiry are recorded on the returned Credentials. Saving is left to the caller.
func Refresh ¶
func Refresh(ctx context.Context, store Store, c Credentials) (Credentials, error)
Refresh exchanges the stored refresh token for a new access token. Oura's refresh tokens are single-use and rotate on every exchange, so the rotated credentials are saved to the store BEFORE this returns — a caller that used the new access token without persisting the new refresh token would strand the account on the next refresh.
type Store ¶
type Store interface {
Save(Credentials) error
Load() (Credentials, error)
Delete() error
Backend() string // "keyring" or "encrypted-file"
}
Store abstracts the two backends so doctor can report which is active.