fix(security): RBAC on legacy create endpoints, hashed API keys, hardening
Addresses findings from the security audit: - H1: add check_project_role to the legacy misc.py create endpoints (milestones=mgr, tasks/supports/meetings=dev) that previously required only authentication — closing a cross-project write bypass available to any logged-in user or agent API key. - M2: comments are always attributed to the authenticated caller; the client-supplied author_id is dropped (no author spoofing). - M3: API keys are stored as SHA-256 hashes (key_hash) plus a short key_prefix for display — never plaintext. Lookup hashes the presented key; listings never expose the secret. Includes an idempotent migration for existing deployments. - M5: the OIDC session cookie's Secure flag is env-driven via SESSION_COOKIE_SECURE (default True; set false for plain-HTTP dev). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
"""Shared auth dependencies."""
|
||||
import hashlib
|
||||
from datetime import datetime, timedelta
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer, APIKeyHeader
|
||||
@@ -59,11 +60,17 @@ async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = De
|
||||
return user
|
||||
|
||||
|
||||
def hash_api_key(raw: str) -> str:
|
||||
"""SHA-256 of a raw API key. Keys are high-entropy random tokens, so a
|
||||
fast hash (not bcrypt) is appropriate and allows O(1) lookup by hash."""
|
||||
return hashlib.sha256(raw.encode()).hexdigest()
|
||||
|
||||
|
||||
def _lookup_api_key(db: Session, key: str) -> models.User | None:
|
||||
"""Resolve an API key string to a User; mark last_used_at on hit."""
|
||||
if not key:
|
||||
return None
|
||||
key_obj = db.query(APIKey).filter(APIKey.key == key, APIKey.is_active == True).first() # noqa: E712
|
||||
key_obj = db.query(APIKey).filter(APIKey.key_hash == hash_api_key(key), APIKey.is_active == True).first() # noqa: E712
|
||||
if not key_obj:
|
||||
return None
|
||||
key_obj.last_used_at = datetime.utcnow()
|
||||
|
||||
Reference in New Issue
Block a user