Skip to content

Commit 14d58cc

Browse files
StuMasonclaude
andcommitted
fix: CSRF exclusion for login/setup + update README
- Add /admin/login and /admin/setup to CSRF exclusions - Update README with per-user API key documentation - Document required ENCRYPTION_KEY for production - Add OAuth integration flow documentation - Add rate limiting documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d6bd934 commit 14d58cc

File tree

2 files changed

+112
-28
lines changed

2 files changed

+112
-28
lines changed

README.md

Lines changed: 110 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ This server:
1212
2. Stores everything in PostgreSQL (your data, your server)
1313
3. Provides an HTMX-powered admin dashboard
1414
4. Exposes REST API for custom integrations
15-
5. Multi-user ready (same codebase for self-hosted and SaaS)
15+
5. Multi-user ready with per-user API keys
1616

1717
## Architecture
1818

@@ -65,6 +65,7 @@ The server syncs data every hour automatically.
6565
The admin panel at `/admin/dashboard` shows:
6666

6767
- **Key Metrics** - HRV, Heart Rate, Training Strain, Alertness, Sleep Score
68+
- **API Keys** - Manage per-user API keys with rate limit tracking
6869
- **Record Counts** - All 9 data types with totals
6970
- **Recent Sleep** - Last 7 days with scores
7071
- **Nightly Recharge** - HRV, ANS charge, recovery status
@@ -87,22 +88,103 @@ The admin panel at `/admin/dashboard` shows:
8788

8889
## Configuration
8990

90-
Environment variables (see `.env.example`):
91+
### Required Environment Variables
92+
93+
| Variable | Description | Required |
94+
|----------|-------------|----------|
95+
| `DATABASE_URL` | PostgreSQL connection string | Yes |
96+
| `ENCRYPTION_KEY` | 32-byte Fernet key for token encryption | **Yes (production)** |
97+
98+
Generate an encryption key:
99+
```bash
100+
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
101+
```
102+
103+
### Optional Environment Variables
104+
105+
| Variable | Description | Default |
106+
|----------|-------------|---------|
107+
| `DEPLOYMENT_MODE` | `self_hosted` or `saas` | `self_hosted` |
108+
| `SYNC_INTERVAL_HOURS` | Auto-sync frequency | `1` |
109+
| `SYNC_ON_STARTUP` | Sync when server starts | `false` |
110+
| `SYNC_DAYS_LOOKBACK` | Days of history to sync | `28` |
111+
| `LOG_LEVEL` | Logging verbosity | `INFO` |
112+
| `API_KEY` | Master API key (bypasses rate limits) | None |
113+
114+
## API Authentication
115+
116+
The server supports per-user API keys with rate limiting.
117+
118+
### Authentication Methods
119+
120+
1. **Per-User API Keys** (recommended) - Each user gets their own API key via OAuth
121+
2. **Master API Key** - Set `API_KEY` env var for full access (bypasses rate limits)
122+
3. **Open Access** - If no `API_KEY` is set and no key provided, endpoints are open
123+
124+
### Using API Keys
125+
126+
```bash
127+
# With per-user API key (includes rate limit headers)
128+
curl -H "X-API-Key: pfk_your_api_key_here" \
129+
http://localhost:8000/users/{user_id}/sleep?days=7
130+
131+
# Response headers include:
132+
# X-RateLimit-Limit: 1000
133+
# X-RateLimit-Remaining: 999
134+
# X-RateLimit-Reset: 1704067200
135+
```
136+
137+
### Rate Limiting
138+
139+
- Default: 1000 requests per hour per API key
140+
- Rate limits reset hourly
141+
- Master API key (`API_KEY` env var) bypasses rate limiting
142+
- Rate limit info returned in response headers
143+
144+
## OAuth Integration
145+
146+
For applications that need to integrate with polar-flow-server (e.g., mobile apps, web frontends):
147+
148+
### OAuth Flow
149+
150+
1. **Start OAuth** - Redirect user to `/oauth/start?client_id=YOUR_CLIENT_ID`
151+
2. **User Authorizes** - User logs into Polar and grants access
152+
3. **Callback** - Server receives auth code, creates user, generates temp code
153+
4. **Exchange** - Your app exchanges temp code for API key:
91154

92155
```bash
93-
# Database
94-
DATABASE_URL=postgresql+asyncpg://polar:polar@postgres:5432/polar
156+
POST /oauth/exchange
157+
Content-Type: application/json
158+
159+
{
160+
"code": "temp_code_from_callback",
161+
"client_id": "YOUR_CLIENT_ID"
162+
}
163+
164+
# Response:
165+
{
166+
"api_key": "pfk_...",
167+
"user_id": "polar_user_id",
168+
"expires_in": null
169+
}
170+
```
95171

