diff --git a/api/apps/__init__.py b/api/apps/__init__.py index 7acef9be5..007e37430 100644 --- a/api/apps/__init__.py +++ b/api/apps/__init__.py @@ -146,10 +146,23 @@ def load_user(web_request): if authorization: try: access_token = str(jwt.loads(authorization)) + + if not access_token or not access_token.strip(): + logging.warning("Authentication attempt with empty access token") + return None + + # Access tokens should be UUIDs (32 hex characters) + if len(access_token.strip()) < 32: + logging.warning(f"Authentication attempt with invalid token format: {len(access_token)} chars") + return None + user = UserService.query( access_token=access_token, status=StatusEnum.VALID.value ) if user: + if not user[0].access_token or not user[0].access_token.strip(): + logging.warning(f"User {user[0].email} has empty access_token in database") + return None return user[0] else: return None diff --git a/api/apps/user_app.py b/api/apps/user_app.py index 597f50971..b8d66ecba 100644 --- a/api/apps/user_app.py +++ b/api/apps/user_app.py @@ -16,6 +16,7 @@ import json import logging import re +import secrets from datetime import datetime from flask import redirect, request, session @@ -465,7 +466,7 @@ def log_out(): schema: type: object """ - current_user.access_token = "" + current_user.access_token = f"INVALID_{secrets.token_hex(16)}" current_user.save() logout_user() return get_json_result(data=True) diff --git a/api/db/services/user_service.py b/api/db/services/user_service.py index 1edd46c10..e8344cb43 100644 --- a/api/db/services/user_service.py +++ b/api/db/services/user_service.py @@ -15,6 +15,7 @@ # import hashlib from datetime import datetime +import logging import peewee from werkzeug.security import generate_password_hash, check_password_hash @@ -39,6 +40,30 @@ class UserService(CommonService): """ model = User + @classmethod + @DB.connection_context() + def query(cls, cols=None, reverse=None, order_by=None, **kwargs): + if 'access_token' in kwargs: + access_token = kwargs['access_token'] + + # Reject empty, None, or whitespace-only access tokens + if not access_token or not str(access_token).strip(): + logging.warning("UserService.query: Rejecting empty access_token query") + return cls.model.select().where(cls.model.id == "INVALID_EMPTY_TOKEN") # Returns empty result + + # Reject tokens that are too short (should be UUID, 32+ chars) + if len(str(access_token).strip()) < 32: + logging.warning(f"UserService.query: Rejecting short access_token query: {len(str(access_token))} chars") + return cls.model.select().where(cls.model.id == "INVALID_SHORT_TOKEN") # Returns empty result + + # Reject tokens that start with "INVALID_" (from logout) + if str(access_token).startswith("INVALID_"): + logging.warning("UserService.query: Rejecting invalidated access_token") + return cls.model.select().where(cls.model.id == "INVALID_LOGOUT_TOKEN") # Returns empty result + + # Call parent query method for valid requests + return super().query(cols=cols, reverse=reverse, order_by=order_by, **kwargs) + @classmethod @DB.connection_context() def filter_by_id(cls, user_id): diff --git a/api/settings.py b/api/settings.py index 2d743f904..22e9d03f4 100644 --- a/api/settings.py +++ b/api/settings.py @@ -15,6 +15,7 @@ # import json import os +import secrets from datetime import date from enum import Enum, IntEnum @@ -73,6 +74,25 @@ SANDBOX_HOST = None BUILTIN_EMBEDDING_MODELS = ["BAAI/bge-large-zh-v1.5@BAAI", "maidalun1020/bce-embedding-base_v1@Youdao"] +def get_or_create_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 + + # Generate a new secure key and warn about it + import logging + new_key = secrets.token_hex(32) + logging.warning( + "SECURITY WARNING: Using auto-generated SECRET_KEY. " + f"Generated key: {new_key}" + ) + return new_key + def init_settings(): global LLM, LLM_FACTORY, LLM_BASE_URL, LIGHTEN, DATABASE_TYPE, DATABASE, FACTORY_LLM_INFOS, REGISTER_ENABLED @@ -121,7 +141,7 @@ def init_settings(): HOST_IP = get_base_config(RAG_FLOW_SERVICE_NAME, {}).get("host", "127.0.0.1") HOST_PORT = get_base_config(RAG_FLOW_SERVICE_NAME, {}).get("http_port") - SECRET_KEY = get_base_config(RAG_FLOW_SERVICE_NAME, {}).get("secret_key", str(date.today())) + SECRET_KEY = get_or_create_secret_key() global AUTHENTICATION_CONF, CLIENT_AUTHENTICATION, HTTP_APP_KEY, GITHUB_OAUTH, FEISHU_OAUTH, OAUTH_CONFIG # authentication