This repository has been archived on 2025-08-23. You can view files and clone it, but cannot push or open issues or pull requests.
Dani d87e9322b5 Added example/dropin replacements for .env.example
Fixed the issue with PlainText (Complete Anon) posting
Need to fix device sign on issues.
Need to make it so that the non-signed in devices can only see their equalivant level of posts. (i.e. plaintext, public-encrypted, private-encrypted)
2025-08-22 22:59:05 -04:00
2025-08-21 20:56:38 -04:00
2025-08-21 20:56:38 -04:00
2025-08-21 20:56:38 -04:00
2025-08-22 12:42:22 -04:00
2025-08-21 20:57:23 -04:00
2025-08-22 18:24:24 -04:00
2025-08-22 12:00:58 -04:00
2025-08-21 20:56:38 -04:00
2025-08-21 20:56:38 -04:00
2025-08-21 20:56:38 -04:00
2025-08-22 12:40:09 -04:00

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:<base64-raw> (or ed25519:…)
      • X-GC-TS: <unix seconds>
      • 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 <meta name="gc-api-base">).
  • 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.<claims>.<sig>
X-GC-Key: p256:<base64-raw>          # or ed25519:<base64-raw>
X-GC-TS: <unix seconds>
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/<hash>
  • Reads (and reindex) also support:
    • objects/<hash>/blob|data|content
    • objects/<hash>/<single file>
    • objects/<prefix>/<hash> (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 <gc2_token>" ^
  -H "X-GC-Key: p256:<base64raw>" ^
  -H "X-GC-TS: <unix>" ^
  -H "X-GC-Proof: <base64sig>"

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.

Description
No description provided
Readme 217 KiB
Languages
Go 58.6%
JavaScript 21.4%
HTML 7.8%
HCL 5.5%
CSS 3.3%
Other 3.4%