Documentation
¶
Overview ¶
Package token provides secure storage and retrieval of Linear API access tokens. It handles token persistence across sessions with proper encryption and file permissions to protect sensitive authentication credentials.
The package is designed to work seamlessly with the OAuth flow, storing tokens after successful authentication and providing them for API requests.
Token Storage ¶
Tokens are stored in a platform-specific secure location (XDG standard):
~/.config/linear/token
The token file has restricted permissions (0600) to prevent unauthorized access. Only the owner can read or write the token file.
Security Features ¶
- File permissions restricted to owner only (0600)
- Token validation before storage
- Automatic directory creation with proper permissions
- Safe file operations with atomic writes
- Clear error messages for permission issues
Usage ¶
Save a token after OAuth authentication:
storage := token.NewFileStorage()
err := storage.SaveToken("your-access-token")
if err != nil {
log.Fatal(err)
}
Retrieve a stored token:
token, err := storage.GetToken()
if err != nil {
if err == token.ErrTokenNotFound {
// Handle missing token - user needs to authenticate
}
log.Fatal(err)
}
Clear stored token on logout:
err := storage.ClearToken()
if err != nil {
log.Fatal(err)
}
Error Handling ¶
The package defines specific errors for common scenarios:
- ErrTokenNotFound: No token file exists (user not authenticated)
- ErrTokenEmpty: Token file exists but is empty
- Permission errors: Token file has incorrect permissions
- I/O errors: File system issues during read/write operations
Token Lifecycle ¶
1. Token Creation: OAuth flow generates access token 2. Token Storage: SaveToken() stores with secure permissions 3. Token Usage: GetToken() retrieves for API requests 4. Token Refresh: OAuth refresh flow updates stored token 5. Token Removal: ClearToken() removes on logout
Best Practices ¶
- Always check for ErrTokenNotFound to detect unauthenticated state
- Handle token refresh proactively before expiration
- Clear tokens on logout to maintain security
- Never log or display token values
- Validate tokens before storage to prevent corruption
Index ¶
- func FormatAuthHeader(token string) string
- func GetDefaultTokenPath() string
- func IsExpired(data *TokenData) bool
- func LoadTokenFromEnv() string
- func LoadTokenWithFallback() string
- func NeedsRefresh(data *TokenData, buffer time.Duration) bool
- func SanitizeToken(token string) string
- func ValidateToken(token string) error
- type NoRefreshTokenError
- type OAuthRefresher
- type RefreshFailedError
- type Refresher
- type RefreshingProvider
- type SessionExpiredError
- type StaticProvider
- type Storage
- func (s *Storage) DeleteToken() error
- func (s *Storage) LoadToken() (string, error)
- func (s *Storage) LoadTokenData() (*TokenData, error)
- func (s *Storage) SaveToken(token string) error
- func (s *Storage) SaveTokenData(data *TokenData) error
- func (s *Storage) TokenExists() booldeprecated
- func (s *Storage) TokenExistsWithError() (bool, error)
- type TokenData
- type TokenProvider
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FormatAuthHeader ¶ added in v1.2.0
FormatAuthHeader formats a token for use in an Authorization header. Linear personal API keys (lin_api_*) must be sent without a Bearer prefix. OAuth access tokens are sent with "Bearer ".
func GetDefaultTokenPath ¶
func GetDefaultTokenPath() string
GetDefaultTokenPath returns the default path for storing tokens.
Token location strategy: - Primary: ~/.config/linear/token (XDG standard) - Fallback: .config/linear/token (current directory)
Why home directory: Tokens are user-specific credentials. Storing them in the home directory ensures they're not accidentally committed to version control and are isolated per user on multi-user systems.
func IsExpired ¶ added in v1.1.0
IsExpired checks if the token has expired. Returns false for tokens without expiration (legacy tokens or long-lived tokens).
func LoadTokenFromEnv ¶ added in v1.6.0
func LoadTokenFromEnv() string
LoadTokenFromEnv loads a Linear token from environment variables. Uses LINEAR_API_KEY as the canonical env var for direct API-key auth.
func LoadTokenWithFallback ¶
func LoadTokenWithFallback() string
LoadTokenWithFallback loads token from default location with env var fallback
func NeedsRefresh ¶ added in v1.1.0
NeedsRefresh checks if the token should be proactively refreshed. Returns true if the token will expire within the buffer duration. Returns false for tokens without expiration.
func SanitizeToken ¶ added in v1.2.0
SanitizeToken removes invalid HTTP header characters from a token. Removes all whitespace characters: spaces, tabs, newlines, carriage returns. This prevents "invalid header field value" errors when setting Authorization headers.
func ValidateToken ¶ added in v1.2.0
ValidateToken checks if a token contains invalid characters. Returns an error if the token contains characters that would cause HTTP header issues.
Types ¶
type NoRefreshTokenError ¶ added in v1.1.0
type NoRefreshTokenError struct{}
Error types for specific refresh failure scenarios
func (*NoRefreshTokenError) Error ¶ added in v1.1.0
func (e *NoRefreshTokenError) Error() string
type OAuthRefresher ¶ added in v1.1.0
OAuthRefresher defines the interface for refreshing OAuth tokens. This allows the refresher to be independent of the oauth package implementation, avoiding circular dependencies.
type RefreshFailedError ¶ added in v1.1.0
type RefreshFailedError struct {
Reason error
}
func (*RefreshFailedError) Error ¶ added in v1.1.0
func (e *RefreshFailedError) Error() string
type Refresher ¶ added in v1.1.0
type Refresher struct {
// contains filtered or unexported fields
}
Refresher handles thread-safe automatic token refresh. It implements double-checked locking to prevent thundering herd on concurrent 401s.
func NewRefresher ¶ added in v1.1.0
func NewRefresher(storage *Storage, oauthHandler OAuthRefresher) *Refresher
NewRefresher creates a new token refresher with default settings.
func (*Refresher) GetValidToken ¶ added in v1.1.0
GetValidToken returns a valid access token, proactively refreshing if needed. This should be called before making API requests to ensure the token is fresh.
func (*Refresher) RefreshIfNeeded ¶ added in v1.1.0
RefreshIfNeeded handles reactive refresh when a 401 error occurs. Uses double-checked locking to prevent multiple concurrent refresh attempts.
Parameters:
- originalToken: The token that was used in the failed request
Returns:
- New access token if refresh succeeded
- Error if refresh failed or no refresh token available
type RefreshingProvider ¶ added in v1.1.0
type RefreshingProvider struct {
// contains filtered or unexported fields
}
RefreshingProvider provides tokens with automatic refresh capability. Used for new OAuth apps (created after Oct 1, 2025) with 24-hour token expiration.
func NewRefreshingProvider ¶ added in v1.1.0
func NewRefreshingProvider(refresher *Refresher) *RefreshingProvider
NewRefreshingProvider creates a provider that automatically refreshes tokens
func (*RefreshingProvider) GetToken ¶ added in v1.1.0
func (p *RefreshingProvider) GetToken() (string, error)
GetToken returns a valid token, proactively refreshing if expiring soon
func (*RefreshingProvider) RefreshIfNeeded ¶ added in v1.1.0
func (p *RefreshingProvider) RefreshIfNeeded(failedToken string) (string, error)
RefreshIfNeeded reactively refreshes the token when a 401 occurs
type SessionExpiredError ¶ added in v1.1.0
type SessionExpiredError struct{}
func (*SessionExpiredError) Error ¶ added in v1.1.0
func (e *SessionExpiredError) Error() string
type StaticProvider ¶ added in v1.1.0
type StaticProvider struct {
// contains filtered or unexported fields
}
StaticProvider provides a static token without refresh capability. Used for: - Legacy OAuth apps with long-lived tokens (10 year expiration) - Environment variable tokens - API tokens without OAuth refresh
func NewStaticProvider ¶ added in v1.1.0
func NewStaticProvider(token string) *StaticProvider
NewStaticProvider creates a provider for non-refreshable tokens
func (*StaticProvider) GetToken ¶ added in v1.1.0
func (p *StaticProvider) GetToken() (string, error)
GetToken returns the static token (sanitized for safety)
func (*StaticProvider) RefreshIfNeeded ¶ added in v1.1.0
func (p *StaticProvider) RefreshIfNeeded(failedToken string) (string, error)
RefreshIfNeeded always fails for static tokens
type Storage ¶
type Storage struct {
// contains filtered or unexported fields
}
Storage handles token storage and retrieval with secure file permissions. Tokens are sensitive credentials that must be protected from unauthorized access.
func NewStorage ¶
NewStorage creates a new token storage instance
func (*Storage) DeleteToken ¶
DeleteToken removes the token file
func (*Storage) LoadTokenData ¶ added in v1.1.0
LoadTokenData loads structured token data with backward compatibility.
Behavior: - If file contains JSON: parse and return TokenData - If file contains plain string: treat as legacy access token (no refresh) - Returns error only on file read failure or invalid JSON
This enables seamless migration from legacy token format to new format.
func (*Storage) SaveToken ¶
SaveToken saves a token to the file system with secure permissions.
Security measures: - Directory created with 0700 (owner only access) - Token file created with 0600 (owner read/write only) - Atomic write operation to prevent partial token storage
Why these permissions: OAuth tokens are bearer tokens - anyone with the token can act as the authenticated user. Restricting file permissions prevents other users on the system from reading the token.
func (*Storage) SaveTokenData ¶ added in v1.1.0
SaveTokenData saves structured token data as JSON with secure permissions. This replaces the legacy plain-string token format and enables automatic refresh.
func (*Storage) TokenExists
deprecated
TokenExists checks if a token file exists. Returns false for file not found and logs errors for other issues like permission denied.
Why log but not fail: This maintains backward compatibility while still alerting developers to potential issues. Permission errors might indicate security problems that should be investigated.
Deprecated: Use TokenExistsWithError for better error handling.
func (*Storage) TokenExistsWithError ¶
TokenExistsWithError checks if a token file exists and returns detailed error information. Returns (false, nil) if file doesn't exist - this is not an error condition. Returns (false, error) if there's an actual error like permission denied.
Why distinguish between "not found" and other errors: - File not found is expected when user hasn't authenticated yet - Permission errors indicate a security or configuration problem - This allows callers to handle these cases differently
type TokenData ¶ added in v1.1.0
type TokenData struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"`
TokenType string `json:"token_type"`
ExpiresAt time.Time `json:"expires_at,omitempty"`
Scope string `json:"scope"`
AuthMode string `json:"auth_mode,omitempty"` // "user" or "agent" - determines how "me" resolves
}
TokenData represents structured OAuth token information with expiration tracking. Supports both refresh-capable tokens (new OAuth apps created after Oct 1, 2025) and legacy tokens without expiration.
type TokenProvider ¶ added in v1.1.0
type TokenProvider interface {
// GetToken returns a valid access token, refreshing if needed (proactive refresh)
GetToken() (string, error)
// RefreshIfNeeded attempts to refresh the token when a 401 error occurs (reactive refresh)
// Takes the token that failed as a parameter to enable double-checked locking
RefreshIfNeeded(failedToken string) (string, error)
}
TokenProvider provides access to authentication tokens with optional refresh capability. This interface abstracts token management for the Linear API client.