From 1d0519d02507dbf87b11d7101d35c0b64ba55ead Mon Sep 17 00:00:00 2001 From: Jin Hai Date: Thu, 7 May 2026 10:10:02 +0800 Subject: [PATCH] Fix secret key inconsistency cross the RAGFlow servers (#14591) ### What problem does this PR solve? A and B, two API servers and a REDIS server. If A and REDIS restart, B will hold the obsolete secret key and will lead to error. TODO: app.config['SECRET_KEY'] and app.secret_key still hold obsolete secret key. ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) --------- Signed-off-by: Jin Hai --- admin/server/auth.py | 2 +- api/apps/__init__.py | 6 ++--- api/db/db_models.py | 2 +- api/utils/api_utils.py | 2 +- common/settings.py | 22 ++++++++++++++++++- .../test_system_app/test_apps_init_unit.py | 1 + 6 files changed, 28 insertions(+), 7 deletions(-) diff --git a/admin/server/auth.py b/admin/server/auth.py index bd3c0c058a..0aa96d0e37 100644 --- a/admin/server/auth.py +++ b/admin/server/auth.py @@ -58,7 +58,7 @@ def setup_auth(login_manager): return None # Decode JWT to get the UUID access_token - jwt = Serializer(secret_key=settings.SECRET_KEY) + jwt = Serializer(secret_key=settings.get_secret_key()) access_token = str(jwt.loads(jwt_token)) if not access_token or not access_token.strip(): diff --git a/api/apps/__init__.py b/api/apps/__init__.py index f245dfe472..e05bbb03d4 100644 --- a/api/apps/__init__.py +++ b/api/apps/__init__.py @@ -79,8 +79,8 @@ app.config["SESSION_REDIS"] = settings.decrypt_database_config(name="redis") app.config["MAX_CONTENT_LENGTH"] = int( os.environ.get("MAX_CONTENT_LENGTH", 1024 * 1024 * 1024) ) -app.config['SECRET_KEY'] = settings.SECRET_KEY -app.secret_key = settings.SECRET_KEY +app.config['SECRET_KEY'] = settings.get_secret_key() +app.secret_key = settings.get_secret_key() commands.register_commands(app) from functools import wraps @@ -93,7 +93,7 @@ P = ParamSpec("P") def _load_user(): - jwt = Serializer(secret_key=settings.SECRET_KEY) + jwt = Serializer(secret_key=settings.get_secret_key()) authorization = request.headers.get("Authorization") g.user = None if not authorization: diff --git a/api/db/db_models.py b/api/db/db_models.py index f1dd46b2bf..5fe64586c0 100644 --- a/api/db/db_models.py +++ b/api/db/db_models.py @@ -726,7 +726,7 @@ class User(DataBaseModel, AuthUser): return self.email def get_id(self): - jwt = Serializer(secret_key=settings.SECRET_KEY) + jwt = Serializer(secret_key=settings.get_secret_key()) return jwt.dumps(str(self.access_token)) class Meta: diff --git a/api/utils/api_utils.py b/api/utils/api_utils.py index fe6f6d0d44..a041ee0819 100644 --- a/api/utils/api_utils.py +++ b/api/utils/api_utils.py @@ -325,7 +325,7 @@ def token_required(func): from common import settings from itsdangerous.url_safe import URLSafeTimedSerializer as Serializer try: - jwt = Serializer(secret_key=settings.SECRET_KEY) + jwt = Serializer(secret_key=settings.get_secret_key()) raw_token = str(jwt.loads(token)) user = UserService.query(access_token=raw_token, status=StatusEnum.VALID.value) if user: diff --git a/common/settings.py b/common/settings.py index 067ae77657..43135fa001 100644 --- a/common/settings.py +++ b/common/settings.py @@ -17,6 +17,8 @@ import os import json import secrets import logging +from datetime import date + from common.constants import RAG_FLOW_SERVICE_NAME from common.file_utils import get_project_base_directory from common.config_utils import get_base_config, decrypt_database_config @@ -139,6 +141,24 @@ def get_svr_queue_name(priority: int) -> str: def get_svr_queue_names(): return [get_svr_queue_name(priority) for priority in [1, 0]] +def init_secret_key(): + secret_key = os.environ.get("RAGFLOW_SECRET_KEY") + if secret_key and len(secret_key) >= 32: + return secret_key + + # Check if there's a configured secret key + configured_key = get_base_config(RAG_FLOW_SERVICE_NAME, {}).get("secret_key") + if configured_key and configured_key != str(date.today()) and len(configured_key) >= 32: + return configured_key + return None + + +def get_secret_key(): + global SECRET_KEY + if SECRET_KEY is None: + return _get_or_create_secret_key() + return SECRET_KEY + def _get_or_create_secret_key(): # secret_key = os.environ.get("RAGFLOW_SECRET_KEY") # if secret_key and len(secret_key) >= 32: @@ -245,7 +265,7 @@ def init_settings(): HOST_PORT = get_base_config(RAG_FLOW_SERVICE_NAME, {}).get("http_port") global SECRET_KEY - SECRET_KEY = _get_or_create_secret_key() + SECRET_KEY = init_secret_key() # authentication diff --git a/test/testcases/test_web_api/test_system_app/test_apps_init_unit.py b/test/testcases/test_web_api/test_system_app/test_apps_init_unit.py index 00d1e5437b..e183100cd3 100644 --- a/test/testcases/test_web_api/test_system_app/test_apps_init_unit.py +++ b/test/testcases/test_web_api/test_system_app/test_apps_init_unit.py @@ -49,6 +49,7 @@ def _load_apps_module(monkeypatch): settings_mod = ModuleType("common.settings") settings_mod.SECRET_KEY = "test-secret-key" + settings_mod.get_secret_key = lambda: "test-secret-key" settings_mod.init_settings = lambda: None settings_mod.decrypt_database_config = lambda name=None: {} monkeypatch.setitem(sys.modules, "common.settings", settings_mod)