From 8c6db1208348cc692d9ba035079e99439d93d3b8 Mon Sep 17 00:00:00 2001 From: Leo Le Bleis Date: Sat, 2 May 2026 23:37:52 +0100 Subject: [PATCH] fix(auth): use Header() instead of APIKeyHeader for WS compat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Security(APIKeyHeader) only works on HTTP routes — its __call__ requires a Request object that WebSocket routes don't pass, causing a 500 with "APIKeyHeader.__call__() missing 1 required positional argument: 'request'" on the terminal WS endpoint. Header() works on both transports. Trade-off: OpenAPI docs lose the security scheme annotation; auth still enforced identically. Also: pre-create /home/appuser/.claude in the image so the named volume inherits appuser ownership when Docker first populates it from the image. Without this the empty mount point is root-owned and 'claude login' can't write OAuth tokens. --- Dockerfile | 5 +++++ src/brew/dependencies.py | 9 ++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4885987..0636456 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,6 +40,11 @@ RUN useradd --create-home appuser USER appuser WORKDIR /app +# Pre-create ~/.claude so the named volume inherits appuser ownership when +# Docker first populates it from the image — otherwise the empty mount point +# is root-owned and `claude login` can't write OAuth tokens. +RUN mkdir -p /home/appuser/.claude + COPY --from=builder /app/.venv /app/.venv COPY --from=frontend-build /app/frontend/dist /app/frontend/dist diff --git a/src/brew/dependencies.py b/src/brew/dependencies.py index bc9465a..b1532fb 100644 --- a/src/brew/dependencies.py +++ b/src/brew/dependencies.py @@ -1,13 +1,10 @@ from functools import lru_cache from typing import Annotated -from fastapi import Depends, HTTPException, Query, Security, status -from fastapi.security import APIKeyHeader +from fastapi import Depends, Header, HTTPException, Query, status from brew.config import Settings -api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False) - @lru_cache(maxsize=1) def get_settings() -> Settings: @@ -16,7 +13,9 @@ def get_settings() -> Settings: async def require_api_key( settings: Annotated[Settings, Depends(get_settings)], - header_key: Annotated[str | None, Security(api_key_header)] = None, + # Header() works on both HTTP and WebSocket routes; fastapi.security.APIKeyHeader + # is HTTP-only — it expects a Request object that WS routes don't provide. + header_key: Annotated[str | None, Header(alias="X-API-Key")] = None, query_key: Annotated[str | None, Query(alias="api_key")] = None, ) -> None: if settings.api_key is None: