From 1e2e395676f7b1bacf0b805373bfcc327e3ac1ec Mon Sep 17 00:00:00 2001 From: Roberto Musso Date: Sun, 22 Mar 2026 01:03:28 +0100 Subject: [PATCH] fix: PEM newline parsing + shared config extra=ignore - Add field_validator to expand literal \n in PEM keys (auth config + shared config) - Set extra='ignore' on shared Settings so service-specific .env vars don't cause ValidationError - Add *.pem to .gitignore --- .gitignore | 3 +++ services/auth/app/config.py | 8 ++++++++ shared/config.py | 13 ++++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b4418da..8e6f860 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ env/ # Environment variables .env +# Cryptographic keys +*.pem + # IDE .vscode/ .idea/ diff --git a/services/auth/app/config.py b/services/auth/app/config.py index 641f1c1..d92e6e8 100644 --- a/services/auth/app/config.py +++ b/services/auth/app/config.py @@ -4,6 +4,7 @@ Contains secrets that ONLY the Auth Service needs (e.g., JWT private key). These are NOT in shared/config.py to prevent other services from accessing them. """ +from pydantic import field_validator from pydantic_settings import BaseSettings, SettingsConfigDict @@ -20,6 +21,13 @@ class AuthSettings(BaseSettings): # openssl rsa -in private.pem -pubout -out public.pem JWT_PUBLIC_KEY: str = "" + @field_validator("JWT_PRIVATE_KEY", "JWT_PUBLIC_KEY", mode="before") + @classmethod + def _expand_pem_newlines(cls, v: str) -> str: + if isinstance(v, str) and r"\n" in v: + return v.replace(r"\n", "\n") + return v + model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8") diff --git a/shared/config.py b/shared/config.py index 50df4a7..5cf0b18 100644 --- a/shared/config.py +++ b/shared/config.py @@ -6,6 +6,7 @@ of the vars, but keeping one Settings class avoids fragmentation. from typing import Literal +from pydantic import field_validator from pydantic_settings import BaseSettings, SettingsConfigDict @@ -18,6 +19,14 @@ class Settings(BaseSettings): # JWTs locally (optional — Traefik ForwardAuth handles this in prod). # The private key lives ONLY in the Auth Service config. JWT_PUBLIC_KEY: str = "" + + @field_validator("JWT_PUBLIC_KEY", mode="before") + @classmethod + def _expand_pem_newlines(cls, v: str) -> str: + if isinstance(v, str) and r"\n" in v: + return v.replace(r"\n", "\n") + return v + JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = 30 @@ -67,7 +76,9 @@ class Settings(BaseSettings): # ── Environment ────────────────────────────────────────────────── ENV: Literal["dev", "prod"] = "dev" - model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8") + model_config = SettingsConfigDict( + env_file=".env", env_file_encoding="utf-8", extra="ignore" + ) settings = Settings()