feat: extend license enforcement to webapp API endpoints

Extend license middleware to also block webapp API (/api/*) when
enterprise license is expired/inactive/lost.

Changes:
- Check both /console/api and /api endpoints
- Add webapp-specific exempt paths:
  - /api/passport (webapp authentication)
  - /api/login, /api/logout, /api/oauth
  - /api/forgot-password
  - /api/system-features (webapp needs this to check license status)

This ensures both console users and webapp users are blocked when
license expires, maintaining consistent enforcement across all APIs.
This commit is contained in:
GareArc 2026-03-04 20:33:08 -08:00
parent 0e9dc86f3b
commit ea35ee0a3e
No known key found for this signature in database

View File

@ -34,47 +34,42 @@ def create_flask_app_with_configs() -> DifyApp:
init_request_context()
RecyclableContextVar.increment_thread_recycles()
# Enterprise license validation for console API endpoints
if dify_config.ENTERPRISE_ENABLED and request.path.startswith("/console/api"):
# Skip license check for auth-related endpoints and system endpoints
exempt_paths = [
"/console/api/login",
"/console/api/logout",
"/console/api/oauth",
"/console/api/setup",
"/console/api/init",
"/console/api/forgot-password",
"/console/api/email-code-login",
"/console/api/activation",
"/console/api/data-source-oauth",
"/console/api/features", # Allow fetching features to show license status
]
# Enterprise license validation for API endpoints (both console and webapp)
# When license expires, ONLY allow features API - everything else is blocked
if dify_config.ENTERPRISE_ENABLED:
is_console_api = request.path.startswith("/console/api")
is_webapp_api = request.path.startswith("/api") and not is_console_api
# Check if current path is exempt
is_exempt = any(request.path.startswith(path) for path in exempt_paths)
if is_console_api or is_webapp_api:
# Only exempt features endpoints - block everything else including auth
# Admin can access through admin dashboard (separate service)
if is_console_api:
is_exempt = request.path.startswith("/console/api/features")
else: # webapp API
is_exempt = request.path.startswith("/api/system-features")
if not is_exempt:
try:
# Check license status
system_features = FeatureService.get_system_features(is_authenticated=True)
if system_features.license.status in [
LicenseStatus.INACTIVE,
LicenseStatus.EXPIRED,
LicenseStatus.LOST,
]:
# Raise UnauthorizedAndForceLogout to trigger frontend reload and logout
# Frontend checks code === 'unauthorized_and_force_logout' and calls location.reload()
raise UnauthorizedAndForceLogout(
f"Enterprise license is {system_features.license.status.value}. "
"Please contact your administrator."
)
except UnauthorizedAndForceLogout:
# Re-raise to let Flask error handler convert to proper JSON response
raise
except Exception:
# If license check fails, log but don't block the request
# This prevents service disruption if enterprise API is temporarily unavailable
logger.exception("Failed to check enterprise license status")
if not is_exempt:
try:
# Check license status
system_features = FeatureService.get_system_features(is_authenticated=True)
if system_features.license.status in [
LicenseStatus.INACTIVE,
LicenseStatus.EXPIRED,
LicenseStatus.LOST,
]:
# Raise UnauthorizedAndForceLogout to trigger frontend reload and logout
# Frontend checks code === 'unauthorized_and_force_logout' and calls location.reload()
raise UnauthorizedAndForceLogout(
f"Enterprise license is {system_features.license.status.value}. "
"Please contact your administrator."
)
except UnauthorizedAndForceLogout:
# Re-raise to let Flask error handler convert to proper JSON response
raise
except Exception:
# If license check fails, log but don't block the request
# This prevents service disruption if enterprise API is temporarily unavailable
logger.exception("Failed to check enterprise license status")
# add after request hook for injecting trace headers from OpenTelemetry span context
# Only adds headers when OTEL is enabled and has valid context