github

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 19, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Overview

Package github provides GitHub authentication for scafctl. This file implements the personal access token (PAT) flow.

Index

Constants

View Source
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.

View Source
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
)
View Source
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).

View Source
const DefaultClientID = "Ov23li6xn492GhPmt4YG"

DefaultClientID is the OAuth App client ID shipped with scafctl.

View Source
const DefaultHostname = "github.com"

DefaultHostname is the default GitHub hostname.

View Source
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

View Source
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

func (c *Config) GetAPIBaseURL() string

GetAPIBaseURL returns the base URL for GitHub API endpoints.

func (*Config) GetAppID

func (c *Config) GetAppID() int64

GetAppID returns the App ID from config or the GITHUB_APP_ID environment variable.

func (*Config) GetInstallationID

func (c *Config) GetInstallationID() int64

GetInstallationID returns the Installation ID from config or the GITHUB_APP_INSTALLATION_ID environment variable.

func (*Config) GetOAuthBaseURL

func (c *Config) GetOAuthBaseURL() string

GetOAuthBaseURL returns the base URL for GitHub OAuth endpoints.

func (*Config) GetPrivateKey

func (c *Config) GetPrivateKey(ctx context.Context, store secrets.Store) ([]byte, error)

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.

func (*Config) Validate

func (c *Config) Validate() error

Validate checks the configuration for required fields.

func (*Config) ValidateAppConfig

func (c *Config) ValidateAppConfig(ctx context.Context, store secrets.Store) error

ValidateAppConfig checks that all required GitHub App fields are present.

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.

func (*DefaultHTTPClient) PostForm

func (c *DefaultHTTPClient) PostForm(ctx context.Context, reqURL string, data url.Values) (*http.Response, error)

PostForm sends a POST request with form-encoded body.

func (*DefaultHTTPClient) PostJSON

func (c *DefaultHTTPClient) PostJSON(ctx context.Context, reqURL string, body any, headers map[string]string) (*http.Response, error)

PostJSON sends a POST request with a JSON body and 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

func New(opts ...Option) (*Handler, error)

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

func (h *Handler) DisplayName() string

DisplayName returns the human-readable name.

func (*Handler) GetToken

func (h *Handler) GetToken(ctx context.Context, opts auth.TokenOptions) (*auth.Token, error)

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

func (h *Handler) InjectAuth(ctx context.Context, req *http.Request, opts auth.TokenOptions) error

InjectAuth adds authentication to an HTTP request.

func (*Handler) ListCachedTokens

func (h *Handler) ListCachedTokens(ctx context.Context) ([]*auth.CachedTokenInfo, error)

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

func (h *Handler) Login(ctx context.Context, opts auth.LoginOptions) (*auth.Result, error)

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) Logout

func (h *Handler) Logout(ctx context.Context) error

Logout clears stored credentials and cached tokens.

func (*Handler) Name

func (h *Handler) Name() string

Name returns the handler identifier.

func (*Handler) PurgeExpiredTokens

func (h *Handler) PurgeExpiredTokens(ctx context.Context) (int, error)

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) Status

func (h *Handler) Status(ctx context.Context) (*auth.Status, error)

Status returns the current authentication status.

func (*Handler) SupportedFlows

func (h *Handler) SupportedFlows() []auth.Flow

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

type MockRequest struct {
	Method   string
	Endpoint string
	Data     url.Values
	Headers  map[string]string
}

MockRequest records a request made via the mock client.

type MockResponse

type MockResponse struct {
	StatusCode int
	Body       any // Will be JSON-encoded
	Err        error
}

MockResponse defines a canned response.

type Option

type Option func(*Handler)

Option configures the Handler.

func WithConfig

func WithConfig(cfg *Config) Option

WithConfig sets the GitHub configuration.

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

func WithLogger(lgr logr.Logger) Option

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

func WithSecretStore(store secrets.Store) Option

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.

type User

type User struct {
	Login     string `json:"login"`
	ID        int64  `json:"id"`
	Name      string `json:"name"`
	Email     string `json:"email"`
	AvatarURL string `json:"avatar_url"`
}

User represents the relevant fields from the GitHub /user API response.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL