diff --git a/api/app_factory.py b/api/app_factory.py index 65cd6dfd4e..881be13549 100644 --- a/api/app_factory.py +++ b/api/app_factory.py @@ -62,12 +62,13 @@ def create_flask_app_with_configs() -> DifyApp: raise UnauthorizedAndForceLogout( f"Enterprise license is {license_status}. Please contact your administrator." ) + if license_status is None: + raise UnauthorizedAndForceLogout( + "Unable to verify enterprise license. Please contact your administrator." + ) except UnauthorizedAndForceLogout: raise except Exception: - # Fail-closed: if we cannot verify the license (Redis down + - # enterprise API unreachable), block the request. An unreachable - # sidecar is itself an abnormal state that should surface. logger.exception("Failed to check enterprise license status") raise UnauthorizedAndForceLogout( "Unable to verify enterprise license. Please contact your administrator." diff --git a/api/services/enterprise/enterprise_service.py b/api/services/enterprise/enterprise_service.py index 2b3ce056c9..5fb1395292 100644 --- a/api/services/enterprise/enterprise_service.py +++ b/api/services/enterprise/enterprise_service.py @@ -239,7 +239,7 @@ class EnterpriseService: """Get enterprise license status with Redis caching to reduce HTTP calls. Caches valid statuses (active/expiring) for 10 minutes and invalid statuses - (inactive/expired/lost) for 1 minute. The shorter TTL for invalid statuses + (inactive/expired/lost) for 30 seconds. The shorter TTL for invalid statuses balances prompt license-fix detection against DoS mitigation — without caching, every request on an expired license would hit the enterprise API. diff --git a/api/tests/test_containers_integration_tests/services/test_feature_service.py b/api/tests/test_containers_integration_tests/services/test_feature_service.py index bd2fd14ffa..b3e7dd2a59 100644 --- a/api/tests/test_containers_integration_tests/services/test_feature_service.py +++ b/api/tests/test_containers_integration_tests/services/test_feature_service.py @@ -358,10 +358,9 @@ class TestFeatureService: assert result is not None assert isinstance(result, SystemFeatureModel) - # --- 1. Verify Response Payload Optimization (Data Minimization) --- - # Ensure only essential UI flags are returned to unauthenticated clients - # to keep the payload lightweight and adhere to architectural boundaries. - assert result.license.status == LicenseStatus.NONE + # --- 1. Verify only license *status* is exposed to unauthenticated clients --- + # Detailed license info (expiry, workspaces) remains auth-gated. + assert result.license.status == LicenseStatus.ACTIVE assert result.license.expired_at == "" assert result.license.workspaces.enabled is False assert result.license.workspaces.limit == 0