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."""
|
"""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.
|
"""Authentication dependencies.
|
||||||
|
|
||||||
T21: get_current_user dependency for protected endpoints.
|
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 import Depends, HTTPException, status
|
||||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||||
from jose import JWTError
|
from jose import JWTError
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from openrouter_monitor.database import get_db
|
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.schemas import TokenData
|
||||||
from openrouter_monitor.services import decode_access_token
|
from openrouter_monitor.services import decode_access_token
|
||||||
|
|
||||||
|
|
||||||
# HTTP Bearer security scheme
|
# HTTP Bearer security schemes
|
||||||
security = HTTPBearer()
|
security = HTTPBearer()
|
||||||
|
api_token_security = HTTPBearer(auto_error=False)
|
||||||
|
|
||||||
|
|
||||||
async def get_current_user(
|
async def get_current_user(
|
||||||
@@ -77,3 +82,74 @@ async def get_current_user(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return 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