Created
February 12, 2026 01:54
-
-
Save SecurityQQ/390aafa12a78910e9563551eb06dd7f5 to your computer and use it in GitHub Desktop.
Varg Gateway-Centric Authentication & Billing Architecture
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Varg Gateway-Centric Authentication & Billing Architecture | |
| ## Overview | |
| This document outlines the proposed architecture where Gateway becomes the single source of truth for user API keys, balance, pricing, and billing. App and Render services consume Gateway APIs and forward user credentials for end-to-end traceability. | |
| --- | |
| ## Current State (Problems) | |
| ``` | |
| ┌─────────────────────────────────────────────────────────────────────────┐ | |
| │ CURRENT ARCHITECTURE │ | |
| │ │ | |
| │ App (Supabase) Render Gateway │ | |
| │ ┌──────────────┐ ┌──────────┐ ┌──────────────┐ │ | |
| │ │ credits │ │ │ │ usage │ │ | |
| │ │ table │ │ Uses ONE │ │ table │ │ | |
| │ │ │ │ service │ │ │ │ | |
| │ │ user balance │ │ API key │ │ tracks per │ │ | |
| │ │ (prepaid) │ │ for ALL │ │ API key │ │ | |
| │ └──────────────┘ │ users │ └──────────────┘ │ | |
| │ └──────────┘ │ | |
| │ │ | |
| │ Problems: │ | |
| │ 1. Two separate billing systems (not synced) │ | |
| │ 2. Single service key = security risk (stolen = unlimited abuse) │ | |
| │ 3. Gateway doesn't know real users │ | |
| │ 4. App hardcodes prices (may drift from Gateway) │ | |
| │ 5. No spending limits on Gateway level │ | |
| └─────────────────────────────────────────────────────────────────────────┘ | |
| ``` | |
| --- | |
| ## Proposed Architecture | |
| ``` | |
| ┌─────────────────────────────────────────────────────────────────────────┐ | |
| │ GATEWAY │ | |
| │ (Source of Truth) │ | |
| │ │ | |
| │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ | |
| │ │ users │ │ api_keys │ │ usage │ │ pricing │ │ | |
| │ │ table │ │ table │ │ table │ │ config │ │ | |
| │ │ │ │ │ │ │ │ │ │ | |
| │ │ id │ │ key_hash │ │ user_id │ │ video: 100¢ │ │ | |
| │ │ email │ │ user_id │ │ cost_cents │ │ image: 50¢ │ │ | |
| │ │ balance_cents│ │ revoked_at │ │ provider │ │ speech: 20¢ │ │ | |
| │ │ created_at │ │ created_at │ │ created_at │ │ music: 30¢ │ │ | |
| │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ | |
| │ │ | |
| │ New APIs: │ | |
| │ • GET /v1/pricing - Get current prices │ | |
| │ • GET /v1/balance - Get user balance (auth by API key) │ | |
| │ • POST /v1/admin/users - Create user + API key (admin auth) │ | |
| │ • DELETE /v1/admin/keys/:id - Revoke key (admin auth) │ | |
| │ • POST /v1/admin/topup - Add credits to user (admin auth) │ | |
| │ │ | |
| │ Billing logic: │ | |
| │ • Check balance BEFORE processing request │ | |
| │ • Deduct cost AFTER successful completion │ | |
| │ • Return 402 Payment Required if insufficient balance │ | |
| └─────────────────────────────────────────────────────────────────────────┘ | |
| ▲ ▲ | |
| │ │ | |
| User API key User API key | |
| (varg_xxx) (forwarded) | |
| │ │ | |
| ┌───────────────────┴───────────────┐ ┌───────┴───────────────────────────┐ | |
| │ APP │ │ RENDER │ | |
| │ │ │ │ | |
| │ At signup: │ │ Receives user API key from App │ | |
| │ 1. Create user in Supabase │ │ Forwards to ALL Gateway calls │ | |
| │ 2. Call Gateway admin API │ │ │ | |
| │ to create user + key │ │ varg = createVarg({ │ | |
| │ 3. Store gateway_api_key │ │ apiKey: userApiKey, // ← user │ | |
| │ in Supabase user profile │ │ baseUrl: GATEWAY_URL, │ | |
| │ │ │ }) │ | |
| │ At render: │ │ │ | |
| │ 1. Get user's gateway_api_key │ │ All costs billed to user's key │ | |
| │ 2. Pass to render service │ │ │ | |
| │ │ │ │ | |
| │ UI shows: │ │ │ | |
| │ • Balance (from Gateway API) │ │ │ | |
| │ • Pricing (from Gateway API) │ │ │ | |
| │ • User's API key (can copy) │ │ │ | |
| │ │ │ │ | |
| │ Credits display: │ │ │ | |
| │ • Fetched from Gateway │ │ │ | |
| │ • Converted: cents → credits │ │ │ | |
| │ (1:1 or with coefficient) │ │ │ | |
| └────────────────────────────────────┘ └────────────────────────────────────┘ | |
| ``` | |
| --- | |
| ## API Specifications | |
| ### 1. Pricing API | |
| **Endpoint:** `GET /v1/pricing` | |
| **Auth:** Public or API key (TBD) | |
| **Response:** | |
| ```json | |
| { | |
| "video": { "cents": 100, "unit": "per_generation" }, | |
| "image": { "cents": 50, "unit": "per_generation" }, | |
| "speech": { "cents": 20, "unit": "per_generation" }, | |
| "music": { "cents": 30, "unit": "per_generation" }, | |
| "transcription": { "cents": 10, "unit": "per_generation" } | |
| } | |
| ``` | |
| ### 2. Balance API | |
| **Endpoint:** `GET /v1/balance` | |
| **Auth:** User API key (Bearer token) | |
| **Response:** | |
| ```json | |
| { | |
| "balance_cents": 5000, | |
| "currency": "USD" | |
| } | |
| ``` | |
| ### 3. Admin: Create User + Key | |
| **Endpoint:** `POST /v1/admin/users` | |
| **Auth:** Admin API key | |
| **Request:** | |
| ```json | |
| { | |
| "email": "user@example.com", | |
| "external_id": "supabase_user_id_123", | |
| "initial_balance_cents": 500 | |
| } | |
| ``` | |
| **Response:** | |
| ```json | |
| { | |
| "user_id": "uuid", | |
| "api_key": "varg_live_abc123...", | |
| "balance_cents": 500 | |
| } | |
| ``` | |
| **Note:** `api_key` is returned ONLY on creation (plain text). Gateway stores hash. | |
| ### 4. Admin: Revoke Key | |
| **Endpoint:** `DELETE /v1/admin/keys/:key_id` | |
| **Auth:** Admin API key | |
| **Response:** | |
| ```json | |
| { | |
| "revoked": true, | |
| "revoked_at": "2024-01-15T10:30:00Z" | |
| } | |
| ``` | |
| ### 5. Admin: Top-up Balance | |
| **Endpoint:** `POST /v1/admin/topup` | |
| **Auth:** Admin API key | |
| **Request:** | |
| ```json | |
| { | |
| "user_id": "uuid", | |
| "amount_cents": 1000, | |
| "reason": "manual_topup" | |
| } | |
| ``` | |
| **Response:** | |
| ```json | |
| { | |
| "balance_cents": 6000, | |
| "transaction_id": "uuid" | |
| } | |
| ``` | |
| --- | |
| ## Request Flow | |
| ### User renders video via App | |
| ``` | |
| ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ | |
| │ User │ │ App │ │ Render │ │Gateway │ | |
| └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘ | |
| │ │ │ │ | |
| │ Click render │ │ │ | |
| │──────────────▶│ │ │ | |
| │ │ │ │ | |
| │ │ Get user's API key │ | |
| │ │ from Supabase profile │ | |
| │ │ │ │ | |
| │ │ POST /api/render │ | |
| │ │ { code, userApiKey } │ | |
| │ │──────────────▶│ │ | |
| │ │ │ │ | |
| │ │ │ POST /v1/video | |
| │ │ │ Auth: Bearer varg_xxx (user's key) | |
| │ │ │──────────────▶│ | |
| │ │ │ │ | |
| │ │ │ │ Check balance | |
| │ │ │ │ If < cost → 402 | |
| │ │ │ │ | |
| │ │ │ │ Process request | |
| │ │ │ │ Deduct cost | |
| │ │ │ │ | |
| │ │ │ { url, cost_cents } | |
| │ │ │◀──────────────│ | |
| │ │ │ │ | |
| │ │ { url } │ │ | |
| │ │◀──────────────│ │ | |
| │ │ │ │ | |
| │ Show video │ │ │ | |
| │◀──────────────│ │ │ | |
| ``` | |
| ### User uses Gateway directly | |
| ``` | |
| ┌────────┐ ┌────────┐ | |
| │ User │ │Gateway │ | |
| └───┬────┘ └───┬────┘ | |
| │ │ | |
| │ POST /v1/image │ | |
| │ Auth: Bearer varg_xxx (own key) │ | |
| │──────────────────────────────────────▶│ | |
| │ │ | |
| │ │ Validate key | |
| │ │ Check balance | |
| │ │ Process | |
| │ │ Deduct cost | |
| │ │ | |
| │ { url, cost_cents } │ | |
| │◀──────────────────────────────────────│ | |
| ``` | |
| --- | |
| ## Database Schema Changes | |
| ### Gateway: Add balance to users table | |
| ```sql | |
| -- Add balance column to existing users table | |
| ALTER TABLE users ADD COLUMN balance_cents INTEGER NOT NULL DEFAULT 0; | |
| ALTER TABLE users ADD COLUMN external_id TEXT; -- Supabase user ID | |
| -- Index for external_id lookups | |
| CREATE INDEX idx_users_external_id ON users(external_id); | |
| ``` | |
| ### Gateway: Add transactions table (optional, for audit) | |
| ```sql | |
| CREATE TABLE balance_transactions ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, | |
| amount_cents INTEGER NOT NULL, -- positive = credit, negative = debit | |
| type TEXT NOT NULL, -- 'topup', 'usage', 'refund', 'signup_grant' | |
| reference_id TEXT, -- job_id or external reference | |
| description TEXT, | |
| balance_after_cents INTEGER NOT NULL, | |
| created_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| CREATE INDEX idx_balance_tx_user ON balance_transactions(user_id, created_at); | |
| ``` | |
| ### App: Add gateway_api_key to users | |
| ```sql | |
| -- In Supabase | |
| ALTER TABLE users ADD COLUMN gateway_api_key TEXT; | |
| ALTER TABLE users ADD COLUMN gateway_user_id UUID; | |
| ``` | |
| --- | |
| ## Security Considerations | |
| ### 1. Admin API Key | |
| - Create a special admin API key for App → Gateway communication | |
| - Store in App's environment: `VARG_ADMIN_KEY` | |
| - Gateway checks `is_admin` flag on key before allowing admin operations | |
| ### 2. Key Security | |
| - User API keys are returned in plain text ONLY on creation | |
| - Gateway stores SHA-256 hash | |
| - App stores encrypted or relies on Supabase RLS | |
| ### 3. Rate Limiting | |
| Consider adding rate limits per API key: | |
| - Requests per minute | |
| - Concurrent requests | |
| - Spending per hour/day | |
| ### 4. Audit Trail | |
| All admin operations should be logged: | |
| - Who created/revoked keys | |
| - Balance changes with reasons | |
| --- | |
| ## Migration Plan | |
| ### Phase 1: Gateway Changes | |
| 1. Add `balance_cents` and `external_id` to users table | |
| 2. Add `balance_transactions` table | |
| 3. Implement balance check before request processing | |
| 4. Implement `GET /v1/pricing` endpoint | |
| 5. Implement `GET /v1/balance` endpoint | |
| 6. Implement admin endpoints (`/v1/admin/*`) | |
| 7. Return 402 when balance insufficient | |
| ### Phase 2: App Changes | |
| 1. Add `gateway_api_key` column to Supabase users | |
| 2. At signup: call Gateway to create user + key | |
| 3. Store returned API key in user profile | |
| 4. Update render call to pass user's API key | |
| 5. Update credits UI to fetch from Gateway | |
| 6. Implement key revocation on account deletion | |
| 7. Remove old `credits` table (or keep as backup) | |
| ### Phase 3: Render Changes | |
| 1. Accept `userApiKey` in request body | |
| 2. Use user's key instead of service key for Gateway calls | |
| 3. Remove `VARG_API_KEY` environment variable (no longer needed) | |
| ### Phase 4: Cleanup | |
| 1. Remove `credits` and `credit_transactions` tables from App | |
| 2. Remove credits-related code from App | |
| 3. Update documentation | |
| --- | |
| ## Open Questions | |
| 1. **Pricing format**: By capability, by model, or both? | |
| 2. **Credits conversion**: 1 cent = 1 credit, or different ratio? | |
| 3. **Initial balance**: How much free credits on signup? (currently SIGNUP_GRANT_CENTS in App) | |
| 4. **Rate limits**: Should Gateway enforce rate limits per key? | |
| 5. **Stripe integration**: Timeline for connecting Gateway billing to Stripe? | |
| --- | |
| ## Benefits | |
| | Aspect | Before | After | | |
| |--------|--------|-------| | |
| | Security | One key for all users | Per-user keys | | |
| | Billing accuracy | Two systems, may drift | Single source of truth | | |
| | User transparency | Hidden from user | User sees/owns their key | | |
| | Abuse protection | None at Gateway | Balance limits per user | | |
| | Audit | Split across services | Centralized in Gateway | | |
| | Direct API access | Not possible | User can use Gateway directly | | |
| --- | |
| ## Appendix: Error Responses | |
| ### 402 Payment Required | |
| ```json | |
| { | |
| "error": { | |
| "code": "insufficient_balance", | |
| "message": "Insufficient balance. Required: 100 cents, Available: 50 cents", | |
| "required_cents": 100, | |
| "available_cents": 50 | |
| } | |
| } | |
| ``` | |
| ### 401 Unauthorized (revoked key) | |
| ```json | |
| { | |
| "error": { | |
| "code": "key_revoked", | |
| "message": "API key has been revoked" | |
| } | |
| } | |
| ``` | |
| ### 403 Forbidden (admin operation without admin key) | |
| ```json | |
| { | |
| "error": { | |
| "code": "admin_required", | |
| "message": "This operation requires admin privileges" | |
| } | |
| } | |
| ``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment