fix(serve): use X-Secret-Key header for REST API auth to match pywidevine

Standardize on X-Secret-Key across all endpoints so RemoteClient, pywidevine CDM, and api-only mode all use the same auth header. Adds lightweight middleware for --api-only mode without pywidevine dep.
This commit is contained in:
Andy
2026-03-16 19:08:03 -06:00
parent b5325c9c47
commit bfab2ad5c9
3 changed files with 13 additions and 3 deletions

View File

@@ -119,13 +119,23 @@ def serve(
config.serve["playready_devices"] = [] config.serve["playready_devices"] = []
config.serve["playready_devices"].extend(list(config.directories.prds.glob("*.prd"))) config.serve["playready_devices"].extend(list(config.directories.prds.glob("*.prd")))
@web.middleware
async def api_key_authentication(request: web.Request, handler) -> web.Response:
"""Authenticate API requests using X-Secret-Key header."""
secret_key = request.headers.get("X-Secret-Key")
if not secret_key:
return web.json_response({"status": 401, "message": "Secret Key is Empty."}, status=401)
if secret_key not in request.app["config"]["users"]:
return web.json_response({"status": 401, "message": "Secret Key is Invalid."}, status=401)
return await handler(request)
if api_only: if api_only:
log.info("Starting REST API server (pywidevine/pyplayready CDM disabled)") log.info("Starting REST API server (pywidevine/pyplayready CDM disabled)")
if no_key: if no_key:
app = web.Application(middlewares=[cors_middleware]) app = web.Application(middlewares=[cors_middleware])
app["config"] = {"users": {}} app["config"] = {"users": {}}
else: else:
app = web.Application(middlewares=[cors_middleware, pywidevine_serve.authentication]) app = web.Application(middlewares=[cors_middleware, api_key_authentication])
app["config"] = {"users": {api_secret: {"devices": [], "username": "api_user"}}} app["config"] = {"users": {api_secret: {"devices": [], "username": "api_user"}}}
app["debug_api"] = debug_api app["debug_api"] = debug_api

View File

@@ -1367,7 +1367,7 @@ async def session_create_handler(data: Dict[str, Any], request: Optional[web.Req
from unshackle.core.config import config as app_config from unshackle.core.config import config as app_config
session_id = str(uuid_mod.uuid4()) session_id = str(uuid_mod.uuid4())
api_key = request.headers.get("X-API-Key", "anonymous") if request else "anonymous" api_key = request.headers.get("X-Secret-Key", "anonymous") if request else "anonymous"
api_key_hash = hashlib.sha256(api_key.encode()).hexdigest()[:12] api_key_hash = hashlib.sha256(api_key.encode()).hexdigest()[:12]
session_cache_tag = f"_sessions/{api_key_hash}/{session_id}/{normalized_service}" session_cache_tag = f"_sessions/{api_key_hash}/{session_id}/{normalized_service}"

View File

@@ -47,7 +47,7 @@ class RemoteClient:
if self._session is None: if self._session is None:
self._session = requests.Session() self._session = requests.Session()
if self.api_key: if self.api_key:
self._session.headers["X-API-Key"] = self.api_key self._session.headers["X-Secret-Key"] = self.api_key
return self._session return self._session
def _request(self, method: str, endpoint: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: def _request(self, method: str, endpoint: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: