diff --git a/src/config.py b/src/config.py index d8a3f4e..01e2f7c 100644 --- a/src/config.py +++ b/src/config.py @@ -1,7 +1,10 @@ import os +import logging +import re +from urllib import parse from typing import Dict, Any -from pydantic import TypeAdapter +from pydantic import TypeAdapter, field_validator, FieldValidationInfo from pydantic.networks import HttpUrl, PostgresDsn from pydantic.types import SecretStr from pydantic_settings import BaseSettings, SettingsConfigDict @@ -31,6 +34,18 @@ def __init__(self, **data): super().__init__(**data) self.set_jwt_opts() + @field_validator("inst_conn", mode="before") + @classmethod + def encode_db_password(cls, postgres_dsn, info: FieldValidationInfo) -> Any: + log = logging.getLogger() + pwd = re.search(".*:.*:(.*)@", postgres_dsn) + if pwd: + pwd_str = pwd.group(1) + encoded_password = parse.quote(pwd_str, safe="") + return postgres_dsn.replace(pwd_str, encoded_password) + else: + log.error(f"Postgres DSN did not contain a properly formatted URL: {postgres_dsn}") + def set_jwt_opts(self) -> None: """ Converts `jwt_opts_` prefixed settings, and env vars into JWT options dictionary. diff --git a/tests/app/test_config.py b/tests/app/test_config.py index 9336bc1..53c5357 100644 --- a/tests/app/test_config.py +++ b/tests/app/test_config.py @@ -2,6 +2,12 @@ from config import Settings +def test_psql_password_encoding(): + mock_config = {"inst_conn": "postgresql+asyncpg://test:\z9-/tgb76@test/test"} + settings = Settings(**mock_config) + assert str(settings.inst_conn) == "postgresql+asyncpg://test:%5Cz9-%2Ftgb76@test/test" + + def test_jwt_opts_valid_values(): mock_config = { "jwt_opts_test1": "true",