Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/forms-flow-data-layer-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 3 additions & 2 deletions forms-flow-data-layer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.13.2-slim-bullseye
FROM python:3.13.9-slim-bookworm

LABEL project="formsflow-data-layer"

Expand Down Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions forms-flow-data-layer/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
25 changes: 14 additions & 11 deletions forms-flow-data-layer/src/utils/keycloak_oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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]
Expand Down
Loading