diff --git a/.github/workflows/forms-flow-data-layer-ci.yml b/.github/workflows/forms-flow-data-layer-ci.yml index 7731d319eb..62e88fc89e 100644 --- a/.github/workflows/forms-flow-data-layer-ci.yml +++ b/.github/workflows/forms-flow-data-layer-ci.yml @@ -64,7 +64,7 @@ jobs: strategy: matrix: - python-version: [3.12.11] + python-version: [3.12.12] env: JWT_OIDC_JWKS_URI: "http://localhost:8081/auth/realms/forms-flow-ai/protocol/openid-connect/certs" diff --git a/forms-flow-data-layer/Dockerfile b/forms-flow-data-layer/Dockerfile index 8c9e8ffa2c..ce6b4a55ae 100644 --- a/forms-flow-data-layer/Dockerfile +++ b/forms-flow-data-layer/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.13.2-slim-bullseye +FROM python:3.13.9-slim-bookworm LABEL project="formsflow-data-layer" @@ -26,7 +26,8 @@ COPY src/ ./src COPY main.py . # Security: Add non-root user -RUN adduser --disabled-password --gecos '' appuser && chown -R appuser /formsflow-data-layer +RUN adduser --disabled-password --gecos '' appuser && chown -R appuser /formsflow-data-layer \ + && pip uninstall -y ecdsa USER appuser EXPOSE 8000 diff --git a/forms-flow-data-layer/requirements.txt b/forms-flow-data-layer/requirements.txt index deb261e677..e28e0ec34e 100644 --- a/forms-flow-data-layer/requirements.txt +++ b/forms-flow-data-layer/requirements.txt @@ -8,7 +8,6 @@ charset-normalizer==3.4.1 click==8.1.8 colorama==0.4.6 dnspython==2.7.0 -ecdsa==0.19.1 email_validator==2.2.0 fastapi==0.116.1 fastapi-cli==0.0.7 @@ -35,7 +34,7 @@ Pygments==2.19.1 pymongo==4.11.3 python-dateutil==2.9.0.post0 python-dotenv==1.0.1 -python-jose==3.4.0 +python-jose[cryptography]==3.4.0 python-multipart==0.0.20 PyYAML==6.0.2 redis==5.2.1 diff --git a/forms-flow-data-layer/src/utils/keycloak_oidc.py b/forms-flow-data-layer/src/utils/keycloak_oidc.py index d82f566703..33aab6c04f 100644 --- a/forms-flow-data-layer/src/utils/keycloak_oidc.py +++ b/forms-flow-data-layer/src/utils/keycloak_oidc.py @@ -33,26 +33,28 @@ async def __fetch_keys(self) -> Dict[str, Any]: response.raise_for_status() jwks = response.json() logger.info("Got response form keycloak [public key]") - keys = jwks.get("keys", []) - # Filter only signing keys with RS256 - signing_keys = { - key["kid"]: jwk.construct(key) - for key in keys - if key.get("use") == "sig" - and key.get("alg") == "RS256" - and key.get("kid") - } - return signing_keys + return jwks except Exception as e: raise RuntimeError(f"Failed to fetch Keycloak public keys: {str(e)}") from e + async def __get__signing_keys(self, public_keys) -> Dict[str, Any]: + """Retrieving signing public keys from the public keys.""" + keys = public_keys.get("keys", []) + signing_public_keys = { + key["kid"]: jwk.construct(key) + for key in keys + if key.get("use") == "sig" and key.get("alg") == "RS256" and key.get("kid") + } + return signing_public_keys + async def __get_public_keys(self) -> Dict[str, Any]: """Retrieve public keys from cache or fetch if not present.""" public_keys = self.cache.get("public_keys") if public_keys is None: public_keys = await self.__fetch_keys() self.cache.set("public_keys", public_keys) - return public_keys + signing_keys = await self.__get__signing_keys(public_keys) + return signing_keys async def verify_token(self, token: str) -> Dict[str, Any]: """Verify the JWT token and return the payload if valid.""" @@ -66,6 +68,7 @@ async def verify_token(self, token: str) -> Dict[str, Any]: public_keys = await self.__fetch_keys() self.cache.set("public_keys", public_keys) kid = headers.get("kid") + public_keys = await self.__get__signing_keys(public_keys) if not kid or kid not in public_keys: raise JWTError("Public key not found for 'kid'") public_key = public_keys[kid]