96-
# Deployment mode
97-
DEPLOYMENT_MODE=self_hosted
172+
5. **Use API Key** - Make authenticated requests with the API key
98173

99-
# Sync settings
100-
SYNC_INTERVAL_HOURS=1
101-
SYNC_ON_STARTUP=false
102-
SYNC_DAYS_LOOKBACK=28
174+
### Key Management
175+
176+
```bash
177+
# Get key info
178+
GET /users/{user_id}/api-key/info
179+
X-API-Key: pfk_...
103180

104-
# Optional: Set explicit encryption key (auto-generated otherwise)
105-
# ENCRYPTION_KEY=your-32-byte-fernet-key
181+
# Regenerate key (invalidates old key)
182+
POST /users/{user_id}/api-key/regenerate
183+
X-API-Key: pfk_...
184+
185+
# Revoke key
186+
POST /users/{user_id}/api-key/revoke
187+
X-API-Key: pfk_...
106188
```
107189

108190
## API Endpoints
@@ -112,23 +194,26 @@ SYNC_DAYS_LOOKBACK=28
112194
curl http://localhost:8000/health
113195

114196
# Get sleep data (last 7 days)
115-
curl "http://localhost:8000/users/{user_id}/sleep?days=7"
197+
curl -H "X-API-Key: pfk_..." \
198+
"http://localhost:8000/users/{user_id}/sleep?days=7"
116199

117200
# Get activity data
118-
curl "http://localhost:8000/users/{user_id}/activity?days=7"
201+
curl -H "X-API-Key: pfk_..." \
202+
"http://localhost:8000/users/{user_id}/activity?days=7"
119203

120204
# Get nightly recharge (HRV)
121-
curl "http://localhost:8000/users/{user_id}/recharge?days=7"
205+
curl -H "X-API-Key: pfk_..." \
206+
"http://localhost:8000/users/{user_id}/recharge?days=7"
122207

123208
# Get exercises
124-
curl "http://localhost:8000/users/{user_id}/exercises?days=30"
209+
curl -H "X-API-Key: pfk_..." \
210+
"http://localhost:8000/users/{user_id}/exercises?days=30"
125211

126212
# Export summary
127-
curl "http://localhost:8000/users/{user_id}/export/summary?days=30"
213+
curl -H "X-API-Key: pfk_..." \
214+
"http://localhost:8000/users/{user_id}/export/summary?days=30"
128215
```
129216

130-
**Optional Authentication:** Set `API_KEY` environment variable to require `X-API-Key` header on all data endpoints. If not set, endpoints are open.
131-
132217
## Development
133218

134219
```bash
@@ -163,24 +248,21 @@ docker-compose -f docker-compose.prod.yml up -d
163248

164249
**Coolify, Railway, Render, etc.** - Point at the GitHub repo, it builds from the Dockerfile.
165250

166-
**Database migrations** run automatically on startup.
167-
168-
### Optional Environment Variables
251+
**Required for production:**
252+
- Set `ENCRYPTION_KEY` environment variable (tokens won't persist across restarts otherwise)
253+
- Set `DATABASE_URL` to your PostgreSQL instance
169254

170-
| Variable | Description | Default |
171-
|----------|-------------|---------|
172-
| `API_KEY` | Require authentication on data endpoints | None (open) |
173-
| `SYNC_INTERVAL_HOURS` | Auto-sync frequency | 1 |
174-
| `LOG_LEVEL` | Logging verbosity | INFO |
255+
**Database migrations** run automatically on startup.
175256

176257
## Multi-Tenancy
177258

178259
The server supports multiple users out of the box:
179260

180261
- Every table includes `user_id` column
181262
- All queries scoped by `user_id`
263+
- Per-user API keys ensure users can only access their own data
182264
- Self-hosted: typically one user
183-
- SaaS: many users, same codebase
265+
- Multi-user: many users, same codebase
184266

185267
## Built With
186268

src/polar_flow_server/app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ def create_app() -> Litestar:
9393
cookie_name="csrf_token",
9494
header_name="X-CSRF-Token",
9595
exclude=[
96+
"/admin/login", # Login form - entry point, no session yet
97+
"/admin/setup", # Setup flow - entry point, no session yet
9698
"/admin/oauth/callback", # OAuth callback from Polar (admin dashboard)
9799
"/oauth/", # OAuth endpoints for SaaS (callback, exchange, start)
98100
"/users/", # API routes use API key auth, not sessions

0 commit comments

Comments
 (0)