Documentation
¶
Overview ¶
Package cloud is the laptop's HTTP client for the user's clank gateway deployment. Provider-agnostic: the gateway exposes an /auth-config discovery endpoint that returns standard OAuth 2.0 endpoints (authorize, token, client_id, scopes), and clank's OAuth client (oauth.go) runs authorization code + PKCE against them.
Designed for the TUI's Cloud panel (internal/tui/cloudview.go) and the `clank login` subcommand. Every call is one-shot, Context-bounded. No background goroutines, no caching: callers own lifecycle.
Index ¶
- Variables
- type AuthCaller
- func (a *AuthCaller) AuthFlowStatus(ctx context.Context, providerID, flowID string) (agent.DeviceFlowStatus, error)
- func (a *AuthCaller) CancelAuthFlow(ctx context.Context, providerID, flowID string) error
- func (a *AuthCaller) ListAuthProviders(ctx context.Context, backend agent.BackendType) ([]agent.ProviderAuthInfo, error)
- func (a *AuthCaller) StartAuthDeviceFlow(ctx context.Context, providerID string) (agent.DeviceFlowStart, error)
- func (a *AuthCaller) StartAuthOAuthCodeFlow(ctx context.Context, providerID string) (agent.DeviceFlowStart, error)
- func (a *AuthCaller) SubmitAuthAPIKey(ctx context.Context, providerID, key string, metadata map[string]string) (agent.DeviceFlowStart, error)
- func (a *AuthCaller) SubmitAuthCode(ctx context.Context, providerID, flowID, code string) error
- type AuthConfig
- type Client
- type OAuthClient
- type Session
Constants ¶
This section is empty.
Variables ¶
var ErrLoginCancelled = errors.New("cloud: login cancelled")
ErrLoginCancelled is returned when the user aborts the flow (closes the browser, or sends ^C in the parent context).
var ErrLoginTimeout = errors.New("cloud: login timed out")
ErrLoginTimeout is returned when the OAuth provider doesn't redirect back within the timeout window. Default 5 minutes (passed via ctx; oauth.go itself doesn't set a budget).
ErrUnauthorized is returned when the bearer is rejected by the gateway (e.g. token expired). Caller should re-prompt sign-in.
Functions ¶
This section is empty.
Types ¶
type AuthCaller ¶
type AuthCaller struct {
// contains filtered or unexported fields
}
AuthCaller wraps an *http.Client targeting the gateway, with the user's OAuth access token attached to each request. Construct one per modal invocation — it is cheap and stateless.
func NewAuthCaller ¶
func NewAuthCaller(gatewayURL, accessToken string, httpClient *http.Client) *AuthCaller
NewAuthCaller constructs a caller against gatewayURL using the given bearer access token. httpClient may be nil for the default.
func (*AuthCaller) AuthFlowStatus ¶
func (a *AuthCaller) AuthFlowStatus(ctx context.Context, providerID, flowID string) (agent.DeviceFlowStatus, error)
AuthFlowStatus mirrors daemonclient.HostClient.AuthFlowStatus.
func (*AuthCaller) CancelAuthFlow ¶
func (a *AuthCaller) CancelAuthFlow(ctx context.Context, providerID, flowID string) error
CancelAuthFlow mirrors daemonclient.HostClient.CancelAuthFlow.
func (*AuthCaller) ListAuthProviders ¶
func (a *AuthCaller) ListAuthProviders(ctx context.Context, backend agent.BackendType) ([]agent.ProviderAuthInfo, error)
ListAuthProviders mirrors hostclient.HTTP.ListAuthProviders.
func (*AuthCaller) StartAuthDeviceFlow ¶
func (a *AuthCaller) StartAuthDeviceFlow(ctx context.Context, providerID string) (agent.DeviceFlowStart, error)
StartAuthDeviceFlow mirrors daemonclient.HostClient.StartAuthDeviceFlow.
func (*AuthCaller) StartAuthOAuthCodeFlow ¶
func (a *AuthCaller) StartAuthOAuthCodeFlow(ctx context.Context, providerID string) (agent.DeviceFlowStart, error)
StartAuthOAuthCodeFlow mirrors daemonclient.HostClient.StartAuthOAuthCodeFlow.
func (*AuthCaller) SubmitAuthAPIKey ¶
func (a *AuthCaller) SubmitAuthAPIKey(ctx context.Context, providerID, key string, metadata map[string]string) (agent.DeviceFlowStart, error)
SubmitAuthAPIKey mirrors daemonclient.HostClient.SubmitAuthAPIKey.
func (*AuthCaller) SubmitAuthCode ¶
func (a *AuthCaller) SubmitAuthCode(ctx context.Context, providerID, flowID, code string) error
SubmitAuthCode mirrors daemonclient.HostClient.SubmitAuthCode.
type AuthConfig ¶
type AuthConfig struct {
// AuthorizeEndpoint is the IdP's /authorize URL, e.g.
// "https://abc.supabase.co/oauth/authorize".
AuthorizeEndpoint string `json:"authorize_endpoint"`
// TokenEndpoint is the IdP's /token URL, e.g.
// "https://abc.supabase.co/oauth/token".
TokenEndpoint string `json:"token_endpoint"`
// ClientID is the public OAuth client identifier the laptop
// presents at both endpoints. PKCE replaces the client secret;
// nothing secret is shipped to the laptop.
ClientID string `json:"client_id"`
// Scopes are the OAuth scopes the laptop should request. Joined
// with spaces on the authorize URL per RFC 6749 §3.3.
Scopes []string `json:"scopes,omitempty"`
// DefaultProvider is an optional IdP hint (e.g. "github") the
// gateway suggests as the primary sign-in option. Passed as the
// non-spec `provider` query param when set; ignored by IdPs that
// don't recognise it.
DefaultProvider string `json:"default_provider,omitempty"`
// CallbackPort, when set, tells the laptop to bind its PKCE
// callback listener to exactly this port (instead of a random
// kernel-assigned port). Required by IdPs that match
// redirect_uris strictly — Supabase OAuth Server, for example.
// The IdP must have the same `http://127.0.0.1:<port>` registered
// as a redirect_uri for the OAuth client.
CallbackPort int `json:"callback_port,omitempty"`
}
AuthConfig is the gateway's reply to GET /auth-config. It tells clank which OAuth 2.0 IdP to talk to. The endpoint is public (no auth) because clank has no token yet at the point it calls it.
All fields are standard OAuth 2.0 — Supabase OAuth Server, Auth0, Okta, Keycloak, etc. all populate the same shape.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client wraps an *http.Client targeting the gateway base URL. It covers the small bootstrap surface the laptop needs *before* it has a session — currently just /auth-config. Authenticated calls go through dedicated clients elsewhere wrapping the same gateway URL.
func New ¶
New constructs a Client targeting the gateway base URL (e.g. "https://gateway.example.com"). httpClient may be nil for the default.
func (*Client) FetchAuthConfig ¶
func (c *Client) FetchAuthConfig(ctx context.Context) (*AuthConfig, error)
FetchAuthConfig calls GET <gateway>/auth-config to discover the IdP details. Public endpoint — no Authorization header.
func (*Client) GatewayURL ¶
GatewayURL returns the configured gateway base URL. Useful for callers that need to construct sub-paths (e.g. sync clients).
type OAuthClient ¶
type OAuthClient struct {
// AuthorizeEndpoint is the IdP's authorize URL, e.g.
// "https://abc.supabase.co/oauth/authorize" or
// "https://auth.example.com/oauth/authorize".
AuthorizeEndpoint string
// TokenEndpoint is the IdP's token URL, e.g.
// "https://abc.supabase.co/oauth/token".
TokenEndpoint string
// ClientID is the public OAuth client identifier registered with
// the IdP. PKCE replaces the client secret, so no secret here.
ClientID string
// Scopes are the OAuth scopes requested at /authorize. Joined
// with spaces per RFC 6749 §3.3. May be empty.
Scopes []string
// Provider is an optional IdP hint passed as the non-standard
// `provider` query parameter on /authorize. Used by Supabase Auth
// (and similar) to route the user straight to GitHub / Google /
// etc. Ignored by IdPs that don't recognise it.
Provider string
// OpenBrowser is the function called to launch the user's
// browser. Optional; defaults to the platform's standard
// open command. Tests inject a no-op.
OpenBrowser func(target string) error
// CallbackHosts is the set of bind hosts the localhost listener
// will try in order. Default ["127.0.0.1", "localhost"]; most
// IdPs allow either as a wildcard in their redirect-URI config.
CallbackHosts []string
// CallbackPort, when non-zero, pins the localhost listener to
// exactly this port. Default 0 = kernel-assigned random port,
// which is what RFC 8252 §7.3 recommends for native apps.
//
// Set this when the IdP rejects redirect_uris with arbitrary
// loopback ports (Supabase OAuth Server, for example, requires
// an exact match against the registered redirect_uris). The
// gateway communicates the required port via AuthConfig.
// If the port is already in use, Login returns an error.
CallbackPort int
// HTTPClient is used for the token exchange. Optional;
// defaults to a 30s-timeout client.
HTTPClient *http.Client
// Prompt receives the manual-paste URL when OpenBrowser fails so
// the user can still complete the flow by opening the link in any
// browser. nil → os.Stderr (visible by default for CLI callers).
// Set to io.Discard from TUI contexts where stderr writes would
// corrupt the display.
Prompt io.Writer
}
OAuthClient runs the PKCE dance against a single IdP. Construct once per login attempt; not reused after Login returns.
func (*OAuthClient) Login ¶
func (c *OAuthClient) Login(ctx context.Context) (*Session, error)
Login runs the full PKCE flow:
- Generate code_verifier + code_challenge (S256).
- Bind a localhost listener on a random free port.
- Open the user's browser to AuthorizeEndpoint with PKCE params and a redirect_uri pointing at the localhost listener.
- Wait for the IdP's redirect (?code=... or ?error=...).
- Exchange the code at TokenEndpoint (form-encoded).
- Return the resulting Session (with sub/email decoded from the JWT payload when the access token is a JWT).
The context's deadline bounds the wait — caller is expected to pass ctx with a reasonable timeout (e.g. 5 minutes). The browser always opens; if OpenBrowser fails, the URL is returned in the error so the user can paste it manually.