code
Clone this repository
https://tangled.org/bretton.dev/coves
git@knot.bretton.dev:bretton.dev/coves
For self-hosted knots, clone URLs may differ based on your setup.
Database schema:
- communities table with pg_trgm indexes for fuzzy search
- community_subscriptions with composite index for lookups
- community_memberships with reputation tracking
- community_moderation (V2 prepared)
Repository features:
- Atomic SubscribeWithCount/UnsubscribeWithCount transactions
- Idempotent operations for Jetstream replay safety
- Full-text search with relevance filtering (>0.2 threshold)
- Pagination and filtering support
- Proper error handling with domain error mapping
Performance optimizations:
- Composite index on (user_did, community_did) for subscription lookups
- GIN indexes for trigram similarity search
- GREATEST(0, count - 1) prevents negative counts
- Domain types: Community, Subscription, Membership
- Service layer implements atProto write-forward architecture
- Creates records on PDS (not directly in DB)
- Repository interface for AppView indexing
- Domain errors with proper categorization
- Authorization checks for community updates
Key architectural decisions:
- Communities are instance-scoped (V1)
- Records stored in instance's repo on PDS
- Community DID is metadata, not repo owner
- Service writes to PDS → Firehose → Consumer → AppView DB
- social.coves.community.create - Procedure for creating communities
- social.coves.community.profile - Community profile record schema
- social.coves.community.update - Procedure for updating communities
- Add lexicon validation test data
- Follows atProto lexicon specification for federated records
- Add CommitEvent struct for handling record commits from firehose
- Extends JetstreamEvent to support commit kind
- Required for indexing community records from Jetstream
- Part of Communities feature preparation
- Implements did:plc DID generation for community entities
- Supports dev mode (mock) and production (PLC directory registration)
- Follows atProto DID specification
- Required for Communities feature implementation
This commit implements a complete, secure OAuth 2.0 + atProto authentication system
for Coves, including comprehensive security fixes based on code review.
## 📋 Core Features
### OAuth 2.0 + atProto Authentication
- **DPoP Token Binding (RFC 9449)**: Each session has unique cryptographic key
- **PKCE (RFC 7636)**: S256 challenge method prevents code interception
- **PAR (RFC 9126)**: Pre-registration of authorization requests
- **Complete OAuth Flow**: Login → Authorize → Callback → Session Management
### Implementation Architecture
- **Handlers**: [internal/api/handlers/oauth/](internal/api/handlers/oauth/)
- `login.go` - Initiates OAuth flow with handle resolution
- `callback.go` - Processes authorization code and creates session
- `logout.go` - Session termination
- `metadata.go` - RFC 7591 client metadata endpoint
- `jwks.go` - Public key exposure (JWK Set)
- **OAuth Client**: [internal/atproto/oauth/](internal/atproto/oauth/)
- `client.go` - OAuth HTTP client with PAR, token exchange, refresh
- `dpop.go` - DPoP proof generation (ES256 signatures)
- `pkce.go` - PKCE challenge generation
- **Session Management**: [internal/core/oauth/](internal/core/oauth/)
- `session.go` - OAuth data models (OAuthRequest, OAuthSession)
- `repository.go` - PostgreSQL storage with atomic operations
- `auth_service.go` - Authentication business logic
- **Middleware**: [internal/api/middleware/auth.go](internal/api/middleware/auth.go)
- `RequireAuth` - Enforces authentication
- `OptionalAuth` - Loads user context if available
- Automatic token refresh (< 5 min to expiry)
### Database Schema
- **oauth_requests**: Temporary state during authorization flow (10-min TTL)
- **oauth_sessions**: Long-lived authenticated sessions
- **Indexes**: Performance optimizations for session queries
- **Auto-cleanup**: Trigger-based expiration handling
### DPoP Transport
- **HTTP RoundTripper**: [internal/atproto/xrpc/dpop_transport.go](internal/atproto/xrpc/dpop_transport.go)
- Automatic DPoP proof injection on all requests
- Nonce rotation handling (automatic retry on 401)
- PDS and auth server nonce tracking
## 🔐 Security Features (PR Review Hardening)
### Critical Security Fixes
✅ **CSRF/Replay Protection**: Atomic `GetAndDeleteRequest()` prevents state reuse
✅ **Cookie Secret Validation**: Enforced minimum 32 bytes for session security
✅ **Error Sanitization**: No internal error details exposed to users
✅ **HTTPS Enforcement**: Production-only HTTPS cookies with explicit localhost checks
✅ **Clean Architecture**: Business logic extracted to `AuthService` layer
### Additional Security Measures
✅ **No Token Leakage**: Never log response bodies containing credentials
✅ **Race-Free**: Fixed concurrent access to DPoP nonces with proper mutex handling
✅ **Input Validation**: Handle format checking, state parameter verification
✅ **Session Isolation**: One active session per DID (upgradeable to multiple)
✅ **Automatic Cleanup**: Hourly background job removes expired sessions/requests
### Token Binding & Proof-of-Possession
- Each session generates unique ES256 key pair
- Access tokens cryptographically bound to client
- DPoP proofs include:
- JWK header (public key)
- HTTP method and URL (prevents token replay)
- Access token hash (`ath` claim)
- JTI (unique token ID)
- Server nonce (when required)
## 🎯 Configuration & Setup
### Environment Variables
```bash
# OAuth Configuration (.env.dev)
OAUTH_PRIVATE_JWK=base64:... # Client private key (ES256)
OAUTH_COOKIE_SECRET=... # Session cookie secret (min 32 bytes)
APPVIEW_PUBLIC_URL=http://127.0.0.1:8081
```
### Base64 Encoding Support
- Helper: `GetEnvBase64OrPlain()` supports both plain and base64-encoded values
- Prevents shell escaping issues with JSON in environment variables
- Format: `OAUTH_PRIVATE_JWK=base64:eyJhbGci...` or plain JSON
### Cookie Store Singleton
- Global singleton initialized at startup: `oauth.InitCookieStore(secret)`
- Shared across all handlers for consistent session management
- Validates secret length on initialization
### Database Migration
```sql
-- Migration 003: OAuth tables
-- Migration 004: Performance indexes
```
## 📊 Code Quality & Testing
### Test Coverage
- `env_test.go` - Base64 environment variable handling (8 test cases)
- `dpop_test.go` - DPoP proof structure validation
- `oauth_test.go` - Integration tests for OAuth endpoints
### Linter Compliance
- Fixed errcheck violations (defer close, error handling)
- Formatted with gofmt
- Added nolint directives where appropriate
### Constants & Configuration
- [constants.go](internal/api/handlers/oauth/constants.go) - Named configuration values
- `SessionMaxAge = 7 * 24 * 60 * 60`
- `TokenRefreshThreshold = 5 * time.Minute`
- `MinCookieSecretLength = 32`
## 🎓 Implementation Decisions
### Custom OAuth vs Indigo Library
- **Decision**: Implement custom OAuth client
- **Rationale**: Indigo OAuth library explicitly unstable; custom implementation gives full control over edge cases and nonce retry logic
- **Future**: Migrate when indigo reaches stable v1.0
### Session Storage
- **Decision**: PostgreSQL with one session per DID
- **Rationale**: Simple for initial implementation, easy to upgrade to multiple sessions later, transaction support
### DPoP Key Management
- **Decision**: Unique key per session, stored in database
- **Rationale**: RFC 9449 compliance, token binding security, survives server restarts
## 📈 Performance Optimizations
- `idx_oauth_sessions_did_expires` - Fast session expiry queries
- Partial index for active sessions (`WHERE expires_at > NOW()`)
- Hourly cleanup prevents table bloat
- Cookie store singleton reduces memory allocations
## ✅ Production Readiness
### Real-World Validation
✅ Successfully tested with live PDS: `https://pds.bretton.dev`
✅ Handle resolution: `bretton.dev` → DID → PDS discovery
✅ Complete authorization flow with DPoP nonce retry
✅ Session storage and retrieval validated
✅ Token refresh logic confirmed working
### Security Checklist
✅ DPoP token binding prevents theft/replay
✅ PKCE prevents authorization code interception
✅ PAR reduces attack surface
✅ Atomic state operations prevent CSRF
✅ HTTP-only, secure, SameSite cookies
✅ Private keys never exposed in public endpoints
✅ Automatic token expiration (60 min access, ~90 day refresh)
## 📦 Files Changed
- **27 files**: 3,130 additions, 1 deletion
- **New packages**: oauth handlers, OAuth client, auth middleware
- **New migrations**: OAuth tables + indexes
- **Updated**: main.go (OAuth initialization), .env.dev (configuration docs)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>