Changelog#
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]#
Fixed#
- Web-demo Token Expiration Handling: Fixed 401 "invalid_token" errors after token expiration
- Enhanced all API handlers (CreatePost, CreateRecord, DeleteRecord) to automatically refresh tokens on 401 errors
- Automatically detects expired token errors and refreshes without user intervention
- Retries the original operation with fresh token after successful refresh
- Provides clear error messages when refresh fails, prompting re-authentication
- Prevents "exp claim timestamp check failed" errors after periods of inactivity
- Improves user experience by seamlessly handling token expiration in the background
- DPoP Replay Error Handling: Fixed automatic retry on "DPoP proof replayed" errors
- Enhanced error detection to recognize replay-related errors ("replayed", "invalid_dpop_proof")
- Automatically requests and uses fresh nonce when server returns replay error
- Prevents unnecessary failures when server detects replayed DPoP proofs
- Added test case
TestDPoPTransportReplayErrorRetryto verify replay error handling - Improves reliability of API calls in high-traffic scenarios
Added#
- Application Type Configuration: Added support for configuring OAuth
application_type- New
ApplicationTypefield inClientOptionswithApplicationTypeWebandApplicationTypeNativeconstants - Defaults to
"web"for backward compatibility - Web applications must use HTTPS redirect URIs (except localhost for development)
- Native applications may use custom URI schemes or
http://localhostredirect URIs - Validates application type and logs warning if invalid value provided
- Complies with OpenID Connect Dynamic Client Registration and AT Protocol OAuth specifications
- Added comprehensive test coverage with 6 new test cases
- Full documentation in README with examples and redirect URI constraints
- New
- Documentation: Added reactive token refresh pattern to README
- Shows how to handle 401 "invalid_token" errors in API calls
- Demonstrates automatic refresh and retry pattern
- Explains when to use proactive vs reactive refresh strategies
- Helps library users implement robust token expiration handling
- Pre-commit Hooks: Added git pre-commit hooks for code quality and security checks
- scripts/pre-commit: Runs gofmt, golangci-lint, govulncheck, tests with race detection, and dependency verification
- scripts/install-hooks.sh: Easy installation script
- .golangci.yml: golangci-lint configuration with 20+ linters enabled
- CONTRIBUTING.md: Contributor guidelines with development setup instructions
- Automatic code formatting check (gofmt)
- Comprehensive code quality checks (golangci-lint with errcheck, gosimple, govet, staticcheck, gosec, and more)
- All checks must pass before commit (can bypass with --no-verify)
- Automatic vulnerability and quality detection before code is committed
- Dependency Security Scanning: Added automated dependency management and security scanning (Issue #14)
- Dependabot configuration for weekly Go module updates
- Groups minor and patch updates to reduce PR noise
- Maximum 5 open PRs to keep review manageable
- Auto-labels with "dependencies" and "go"
- GitHub Actions security workflow with govulncheck
- Runs on push, pull requests, and weekly schedule
- Test suite with race detection and coverage reporting
- Proactive vulnerability detection and automated updates
- Context Timeout Handling: Added explicit timeout configurations for all HTTP operations (Issue #13)
- Default HTTP client with 30 second total timeout
- Connection timeout: 10 seconds (TCP handshake)
- TLS handshake timeout: 10 seconds
- Response header timeout: 10 seconds
- Idle connection reuse: 90 seconds
- Connection pooling: Max 100 idle connections, 10 per host
- All
http.Get()calls replaced with context-awarehttp.NewRequestWithContext() - All
http.NewRequest()calls updated to use context - Added
SetHTTPClient()andGetHTTPClient()for custom HTTP client configuration - Added
HTTPClientfield toClientOptionsfor per-client timeout configuration - Added
IsTimeoutError()helper function to detect timeout errors (context.DeadlineExceeded, net.Error timeouts, os.ErrDeadlineExceeded) - Timeout-specific error logging throughout OAuth flow
- 10 comprehensive test cases for timeout behavior and error detection (timeout_test.go, errors_test.go)
- Full documentation in README with examples for custom timeouts, context timeouts, and testing
- Zero breaking changes - all additions backwards compatible with sensible defaults
- Token Refresh Support: Implemented refresh token functionality (Issue #12)
- Added
RefreshToken()method to exchange refresh tokens for new access tokens - Added token expiration tracking to
Sessionstruct withAccessTokenExpiresAtandRefreshTokenExpiresAtfields - Token expiration is automatically parsed from OAuth token responses
- Added helper methods:
IsAccessTokenExpired(),IsRefreshTokenExpired(),TimeUntilAccessTokenExpiry() - Added
UpdateSession()method to update sessions after refresh - DPoP binding maintained across token refresh per AT Protocol spec
- Single-use refresh tokens per AT Protocol spec (old refresh token invalidated after use)
- Comprehensive logging for refresh operations (success, failures, expiration)
- Added
refreshTokenRequest()helper with DPoP nonce retry support - 6 new test cases for token expiration and refresh functionality
- Full documentation in README with examples and error handling
- Client metadata already includes
refresh_tokengrant type - Zero breaking changes - all additions backwards compatible
- Added
- Structured Logging: Added comprehensive structured logging using Go's standard
log/slog- Environment-based configuration (Info for localhost, Error for production)
- Silent by default (logs to
io.Discardunless configured) - Automatic format selection: Text for localhost, JSON for production
- Context-aware logging with request ID and session ID correlation
- Security event logging at ERROR level (issuer mismatches, invalid states)
- Logging throughout OAuth flow, session management, API operations, and rate limiting
- New functions:
SetLogger(),NewLoggerFromEnv(),LogLevelFromEnv(),NewDefaultLogger(),NewTextLogger() - Context helpers:
WithRequestID(),WithSessionID(),LoggerFromContext(),GenerateRequestID() - 11 comprehensive test cases for logger functionality
- Added
DIDfield tointernalOAuthStatefor reliable session creation - Added
JWKSURIfield toAuthServerMetadata(not currently used, reserved for future) - JWT validation functionality is available in internal/jwt package
- OAuth state store now includes automatic expiration and cleanup mechanism
- SECURITY: Added comprehensive input validation module (validation.go)
- Handle format validation using AT Protocol syntax package
- Post text length validation (300 character limit per AT Protocol spec)
- Generic text field validation with configurable limits
- Record field validation for custom records
- Collection NSID validation
- 36 validation test cases covering handles, text, records, and NSIDs
- Added
ValidateHandle()function to validate Bluesky handles per AT Protocol spec - Added
ValidatePostText()function to validate post text (max 300 chars, UTF-8, no null bytes) - Added
ValidateTextField()function for custom text field validation with configurable limits - Added
ValidateRecordFields()function to validate record structures and common fields - Added
ValidateCollectionNSID()function to validate collection names - Added comprehensive validation error types (ErrHandleInvalid, ErrTextTooLong, etc.)
- SECURITY: Enhanced security headers middleware with Bluesky API integration and customization support
- Added
SecurityHeadersOptionsstruct for customizing security headers - Added
SecurityHeadersMiddlewareWithOptions()for custom configurations - CSP now includes Bluesky domains by default:
form-actionincludes'self' https://*.bsky.social https://bsky.socialconnect-srcincludes'self' https://*.bsky.social https://bsky.social
- Enables HTML forms to POST directly to Bluesky API endpoints
- Enables client-side JavaScript API calls to Bluesky servers
- Wildcard
*.bsky.socialsupports user-specific PDS domains - Added support for custom CSP directives via
AdditionalCSPDirectives - Added support for custom HTTP headers via
CustomHeaders - Added flags to disable specific headers:
DisableXFrameOptions,DisableHSTS - Added 10 new comprehensive test cases (total 23 tests for security headers)
- Automatic localhost detection for relaxed CSP in development
- Strict CSP for production (no unsafe-inline or unsafe-eval)
- HSTS automatically enabled for HTTPS in production (not localhost)
- Works with reverse proxies (checks X-Forwarded-Proto header)
- Zero configuration required - detects environment from HTTP request
- Added
Changed#
-
IMPORTANT: Removed automatic JWT signature validation to comply with AT Protocol OAuth spec
- Per AT Protocol spec, access tokens are "opaque from the client's perspective"
- Token validation is the responsibility of the Resource Server (PDS), not the client
- DPoP proof-of-possession provides token binding security
- Tokens are validated by the PDS when used, not during OAuth flow
-
Access tokens now treated as opaque strings per spec
-
JWT parsing only used to extract DID for session management (fallback to OAuth state DID if unavailable)
-
Improved resilience: if token parsing fails, DID from OAuth state is used instead
-
SECURITY: Added comprehensive HTTPS enforcement documentation in README
-
SECURITY: Added production deployment security checklist
-
SECURITY: Added reverse proxy configuration examples (nginx, Caddy)
-
Added HTTPS validation warnings in web-demo example application
-
Added security best practices section covering cookies, session storage, rate limiting
-
SECURITY: Added issuer validation to prevent authorization code injection attacks
-
SECURITY: Added
ErrIssuerMismatcherror for detecting attack attempts -
SECURITY: Added security event logging for issuer mismatch detection
-
Added
ExpectedIssuerfield to OAuth state for validation during callback -
SECURITY: Added IP-based rate limiting using golang.org/x/time/rate
-
Added
RateLimitertype with configurable rate and burst limits -
Added rate limiting middleware for HTTP endpoints
-
Web-demo example now includes rate limiting on auth and API endpoints
-
Added comprehensive test suite for rate limiter (ratelimit_test.go)
-
Added 10 test cases covering rate limiting behavior, IP extraction, cleanup, and concurrency
-
Added comprehensive test suite for session management (session_test.go)
-
Added 15 test cases covering session store operations, ID generation, concurrency, and stress testing
-
Added comprehensive test suite for Client functionality (client_test.go)
-
Added 16 test cases covering client initialization, metadata, session management, and edge cases
-
Added comprehensive test suite for DPoP functionality (dpop_test.go)
-
Added 22 test cases covering key generation, proof creation, JWT structure, transport, and nonce handling
-
SECURITY: Added session expiration and automatic cleanup to MemorySessionStore
-
Added
NewMemorySessionStoreWithTTL()for custom session lifetimes -
Added
Stop()method for graceful cleanup goroutine shutdown -
Added 7 test cases for session expiration, cleanup, and TTL configuration
-
SECURITY: Sessions now automatically expire after 30 days (matches cookie MaxAge, configurable)
-
SECURITY: MemorySessionStore enhanced with TTL tracking and automatic cleanup
-
Session cleanup goroutine runs every 5 minutes to remove expired sessions
-
Get()validates expiration before returning sessions (defense-in-depth) -
Internal session storage uses
sessionEntrywrapper with expiration timestamps -
SECURITY: OAuth state entries now automatically expire after 10 minutes to prevent memory leaks
-
Internal
oauthStateStorestructure enhanced with TTL tracking and expiration timestamps -
State store
get()method now validates expiration before returning entries -
DOCUMENTATION: Expanded Security section in README with prominent HTTPS warnings
-
DOCUMENTATION: Added detailed production deployment guidelines
-
DOCUMENTATION: Added dedicated JWT Token Validation section in README
-
DOCUMENTATION: Added comprehensive Input Validation section to README with usage examples
-
Example application now validates BASE_URL and warns when HTTPS is not used
-
SECURITY: Session cookies now include Secure flag when using HTTPS
-
SECURITY: Session cookies now have 30-day MaxAge expiration
-
Logout handler now properly clears cookies with matching security attributes
-
SECURITY:
LoginHandlernow validates handle format before starting OAuth flow -
SECURITY:
CreatePostnow validates text length and content before API call -
SECURITY:
CreateRecordnow validates collection NSID and record fields before API call -
SECURITY: Web-demo
postHandlernow validates post text client-side -
SECURITY: Web-demo
createOngakuHandlernow validates text field with 1000 char limit
Fixed#
- SECURITY: Fixed memory leak where abandoned OAuth authorization flows would persist indefinitely
- SECURITY: Fixed potential DoS vector from accumulating expired state entries
- Fixed DPoP private keys remaining in memory after failed/abandoned auth flows
- SECURITY: Fixed missing issuer validation allowing potential authorization code injection
- CRITICAL: Fixed DPoP proof replay detection by improving JTI uniqueness
- SECURITY: Fixed error information disclosure - sanitized error messages to prevent leaking internal details
- CRITICAL: Fixed DPoP nonce not persisting across requests causing replay errors
Technical Details#
- Access Token Handling: Per AT Protocol spec, access tokens treated as opaque strings
- Tokens validated server-side by PDS when used, not during OAuth flow
- DPoP proof-of-possession provides token binding security
- JWT parsing used only to extract DID for session management (with fallback to OAuth state)
- Session expiration: Default 30-day TTL (2,592,000 seconds) matches cookie MaxAge
- Sessions wrapped in
sessionEntrystruct withexpiresAttimestamp - Session cleanup goroutine runs every 5 minutes (configurable)
- Session expiration validated on
Get()and during cleanup (defense-in-depth) - Expired sessions treated as
ErrSessionNotFound NewMemorySessionStoreWithTTL()allows custom TTL and cleanup intervalsStop()method gracefully shuts down cleanup goroutine- Thread-safe with proper RWMutex usage for concurrent access
- OAuth state entries are now wrapped in
stateEntrystruct withexpiresAttimestamp - OAuth cleanup goroutine runs every minute to purge expired entries
- State validation checks expiration on retrieval, providing defense-in-depth
- Thread-safe operations maintained with proper mutex usage
- Issuer validation performed in
CompleteAuthFlowbefore token exchange - Expected issuer stored during
StartAuthFlowand validated during callback - Security events logged to stderr for monitoring and alerting
- DPoP JTI now generated with
generateUniqueJTI()using 24 bytes of cryptographic random data - Each DPoP proof guaranteed unique with 192 bits of entropy
- DPoP nonce now persisted in Session struct and reused across requests
NewDPoPTransport()accepts nonce parameter to initialize with existing nonce- All client methods (CreatePost, CreateRecord, DeleteRecord) update session nonce after requests
- Prevents "DPoP proof replayed" errors on subsequent API calls
- Error messages sanitized: detailed errors logged to stderr, generic messages returned to users
- Two error locations sanitized: auth metadata requests and token exchange failures
- Rate limiting implemented using token bucket algorithm from golang.org/x/time/rate
- Separate rate limiters for auth endpoints (5 req/s) and API endpoints (10 req/s)
- IP-based rate limiting with X-Forwarded-For support for proxied requests
- Automatic cleanup of idle rate limiters to prevent memory leaks
- Cookie security: Secure flag automatically enabled for HTTPS deployments
- Cookie expiration: 30-day MaxAge prevents indefinite session lifetime
- Cookie attributes preserved during logout for proper cookie deletion
- Input validation: Handles validated per AT Protocol spec (max 253 chars, proper format)
- Input validation: Post text validated per AT Protocol spec (max 300 chars, UTF-8, no null bytes)
- Input validation: Custom records validated with deep nesting prevention (max 10 levels)
- Input validation: Collection NSIDs validated using AT Protocol syntax package
- Input validation: All validation errors return descriptive messages for debugging
- Validation functions exported for use by library consumers
- Test count increased from 127 to 193 tests (66 new validation tests added)
- Security headers: CSP now includes Bluesky domains in both connect-src and form-action
connect-src 'self' https://*.bsky.social https://bsky.socialform-action 'self' https://*.bsky.social https://bsky.social
- Security headers: Wildcard
*.bsky.socialallows any user PDS (e.g., alice.bsky.social) - Security headers: Request-based localhost detection (checks r.Host for localhost, 127.0.0.1, [::1], 0.0.0.0)
- Security headers: HTTPS detection via r.TLS or X-Forwarded-Proto header
- Security headers: Localhost CSP includes 'unsafe-inline' and 'unsafe-eval' for development
- Security headers: Production CSP strict with no unsafe directives, includes frame-ancestors 'none'
- Security headers: HSTS only applied for HTTPS in production (never for localhost)
- Security headers: Modular CSP builder with
buildCSP()and option merging logic - Security headers: Works automatically with reverse proxies, Docker, and cloud platforms
- Security headers: Applied to web-demo example with single line of code
- Security headers: Server-side Go HTTP client NOT affected by CSP (operates outside browser context)
- Security headers: CSP changes enable browser-based form submissions and XHR/fetch requests to Bluesky
Migration Notes#
- This is a backwards-compatible change with no API modifications
- Existing code will continue to work without changes
- OAuth flows must complete within 10 minutes (standard practice for OAuth state parameters)