commits
- Fix handle resolution to use custom PDS domains for OAuth discovery
- Implement DPoP nonce retry logic using server-provided DPoP-Nonce header
- Fix client_assertion aud claim to use issuer URL per RFC 7523
- Support both custom PDS handles (selfhosted.social) and Bluesky-hosted PDSs
The multi-PDS authentication feature now correctly handles:
- Custom PDS handles: authentication goes to the handle's PDS
- Bluesky-hosted PDSs: authentication uses bsky.social OAuth
- DPoP nonce management with automatic retry on use_dpop_nonce error
- Add error types for PDS discovery with proper context
- Add HTTP timeout to PDS discovery requests
- Add error handling tests for PDS discovery
- Add integration test for multi-PDS auth flow
- Add comprehensive documentation for multi-PDS auth
- Fix broken pds_request_test.go file
- Improve error messages and logging
This enables authentication with any AT Protocol PDS, not just Bluesky.
- Store authorization server URL in OAuth session
- Refresh tokens using PDS-specific token endpoint
- Support OAuth refresh_token grant type
- Maintain backward compatibility with legacy sessions
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace inline resolveHandle() and resolvePDSFromPLC() functions
with services.ResolveHandleToDID() and services.ResolvePDSEndpoint()
as specified in Task 2.
Changes:
- Import services package in routes_login.go
- Use services.ResolveHandleToDID() for handle resolution
- Use services.ResolvePDSEndpoint() for PDS and auth server discovery
- Remove duplicate inline functions resolveHandle() and resolvePDSFromPLC()
- Maintain graceful fallback to bsky.social on resolution failures
- Add logging for handle resolution and PDS discovery steps
All tests passing.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Resolve handle to DID and discover PDS endpoint
- Use PDS-specific authorization and token endpoints
- Store authorization server in OAuth session
- Support non-Bluesky PDS instances
- Resolve AT Protocol handles to DIDs
- Discover PDS endpoints from DID documents (did:plc and did:web)
- Support authorization server discovery
- Fallback to Bluesky directory for handle resolution
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Reduced main.go from 1,320 lines to 143 lines (89% reduction) by extracting
reusable components into dedicated packages:
## New Packages
- **middleware/auth.go**: Authentication and session management
- PDSRequest: DPoP-based ATProto authentication with retry logic
- AuthedDo: Authenticated HTTP requests with auto token refresh
- GetDIDAndHandle: User identity resolution (OAuth + legacy)
- RefreshSession: Token refresh handling
- **services/blob.go**: Document and blob operations
- UploadBlob: Blob upload with retry
- GetDocNameAndText: Document fetching from ATProto
- RenderPDF: Screenplay PDF generation (moved from pdf.go)
- SanitizeFilename: Safe filename handling
- **services/atproto.go**: ATProto protocol utilities
- ResolveHandle: Handle to DID resolution
- ResolvePDSFromPLC: PDS endpoint discovery
- **handlers/static/handler.go**: Static file serving
- Precompressed file support (Brotli/Gzip)
- ETag caching
- Content-type handling
## Benefits
- Separation of concerns: each package has single responsibility
- Testability: all logic can be unit tested independently
- Maintainability: changes localized to specific packages
- Reusability: services used by multiple handlers
- Clean dependency injection through constructors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Summary
- Extracted handlers into packages:
- handlers/atp: /atp/session, /atp/post, /atp/doc via DI
- handlers/oauth: OAuthManager (ES256 DPoP + client assertion), routes for metadata, JWKS, logout, resume, login, callback
- Removed legacy OAuth code from server/main.go; replaced global wiring with package manager (oauthManager = om)
- Kept pdsBaseFromUser and pdsRequest using oauthManager; exported GenerateDPoPProofWithToken
Auth decisions implemented (per team memory)
- Start auth by handle (login flow resolves PDS from handle/DID)
- Build DPoP JWTs using Go stdlib (ES256)
- Token exchange includes resource & scope
- Dual-scheme PDS requests: DPoP first with nonce retry; Bearer+DPoP fallback for non-DPoP tokens
Tests
- handlers/oauth: metadata, JWKS, cookie session, login redirect, resource fallback, callback nonce retry + legacy session hook
- server: pdsRequest integration tests for nonce retry and DPoP->Bearer fallback
Build & Dev
- Dockerfile: copy entire server/ tree so internal packages resolve; keep module cache
- Added go.work at repo root (go 1.24.0) to support root-based builds (buildpacks/CI)
- go.mod tidy: removed unused modules and stabilized indirects
Result
- Local build/tests pass
- Staging deploy via Fly succeeds (uses Dockerfile)
- Fix handle resolution to use custom PDS domains for OAuth discovery
- Implement DPoP nonce retry logic using server-provided DPoP-Nonce header
- Fix client_assertion aud claim to use issuer URL per RFC 7523
- Support both custom PDS handles (selfhosted.social) and Bluesky-hosted PDSs
The multi-PDS authentication feature now correctly handles:
- Custom PDS handles: authentication goes to the handle's PDS
- Bluesky-hosted PDSs: authentication uses bsky.social OAuth
- DPoP nonce management with automatic retry on use_dpop_nonce error
- Add error types for PDS discovery with proper context
- Add HTTP timeout to PDS discovery requests
- Add error handling tests for PDS discovery
- Add integration test for multi-PDS auth flow
- Add comprehensive documentation for multi-PDS auth
- Fix broken pds_request_test.go file
- Improve error messages and logging
This enables authentication with any AT Protocol PDS, not just Bluesky.
Replace inline resolveHandle() and resolvePDSFromPLC() functions
with services.ResolveHandleToDID() and services.ResolvePDSEndpoint()
as specified in Task 2.
Changes:
- Import services package in routes_login.go
- Use services.ResolveHandleToDID() for handle resolution
- Use services.ResolvePDSEndpoint() for PDS and auth server discovery
- Remove duplicate inline functions resolveHandle() and resolvePDSFromPLC()
- Maintain graceful fallback to bsky.social on resolution failures
- Add logging for handle resolution and PDS discovery steps
All tests passing.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Reduced main.go from 1,320 lines to 143 lines (89% reduction) by extracting
reusable components into dedicated packages:
## New Packages
- **middleware/auth.go**: Authentication and session management
- PDSRequest: DPoP-based ATProto authentication with retry logic
- AuthedDo: Authenticated HTTP requests with auto token refresh
- GetDIDAndHandle: User identity resolution (OAuth + legacy)
- RefreshSession: Token refresh handling
- **services/blob.go**: Document and blob operations
- UploadBlob: Blob upload with retry
- GetDocNameAndText: Document fetching from ATProto
- RenderPDF: Screenplay PDF generation (moved from pdf.go)
- SanitizeFilename: Safe filename handling
- **services/atproto.go**: ATProto protocol utilities
- ResolveHandle: Handle to DID resolution
- ResolvePDSFromPLC: PDS endpoint discovery
- **handlers/static/handler.go**: Static file serving
- Precompressed file support (Brotli/Gzip)
- ETag caching
- Content-type handling
## Benefits
- Separation of concerns: each package has single responsibility
- Testability: all logic can be unit tested independently
- Maintainability: changes localized to specific packages
- Reusability: services used by multiple handlers
- Clean dependency injection through constructors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Summary
- Extracted handlers into packages:
- handlers/atp: /atp/session, /atp/post, /atp/doc via DI
- handlers/oauth: OAuthManager (ES256 DPoP + client assertion), routes for metadata, JWKS, logout, resume, login, callback
- Removed legacy OAuth code from server/main.go; replaced global wiring with package manager (oauthManager = om)
- Kept pdsBaseFromUser and pdsRequest using oauthManager; exported GenerateDPoPProofWithToken
Auth decisions implemented (per team memory)
- Start auth by handle (login flow resolves PDS from handle/DID)
- Build DPoP JWTs using Go stdlib (ES256)
- Token exchange includes resource & scope
- Dual-scheme PDS requests: DPoP first with nonce retry; Bearer+DPoP fallback for non-DPoP tokens
Tests
- handlers/oauth: metadata, JWKS, cookie session, login redirect, resource fallback, callback nonce retry + legacy session hook
- server: pdsRequest integration tests for nonce retry and DPoP->Bearer fallback
Build & Dev
- Dockerfile: copy entire server/ tree so internal packages resolve; keep module cache
- Added go.work at repo root (go 1.24.0) to support root-based builds (buildpacks/CI)
- go.mod tidy: removed unused modules and stabilized indirects
Result
- Local build/tests pass
- Staging deploy via Fly succeeds (uses Dockerfile)