fix(fitness): eliminate cross-user data access (#4)
- All user_id query params now enforced to authenticated user's own ID - /api/users restricted to return only current user (no user enumeration) - Wildcard CORS headers removed (service is internal-only via gateway) - Covers: entries, totals, goals, templates, favorites, goal setting Closes #4
This commit is contained in:
@@ -1977,7 +1977,7 @@ class CalorieHandler(BaseHTTPRequestHandler):
|
||||
self.send_response(status)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.send_header('Content-Length', len(body))
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
# CORS removed — service is internal only
|
||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key, X-Telegram-User-Id')
|
||||
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS')
|
||||
self.end_headers()
|
||||
@@ -2002,7 +2002,7 @@ class CalorieHandler(BaseHTTPRequestHandler):
|
||||
def do_OPTIONS(self):
|
||||
"""Handle CORS preflight."""
|
||||
self.send_response(204)
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
# CORS removed — service is internal only
|
||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key, X-Telegram-User-Id')
|
||||
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS')
|
||||
self.end_headers()
|
||||
@@ -2116,21 +2116,21 @@ class CalorieHandler(BaseHTTPRequestHandler):
|
||||
# GET /api/entries?date=...&user_id=...
|
||||
if path == '/api/entries':
|
||||
entry_date = params.get('date', [date.today().isoformat()])[0]
|
||||
target_user = params.get('user_id', [user['id']])[0]
|
||||
target_user = user['id'] # enforced: no cross-user access
|
||||
entries = get_entries_by_date(target_user, entry_date)
|
||||
return self._send_json(entries)
|
||||
|
||||
# GET /api/entries/totals?date=...
|
||||
if path == '/api/entries/totals':
|
||||
entry_date = params.get('date', [date.today().isoformat()])[0]
|
||||
target_user = params.get('user_id', [user['id']])[0]
|
||||
target_user = user['id'] # enforced: no cross-user access
|
||||
totals = get_daily_totals(target_user, entry_date)
|
||||
return self._send_json(totals)
|
||||
|
||||
# GET /api/goals/for-date?date=...&user_id=...
|
||||
if path == '/api/goals/for-date':
|
||||
for_date = params.get('date', [date.today().isoformat()])[0]
|
||||
target_user = params.get('user_id', [user['id']])[0]
|
||||
target_user = user['id'] # enforced: no cross-user access
|
||||
goal = get_goals_for_date(target_user, for_date)
|
||||
if goal:
|
||||
return self._send_json(goal)
|
||||
@@ -2138,7 +2138,7 @@ class CalorieHandler(BaseHTTPRequestHandler):
|
||||
|
||||
# GET /api/goals?user_id=...
|
||||
if path == '/api/goals':
|
||||
target_user = params.get('user_id', [user['id']])[0]
|
||||
target_user = user['id'] # enforced: no cross-user access
|
||||
conn = get_db()
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM goals WHERE user_id = ? ORDER BY start_date DESC",
|
||||
@@ -2190,12 +2190,9 @@ class CalorieHandler(BaseHTTPRequestHandler):
|
||||
conn.close()
|
||||
return self._send_json([dict(r) for r in rows])
|
||||
|
||||
# GET /api/users (list all users — for switching view)
|
||||
# GET /api/users — restricted: only returns the current user
|
||||
if path == '/api/users':
|
||||
conn = get_db()
|
||||
rows = conn.execute("SELECT id, username, display_name FROM users").fetchall()
|
||||
conn.close()
|
||||
return self._send_json([dict(r) for r in rows])
|
||||
return self._send_json([{"id": user["id"], "username": user["username"], "display_name": user["display_name"]}])
|
||||
|
||||
self._send_json({'error': 'Not found'}, 404)
|
||||
|
||||
@@ -2222,7 +2219,7 @@ class CalorieHandler(BaseHTTPRequestHandler):
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.send_header('Set-Cookie', f'session={token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000')
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
# CORS removed — service is internal only
|
||||
body = json.dumps({'token': token, 'user': {
|
||||
'id': user['id'], 'username': user['username'], 'display_name': user['display_name']
|
||||
}}).encode()
|
||||
@@ -2583,7 +2580,7 @@ class CalorieHandler(BaseHTTPRequestHandler):
|
||||
# PUT /api/goals
|
||||
if path == '/api/goals':
|
||||
data = self._read_body()
|
||||
target_user = data.get('user_id', user['id'])
|
||||
target_user = user['id'] # enforced: no cross-user access
|
||||
start_date = data.get('start_date', date.today().isoformat())
|
||||
|
||||
conn = get_db()
|
||||
|
||||
Reference in New Issue
Block a user