# GreenCoast A privacy-first, shardable social backend + minimalist client. **Zero PII**, **zero passwords**, optional **E2EE per post**, and **public-key accounts**. Includes **DPoP-style proof-of-possession**, **Discord SSO with PKCE**, and a tiny static client. --- ## Features - **Zero-trust by design**: server stores no emails or passwords. - **Accounts = public keys** (Ed25519 or P-256). No usernames required. - **Proof-of-possession (PoP)** on every authenticated API call. - **Short-lived tokens** (HMAC “gc2”) bound to device keys. - **Shardable storage** (mTLS or signed shard requests). - **No fingerprinting**: no IP/UA logs; coarse timestamps optional. - **Static client** with strong CSP; optional E2EE per post. - **Discord SSO (PKCE)** as an *optional* convenience. - **Filesystem storage** supports both **flat** and **nested** object layouts. --- ## Architecture (brief) - **Shard**: stateless API + local FS object store + in-memory index. - **Client**: static files (HTML/JS/CSS) served by the shard or any static host. - **Identity**: device key (P-256/Ed25519) or passkey; server mints short-lived **gc2** tokens bound to the device key (`cnf` claim). - **Privacy**: objects can be plaintext (public) or client-encrypted (private). --- ## Security posture - **Zero-trust**: no passwords/emails; optional SSO is *linking*, not source-of-truth. - **DPoP-style PoP** on requests: - Client sends: - `Authorization: Bearer gc2.…` - `X-GC-Key: p256:` (or `ed25519:…`) - `X-GC-TS: ` - `X-GC-Proof: sig( METHOD "\n" URL "\n" TS "\n" SHA256(body) )` - Server verifies `gc2` signature, key binding (`cnf`), timestamp window, and replay cache. - **Replay protection**: 10-minute proof cache. - **No fingerprinting/logging**: no IPs, no UAs. - **Strict CSP** for client: blocks XSS/token theft. - **Limits**: request body limits (default 10 MiB), simple per-account rate limiting. - **Shard↔shard**: mTLS or per-shard signatures with timestamp + replay cache. --- ## Requirements - Go 1.21+ - Docker (optional) - A signing key for tokens: `GC_SIGNING_SECRET_HEX` (32+ bytes hex) - (Optional) Discord OAuth app (Client ID/Secret + redirect URI) - (Optional) Cloudflare Tunnel or other TLS reverse proxy --- ## Environment variables GC_HTTP_ADDR=:9080 GC_HTTPS_ADDR= # optional GC_TLS_CERT= # optional GC_TLS_KEY= # optional GC_STATIC_ADDR=:9082 GC_STATIC_DIR=/opt/greencoast/client GC_DATA_DIR=/var/lib/greencoast GC_ZERO_TRUST=true GC_COARSE_TS=false GC_SIGNING_SECRET_HEX=<64+ hex chars> # required for gc2 tokens GC_REQUIRE_POP=true # default true; set false for first-run # Dev convenience (testing only; disable for production) GC_DEV_ALLOW_UNAUTH=false GC_DEV_BEARER= # Discord SSO (optional) GC_DISCORD_CLIENT_ID= GC_DISCORD_CLIENT_SECRET= GC_DISCORD_REDIRECT_URI=https://greencoast.example.com/auth-callback.html --- ## Quickstart (Docker) Minimal compose for local testing (PoP disabled + dev unauth allowed for first run): services: shard-test: build: . environment: - GC_HTTP_ADDR=:9080 - GC_STATIC_ADDR=:9082 - GC_STATIC_DIR=/opt/greencoast/client - GC_DATA_DIR=/var/lib/greencoast - GC_ZERO_TRUST=true - GC_SIGNING_SECRET_HEX=7f6e1a0f2b4d7e3a... # replace with your secret - GC_REQUIRE_POP=false # easier first-run - GC_DEV_ALLOW_UNAUTH=true volumes: - ./testdata:/var/lib/greencoast - ./client:/opt/greencoast/client:ro ports: - "9080:9080" - "9082:9082" Open `http://localhost:9082` → set the Shard URL (`http://localhost:9080`) → publish a test post. When ready, **turn PoP on** by removing `GC_REQUIRE_POP=false` and disabling `GC_DEV_ALLOW_UNAUTH`. --- ## Cloudflare Tunnel example ingress: - hostname: greencoast.example.com service: http://shard-test:9082 - hostname: api-gc.greencoast.example.com service: http://shard-test:9080 - service: http_status:404 Use “Full (strict)” TLS and ensure your cert covers both hosts. --- ## Client usage - **Shard URL**: set it in the top “Connect” section (or use `?api=` query or ``). - **Device key sign-in (no OAuth)**: 1) Client generates/stores a P-256 device key in the browser. 2) Client calls `/v1/auth/key/challenge` then `/v1/auth/key/verify` to obtain a **gc2** token bound to that key. - **Discord SSO (optional)**: - Requires `GC_DISCORD_CLIENT_*` env vars and a valid `GC_DISCORD_REDIRECT_URI`. - Uses PKCE (`S256`) and binds the minted **gc2** token to the device key presented at `/start`. --- ## API (overview) - `GET /healthz` – liveness - `PUT /v1/object` – upload blob (headers: optional `X-GC-Private: 1`, `X-GC-TZ`) - `GET /v1/object/{hash}` – download blob - `DELETE /v1/object/{hash}` – delete blob - `GET /v1/index` – list indexed entries (latest first) - `GET /v1/index/stream` – SSE updates - `POST /v1/admin/reindex` – rebuild index from disk - **Auth** - `POST /v1/auth/key/challenge` → `{nonce, exp}` - `POST /v1/auth/key/verify` `{nonce, alg, pub, sig}` → `{bearer, sub, exp}` - `POST /v1/auth/discord/start` (requires `X-GC-3P-Assent: 1` and `X-GC-Key`) - `GET /v1/auth/discord/callback` → redirects with `#bearer=…` - **GDPR** - `GET /v1/gdpr/policy` – current data-handling posture > When `GC_REQUIRE_POP=true`, all authenticated endpoints require PoP headers. ### PoP header format (pseudocode) Authorization: Bearer gc2.. X-GC-Key: p256: # or ed25519: X-GC-TS: X-GC-Proof: base64( Sign_device_key( UPPER(METHOD) + "\n" + URL + "\n" + X-GC-TS + "\n" + SHA256(body) ) ) --- ## Storage layout & migration - **Writes** are flat: `objects/` - **Reads** (and reindex) also support: - `objects//blob|data|content` - `objects//` - `objects//` (two-level prefix) - To **restore** data into a fresh container: 1) Mount your objects at `/var/lib/greencoast/objects` 2) Call `POST /v1/admin/reindex` (with auth+PoP or enable dev unauth briefly) --- ## Reindex examples Unauth (dev only): curl -X POST https://api-gc.yourdomain/v1/admin/reindex With bearer + PoP (placeholders): curl -X POST https://api-gc.yourdomain/v1/admin/reindex ^ -H "Authorization: Bearer " ^ -H "X-GC-Key: p256:" ^ -H "X-GC-TS: " ^ -H "X-GC-Proof: " --- ## Hardening checklist (prod) - Set `GC_REQUIRE_POP=true`, remove dev bypass. - Keep access token TTL ≤ 8h; rotate signing key periodically. - Static client served with strong CSP (already enabled). - Containers run non-root, read-only FS, `no-new-privileges`, `cap_drop: ["ALL"]`. - Edge WAF/rate limits; 10 MiB default request cap (tunable). - Commit `go.sum`; run `go mod verify` in CI. --- ## GDPR - Server stores **no PII** (no emails, no IP/UA logs). - Timestamps are UTC (or coarse UTC if enabled). - `/v1/gdpr/policy` exposes current posture. - Roadmap: `/v1/gdpr/export` and `/v1/gdpr/delete` to enumerate/remove blobs signed by a given key. --- ## License This project is licensed under **The Unlicense**. See `LICENSE` for details.