Vibe-guided bskyoauth and custom repo example code in Golang 馃 probably not safe to use in prod

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 TestDPoPTransportReplayErrorRetry to 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 ApplicationType field in ClientOptions with ApplicationTypeWeb and ApplicationTypeNative constants
    • 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://localhost redirect 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
  • 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-aware http.NewRequestWithContext()
    • All http.NewRequest() calls updated to use context
    • Added SetHTTPClient() and GetHTTPClient() for custom HTTP client configuration
    • Added HTTPClient field to ClientOptions for 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 Session struct with AccessTokenExpiresAt and RefreshTokenExpiresAt fields
    • 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_token grant type
    • Zero breaking changes - all additions backwards compatible
  • 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.Discard unless 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 DID field to internalOAuthState for reliable session creation
  • Added JWKSURI field to AuthServerMetadata (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 SecurityHeadersOptions struct for customizing security headers
    • Added SecurityHeadersMiddlewareWithOptions() for custom configurations
    • CSP now includes Bluesky domains by default:
      • form-action includes 'self' https://*.bsky.social https://bsky.social
      • connect-src includes '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.social supports 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

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 ErrIssuerMismatch error for detecting attack attempts

  • SECURITY: Added security event logging for issuer mismatch detection

  • Added ExpectedIssuer field to OAuth state for validation during callback

  • SECURITY: Added IP-based rate limiting using golang.org/x/time/rate

  • Added RateLimiter type 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 sessionEntry wrapper with expiration timestamps

  • SECURITY: OAuth state entries now automatically expire after 10 minutes to prevent memory leaks

  • Internal oauthStateStore structure 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: LoginHandler now validates handle format before starting OAuth flow

  • SECURITY: CreatePost now validates text length and content before API call

  • SECURITY: CreateRecord now validates collection NSID and record fields before API call

  • SECURITY: Web-demo postHandler now validates post text client-side

  • SECURITY: Web-demo createOngakuHandler now 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 sessionEntry struct with expiresAt timestamp
  • 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 intervals
  • Stop() method gracefully shuts down cleanup goroutine
  • Thread-safe with proper RWMutex usage for concurrent access
  • OAuth state entries are now wrapped in stateEntry struct with expiresAt timestamp
  • 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 CompleteAuthFlow before token exchange
  • Expected issuer stored during StartAuthFlow and 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.social
    • form-action 'self' https://*.bsky.social https://bsky.social
  • Security headers: Wildcard *.bsky.social allows 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)