commits
The static callback.html was intercepting OAuth callbacks before they
reached the Go handler. This prevented proper token exchange and caused
"Sign in successful" HTML to be shown instead of redirecting to the
mobile app's Universal Link callback URL.
Now all /oauth/callback requests go through the Go handler which:
- Exchanges OAuth code for tokens
- Creates sealed session tokens
- Redirects mobile flows to Universal Link URL with credentials
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The atproto OAuth spec requires client_id to be the URL of the client
metadata document, not just the domain. Changed from:
https://coves.social
To:
https://coves.social/oauth/client-metadata.json
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Without this, OAuth client uses localhost which causes PAR request
to fail with "localhost hostname is not allowed (RFC 8252)".
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Required for sealing session tokens in production.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Delete internal/atproto/auth/ directory (JWT/DPoP verification - unused)
- Delete cmd/genjwks/ (confidential client key generator - unused)
- Remove ClientSecret/ClientKID from OAuthConfig (public client only)
- Remove HandleJWKS endpoint and routes (not needed for public clients)
- Remove OAUTH_PRIVATE_JWK from docker-compose.prod.yml
- Update tests and integration helpers
Coves is a public OAuth client - this cleanup removes ~1,500 lines of
dead code that was never being used.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove unused OAUTH_CLIENT_ID, OAUTH_REDIRECT_URI, OAUTH_PRIVATE_JWK from .env.prod.example
- Add OAUTH_SEAL_SECRET to .env.dev for local development
- Clarify that OAUTH_SEAL_SECRET is required, client secret/kid are optional
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add OAuth and Universal Links env vars to example
- Update go.mod/go.sum with required dependencies
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update test helpers for new OAuth flow
- Adapt aggregator, community, post tests
- Update user journey tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- E2E tests for OAuth flows
- Session fixation attack prevention tests
- Token verification tests
- Rate limiting tests
- Remove obsolete JWT verification test (merged into new suite)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Simplify auth middleware implementation
- Update routes to use consistent auth patterns
- Improve test coverage for auth flows
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Register OAuth handlers at /oauth/* endpoints
- Register well-known routes for mobile verification
- Add /app/oauth/callback for Universal Links
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- /.well-known/apple-app-site-association for iOS
- /.well-known/assetlinks.json for Android
- Configurable via APPLE_APP_ID and ANDROID_* env vars
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- OAuth client for atproto authentication flow
- Session store with CSRF protection and secure token sealing
- Mobile-specific handlers with Universal Links redirect
- Database migrations for OAuth sessions and CSRF tokens
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add secp256k1 (ES256K) support to JWT access token verification using
Bluesky's indigo crypto package. This enables authentication from
external PDSes that use ES256K-signed tokens.
Changes:
- jwt.go: Add ES256K detection and verification using indigo's crypto
- New verifyES256KToken() for ES256K-specific verification
- New parseJWKMapToIndigoPublicKey() to convert JWK to indigo key
- New verifyJWTSignatureWithIndigoKey() for indigo signature verification
- New parseJWTClaimsManually() to parse claims without golang-jwt
- Update ToPublicKey() to return JWK map for secp256k1 curves
- did_key_fetcher.go: Return indigo PublicKey for secp256k1 keys
- FetchPublicKey now returns indigoCrypto.PublicKey for secp256k1
- NIST curves (P-256, P-384, P-521) still return *ecdsa.PublicKey
This complements the DPoP ES256K support added earlier, completing
full ES256K support across the authentication stack.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add ES256K (secp256k1) algorithm support using indigo's crypto package
- Add algorithm-curve binding validation to prevent algorithm confusion attacks
- Restore exp/nbf claim validation for DPoP proofs (security regression fix)
- Replace golang-jwt parsing with manual JWT parsing to support ES256K
- Add comprehensive test coverage for ES256K and security validations
- Update Caddyfile with proper Host headers for DPoP htu matching
Security fixes:
- Validate JWK curve matches claimed algorithm (ES256K->secp256k1, ES256->P-256, etc.)
- Validate exp claim if present (with clock skew tolerance)
- Validate nbf claim if present (with clock skew tolerance)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
DPoP security improvements addressing PR review findings:
- feat(auth): comprehensive DPoP security improvements
- Access token hash (ath) validation per RFC 9449
- Proxy header support (X-Forwarded-Host, RFC 7239 Forwarded)
- EscapedPath for percent-encoded URLs
- Case-insensitive DPoP scheme per RFC 7235
- fix(auth): prevent goroutine leak from DPoP replay cache
- Graceful server shutdown with signal handling
- Proper cleanup in integration tests
- docs: update authentication documentation for DPoP scheme
Add comment clarifying that PDS uploadBlob calls use Bearer scheme
(standard atproto server auth) rather than DPoP (AppView client auth).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update documentation to reflect the transition from Bearer tokens
to DPoP-bound tokens for client authentication:
- federation-prd.md: Update auth examples to use DPoP scheme
- Add note about Bearer vs DPoP for server-to-server auth
- Update request examples with DPoP header
- COMMENT_SYSTEM_IMPLEMENTATION.md: Update auth references
- "Bearer token" → "DPoP-bound access token"
- Document DPoP proof validation in OptionalAuth
- FEED_SYSTEM_IMPLEMENTATION.md: Update curl examples
- Add DPoP header alongside Authorization header
- Update auth requirement description
- PRD_OAUTH.md, aggregators/SETUP_GUIDE.md, auth/README.md:
- Minor terminology updates for consistency
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The DPoP verifier starts a background goroutine for nonce cache cleanup.
Without calling Stop(), this goroutine persists and accumulates across
server reloads and test runs.
Changes:
- cmd/server/main.go: Add graceful shutdown with signal handling
- Listen for SIGINT/SIGTERM
- Call authMiddleware.Stop() during shutdown
- Use http.Server.Shutdown() for graceful connection draining
- Integration tests: Add defer authMiddleware.Stop() after creation
- user_journey_e2e_test.go
- post_e2e_test.go
- community_e2e_test.go
- aggregator_e2e_test.go
- jwt_verification_test.go (2 locations)
This prevents NonceCache cleanup goroutines from leaking in both
production and test environments.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit addresses multiple security findings from PR review:
1. Access Token Hash (ath) Validation (RFC 9449 Section 4.2)
- Added VerifyAccessTokenHash() to verify DPoP proof's ath claim
- If ath is present, it MUST match SHA-256 hash of access token
- Prevents proof reuse across different tokens
2. Proxy Header Support for htu Verification
- Added extractSchemeAndHost() for X-Forwarded-Proto/Host support
- RFC 7239 Forwarded header parsing with mixed-case keys and quotes
- Critical for DPoP verification behind TLS-terminating proxies
3. Percent-Encoded Path Handling
- Use r.URL.EscapedPath() instead of r.URL.Path
- Preserves percent-encoding for accurate htu matching
4. Case-Insensitive DPoP Scheme (RFC 7235)
- Added extractDPoPToken() helper with strings.EqualFold()
- Accepts "DPoP", "dpop", "DPOP" per HTTP auth spec
Tests added for all security improvements:
- TestVerifyDPoPBinding_UsesForwardedHost
- TestVerifyDPoPBinding_UsesStandardForwardedHeader
- TestVerifyDPoPBinding_ForwardedMixedCaseAndQuotes
- TestVerifyDPoPBinding_AthValidation
- TestRequireAuth_CaseInsensitiveScheme
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The latest indigo (Nov 27, 2025) requires Go 1.25 which isn't available
in Docker Hub yet. Pin to the Oct 10, 2025 commit which is the last
Go 1.24-compatible version.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The local Go 1.25.1 is a pre-release version not available in Docker Hub.
Set minimum go version to 1.24 with toolchain directive for local dev.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement DPoP (RFC 9449) token binding for OAuth access tokens.
Features:
- DPoP proof verification with ES256 signing
- NonceCache for jti-based replay protection
- JWK thumbprint calculation per RFC 7638
- Middleware integration with Stop() for clean shutdown
- X-Forwarded-Proto support for reverse proxy deployments
Security:
- DPoP is additional security, never a fallback
- Tokens with cnf.jkt require valid DPoP proof
- 5-minute proof validity window
- Replay attack prevention via jti tracking
- Add github.com/google/uuid for DPoP proof jti generation
- Add .cache/ to .gitignore for Go build cache
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Document DPoP token binding implementation:
- Explain DPoP security model and why it's not a fallback
- Add flow diagrams for DPoP verification process
- Document replay protection with NonceCache
- Add code examples for DPoP verification
- List implemented security features and future enhancements
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
JWT improvements:
- Add Confirmation field for DPoP cnf.jkt claim binding
- Reorder Claims struct fields for optimal memory alignment
Test improvements:
- Replace os.Setenv/os.Unsetenv with t.Setenv for cleaner tests
- Use t.Cleanup for automatic environment restoration
- Use UUID for DPoP proof jti to ensure test uniqueness
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enhance AtProtoAuthMiddleware with DPoP token binding support:
- Add Stop() method to prevent goroutine leaks on shutdown
- Require DPoP proof when token has cnf.jkt claim
- Treat DPoP-bound tokens without proof as unauthenticated in OptionalAuth
- Honor X-Forwarded-Proto header for URI verification behind proxies
Security model:
- DPoP is ADDITIONAL security, never a fallback for failed verification
- Token signature must be verified BEFORE checking DPoP binding
- Missing DPoP proof for bound tokens results in rejection
Tests added for:
- Middleware Stop() cleanup
- OptionalAuth with DPoP-bound tokens
- X-Forwarded-Proto handling
- DPoP replay protection integration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add proof-of-possession verification for OAuth access tokens:
- DPoPVerifier for validating DPoP proof JWTs
- NonceCache for replay attack prevention with background cleanup
- JWK thumbprint calculation per RFC 7638
- Support for ES256 signing algorithm
- Configurable clock skew and proof age limits
Security features:
- Validates htm (HTTP method) and htu (HTTP URI) claims
- Enforces iat freshness within 5-minute window
- Tracks jti values to prevent proof reuse
- Calculates and validates JWK thumbprints for token binding
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add ES256 federation support and JWT config caching:
- DID-based key fetcher for verifying tokens from any PDS
- O(1) issuer whitelist lookups with cached config
- Environment configuration documentation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Document the dual JWT verification methods (HS256 + ES256) in environment
configuration files:
- HS256: For your own PDS (fast, shared secret, no network calls)
- ES256: For federated users (DID resolution, works with any PDS)
Updates:
- .env.dev: Add HS256_ISSUERS for local development
- .env.prod.example: Add JWT Authentication section with documentation
- docker-compose.prod.yml: Pass PDS_JWT_SECRET and HS256_ISSUERS to appview
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Cache HS256_ISSUERS, PDS_JWT_SECRET, and IS_DEV_ENV at startup instead
of reading environment variables on every token verification request.
- Add jwtConfig struct with sync.Once initialization
- Use map[string]struct{} for O(1) issuer whitelist lookups
- Add InitJWTConfig() for explicit startup initialization
- Add ResetJWTConfigForTesting() for test isolation
- Update main.go to call InitJWTConfig() at startup
Before: 2-3 os.Getenv() calls + O(n) string iteration per request
After: Single pointer dereference + O(1) map lookup per request
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add support for verifying ES256 service auth tokens from federated users.
This enables users from any PDS (bsky.social, etc.) to authenticate with
Coves instances.
- DIDKeyFetcher: resolves DID documents via PLC directory to get public keys
- CombinedKeyFetcher: routes to DID or JWKS based on issuer format
- Supports did:plc: and did:web: issuers
- Converts atcrypto JWK to Go ecdsa.PublicKey for jwt-go verification
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Only did:web:coves.social can now create communities in production.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace local disk blobstore with S3-compatible storage configuration.
This allows blobs to be stored in OVH Object Storage while keeping
record data (CAR files, SQLite) on local NVMe.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adds optional sources field to social.coves.embed.external lexicon
to support aggregator megathreads that combine multiple news sources.
Changes:
- Add #source definition with uri, title, domain, and optional sourcePost
- Add sources array (max 50) to #external for aggregated links
- Add maxLength constraints to domain (253) and provider (100) fields
- Update descriptions to clarify primary vs aggregated content
This enables LLM aggregators to create megathread posts that reference
multiple source articles, with optional strongRef to existing Coves
posts for future feed deprioritization.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed camelCase NSIDs to lowercase to comply with atProto Lexicon
specification which requires NSIDs to use only lowercase letters:
- social.coves.actor.getProfile → social.coves.actor.getprofile
- social.coves.actor.updateProfile → social.coves.actor.updateprofile
Updated all code references including routes, tests, and documentation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Apply gofumpt formatting with extra-rules across all packages
- Fix mock interface signatures to match updated Service/Repository interfaces
- Fix ineffassign bugs in community_repo.go (sortColumn/sortOrder)
- Fix unchecked error returns in production code (register.go)
- Fix unchecked error returns in test files (defer closures)
- Optimize struct field alignment per govet fieldalignment
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Standardize import ordering and formatting using gofumpt.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add configurable allowlist to restrict who can create communities during alpha.
Self-hosters can set their own DID in the env var.
- Add allowedCommunityCreators field to CreateHandler
- Load comma-separated DIDs from COMMUNITY_CREATORS env var
- Return 403 CommunityCreationRestricted for non-allowed users
- Empty/unset env var allows all authenticated users
- Filter empty strings from allowlist defensively
- Add comprehensive unit tests for allowlist behavior
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update Go to 1.24 in Dockerfile
- Fix migrations path (internal/db/migrations)
- Add /xrpc/_health endpoint for Docker healthcheck
- Fix PORT env var precedence (PORT > APPVIEW_PORT)
- Add custom lexicon Jetstream URLs
- Add CURSOR_SECRET env var
- Comment out partial email config (PDS requires both or neither)
- Update Go to 1.24 in Dockerfile
- Fix migrations path (internal/db/migrations)
- Add custom lexicon Jetstream URLs
- Add CURSOR_SECRET env var
- Comment out partial email config (PDS requires both or neither)
- Docker configuration (Dockerfile, docker-compose.prod.yml)
- Caddy reverse proxy with HSTS, CSP, wildcard SSL
- Deployment scripts (deploy.sh, setup-production.sh, backup.sh)
- DID key generation script
- OAuth callback with XSS protection
- Environment template (.env.prod.example)
Align social.coves.community.list endpoint to lexicon specification
with comprehensive testing and atProto compliance.
**Summary:**
- ✅ Lexicon-compliant parameter handling
- ✅ atProto-standard pagination (cursor-based)
- ✅ Input validation for all parameters
- ✅ Performance optimization (removed COUNT query)
- ✅ Comprehensive test coverage (8 new test cases)
- ✅ All tests passing
**Changes:**
- Add visibility parameter to lexicon
- Implement sort enum (popular/active/new/alphabetical)
- Fix cursor type (string vs int)
- Remove undocumented "total" field
- Add input validation for visibility and sort
- Update test suite with comprehensive coverage
Ready for alpha deployment 🚀
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive test coverage for social.coves.community.list
endpoint with all parameter combinations.
**New Test Cases:**
- List with sort=popular (default)
- List with sort=active
- List with sort=new
- List with sort=alphabetical (validates actual ordering)
- List with invalid sort value (expects 400)
- List with visibility filter
- List with default sort (no parameter)
- List with limit bounds validation
**Test Cleanup:**
- Remove deprecated "total" field from response structs
- Add "cursor" field to all list response structs
- Update repository tests for new List() signature
All tests passing ✅
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Align social.coves.community.list handler to lexicon specification
following atProto standards.
**Changes:**
- Add visibility parameter (public/unlisted/private) to lexicon
- Implement sort enum mapping (popular→subscriber_count,
active→post_count, new→created_at, alphabetical→name)
- Add input validation for sort and visibility parameters
- Enforce limit bounds (1-100, default 50)
- Update ListCommunitiesRequest struct with new parameters
- Remove deprecated hostedBy parameter
**atProto Compliance:**
- Use string cursor type (not int)
- Remove undocumented "total" field (follows Bluesky patterns)
- Eliminate COUNT query for better performance
- Return empty cursor when pagination complete
**Performance:**
- Single query instead of COUNT + SELECT
- Proper cursor-based pagination
**Code Quality:**
- Fix magic number in GetDisplayHandle (11 → len(".community."))
- Add TODO comments for future category/language filters
Addresses lexicon contract violations and follows atProto design
patterns from bluesky-social/atproto#4245.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add bd (beads) issue tracking section to project instructions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive documentation for AI agents on using bd (beads)
for issue tracking, including workflow, priorities, and best practices
for managing planning documents in history/ directory.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Create starter issues for tracking Coves development:
- Complete post creation and moderation features [P1]
- Implement aggregator feed federation [P1]
- Add comprehensive API documentation [P2, blocked by features]
Issues tracked in .beads/issues.jsonl with dependency graph.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Set up Beads dependency-aware issue tracker to provide persistent
memory and task tracking across agent sessions. Issues will use
prefix 'Coves-1, Coves-2, etc.' and sync via JSONL in git.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update project guidelines and best practices.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add complete Docker configuration for containerized deployment.
Files added:
- Dockerfile: Multi-stage Python 3.11 image with cron scheduler
- docker-compose.yml: Simple deployment configuration
- docker-entrypoint.sh: Startup script with validation
- .dockerignore: Build optimization
Features:
- Automated cron scheduling (daily at 1 PM UTC)
- Health checks (verifies cron is running)
- Log rotation (10MB max, 3 files)
- Auto-restart on failure
- Environment-based configuration
- Single command deployment: docker compose up -d
The container runs cron internally and streams logs to stdout,
making it production-ready and easy to monitor.
Updated README with comprehensive Docker deployment documentation
including quick start, configuration, testing, and production
deployment best practices.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add Kagi-specific automated registration script and update README.
Changes:
- Move setup-kagi-aggregator.sh to kagi-news/scripts/setup.sh
- Add comprehensive Registration section to README
- Document automated vs manual setup options
- Explain registration workflow and requirements
- Update project structure to reflect new scripts
The setup script automates all 4 registration steps:
1. PDS account creation
2. .well-known file generation
3. Coves registration via XRPC
4. Service declaration creation
This makes the Kagi aggregator self-contained and ready to be
split into its own repository.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The static callback.html was intercepting OAuth callbacks before they
reached the Go handler. This prevented proper token exchange and caused
"Sign in successful" HTML to be shown instead of redirecting to the
mobile app's Universal Link callback URL.
Now all /oauth/callback requests go through the Go handler which:
- Exchanges OAuth code for tokens
- Creates sealed session tokens
- Redirects mobile flows to Universal Link URL with credentials
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The atproto OAuth spec requires client_id to be the URL of the client
metadata document, not just the domain. Changed from:
https://coves.social
To:
https://coves.social/oauth/client-metadata.json
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Delete internal/atproto/auth/ directory (JWT/DPoP verification - unused)
- Delete cmd/genjwks/ (confidential client key generator - unused)
- Remove ClientSecret/ClientKID from OAuthConfig (public client only)
- Remove HandleJWKS endpoint and routes (not needed for public clients)
- Remove OAUTH_PRIVATE_JWK from docker-compose.prod.yml
- Update tests and integration helpers
Coves is a public OAuth client - this cleanup removes ~1,500 lines of
dead code that was never being used.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove unused OAUTH_CLIENT_ID, OAUTH_REDIRECT_URI, OAUTH_PRIVATE_JWK from .env.prod.example
- Add OAUTH_SEAL_SECRET to .env.dev for local development
- Clarify that OAUTH_SEAL_SECRET is required, client secret/kid are optional
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- OAuth client for atproto authentication flow
- Session store with CSRF protection and secure token sealing
- Mobile-specific handlers with Universal Links redirect
- Database migrations for OAuth sessions and CSRF tokens
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add secp256k1 (ES256K) support to JWT access token verification using
Bluesky's indigo crypto package. This enables authentication from
external PDSes that use ES256K-signed tokens.
Changes:
- jwt.go: Add ES256K detection and verification using indigo's crypto
- New verifyES256KToken() for ES256K-specific verification
- New parseJWKMapToIndigoPublicKey() to convert JWK to indigo key
- New verifyJWTSignatureWithIndigoKey() for indigo signature verification
- New parseJWTClaimsManually() to parse claims without golang-jwt
- Update ToPublicKey() to return JWK map for secp256k1 curves
- did_key_fetcher.go: Return indigo PublicKey for secp256k1 keys
- FetchPublicKey now returns indigoCrypto.PublicKey for secp256k1
- NIST curves (P-256, P-384, P-521) still return *ecdsa.PublicKey
This complements the DPoP ES256K support added earlier, completing
full ES256K support across the authentication stack.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add ES256K (secp256k1) algorithm support using indigo's crypto package
- Add algorithm-curve binding validation to prevent algorithm confusion attacks
- Restore exp/nbf claim validation for DPoP proofs (security regression fix)
- Replace golang-jwt parsing with manual JWT parsing to support ES256K
- Add comprehensive test coverage for ES256K and security validations
- Update Caddyfile with proper Host headers for DPoP htu matching
Security fixes:
- Validate JWK curve matches claimed algorithm (ES256K->secp256k1, ES256->P-256, etc.)
- Validate exp claim if present (with clock skew tolerance)
- Validate nbf claim if present (with clock skew tolerance)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
DPoP security improvements addressing PR review findings:
- feat(auth): comprehensive DPoP security improvements
- Access token hash (ath) validation per RFC 9449
- Proxy header support (X-Forwarded-Host, RFC 7239 Forwarded)
- EscapedPath for percent-encoded URLs
- Case-insensitive DPoP scheme per RFC 7235
- fix(auth): prevent goroutine leak from DPoP replay cache
- Graceful server shutdown with signal handling
- Proper cleanup in integration tests
- docs: update authentication documentation for DPoP scheme
Update documentation to reflect the transition from Bearer tokens
to DPoP-bound tokens for client authentication:
- federation-prd.md: Update auth examples to use DPoP scheme
- Add note about Bearer vs DPoP for server-to-server auth
- Update request examples with DPoP header
- COMMENT_SYSTEM_IMPLEMENTATION.md: Update auth references
- "Bearer token" → "DPoP-bound access token"
- Document DPoP proof validation in OptionalAuth
- FEED_SYSTEM_IMPLEMENTATION.md: Update curl examples
- Add DPoP header alongside Authorization header
- Update auth requirement description
- PRD_OAUTH.md, aggregators/SETUP_GUIDE.md, auth/README.md:
- Minor terminology updates for consistency
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The DPoP verifier starts a background goroutine for nonce cache cleanup.
Without calling Stop(), this goroutine persists and accumulates across
server reloads and test runs.
Changes:
- cmd/server/main.go: Add graceful shutdown with signal handling
- Listen for SIGINT/SIGTERM
- Call authMiddleware.Stop() during shutdown
- Use http.Server.Shutdown() for graceful connection draining
- Integration tests: Add defer authMiddleware.Stop() after creation
- user_journey_e2e_test.go
- post_e2e_test.go
- community_e2e_test.go
- aggregator_e2e_test.go
- jwt_verification_test.go (2 locations)
This prevents NonceCache cleanup goroutines from leaking in both
production and test environments.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit addresses multiple security findings from PR review:
1. Access Token Hash (ath) Validation (RFC 9449 Section 4.2)
- Added VerifyAccessTokenHash() to verify DPoP proof's ath claim
- If ath is present, it MUST match SHA-256 hash of access token
- Prevents proof reuse across different tokens
2. Proxy Header Support for htu Verification
- Added extractSchemeAndHost() for X-Forwarded-Proto/Host support
- RFC 7239 Forwarded header parsing with mixed-case keys and quotes
- Critical for DPoP verification behind TLS-terminating proxies
3. Percent-Encoded Path Handling
- Use r.URL.EscapedPath() instead of r.URL.Path
- Preserves percent-encoding for accurate htu matching
4. Case-Insensitive DPoP Scheme (RFC 7235)
- Added extractDPoPToken() helper with strings.EqualFold()
- Accepts "DPoP", "dpop", "DPOP" per HTTP auth spec
Tests added for all security improvements:
- TestVerifyDPoPBinding_UsesForwardedHost
- TestVerifyDPoPBinding_UsesStandardForwardedHeader
- TestVerifyDPoPBinding_ForwardedMixedCaseAndQuotes
- TestVerifyDPoPBinding_AthValidation
- TestRequireAuth_CaseInsensitiveScheme
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement DPoP (RFC 9449) token binding for OAuth access tokens.
Features:
- DPoP proof verification with ES256 signing
- NonceCache for jti-based replay protection
- JWK thumbprint calculation per RFC 7638
- Middleware integration with Stop() for clean shutdown
- X-Forwarded-Proto support for reverse proxy deployments
Security:
- DPoP is additional security, never a fallback
- Tokens with cnf.jkt require valid DPoP proof
- 5-minute proof validity window
- Replay attack prevention via jti tracking
- Add github.com/google/uuid for DPoP proof jti generation
- Add .cache/ to .gitignore for Go build cache
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Document DPoP token binding implementation:
- Explain DPoP security model and why it's not a fallback
- Add flow diagrams for DPoP verification process
- Document replay protection with NonceCache
- Add code examples for DPoP verification
- List implemented security features and future enhancements
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
JWT improvements:
- Add Confirmation field for DPoP cnf.jkt claim binding
- Reorder Claims struct fields for optimal memory alignment
Test improvements:
- Replace os.Setenv/os.Unsetenv with t.Setenv for cleaner tests
- Use t.Cleanup for automatic environment restoration
- Use UUID for DPoP proof jti to ensure test uniqueness
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enhance AtProtoAuthMiddleware with DPoP token binding support:
- Add Stop() method to prevent goroutine leaks on shutdown
- Require DPoP proof when token has cnf.jkt claim
- Treat DPoP-bound tokens without proof as unauthenticated in OptionalAuth
- Honor X-Forwarded-Proto header for URI verification behind proxies
Security model:
- DPoP is ADDITIONAL security, never a fallback for failed verification
- Token signature must be verified BEFORE checking DPoP binding
- Missing DPoP proof for bound tokens results in rejection
Tests added for:
- Middleware Stop() cleanup
- OptionalAuth with DPoP-bound tokens
- X-Forwarded-Proto handling
- DPoP replay protection integration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add proof-of-possession verification for OAuth access tokens:
- DPoPVerifier for validating DPoP proof JWTs
- NonceCache for replay attack prevention with background cleanup
- JWK thumbprint calculation per RFC 7638
- Support for ES256 signing algorithm
- Configurable clock skew and proof age limits
Security features:
- Validates htm (HTTP method) and htu (HTTP URI) claims
- Enforces iat freshness within 5-minute window
- Tracks jti values to prevent proof reuse
- Calculates and validates JWK thumbprints for token binding
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Document the dual JWT verification methods (HS256 + ES256) in environment
configuration files:
- HS256: For your own PDS (fast, shared secret, no network calls)
- ES256: For federated users (DID resolution, works with any PDS)
Updates:
- .env.dev: Add HS256_ISSUERS for local development
- .env.prod.example: Add JWT Authentication section with documentation
- docker-compose.prod.yml: Pass PDS_JWT_SECRET and HS256_ISSUERS to appview
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Cache HS256_ISSUERS, PDS_JWT_SECRET, and IS_DEV_ENV at startup instead
of reading environment variables on every token verification request.
- Add jwtConfig struct with sync.Once initialization
- Use map[string]struct{} for O(1) issuer whitelist lookups
- Add InitJWTConfig() for explicit startup initialization
- Add ResetJWTConfigForTesting() for test isolation
- Update main.go to call InitJWTConfig() at startup
Before: 2-3 os.Getenv() calls + O(n) string iteration per request
After: Single pointer dereference + O(1) map lookup per request
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add support for verifying ES256 service auth tokens from federated users.
This enables users from any PDS (bsky.social, etc.) to authenticate with
Coves instances.
- DIDKeyFetcher: resolves DID documents via PLC directory to get public keys
- CombinedKeyFetcher: routes to DID or JWKS based on issuer format
- Supports did:plc: and did:web: issuers
- Converts atcrypto JWK to Go ecdsa.PublicKey for jwt-go verification
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adds optional sources field to social.coves.embed.external lexicon
to support aggregator megathreads that combine multiple news sources.
Changes:
- Add #source definition with uri, title, domain, and optional sourcePost
- Add sources array (max 50) to #external for aggregated links
- Add maxLength constraints to domain (253) and provider (100) fields
- Update descriptions to clarify primary vs aggregated content
This enables LLM aggregators to create megathread posts that reference
multiple source articles, with optional strongRef to existing Coves
posts for future feed deprioritization.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed camelCase NSIDs to lowercase to comply with atProto Lexicon
specification which requires NSIDs to use only lowercase letters:
- social.coves.actor.getProfile → social.coves.actor.getprofile
- social.coves.actor.updateProfile → social.coves.actor.updateprofile
Updated all code references including routes, tests, and documentation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Apply gofumpt formatting with extra-rules across all packages
- Fix mock interface signatures to match updated Service/Repository interfaces
- Fix ineffassign bugs in community_repo.go (sortColumn/sortOrder)
- Fix unchecked error returns in production code (register.go)
- Fix unchecked error returns in test files (defer closures)
- Optimize struct field alignment per govet fieldalignment
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add configurable allowlist to restrict who can create communities during alpha.
Self-hosters can set their own DID in the env var.
- Add allowedCommunityCreators field to CreateHandler
- Load comma-separated DIDs from COMMUNITY_CREATORS env var
- Return 403 CommunityCreationRestricted for non-allowed users
- Empty/unset env var allows all authenticated users
- Filter empty strings from allowlist defensively
- Add comprehensive unit tests for allowlist behavior
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update Go to 1.24 in Dockerfile
- Fix migrations path (internal/db/migrations)
- Add /xrpc/_health endpoint for Docker healthcheck
- Fix PORT env var precedence (PORT > APPVIEW_PORT)
- Add custom lexicon Jetstream URLs
- Add CURSOR_SECRET env var
- Comment out partial email config (PDS requires both or neither)
- Docker configuration (Dockerfile, docker-compose.prod.yml)
- Caddy reverse proxy with HSTS, CSP, wildcard SSL
- Deployment scripts (deploy.sh, setup-production.sh, backup.sh)
- DID key generation script
- OAuth callback with XSS protection
- Environment template (.env.prod.example)
Align social.coves.community.list endpoint to lexicon specification
with comprehensive testing and atProto compliance.
**Summary:**
- ✅ Lexicon-compliant parameter handling
- ✅ atProto-standard pagination (cursor-based)
- ✅ Input validation for all parameters
- ✅ Performance optimization (removed COUNT query)
- ✅ Comprehensive test coverage (8 new test cases)
- ✅ All tests passing
**Changes:**
- Add visibility parameter to lexicon
- Implement sort enum (popular/active/new/alphabetical)
- Fix cursor type (string vs int)
- Remove undocumented "total" field
- Add input validation for visibility and sort
- Update test suite with comprehensive coverage
Ready for alpha deployment 🚀
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive test coverage for social.coves.community.list
endpoint with all parameter combinations.
**New Test Cases:**
- List with sort=popular (default)
- List with sort=active
- List with sort=new
- List with sort=alphabetical (validates actual ordering)
- List with invalid sort value (expects 400)
- List with visibility filter
- List with default sort (no parameter)
- List with limit bounds validation
**Test Cleanup:**
- Remove deprecated "total" field from response structs
- Add "cursor" field to all list response structs
- Update repository tests for new List() signature
All tests passing ✅
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Align social.coves.community.list handler to lexicon specification
following atProto standards.
**Changes:**
- Add visibility parameter (public/unlisted/private) to lexicon
- Implement sort enum mapping (popular→subscriber_count,
active→post_count, new→created_at, alphabetical→name)
- Add input validation for sort and visibility parameters
- Enforce limit bounds (1-100, default 50)
- Update ListCommunitiesRequest struct with new parameters
- Remove deprecated hostedBy parameter
**atProto Compliance:**
- Use string cursor type (not int)
- Remove undocumented "total" field (follows Bluesky patterns)
- Eliminate COUNT query for better performance
- Return empty cursor when pagination complete
**Performance:**
- Single query instead of COUNT + SELECT
- Proper cursor-based pagination
**Code Quality:**
- Fix magic number in GetDisplayHandle (11 → len(".community."))
- Add TODO comments for future category/language filters
Addresses lexicon contract violations and follows atProto design
patterns from bluesky-social/atproto#4245.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Create starter issues for tracking Coves development:
- Complete post creation and moderation features [P1]
- Implement aggregator feed federation [P1]
- Add comprehensive API documentation [P2, blocked by features]
Issues tracked in .beads/issues.jsonl with dependency graph.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add complete Docker configuration for containerized deployment.
Files added:
- Dockerfile: Multi-stage Python 3.11 image with cron scheduler
- docker-compose.yml: Simple deployment configuration
- docker-entrypoint.sh: Startup script with validation
- .dockerignore: Build optimization
Features:
- Automated cron scheduling (daily at 1 PM UTC)
- Health checks (verifies cron is running)
- Log rotation (10MB max, 3 files)
- Auto-restart on failure
- Environment-based configuration
- Single command deployment: docker compose up -d
The container runs cron internally and streams logs to stdout,
making it production-ready and easy to monitor.
Updated README with comprehensive Docker deployment documentation
including quick start, configuration, testing, and production
deployment best practices.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add Kagi-specific automated registration script and update README.
Changes:
- Move setup-kagi-aggregator.sh to kagi-news/scripts/setup.sh
- Add comprehensive Registration section to README
- Document automated vs manual setup options
- Explain registration workflow and requirements
- Update project structure to reflect new scripts
The setup script automates all 4 registration steps:
1. PDS account creation
2. .well-known file generation
3. Coves registration via XRPC
4. Service declaration creation
This makes the Kagi aggregator self-contained and ready to be
split into its own repository.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>