diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29b..c606c93c 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,5 @@ +- bump: patch + changes: + fixed: + - Fail closed when authentication is enabled but Auth0 configuration is + incomplete. diff --git a/policyengine_household_api/decorators/auth.py b/policyengine_household_api/decorators/auth.py index a675b45f..88f95d62 100644 --- a/policyengine_household_api/decorators/auth.py +++ b/policyengine_household_api/decorators/auth.py @@ -10,7 +10,7 @@ from authlib.integrations.flask_oauth2 import ResourceProtector from authlib.oauth2.rfc6750 import BearerTokenValidator from ..auth.validation import Auth0JWTBearerTokenValidator -from ..utils.config_loader import get_config, get_config_value +from ..utils.config_loader import get_config_value class StaticBearerToken: @@ -120,10 +120,15 @@ def _setup_authentication(self) -> None: resource_protector.register_token_validator(validator) self._decorator = resource_protector else: - # Auth was requested but configuration is missing - print("Warning: Auth enabled but Auth0 configuration missing") - self._auth_enabled = False - self._decorator = NoOpDecorator() + missing = [] + if not auth0_address: + missing.append("auth.auth0.address") + if not auth0_audience: + missing.append("auth.auth0.audience") + raise RuntimeError( + "Authentication is enabled but required Auth0 " + f"configuration is missing: {', '.join(missing)}" + ) else: # Authentication is disabled self._decorator = NoOpDecorator() diff --git a/tests/unit/decorators/test_auth.py b/tests/unit/decorators/test_auth.py index a2089d43..0d549585 100644 --- a/tests/unit/decorators/test_auth.py +++ b/tests/unit/decorators/test_auth.py @@ -3,6 +3,7 @@ """ from unittest.mock import Mock +import pytest from policyengine_household_api.decorators.auth import ( NoOpDecorator, ConditionalAuthDecorator, @@ -122,26 +123,26 @@ def test__given_auth_enabled_with_valid_config__auth0_is_configured( auth_enabled_environment.assert_any_call("auth.auth0.address", "") auth_enabled_environment.assert_any_call("auth.auth0.audience", "") - def test__given_auth_enabled_missing_config__falls_back_to_noop( + def test__given_auth_enabled_missing_config__raises_configuration_error( self, auth_enabled_missing_config_environment, mock_resource_protector, mock_auth0_validator, ): - """Test fallback to NoOp when auth is enabled but config is missing.""" + """Test auth fails closed when enabled but config is missing.""" mock_protector_class, _ = mock_resource_protector mock_validator_class, _ = mock_auth0_validator - decorator = ConditionalAuthDecorator() + with pytest.raises( + RuntimeError, + match="Authentication is enabled but required Auth0 configuration is missing", + ): + ConditionalAuthDecorator() # Verify Auth0 components were not created mock_validator_class.assert_not_called() mock_protector_class.assert_not_called() - # Verify we get a NoOpDecorator - assert isinstance(decorator.get_decorator(), NoOpDecorator) - assert decorator.is_enabled is False - # Verify configuration was checked auth_enabled_missing_config_environment.assert_any_call( "auth.enabled", False