an ATProto application with OAuth 2.1 proxy support for extended session management.
istat provides an OAuth proxy layer between ATProto clients and Personal Data Servers (PDSes), enabling:
- longer-lived sessions - 1 year tokens upstream vs 1 week without proxy
- confidential client mode - secure OAuth flow with client secrets
- transparent proxying - drop-in replacement for direct PDS communication
three main components:
- jacquard-oatproxy - reusable Rust library implementing OAuth 2.1 authorization server/proxy
- server - backend running the proxy and XRPC endpoints
- frontend - React/TypeScript UI with TanStack Router
the proxy acts as both OAuth server (downstream to clients) and OAuth client (upstream to PDSes), handling DPoP proof generation and token refresh automatically.
# run development server
cargo run --bin server
# build release
cargo build --release --bin server
# server runs on port 8080 by default
# database: sqlite://istat.db# install dependencies
pnpm install
# run dev server (port 3000)
pnpm dev
# build for production
pnpm buildmultiarch images (x86_64 + arm64) published to GitHub Container Registry:
# latest release
docker pull ghcr.io/espeon/istat:latest
# specific version
docker pull ghcr.io/espeon/istat:v1.0.0
# latest main branch
docker pull ghcr.io/espeon/istat:main-<sha>run with frontend:
docker run -p 8080:8080 -v $(pwd)/data:/data ghcr.io/espeon/istat:latestrun API-only (frontend served separately):
docker run -p 8080:8080 -v $(pwd)/data:/data \
-e ISTAT_DISABLE_FRONTEND=true \
ghcr.io/espeon/istat:latestfor production deployments, use the provided docker-compose.yml:
# edit docker-compose.yml and customize environment variables
# especially PUBLIC_URL, ISTAT_CLIENT_NAME, and policy URLs
docker compose up -dthe compose file includes:
- persistent volume for database
- all configuration via environment variables
- optional reverse proxy labels (traefik/caddy)
for HTTPS, uncomment the caddy service or use your preferred reverse proxy. see Caddyfile.example for caddy configuration.
server configuration:
DATABASE_URL- database connection string (default:sqlite:/data/istat.db)PUBLIC_URL- public-facing URL for OAuth redirects (default:http://localhost:3000)BIND_ADDR- address to bind server (default:0.0.0.0:8080)RUST_LOG- logging configuration (default:simple_server=debug,jacquard_oauth_proxy=debug,info)
feature toggles:
DEV_MODE- proxy to Vite dev server on localhost:3001 (default:false)ISTAT_DISABLE_FRONTEND- disable frontend serving, API/OAuth only (default:false)ISTAT_ENABLE_JETSTREAM- enable Jetstream event ingestion (default:true)ISTAT_VITE_PORT- Vite dev server port for DEV_MODE (default:3001)STATIC_DIR- directory to serve static files from (default:dist)
upstream oauth client metadata (shown to users during authorization):
ISTAT_CLIENT_NAME- application name shown to users (default:istat OAuth Proxy)ISTAT_TOS_URI- terms of service URLISTAT_POLICY_URI- privacy policy URLISTAT_LOGO_URI- logo image URL
lexicon schemas live in lex/. after modifying:
# backend (regenerate Rust types)
jacquard-codegen --input lex --output lexicons
# frontend (regenerate TypeScript types)
pnpm codegenmigrations in server/migrations/ run automatically on startup (001, 002, 003...). SQLite backend with default path sqlite://istat.db.
# rust
cargo test
# frontend
pnpm test- client initiates OAuth with proxy
- proxy performs PAR with upstream PDS using confidential credentials
- user authorizes at PDS
- proxy exchanges code for long-lived upstream tokens (1 year)
- proxy issues short-lived JWTs to client (1 hour)
- client makes XRPC requests with JWT
- proxy validates JWT, creates DPoP proof, forwards to PDS
dual licensed under MIT or Apache 2.0