Documentation
¶
Overview ¶
Package github provides GitHub authentication for scafctl. This file implements the personal access token (PAT) flow.
Index ¶
- Constants
- Variables
- func GetHostnameFromEnv() string
- func GetPATFromEnv() string
- func HasPATCredentials() bool
- type AppInfo
- type Config
- func (c *Config) GetAPIBaseURL() string
- func (c *Config) GetAppID() int64
- func (c *Config) GetInstallationID() int64
- func (c *Config) GetOAuthBaseURL() string
- func (c *Config) GetPrivateKey(ctx context.Context, store secrets.Store) ([]byte, error)
- func (c *Config) Validate() error
- func (c *Config) ValidateAppConfig(ctx context.Context, store secrets.Store) error
- type DefaultHTTPClient
- func (c *DefaultHTTPClient) Get(ctx context.Context, reqURL string, headers map[string]string) (*http.Response, error)
- func (c *DefaultHTTPClient) PostForm(ctx context.Context, reqURL string, data url.Values) (*http.Response, error)
- func (c *DefaultHTTPClient) PostJSON(ctx context.Context, reqURL string, body any, headers map[string]string) (*http.Response, error)
- type DeviceCodeResponse
- type HTTPClient
- type Handler
- func (h *Handler) Capabilities() []auth.Capability
- func (h *Handler) DisplayName() string
- func (h *Handler) GetToken(ctx context.Context, opts auth.TokenOptions) (*auth.Token, error)
- func (h *Handler) InjectAuth(ctx context.Context, req *http.Request, opts auth.TokenOptions) error
- func (h *Handler) ListCachedTokens(ctx context.Context) ([]*auth.CachedTokenInfo, error)
- func (h *Handler) Login(ctx context.Context, opts auth.LoginOptions) (*auth.Result, error)
- func (h *Handler) Logout(ctx context.Context) error
- func (h *Handler) Name() string
- func (h *Handler) PurgeExpiredTokens(ctx context.Context) (int, error)
- func (h *Handler) Status(ctx context.Context) (*auth.Status, error)
- func (h *Handler) SupportedFlows() []auth.Flow
- type InstallationTokenResponse
- type MockHTTPClient
- func (m *MockHTTPClient) AddError(err error) *MockHTTPClient
- func (m *MockHTTPClient) AddResponse(statusCode int, body any) *MockHTTPClient
- func (m *MockHTTPClient) Get(ctx context.Context, endpoint string, headers map[string]string) (*http.Response, error)
- func (m *MockHTTPClient) GetRequests() []*MockRequest
- func (m *MockHTTPClient) PostForm(ctx context.Context, endpoint string, data url.Values) (*http.Response, error)
- func (m *MockHTTPClient) PostJSON(ctx context.Context, endpoint string, _ any, headers map[string]string) (*http.Response, error)
- func (m *MockHTTPClient) Reset()
- type MockRequest
- type MockResponse
- type Option
- type TokenErrorResponse
- type TokenMetadata
- type TokenResponse
- type User
Constants ¶
const ( // EnvGitHubAppID is the environment variable for the GitHub App ID. EnvGitHubAppID = "GITHUB_APP_ID" // EnvGitHubAppInstallationID is the environment variable for the GitHub App installation ID. EnvGitHubAppInstallationID = "GITHUB_APP_INSTALLATION_ID" // EnvGitHubAppPrivateKey is the environment variable for the inline PEM-encoded private key. EnvGitHubAppPrivateKey = "GITHUB_APP_PRIVATE_KEY" //nolint:gosec // env var name, not a credential // EnvGitHubAppPrivateKeyPath is the environment variable for the private key file path. EnvGitHubAppPrivateKeyPath = "GITHUB_APP_PRIVATE_KEY_PATH" //nolint:gosec // env var name, not a credential )
GitHub App environment variable names.
const ( // HandlerName is the unique identifier for the GitHub auth handler. HandlerName = "github" // HandlerDisplayName is the human-readable name for the handler. HandlerDisplayName = "GitHub" // SecretKeyRefreshToken is the secret key for storing the refresh token. SecretKeyRefreshToken = "scafctl.auth.github.refresh_token" //nolint:gosec // This is a key name, not a credential // SecretKeyAccessToken is the secret key for storing the access token. SecretKeyAccessToken = "scafctl.auth.github.access_token" //nolint:gosec // This is a key name, not a credential // SecretKeyMetadata is the secret key for storing token metadata. SecretKeyMetadata = "scafctl.auth.github.metadata" //nolint:gosec // This is a key name, not a credential // SecretKeyTokenPrefix is the prefix for cached access tokens. // Full key format: scafctl.auth.github.token.<base64url-encoded-scope> SecretKeyTokenPrefix = "scafctl.auth.github.token." //nolint:gosec // This is a key prefix, not a credential // DefaultTimeout is the default timeout for device code flow. DefaultTimeout = 5 * time.Minute // DefaultMinPollInterval is the minimum polling interval for device code flow. DefaultMinPollInterval = 5 * time.Second )
const ( // EnvGitHubToken is the environment variable for the GitHub token. // Used by GitHub Actions and many CI systems. EnvGitHubToken = "GITHUB_TOKEN" //nolint:gosec // This is the env var name, not a credential // EnvGHToken is the environment variable used by the GitHub CLI (gh). EnvGHToken = "GH_TOKEN" //nolint:gosec // This is the env var name, not a credential // EnvGHHost is the environment variable for the GitHub hostname (for GHES). EnvGHHost = "GH_HOST" )
PAT environment variable names (following GitHub CLI conventions).
const DefaultClientID = "Ov23li6xn492GhPmt4YG"
DefaultClientID is the OAuth App client ID shipped with scafctl.
const DefaultHostname = "github.com"
DefaultHostname is the default GitHub hostname.
const SecretKeyAppJWT = "scafctl.auth.github.app_metadata" //nolint:gosec // This is a key name, not a credential
SecretKeyAppJWT is the secret key for storing the GitHub App JWT metadata.
Variables ¶
var BrowserOpener = oauth.OpenBrowser
BrowserOpener is a function that opens a URL in the system browser. It is a package-level variable so tests can override it.
Functions ¶
func GetHostnameFromEnv ¶
func GetHostnameFromEnv() string
GetHostnameFromEnv retrieves the GitHub hostname from environment variables. Returns empty string if not configured.
func GetPATFromEnv ¶
func GetPATFromEnv() string
GetPATFromEnv retrieves a personal access token from environment variables. Returns empty string if no token is configured. GITHUB_TOKEN takes precedence over GH_TOKEN.
func HasPATCredentials ¶
func HasPATCredentials() bool
HasPATCredentials checks if PAT credentials are configured in environment.
Types ¶
type AppInfo ¶
type AppInfo struct {
ID int64 `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
Description string `json:"description"`
Owner struct {
Login string `json:"login"`
ID int64 `json:"id"`
} `json:"owner"`
}
AppInfo represents the response from the GET /app endpoint.
type Config ¶
type Config struct {
// ClientID is the GitHub OAuth App client ID.
ClientID string `json:"clientId" yaml:"clientId" doc:"GitHub OAuth App client ID" example:"Ov23li6xn492GhPmt4YG"`
// ClientSecret is the GitHub OAuth App client secret.
// Required for the interactive (authorization code) flow. When not set,
// the interactive flow falls back to device code with browser auto-open —
// the same behaviour as 'gh auth login'. Not required for device code or PAT flows.
ClientSecret string `` //nolint:gosec // G117: config field, not a hardcoded credential
/* 149-byte string literal not displayed */
// Hostname is the GitHub hostname (e.g. github.com or github.example.com for GHES).
Hostname string `json:"hostname" yaml:"hostname" doc:"GitHub hostname" example:"github.com"`
// DefaultScopes is the list of OAuth scopes to request by default.
DefaultScopes []string `json:"defaultScopes" yaml:"defaultScopes" doc:"Default OAuth scopes to request" maxItems:"20"`
// MinPollInterval is the minimum polling interval for device code flow.
MinPollInterval time.Duration `json:"-" yaml:"-"`
// SlowDownIncrement is the amount to add to polling interval on slow_down.
SlowDownIncrement time.Duration `json:"-" yaml:"-"`
// AppID is the GitHub App ID for the installation token flow.
AppID int64 `json:"appId,omitempty" yaml:"appId,omitempty" doc:"GitHub App ID for installation token flow" example:"123456"`
// InstallationID is the GitHub App installation ID.
InstallationID int64 `json:"installationId,omitempty" yaml:"installationId,omitempty" doc:"GitHub App installation ID" example:"78901234"`
// PrivateKey is the inline PEM-encoded private key for the GitHub App.
// Can also be provided via the GITHUB_APP_PRIVATE_KEY environment variable.
PrivateKey string `` //nolint:gosec // Field name, not a credential
/* 128-byte string literal not displayed */
// PrivateKeyPath is the file path to the PEM-encoded private key for the GitHub App.
// Can also be provided via the GITHUB_APP_PRIVATE_KEY_PATH environment variable.
PrivateKeyPath string `` /* 177-byte string literal not displayed */
// PrivateKeySecretName is the name of the secret in the secret store that
// contains the PEM-encoded private key for the GitHub App.
PrivateKeySecretName string `` /* 145-byte string literal not displayed */
}
Config holds configuration for the GitHub auth handler.
func DefaultConfig ¶
func DefaultConfig() *Config
DefaultConfig returns the default GitHub auth configuration.
func (*Config) GetAPIBaseURL ¶
GetAPIBaseURL returns the base URL for GitHub API endpoints.
func (*Config) GetAppID ¶
GetAppID returns the App ID from config or the GITHUB_APP_ID environment variable.
func (*Config) GetInstallationID ¶
GetInstallationID returns the Installation ID from config or the GITHUB_APP_INSTALLATION_ID environment variable.
func (*Config) GetOAuthBaseURL ¶
GetOAuthBaseURL returns the base URL for GitHub OAuth endpoints.
func (*Config) GetPrivateKey ¶
GetPrivateKey resolves the GitHub App private key from (in priority order): 1. Inline PrivateKey field / GITHUB_APP_PRIVATE_KEY env var 2. PrivateKeyPath field / GITHUB_APP_PRIVATE_KEY_PATH env var (read from file) 3. PrivateKeySecretName in the provided secret store
Returns the PEM-encoded key bytes, or an error if no source provides a key.
type DefaultHTTPClient ¶
type DefaultHTTPClient struct {
// contains filtered or unexported fields
}
DefaultHTTPClient is the standard HTTP client implementation.
func NewDefaultHTTPClient ¶
func NewDefaultHTTPClient(logger logr.Logger) *DefaultHTTPClient
NewDefaultHTTPClient creates a new DefaultHTTPClient backed by httpc. Caching is disabled because token-exchange responses must never be served from cache. The logger parameter controls HTTP-level logging; pass logr.Discard() to suppress.
func (*DefaultHTTPClient) Get ¶
func (c *DefaultHTTPClient) Get(ctx context.Context, reqURL string, headers map[string]string) (*http.Response, error)
Get sends a GET request with custom headers.
type DeviceCodeResponse ¶
type DeviceCodeResponse struct {
DeviceCode string `json:"device_code"`
UserCode string `json:"user_code"`
VerificationURI string `json:"verification_uri"`
ExpiresIn int `json:"expires_in"`
Interval int `json:"interval"`
}
DeviceCodeResponse represents the response from GitHub's device code endpoint.
type HTTPClient ¶
type HTTPClient interface {
// PostForm sends a POST request with form-encoded body and returns the response.
PostForm(ctx context.Context, url string, data url.Values) (*http.Response, error)
// PostJSON sends a POST request with JSON body and custom headers.
PostJSON(ctx context.Context, url string, body any, headers map[string]string) (*http.Response, error)
// Get sends a GET request with the given headers and returns the response.
Get(ctx context.Context, url string, headers map[string]string) (*http.Response, error)
}
HTTPClient abstracts HTTP calls for testability.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler implements auth.Handler for GitHub.
func New ¶
New creates a new GitHub auth handler. Secret store initialization is deferred — if it fails, the handler is still created so that metadata operations (Name, SupportedFlows, etc.) work. Operations requiring secrets (Login, Logout, Status, GetToken) will return the deferred error.
func (*Handler) Capabilities ¶
func (h *Handler) Capabilities() []auth.Capability
Capabilities returns the set of capabilities this handler supports. GitHub supports scopes at login time (device code / interactive flows) and hostname for GHES, but does NOT support per-request scopes (scopes are fixed at login time and cannot be changed on token refresh). CallbackPort is supported for the interactive (browser/PKCE) flow.
func (*Handler) DisplayName ¶
DisplayName returns the human-readable name.
func (*Handler) GetToken ¶
GetToken returns a valid access token for the specified options. Unlike Entra, GitHub does not support per-request scopes — the scope field in opts is ignored. Scopes are fixed at login time.
func (*Handler) InjectAuth ¶
InjectAuth adds authentication to an HTTP request.
func (*Handler) ListCachedTokens ¶
ListCachedTokens returns metadata for all tokens stored by the GitHub handler. It includes the OAuth refresh token (device code flow), any directly stored access token (PAT or non-expiring OAuth App), and all minted access tokens from the on-disk cache. Actual token values are intentionally excluded.
func (*Handler) Login ¶
Login initiates the authentication flow. Default interactive flow behaviour:
- With client_secret configured → OAuth authorization code + PKCE (browser redirect)
- Without client_secret → device code flow with browser auto-open (same as 'gh auth login')
Use --flow device-code to force the headless code-prompt (no browser). Use --flow github-app for service-to-service installation tokens.
func (*Handler) PurgeExpiredTokens ¶
PurgeExpiredTokens removes expired access tokens from the on-disk cache. The refresh token and valid access tokens are left untouched. Returns the number of tokens removed.
func (*Handler) SupportedFlows ¶
SupportedFlows returns the authentication flows this handler supports.
type InstallationTokenResponse ¶
type InstallationTokenResponse struct {
Token string `json:"token"` //nolint:gosec // Not a hardcoded credential
ExpiresAt time.Time `json:"expires_at"` //nolint:gosec // Not a hardcoded credential
Permissions map[string]string `json:"permissions"`
}
InstallationTokenResponse represents the response from POST /app/installations/{id}/access_tokens.
type MockHTTPClient ¶
type MockHTTPClient struct {
Requests []*MockRequest
Responses []*MockResponse
// contains filtered or unexported fields
}
MockHTTPClient is a configurable mock of HTTPClient for unit tests.
func NewMockHTTPClient ¶
func NewMockHTTPClient() *MockHTTPClient
NewMockHTTPClient creates a new mock HTTP client.
func (*MockHTTPClient) AddError ¶
func (m *MockHTTPClient) AddError(err error) *MockHTTPClient
AddError adds an error response to the queue.
func (*MockHTTPClient) AddResponse ¶
func (m *MockHTTPClient) AddResponse(statusCode int, body any) *MockHTTPClient
AddResponse adds a response to the queue.
func (*MockHTTPClient) Get ¶
func (m *MockHTTPClient) Get(ctx context.Context, endpoint string, headers map[string]string) (*http.Response, error)
Get implements HTTPClient.Get.
func (*MockHTTPClient) GetRequests ¶
func (m *MockHTTPClient) GetRequests() []*MockRequest
GetRequests returns all recorded requests.
func (*MockHTTPClient) PostForm ¶
func (m *MockHTTPClient) PostForm(ctx context.Context, endpoint string, data url.Values) (*http.Response, error)
PostForm implements HTTPClient.PostForm.
func (*MockHTTPClient) PostJSON ¶
func (m *MockHTTPClient) PostJSON(ctx context.Context, endpoint string, _ any, headers map[string]string) (*http.Response, error)
PostJSON implements HTTPClient.PostJSON.
func (*MockHTTPClient) Reset ¶
func (m *MockHTTPClient) Reset()
Reset clears all recorded requests and responses.
type MockRequest ¶
MockRequest records a request made via the mock client.
type MockResponse ¶
MockResponse defines a canned response.
type Option ¶
type Option func(*Handler)
Option configures the Handler.
func WithHTTPClient ¶
func WithHTTPClient(client HTTPClient) Option
WithHTTPClient sets a custom HTTP client for API requests.
func WithHTTPClientConfig ¶ added in v0.6.0
func WithHTTPClientConfig(cfg *config.HTTPClientConfig) Option
WithHTTPClientConfig configures the handler's HTTP client from application config. The config is merged: global HTTPClientConfig → auth-level HTTPClient → handler-level HTTPClient.
func WithLogger ¶
WithLogger sets the logger for the handler. The logger is offset by authHTTPLogLevel before being passed to the HTTP transport so that auth HTTP traffic only appears at high verbosity.
func WithSecretStore ¶
WithSecretStore sets a custom secrets store.
type TokenErrorResponse ¶
type TokenErrorResponse struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
ErrorURI string `json:"error_uri,omitempty"`
}
TokenErrorResponse represents an error from the GitHub OAuth token endpoint.
type TokenMetadata ¶
type TokenMetadata struct {
Claims *auth.Claims `json:"claims"`
RefreshTokenExpiresAt time.Time `json:"refreshTokenExpiresAt,omitempty"`
LastRefresh time.Time `json:"lastRefresh"`
Hostname string `json:"hostname"`
ClientID string `json:"clientId,omitempty"`
Scopes []string `json:"scopes,omitempty"`
IdentityType string `json:"identityType,omitempty"`
// SessionID is a stable identifier for the authentication session.
// Generated once at login time and preserved across refresh-token rotations.
SessionID string `json:"sessionId,omitempty"`
}
TokenMetadata stores information about the stored credentials.
type TokenResponse ¶
type TokenResponse struct {
AccessToken string `json:"access_token"` //nolint:gosec // Not a hardcoded credential
RefreshToken string `json:"refresh_token,omitempty"` //nolint:gosec // Not a hardcoded credential
TokenType string `json:"token_type"`
Scope string `json:"scope"`
ExpiresIn int `json:"expires_in,omitempty"` // Present when token expiration is enabled
RefreshTokenExpiresIn int `json:"refresh_token_expires_in,omitempty"` // Present when token expiration is enabled
}
TokenResponse represents the response from the GitHub OAuth token endpoint.