···7788/// Handler for `/.well-known/atproto-did`.
99///
1010-/// Returns the server's DID from the `ATPROTO_SERVER_DID` environment variable.
1111-/// Used for domain verification in AT Protocol.
1010+/// Returns the server's DID from the `ATPROTO_SERVER_DID` environment variable for domain verification in AT Protocol.
1211pub async fn atproto_did_handler() -> impl IntoResponse {
1312 std::env::var("ATPROTO_SERVER_DID").unwrap_or_default()
1413}
+114
docs/local-dev.md
···11+# Local Development
22+33+## Prerequisites
44+55+### Required Tools
66+77+- Rust (latest stable)
88+- Node.js 18+ and pnpm
99+- PostgreSQL 14+
1010+- Docker (optional, for containerized Postgres)
1111+1212+### Bluesky Account Setup
1313+1414+1. Create a Bluesky account at <https://bsky.app>
1515+2. Generate an App Password (Settings → App Passwords)
1616+3. Configure `.env` with your credentials:
1717+1818+```bash
1919+APP_USERNAME=your-handle.bsky.social
2020+APP_PASSWORD=your-app-password-here
2121+DB_URL="postgres://postgres:postgres@localhost:5432/malfestio_dev?sslmode=disable"
2222+```
2323+2424+## Testing OAuth Flow
2525+2626+### Step-by-Step
2727+2828+1. **Start PostgreSQL**
2929+3030+ ```bash
3131+ # Using Docker
3232+ docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres:14
3333+3434+ # Or use your local PostgreSQL installation
3535+ ```
3636+3737+2. **Run migrations**
3838+3939+ ```bash
4040+ just migrate
4141+ ```
4242+4343+3. **Start backend**
4444+4545+ ```bash
4646+ just start
4747+ ```
4848+4949+ Server runs on <http://localhost:8080>
5050+5151+4. **Start frontend**
5252+5353+ ```bash
5454+ just web-dev
5555+ ```
5656+5757+ Frontend runs on <http://localhost:3000>
5858+5959+5. **Test OAuth login**
6060+ - Navigate to <http://localhost:3000/login>
6161+ - Enter your Bluesky handle (e.g., `thunderbot.bsky.social`)
6262+ - Authorize the application on bsky.social
6363+ - Verify redirect back to app with successful login
6464+6565+### OAuth Flow Details
6666+6767+When you enter a handle like `thunderbot.bsky.social`, the system:
6868+6969+1. **Handle Resolution**: DNS TXT lookup at `_atproto.thunderbot.bsky.social` or HTTP `https://thunderbot.bsky.social/.well-known/atproto-did`
7070+2. **DID Resolution**: Resolved DID (e.g., `did:plc:...`) queries `https://plc.directory` for PDS endpoint
7171+3. **OAuth Discovery**: `https://bsky.social/.well-known/oauth-authorization-server` fetched for endpoints
7272+4. **Authorization**: User redirected to PDS authorization page with PKCE challenge
7373+5. **Token Exchange**: Authorization code exchanged for access/refresh tokens with DPoP binding
7474+6. **Storage**: Tokens stored in database with encrypted DPoP keypair
7575+7676+## Testing Record Publishing
7777+7878+After successful OAuth login:
7979+8080+1. Create a deck or note in the UI
8181+2. Click "Publish" to publish to your PDS
8282+3. Check your Bluesky profile at <https://bsky.app> to see the published record
8383+4. Verify record appears in your AT Protocol repository
8484+8585+## Environment Variables
8686+8787+### Required
8888+8989+```bash
9090+APP_USERNAME=your-handle.bsky.social
9191+APP_PASSWORD=your-app-password
9292+DB_URL="postgres://postgres:postgres@localhost:5432/malfestio_dev?sslmode=disable"
9393+```
9494+9595+### Optional
9696+9797+```bash
9898+# Server configuration
9999+SERVER_HOST=127.0.0.1
100100+SERVER_PORT=8080
101101+102102+# Frontend proxy
103103+VITE_API_URL=http://localhost:8080
104104+105105+# Logging
106106+RUST_LOG=info,malfestio_server=debug
107107+```
108108+109109+## Additional Resources
110110+111111+- [AT Protocol OAuth Guide](https://docs.bsky.app/blog/oauth-atproto)
112112+- [OAuth Client Implementation](https://docs.bsky.app/docs/advanced-guides/oauth-client)
113113+- [PDS Self-Hosting](https://atproto.com/guides/self-hosting)
114114+- [AT Protocol Specifications](https://atproto.com)
+14-17
docs/todo.md
···42424343- [x] OAuth login directly to user's PDS
4444- [x] Handle resolution via DNS TXT or `/.well-known/atproto-did`
4545- - <https://malfestio.stormlightlabs.org>
4645- [x] DPoP token binding for secure API calls
47464747+**Local Development:**
4848+4949+- [x] Document local testing with real Bluesky accounts
5050+- [x] Add justfile commands for common dev tasks
5151+- [x] Environment variable configuration guide
5252+- [x] Update health check endpoint for service monitoring
5353+- [x] Add logging for OAuth flow steps
5454+4855**Sync & Conflict Resolution:**
49565057- [ ] Bi-directional sync: local drafts → PDS records, PDS records → local cache
5158- [ ] Conflict resolution strategy for concurrent edits (last-write-wins or merge UI)
5259- [ ] Offline queue for pending publishes
6060+- [ ] Sync status UI indicators
53615462**Deep Linking:**
55635664- [ ] AT-URI deep linking from external clients
5765- [ ] Handle `at://` URL scheme in app
6666+- [ ] Link preview generation for shared content
58675968#### Acceptance
60696161-- User can log in with their existing Bluesky/PDS identity.
6262-- Local drafts sync correctly after reconnecting.
6363-6464-#### Implementation Details
6565-6666-**Considerations:**
6767-6868-- Scalability: substantial compute; caching, DB optimization, distributed processing
6969-- Lexicon Validation: validate schemas, ignore invalid records gracefully
7070-- Account State: track latest processed revision per repo; handle deletions
7171-- Bluesky's AppView uses PostgreSQL or ScyllaDB + image proxy + AppView core
7272-7373-**Identity:**
7474-7575-- Use `did:web` for simplicity, `did:plc` for long-term stability
7676-- ATProto OAuth is the forward path
7070+- User can log in with existing Bluesky/PDS identity
7171+- OAuth flow works with production bsky.social accounts
7272+- Developers can test locally using real accounts (see [Local Development Guide](./local-dev.md))
7373+- Local drafts sync correctly after reconnecting
77747875### Milestone M - Reliability, Observability, Launch (v0.1.0)
7976
+81
justfile
···11+# Malfestio
22+33+# Build all Rust crates
44+build:
55+ cargo build
66+77+# Build for release
88+build-release:
99+ cargo build --release
1010+1111+# Run the server via CLI
1212+start:
1313+ cargo run --bin malfestio-cli start
1414+1515+# Run all tests
1616+test:
1717+ cargo test --quiet
1818+1919+# Check code without building
2020+check:
2121+ cargo check
2222+2323+# Run clippy lints
2424+lint:
2525+ cargo clippy --fix --allow-dirty
2626+2727+# Format code
2828+fmt:
2929+ cargo fmt
3030+3131+# Install frontend dependencies
3232+web-install:
3333+ cd web && pnpm install
3434+3535+# Run development server
3636+web-dev:
3737+ cd web && pnpm dev
3838+3939+# Build frontend for production
4040+web-build:
4141+ cd web && pnpm build
4242+4343+# Run frontend tests
4444+web-test:
4545+ cd web && pnpm test
4646+4747+# Type check frontend
4848+web-check:
4949+ cd web && pnpm check
5050+5151+# Lint frontend
5252+web-lint:
5353+ cd web && pnpm lint
5454+5555+# Start both backend and frontend (in separate terminals recommended)
5656+dev:
5757+ @echo "Start backend: just start"
5858+ @echo "Start frontend: just web-dev"
5959+6060+# Run all tests (backend + frontend)
6161+test-all: test web-test
6262+6363+# Run database migrations
6464+migrate:
6565+ cargo run --bin malfestio-cli migrate
6666+6767+# Setup and test OAuth flow with real Bluesky account
6868+test-oauth:
6969+ @echo "Testing OAuth with Bluesky account..."
7070+ @echo "1. Ensure PostgreSQL is running"
7171+ @echo "2. Running migrations..."
7272+ @just migrate
7373+ @echo "3. Start backend with: just start"
7474+ @echo "4. Start frontend with: just web-dev"
7575+ @echo "5. Navigate to http://localhost:3000/login"
7676+ @echo "6. Enter your Bluesky handle from .env"
7777+7878+# Clean build artifacts
7979+clean:
8080+ cargo clean
8181+ cd web && rm -rf dist node_modules/.vite