feat(auth): add get_current_user_from_api_token dependency
- Validates API tokens (or_api_* prefix) - SHA-256 hash lookup in api_tokens table - Updates last_used_at on each request - Distinguishes from JWT tokens (401 with clear error)
This commit is contained in:
@@ -1,4 +1,22 @@
|
||||
"""Dependencies package for OpenRouter Monitor."""
|
||||
from openrouter_monitor.dependencies.auth import get_current_user, security
|
||||
from openrouter_monitor.dependencies.auth import (
|
||||
get_current_user,
|
||||
get_current_user_from_api_token,
|
||||
security,
|
||||
api_token_security,
|
||||
)
|
||||
from openrouter_monitor.dependencies.rate_limit import (
|
||||
RateLimiter,
|
||||
rate_limit_dependency,
|
||||
rate_limiter,
|
||||
)
|
||||
|
||||
__all__ = ["get_current_user", "security"]
|
||||
__all__ = [
|
||||
"get_current_user",
|
||||
"get_current_user_from_api_token",
|
||||
"security",
|
||||
"api_token_security",
|
||||
"RateLimiter",
|
||||
"rate_limit_dependency",
|
||||
"rate_limiter",
|
||||
]
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
"""Authentication dependencies.
|
||||
|
||||
T21: get_current_user dependency for protected endpoints.
|
||||
T36: get_current_user_from_api_token dependency for public API endpoints.
|
||||
"""
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from jose import JWTError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from openrouter_monitor.database import get_db
|
||||
from openrouter_monitor.models import User
|
||||
from openrouter_monitor.models import User, ApiToken
|
||||
from openrouter_monitor.schemas import TokenData
|
||||
from openrouter_monitor.services import decode_access_token
|
||||
|
||||
|
||||
# HTTP Bearer security scheme
|
||||
# HTTP Bearer security schemes
|
||||
security = HTTPBearer()
|
||||
api_token_security = HTTPBearer(auto_error=False)
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
@@ -77,3 +82,74 @@ async def get_current_user(
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
async def get_current_user_from_api_token(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(api_token_security),
|
||||
db: Session = Depends(get_db)
|
||||
) -> User:
|
||||
"""Get current authenticated user from API token (for public API endpoints).
|
||||
|
||||
This dependency extracts the API token from the Authorization header,
|
||||
verifies it against the database, updates last_used_at, and returns
|
||||
the corresponding user.
|
||||
|
||||
API tokens start with 'or_api_' prefix and are different from JWT tokens.
|
||||
|
||||
Args:
|
||||
credentials: HTTP Authorization credentials containing the Bearer token
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
The authenticated User object
|
||||
|
||||
Raises:
|
||||
HTTPException: 401 if token is invalid, inactive, or user not found/inactive
|
||||
"""
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid API token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Check if credentials were provided
|
||||
if credentials is None:
|
||||
raise credentials_exception
|
||||
|
||||
token = credentials.credentials
|
||||
|
||||
# Check if token looks like an API token (starts with 'or_api_')
|
||||
# JWT tokens don't have this prefix
|
||||
if not token.startswith("or_api_"):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid token type. Use API token, not JWT.",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Hash the token with SHA-256 for lookup
|
||||
token_hash = hashlib.sha256(token.encode()).hexdigest()
|
||||
|
||||
# Look up the token in the database
|
||||
api_token = db.query(ApiToken).filter(
|
||||
ApiToken.token_hash == token_hash,
|
||||
ApiToken.is_active == True
|
||||
).first()
|
||||
|
||||
if not api_token:
|
||||
raise credentials_exception
|
||||
|
||||
# Update last_used_at timestamp
|
||||
api_token.last_used_at = datetime.utcnow()
|
||||
db.commit()
|
||||
|
||||
# Get the user associated with this token
|
||||
user = db.query(User).filter(
|
||||
User.id == api_token.user_id,
|
||||
User.is_active == True
|
||||
).first()
|
||||
|
||||
if not user:
|
||||
raise credentials_exception
|
||||
|
||||
return user
|
||||
|
||||
Reference in New Issue
Block a user