Files
ai-gateway/TASKS.md
T

73 lines
3.2 KiB
Markdown

# AI Gateway — Development Tasks
## Per-Plugin Module Keys (Security)
> Goal: Replace the single shared `AI_GATEWAY_KEY` embedded in exported Live Practice plugin HTML with scoped, expiring, per-plugin keys. Existing modules and endpoints must not break.
---
### Step 1 — DB Schema: Add `expires_at` and `rate_limit_per_hour` to Module
- [ ] Add `expires_at: DateTime, nullable` to `app/models/module.py``None` = no expiry (existing modules unaffected)
- [ ] Add `rate_limit_per_hour: Integer, nullable` to `app/models/module.py``None` = falls back to global slowapi limit
- [ ] Create `supabase/migrations/YYYYMMDD_plugin_keys.sql`:
```sql
ALTER TABLE public.modules ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ NULL;
ALTER TABLE public.modules ADD COLUMN IF NOT EXISTS rate_limit_per_hour INTEGER NULL;
```
- [ ] Run migration against Supabase DB
---
### Step 2 — Enforce expiry in `deps.py`
- [ ] In `get_api_key()` (`app/api/deps.py`): after DB lookup, check `module.expires_at` — if set and in the past, raise HTTP 403
- [ ] Same check in `get_current_module()` (`app/api/deps.py`)
- [ ] Document in a comment that the TTLCache (5-min TTL) means expired keys have up to a 5-minute grace period
---
### Step 3 — Per-key rate limiting
- [ ] In `app/core/limiter.py`, replace `key_func=get_remote_address` with a custom function that returns the `X-API-Key` header value when present, falling back to IP:
```python
def get_api_key_or_ip(request: Request) -> str:
return request.headers.get("X-API-Key") or get_remote_address(request)
```
- [ ] Verify existing endpoints still receive 429 responses correctly after the change
---
### Step 4 — Provisioning endpoint
- [ ] Create `app/api/endpoints/provision.py` — `POST /api/v1/modules/provision`:
- Auth: global `API_KEY` via `X-API-Key` header (reuses existing static key check)
- Body: `name`, `expires_in_days` (int, default 365), `rate_limit_per_hour` (int, optional)
- Creates a new `Module` record with `secrets.token_hex(32)` as `secret_key`
- Sets `expires_at = utcnow() + timedelta(days=expires_in_days)`
- Returns `{ secret_key, module_id, expires_at }`
- [ ] Register in `app/api/router.py`:
```python
api_router.include_router(provision.router, prefix="/modules", tags=["provisioning"])
```
- [ ] Update `api_documentation.md` with the new endpoint
---
### Step 5 — Live Practice integration (handled in Live Practice repo)
> These tasks are tracked in the Live Practice `TASKS.md`, not here. Listed for cross-reference only.
- [ ] Add `ai_gateway_key` column to `plugins` table
- [ ] Export route provisions a scoped key via `POST /api/v1/modules/provision` (using server-side `AI_GATEWAY_KEY` as admin credential)
- [ ] Scoped per-plugin key is embedded in exported HTML instead of the shared `AI_GATEWAY_KEY`
- [ ] Preview-html route continues using `AI_GATEWAY_KEY` directly (server-side only, safe)
---
## Backlog
- [ ] Respect `rate_limit_per_hour` column per-module (currently stored but not enforced dynamically — global `RATE_LIMIT` setting applies uniformly)
- [ ] Admin UI or CLI command to list/revoke/rotate plugin keys
- [ ] Webhook or callback when a key nears expiry