stream-fusion-old/stream_fusion/web/api/auth/views.py
2024-09-23 13:56:06 +00:00

278 lines
No EOL
10 KiB
Python

from datetime import datetime, timezone
from typing import List, Optional
from uuid import UUID
import aiohttp
from fastapi import APIRouter, Depends, HTTPException, Query
from stream_fusion.services.postgresql.dao.apikey_dao import APIKeyDAO
from stream_fusion.services.postgresql.schemas.apikey_schemas import APIKeyCreate, APIKeyUpdate
from stream_fusion.services.rd_conn.token_manager import RealDebridService
from stream_fusion.settings import settings
from stream_fusion.utils.security import secret_based_security
from stream_fusion.web.api.auth.schemas import UsageLog, UsageLogs
from stream_fusion.logging_config import logger
router = APIRouter()
rd_service = RealDebridService()
def ensure_uuid(api_key: str) -> UUID:
if isinstance(api_key, UUID):
return api_key
try:
return UUID(api_key)
except ValueError:
logger.error(f"Invalid API key format: {api_key}")
raise HTTPException(status_code=400, detail="Invalid API key format")
@router.post(
"/new",
dependencies=[Depends(secret_based_security)],
include_in_schema=settings.security_hide_docs,
)
async def create_new_api_key(
name: str = Query(None, description="Set API key name"),
never_expires: bool = Query(False, description="If set, the created API key will never be considered expired"),
apikey_dao: APIKeyDAO = Depends()
) -> str:
logger.info(f"Creating new API key. Name: {name}, Never expires: {never_expires}")
api_key = await apikey_dao.create_key(APIKeyCreate(
name=name,
never_expire=never_expires
))
logger.info(f"New API key created successfully. Key: {api_key.api_key}")
return api_key.api_key
@router.get(
"/get/{api_key}",
dependencies=[Depends(secret_based_security)],
include_in_schema=settings.security_hide_docs,
)
async def get_api_key(
api_key: str,
apikey_dao: APIKeyDAO = Depends()
):
api_key_uuid = ensure_uuid(api_key)
logger.info(f"Retrieving API key: {api_key_uuid}")
key = await apikey_dao.get_key_by_uuid(api_key_uuid)
if not key:
logger.warning(f"API key not found: {api_key_uuid}")
raise HTTPException(status_code=404, detail="API key not found")
return key.dict()
@router.get(
"/get_by_name/{name}",
dependencies=[Depends(secret_based_security)],
include_in_schema=settings.security_hide_docs,
)
async def get_api_keys_by_name(
name: str,
apikey_dao: APIKeyDAO = Depends()
):
logger.info(f"Retrieving API keys with name: {name}")
keys = await apikey_dao.get_keys_by_name(name)
return [key.dict() for key in keys]
@router.put(
"/revoke/{api_key}",
dependencies=[Depends(secret_based_security)],
include_in_schema=settings.security_hide_docs,
)
async def revoke_api_key(
api_key: str,
apikey_dao: APIKeyDAO = Depends()
):
api_key_uuid = ensure_uuid(api_key)
logger.info(f"Attempting to revoke API key: {api_key_uuid}")
result = await apikey_dao.revoke_key(api_key_uuid)
if not result:
logger.warning(f"API key not found for revocation: {api_key_uuid}")
raise HTTPException(status_code=404, detail="API key not found")
logger.info(f"API key successfully revoked: {api_key_uuid}")
return {"message": "API key successfully revoked"}
@router.put(
"/renew/{api_key}",
dependencies=[Depends(secret_based_security)],
include_in_schema=settings.security_hide_docs,
)
async def renew_api_key(
api_key: str,
apikey_dao: APIKeyDAO = Depends()
):
api_key_uuid = ensure_uuid(api_key)
logger.info(f"Attempting to renew API key: {api_key_uuid}")
try:
updated_key = await apikey_dao.renew_key(api_key_uuid)
logger.info(f"API key renewed successfully: {api_key_uuid}")
return updated_key.dict()
except HTTPException as e:
logger.error(f"HTTP exception occurred while renewing API key {api_key_uuid}: {str(e)}")
raise e
except Exception as e:
logger.error(f"Unexpected error occurred while renewing API key {api_key_uuid}: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@router.get(
"/logs",
dependencies=[Depends(secret_based_security)],
response_model=UsageLogs,
include_in_schema=settings.security_hide_docs,
)
async def get_api_key_usage_logs(
apikey_dao: APIKeyDAO = Depends()
):
logger.info("Retrieving usage logs for all API keys")
usage_stats = await apikey_dao.get_usage_stats()
logger.info(f"Retrieved usage stats for {len(usage_stats)} API keys")
current_time = datetime.now(timezone.utc)
return UsageLogs(
logs=[
UsageLog(
api_key=key.api_key,
# Update is_active based on expiration status
is_active=key.is_active and (key.never_expire or (key.expiration_date and key.expiration_date > current_time)),
never_expire=key.never_expire,
expiration_date=key.expiration_date,
latest_query_date=key.latest_query_date,
total_queries=key.total_queries,
name=key.name,
)
for key in usage_stats
],
)
@router.get(
"/list",
dependencies=[Depends(secret_based_security)],
include_in_schema=settings.security_hide_docs,
)
async def list_active_api_keys(
apikey_dao: APIKeyDAO = Depends()
) -> List[dict]:
logger.info("Retrieving list of active API keys")
active_keys = await apikey_dao.list_active_keys()
logger.info(f"Retrieved {len(active_keys)} active API keys")
return [key.dict() for key in active_keys]
@router.put(
"/update/{api_key}",
dependencies=[Depends(secret_based_security)],
include_in_schema=settings.security_hide_docs,
)
async def update_api_key(
api_key: str,
name: Optional[str] = Query(None, description="New name for the API key"),
is_active: Optional[bool] = Query(None, description="Set the active status of the API key"),
expiration_date: Optional[datetime] = Query(None, description="New expiration date for the API key"),
apikey_dao: APIKeyDAO = Depends()
):
api_key_uuid = ensure_uuid(api_key)
logger.info(f"Attempting to update API key: {api_key_uuid}")
update_data = APIKeyUpdate(
name=name,
is_active=is_active,
expiration_date=expiration_date
)
try:
updated_key = await apikey_dao.update_key(api_key_uuid, update_data)
logger.info(f"API key updated successfully: {api_key_uuid}")
return updated_key.dict()
except HTTPException as e:
logger.error(f"HTTP exception occurred while updating API key {api_key_uuid}: {str(e)}")
raise e
except Exception as e:
logger.error(f"Unexpected error occurred while updating API key {api_key_uuid}: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@router.delete(
"/delete/{api_key}",
dependencies=[Depends(secret_based_security)],
include_in_schema=settings.security_hide_docs,
)
async def delete_api_key(
api_key: str,
apikey_dao: APIKeyDAO = Depends()
):
api_key_uuid = ensure_uuid(api_key)
logger.info(f"Attempting to delete API key: {api_key_uuid}")
try:
result = await apikey_dao.delete_key(api_key_uuid)
if result:
logger.info(f"API key deleted successfully: {api_key_uuid}")
return {"message": "API key successfully deleted"}
else:
logger.warning(f"API key not found for deletion: {api_key_uuid}")
raise HTTPException(status_code=404, detail="API key not found")
except Exception as e:
logger.error(f"An error occurred while deleting API key {api_key_uuid}: {str(e)}")
raise HTTPException(status_code=500, detail=f"An error occurred while deleting the API key: {str(e)}")
@router.get(
"/check/{api_key}",
dependencies=[Depends(secret_based_security)],
include_in_schema=settings.security_hide_docs,
)
async def check_api_key(
api_key: str,
apikey_dao: APIKeyDAO = Depends()
):
api_key_uuid = ensure_uuid(api_key)
logger.info(f"Checking API key: {api_key_uuid}")
is_valid = await apikey_dao.check_key(api_key_uuid)
return {"is_valid": is_valid}
@router.post(
"/realdebrid/device_code",
include_in_schema=settings.security_hide_docs,
)
async def get_device_code():
logger.info("Requesting device code from RealDebrid")
results = await rd_service.get_device_code()
logger.info("Device code received successfully")
return results
@router.post("/realdebrid/credentials",
include_in_schema=settings.security_hide_docs
)
async def get_credentials(device_code: str):
logger.info(f"Requesting credentials for device code: {device_code}")
results = await rd_service.get_credentials(device_code)
logger.info("Credentials received successfully")
return results
@router.post("/realdebrid/token",
include_in_schema=settings.security_hide_docs
)
async def get_token(client_id: str, client_secret: str, device_code: str):
logger.info(f"Requesting token for device code: {device_code}")
results = await rd_service.get_token(client_id, client_secret, device_code)
logger.info("Token received successfully")
return results
@router.get("/alldebrid/pin/get")
async def get_alldebrid_pin():
logger.info("Requesting PIN code for AllDebrid")
async with aiohttp.ClientSession() as session:
async with session.get(f"https://api.alldebrid.com/v4/pin/get?agent={settings.ad_user_app}") as response:
if response.status != 200:
logger.error(f"Error retrieving AllDebrid PIN: {response.status}")
raise HTTPException(status_code=response.status, detail="Error retrieving AllDebrid PIN")
data = await response.json()
logger.info("AllDebrid PIN code received successfully")
return data
@router.get("/alldebrid/pin/check")
async def check_alldebrid_pin(check: str, pin: str):
logger.info(f"Verifying AllDebrid PIN code: {pin}")
async with aiohttp.ClientSession() as session:
async with session.get(f"https://api.alldebrid.com/v4/pin/check?check={check}&pin={pin}&agent={settings.ad_user_app}") as response:
if response.status != 200:
logger.error(f"Error verifying AllDebrid PIN: {response.status}")
raise HTTPException(status_code=response.status, detail="Error verifying AllDebrid PIN")
data = await response.json()
logger.info("AllDebrid PIN code verification successful")
return data