Files
member-portal/README.md
T

4.3 KiB

Member Portal — membership middleware for the Inference Cooperative

Reconciles three systems into one membership lifecycle:

System Role Source of truth for
Open Collective Billing Who is a paying member
Cloudron Identity/SSO Who can log in
LiteLLM Inference Who can use models, and how much

Architecture

Open Collective (billing)
        │ webhook
        ▼
┌─────────────────────────────┐
│  Member Portal (this app)   │
│  - OC webhook handler       │
│  - Cloudron user management │
│  - LiteLLM key management   │
│  - key-injection proxy      │
└─────────────────────────────┘
        ▲
        │ X-User-Email header (LibreChat forwards identity)
        │
LibreChat ──→ Member Portal ──→ LiteLLM ──→ backend

The three responsibilities

  1. Webhook handler (POST /webhook/opencollective) — listens for Open Collective membership events and activates/deactivates members.
  2. Key injector (/v1/*) — reads the member's email from the X-User-Email header, looks up their LiteLLM key, and forwards the request to the gateway with that key.
  3. Admin (/health, /) — health and status.

Configuration (environment variables)

Variable Purpose
CLOUDRON_API Cloudron API origin (auto-set by Cloudron)
CLOUDRON_TOKEN Cloudron API token (Read+Write)
MEMBERS_GROUP_ID Cloudron group ID for members (grants chat access)
LITELLM_BASE LiteLLM gateway URL
LITELLM_MASTER_KEY LiteLLM master key
OPENCOLLECTIVE_WEBHOOK_SECRET Optional webhook signature secret

Tier budgets

Governance decision (set in Loomio). Defaults:

  • free — $2/month of tokens
  • member — $15/month
  • supporter — $30/month

LibreChat wiring

Point LibreChat's custom endpoint at this portal (not directly at LiteLLM), and add the identity header:

endpoints:
  custom:
    - name: "Inference Cooperative"
      apiKey: "${LITELLM_KEY}"
      baseURL: "https://portal.inference.coop/v1"
      headers:
        X-User-Email: "{{LIBRECHAT_USER_EMAIL}}"
      models:
        default: ["deepseek-v4-flash", "gpt-oss-120b"]
        fetch: true

Status

Scaffold — the three handlers are stubbed with the correct integration points. Remaining work before production:

  • Verify Open Collective webhook event names + payload shape → order.processed (every payment), new member (first only), firstPayment flag
  • Verify Cloudron user-creation API payload (role/group assignment) → POST /api/v1/users with {username, email, displayName, role, active} → group assignment via PUT /api/v1/users/:userId/groups with {groupIds: [...]}
  • Add a persistent store (SQLite) for email→key mapping → key/list returns token strings (not objects), so we store email→token locally
  • Verify LiteLLM key/generate + key/list payload shapes against live gateway → key/generate returns {key: "sk-..."}; key/list returns {keys: ["<token>", ...]}
  • Add HMAC signature verification for the OC webhook (if OC supports it)
  • Wire the OIDC addon for admin access

Scheduled tasks (scheduler addon)

Two jobs run inside the container via CloudronManifest.json → addons.scheduler:

  • membership_sweep (nightly 3am) — sweep.sh calls the portal's own /admin/sweep (auth: X-Admin-Token = WEBHOOK_TOKEN) to reconcile members against the live Open Collective list: provisions missed signups, deactivates lapsed members, syncs Loomio + Matrix. Idempotent; a missed webhook self-heals within 24 hours.
  • rag_refresh (nightly 3:30am) — rag_refresh.py pulls this repo's sibling docs from the Gitea raw API (public, no credentials), uploads changed files to Open WebUI (OWUI_API_KEY), attaches them to the shared knowledge collection (DOCS_KNOWLEDGE_ID), reindexes, and re-grants the members group. State persists in /app/data/rag_refresh_state.json.

Verify after any manifest change: the deploy log must show setupAddons: ... scheduler ... listing both tasks, and each script must succeed when run manually inside the container.