mirror of
https://github.com/langgenius/dify.git
synced 2026-05-12 15:58:19 +08:00
fix(security): harden self-hosted SECRET_KEY bootstrap (#36049)
Co-authored-by: EndlessLucky <66432853+EndlessLucky@users.noreply.github.com>
This commit is contained in:
parent
1a93af5cd0
commit
cbedcd2882
@ -181,7 +181,6 @@ def initialize_extensions(app: DifyApp):
|
||||
ext_import_modules,
|
||||
ext_orjson,
|
||||
ext_forward_refs,
|
||||
ext_set_secretkey,
|
||||
ext_compress,
|
||||
ext_code_based_extension,
|
||||
ext_database,
|
||||
@ -189,6 +188,7 @@ def initialize_extensions(app: DifyApp):
|
||||
ext_migrate,
|
||||
ext_redis,
|
||||
ext_storage,
|
||||
ext_set_secretkey,
|
||||
ext_logstore, # Initialize logstore after storage, before celery
|
||||
ext_celery,
|
||||
ext_login,
|
||||
|
||||
@ -23,9 +23,9 @@ class SecurityConfig(BaseSettings):
|
||||
"""
|
||||
|
||||
SECRET_KEY: str = Field(
|
||||
description="Secret key for secure session cookie signing."
|
||||
"Make sure you are changing this key for your deployment with a strong key."
|
||||
"Generate a strong key using `openssl rand -base64 42` or set via the `SECRET_KEY` environment variable.",
|
||||
description="Secret key for secure session cookie signing. "
|
||||
"Leave empty to let Dify generate a persistent key in the storage directory, "
|
||||
"or set a strong value via the `SECRET_KEY` environment variable.",
|
||||
default="",
|
||||
)
|
||||
|
||||
|
||||
38
api/configs/secret_key.py
Normal file
38
api/configs/secret_key.py
Normal file
@ -0,0 +1,38 @@
|
||||
"""SECRET_KEY persistence helpers for runtime setup."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import secrets
|
||||
|
||||
from extensions.ext_storage import storage
|
||||
|
||||
GENERATED_SECRET_KEY_FILENAME = ".dify_secret_key"
|
||||
|
||||
|
||||
def resolve_secret_key(secret_key: str) -> str:
|
||||
"""Return an explicit SECRET_KEY or a generated key persisted in storage."""
|
||||
if secret_key:
|
||||
return secret_key
|
||||
|
||||
return _load_or_create_secret_key()
|
||||
|
||||
|
||||
def _load_or_create_secret_key() -> str:
|
||||
try:
|
||||
persisted_key = storage.load_once(GENERATED_SECRET_KEY_FILENAME).decode("utf-8").strip()
|
||||
if persisted_key:
|
||||
return persisted_key
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
generated_key = secrets.token_urlsafe(48)
|
||||
|
||||
try:
|
||||
storage.save(GENERATED_SECRET_KEY_FILENAME, f"{generated_key}\n".encode())
|
||||
except Exception as exc:
|
||||
raise ValueError(
|
||||
f"SECRET_KEY is not set and could not be generated at {GENERATED_SECRET_KEY_FILENAME}. "
|
||||
"Set SECRET_KEY explicitly or make storage writable."
|
||||
) from exc
|
||||
|
||||
return generated_key
|
||||
@ -128,7 +128,7 @@ class DifyWorkflowFileRuntime(WorkflowFileRuntimeProtocol):
|
||||
|
||||
@staticmethod
|
||||
def _secret_key() -> bytes:
|
||||
return dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
|
||||
return dify_config.SECRET_KEY.encode()
|
||||
|
||||
def _sign_query(self, *, payload: str) -> dict[str, str]:
|
||||
timestamp = str(int(time.time()))
|
||||
|
||||
@ -35,8 +35,11 @@ class DatasourceFileManager:
|
||||
timestamp = str(int(time.time()))
|
||||
nonce = os.urandom(16).hex()
|
||||
data_to_sign = f"file-preview|{datasource_file_id}|{timestamp}|{nonce}"
|
||||
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
|
||||
sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
|
||||
sign = hmac.new(
|
||||
dify_config.SECRET_KEY.encode(),
|
||||
data_to_sign.encode(),
|
||||
hashlib.sha256,
|
||||
).digest()
|
||||
encoded_sign = base64.urlsafe_b64encode(sign).decode()
|
||||
|
||||
return f"{file_preview_url}?timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}"
|
||||
@ -47,8 +50,11 @@ class DatasourceFileManager:
|
||||
verify signature
|
||||
"""
|
||||
data_to_sign = f"file-preview|{datasource_file_id}|{timestamp}|{nonce}"
|
||||
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
|
||||
recalculated_sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
|
||||
recalculated_sign = hmac.new(
|
||||
dify_config.SECRET_KEY.encode(),
|
||||
data_to_sign.encode(),
|
||||
hashlib.sha256,
|
||||
).digest()
|
||||
recalculated_encoded_sign = base64.urlsafe_b64encode(recalculated_sign).decode()
|
||||
|
||||
# verify signature
|
||||
|
||||
@ -8,6 +8,10 @@ import urllib.parse
|
||||
from configs import dify_config
|
||||
|
||||
|
||||
def _secret_key() -> bytes:
|
||||
return dify_config.SECRET_KEY.encode()
|
||||
|
||||
|
||||
def sign_tool_file(tool_file_id: str, extension: str, for_external: bool = True) -> str:
|
||||
"""
|
||||
sign file to get a temporary url for plugin access
|
||||
@ -19,8 +23,7 @@ def sign_tool_file(tool_file_id: str, extension: str, for_external: bool = True)
|
||||
timestamp = str(int(time.time()))
|
||||
nonce = os.urandom(16).hex()
|
||||
data_to_sign = f"file-preview|{tool_file_id}|{timestamp}|{nonce}"
|
||||
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
|
||||
sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
|
||||
sign = hmac.new(_secret_key(), data_to_sign.encode(), hashlib.sha256).digest()
|
||||
encoded_sign = base64.urlsafe_b64encode(sign).decode()
|
||||
|
||||
return f"{file_preview_url}?timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}"
|
||||
@ -39,8 +42,7 @@ def sign_upload_file_preview_url(upload_file_id: str, extension: str) -> str:
|
||||
timestamp = str(int(time.time()))
|
||||
nonce = os.urandom(16).hex()
|
||||
data_to_sign = f"image-preview|{upload_file_id}|{timestamp}|{nonce}"
|
||||
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
|
||||
sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
|
||||
sign = hmac.new(_secret_key(), data_to_sign.encode(), hashlib.sha256).digest()
|
||||
encoded_sign = base64.urlsafe_b64encode(sign).decode()
|
||||
|
||||
return f"{file_preview_url}?timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}"
|
||||
@ -51,8 +53,7 @@ def verify_tool_file_signature(file_id: str, timestamp: str, nonce: str, sign: s
|
||||
verify signature
|
||||
"""
|
||||
data_to_sign = f"file-preview|{file_id}|{timestamp}|{nonce}"
|
||||
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
|
||||
recalculated_sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
|
||||
recalculated_sign = hmac.new(_secret_key(), data_to_sign.encode(), hashlib.sha256).digest()
|
||||
recalculated_encoded_sign = base64.urlsafe_b64encode(recalculated_sign).decode()
|
||||
|
||||
# verify signature
|
||||
@ -71,8 +72,7 @@ def get_signed_file_url_for_plugin(filename: str, mimetype: str, tenant_id: str,
|
||||
timestamp = str(int(time.time()))
|
||||
nonce = os.urandom(16).hex()
|
||||
data_to_sign = f"upload|{filename}|{mimetype}|{tenant_id}|{user_id}|{timestamp}|{nonce}"
|
||||
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
|
||||
sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
|
||||
sign = hmac.new(_secret_key(), data_to_sign.encode(), hashlib.sha256).digest()
|
||||
encoded_sign = base64.urlsafe_b64encode(sign).decode()
|
||||
query = urllib.parse.urlencode(
|
||||
{
|
||||
@ -92,8 +92,7 @@ def verify_plugin_file_signature(
|
||||
"""Verify the signature used by the plugin-facing file upload endpoint."""
|
||||
|
||||
data_to_sign = f"upload|{filename}|{mimetype}|{tenant_id}|{user_id}|{timestamp}|{nonce}"
|
||||
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
|
||||
recalculated_sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
|
||||
recalculated_sign = hmac.new(_secret_key(), data_to_sign.encode(), hashlib.sha256).digest()
|
||||
recalculated_encoded_sign = base64.urlsafe_b64encode(recalculated_sign).decode()
|
||||
|
||||
if sign != recalculated_encoded_sign:
|
||||
|
||||
@ -51,8 +51,11 @@ class ToolFileManager:
|
||||
timestamp = str(int(time.time()))
|
||||
nonce = os.urandom(16).hex()
|
||||
data_to_sign = f"file-preview|{tool_file_id}|{timestamp}|{nonce}"
|
||||
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
|
||||
sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
|
||||
sign = hmac.new(
|
||||
dify_config.SECRET_KEY.encode(),
|
||||
data_to_sign.encode(),
|
||||
hashlib.sha256,
|
||||
).digest()
|
||||
encoded_sign = base64.urlsafe_b64encode(sign).decode()
|
||||
|
||||
return f"{file_preview_url}?timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}"
|
||||
@ -63,8 +66,11 @@ class ToolFileManager:
|
||||
verify signature
|
||||
"""
|
||||
data_to_sign = f"file-preview|{file_id}|{timestamp}|{nonce}"
|
||||
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
|
||||
recalculated_sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
|
||||
recalculated_sign = hmac.new(
|
||||
dify_config.SECRET_KEY.encode(),
|
||||
data_to_sign.encode(),
|
||||
hashlib.sha256,
|
||||
).digest()
|
||||
recalculated_encoded_sign = base64.urlsafe_b64encode(recalculated_sign).decode()
|
||||
|
||||
# verify signature
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
from configs import dify_config
|
||||
from configs.secret_key import resolve_secret_key
|
||||
from dify_app import DifyApp
|
||||
|
||||
|
||||
def init_app(app: DifyApp):
|
||||
app.secret_key = dify_config.SECRET_KEY
|
||||
def init_app(app: DifyApp) -> None:
|
||||
"""Resolve SECRET_KEY after config loading and before session/login setup."""
|
||||
secret_key = dify_config.SECRET_KEY
|
||||
if not secret_key:
|
||||
secret_key = resolve_secret_key(secret_key)
|
||||
dify_config.SECRET_KEY = secret_key
|
||||
app.config["SECRET_KEY"] = secret_key
|
||||
app.secret_key = secret_key
|
||||
|
||||
@ -945,7 +945,7 @@ class DocumentSegment(Base):
|
||||
nonce = os.urandom(16).hex()
|
||||
timestamp = str(int(time.time()))
|
||||
data_to_sign = f"image-preview|{upload_file_id}|{timestamp}|{nonce}"
|
||||
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
|
||||
secret_key = dify_config.SECRET_KEY.encode()
|
||||
sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
|
||||
encoded_sign = base64.urlsafe_b64encode(sign).decode()
|
||||
|
||||
@ -962,7 +962,7 @@ class DocumentSegment(Base):
|
||||
nonce = os.urandom(16).hex()
|
||||
timestamp = str(int(time.time()))
|
||||
data_to_sign = f"file-preview|{upload_file_id}|{timestamp}|{nonce}"
|
||||
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
|
||||
secret_key = dify_config.SECRET_KEY.encode()
|
||||
sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
|
||||
encoded_sign = base64.urlsafe_b64encode(sign).decode()
|
||||
|
||||
@ -981,7 +981,7 @@ class DocumentSegment(Base):
|
||||
nonce = os.urandom(16).hex()
|
||||
timestamp = str(int(time.time()))
|
||||
data_to_sign = f"file-preview|{upload_file_id}|{timestamp}|{nonce}"
|
||||
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
|
||||
secret_key = dify_config.SECRET_KEY.encode()
|
||||
sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
|
||||
encoded_sign = base64.urlsafe_b64encode(sign).decode()
|
||||
|
||||
@ -1019,7 +1019,7 @@ class DocumentSegment(Base):
|
||||
nonce = os.urandom(16).hex()
|
||||
timestamp = str(int(time.time()))
|
||||
data_to_sign = f"image-preview|{upload_file_id}|{timestamp}|{nonce}"
|
||||
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
|
||||
secret_key = dify_config.SECRET_KEY.encode()
|
||||
sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
|
||||
encoded_sign = base64.urlsafe_b64encode(sign).decode()
|
||||
|
||||
|
||||
@ -8,6 +8,47 @@ from yarl import URL
|
||||
from configs.app_config import DifyConfig
|
||||
|
||||
|
||||
def _set_basic_config_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
os.environ.clear()
|
||||
monkeypatch.setenv("CONSOLE_API_URL", "https://example.com")
|
||||
monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com")
|
||||
monkeypatch.setenv("DB_TYPE", "postgresql")
|
||||
monkeypatch.setenv("DB_USERNAME", "postgres")
|
||||
monkeypatch.setenv("DB_PASSWORD", "postgres")
|
||||
monkeypatch.setenv("DB_HOST", "localhost")
|
||||
monkeypatch.setenv("DB_PORT", "5432")
|
||||
monkeypatch.setenv("DB_DATABASE", "dify")
|
||||
|
||||
|
||||
def test_dify_config_keeps_secret_key_empty_when_missing(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path,
|
||||
) -> None:
|
||||
_set_basic_config_env(monkeypatch)
|
||||
monkeypatch.delenv("SECRET_KEY", raising=False)
|
||||
monkeypatch.setenv("OPENDAL_FS_ROOT", str(tmp_path))
|
||||
|
||||
config = DifyConfig(_env_file=None)
|
||||
|
||||
assert config.SECRET_KEY == ""
|
||||
assert not hasattr(config, "OPENDAL_FS_ROOT")
|
||||
assert not (tmp_path / ".dify_secret_key").exists()
|
||||
|
||||
|
||||
def test_dify_config_preserves_explicit_secret_key(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path,
|
||||
) -> None:
|
||||
_set_basic_config_env(monkeypatch)
|
||||
monkeypatch.setenv("SECRET_KEY", "explicit")
|
||||
monkeypatch.setenv("OPENDAL_FS_ROOT", str(tmp_path))
|
||||
|
||||
config = DifyConfig(_env_file=None)
|
||||
|
||||
assert config.SECRET_KEY == "explicit"
|
||||
assert not (tmp_path / ".dify_secret_key").exists()
|
||||
|
||||
|
||||
def test_dify_config(monkeypatch: pytest.MonkeyPatch):
|
||||
# clear system environment variables
|
||||
os.environ.clear()
|
||||
|
||||
@ -34,20 +34,6 @@ class TestDatasourceFileManager:
|
||||
assert f"nonce={mock_urandom.return_value.hex()}" in signed_url
|
||||
assert "sign=" in signed_url
|
||||
|
||||
@patch("core.datasource.datasource_file_manager.time.time")
|
||||
@patch("core.datasource.datasource_file_manager.os.urandom")
|
||||
@patch("core.datasource.datasource_file_manager.dify_config")
|
||||
def test_sign_file_empty_secret(self, mock_config, mock_urandom, mock_time):
|
||||
# Setup
|
||||
mock_config.FILES_URL = "http://localhost:5001"
|
||||
mock_config.SECRET_KEY = None # Empty secret
|
||||
mock_time.return_value = 1700000000
|
||||
mock_urandom.return_value = b"1234567890abcdef"
|
||||
|
||||
# Execute
|
||||
signed_url = DatasourceFileManager.sign_file("file_id", ".png")
|
||||
assert "sign=" in signed_url
|
||||
|
||||
@patch("core.datasource.datasource_file_manager.time.time")
|
||||
@patch("core.datasource.datasource_file_manager.dify_config")
|
||||
def test_verify_file(self, mock_config, mock_time):
|
||||
@ -76,25 +62,6 @@ class TestDatasourceFileManager:
|
||||
mock_time.return_value = 1700000500 # 700 seconds after timestamp (300 is timeout)
|
||||
assert DatasourceFileManager.verify_file(datasource_file_id, timestamp, nonce, encoded_sign) is False
|
||||
|
||||
@patch("core.datasource.datasource_file_manager.time.time")
|
||||
@patch("core.datasource.datasource_file_manager.dify_config")
|
||||
def test_verify_file_empty_secret(self, mock_config, mock_time):
|
||||
# Setup
|
||||
mock_config.SECRET_KEY = "" # Empty string secret
|
||||
mock_config.FILES_ACCESS_TIMEOUT = 300
|
||||
mock_time.return_value = 1700000000
|
||||
|
||||
datasource_file_id = "file_id_123"
|
||||
timestamp = "1699999800"
|
||||
nonce = "some_nonce"
|
||||
|
||||
# Calculate with empty secret
|
||||
data_to_sign = f"file-preview|{datasource_file_id}|{timestamp}|{nonce}"
|
||||
sign = hmac.new(b"", data_to_sign.encode(), hashlib.sha256).digest()
|
||||
encoded_sign = base64.urlsafe_b64encode(sign).decode()
|
||||
|
||||
assert DatasourceFileManager.verify_file(datasource_file_id, timestamp, nonce, encoded_sign) is True
|
||||
|
||||
@patch("core.datasource.datasource_file_manager.db")
|
||||
@patch("core.datasource.datasource_file_manager.storage")
|
||||
@patch("core.datasource.datasource_file_manager.uuid4")
|
||||
|
||||
74
api/tests/unit_tests/extensions/test_set_secretkey.py
Normal file
74
api/tests/unit_tests/extensions/test_set_secretkey.py
Normal file
@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
from extensions import ext_set_secretkey
|
||||
|
||||
|
||||
class InMemoryStorage:
|
||||
def __init__(self, files: dict[str, bytes] | None = None) -> None:
|
||||
self.files = files or {}
|
||||
self.saved_files: list[tuple[str, bytes]] = []
|
||||
|
||||
def load_once(self, filename: str) -> bytes:
|
||||
try:
|
||||
return self.files[filename]
|
||||
except KeyError:
|
||||
raise FileNotFoundError(filename)
|
||||
|
||||
def save(self, filename: str, data: bytes) -> None:
|
||||
self.files[filename] = data
|
||||
self.saved_files.append((filename, data))
|
||||
|
||||
|
||||
def test_init_app_uses_configured_secret_key(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
secret_key = "configured-secret-key"
|
||||
storage = InMemoryStorage()
|
||||
monkeypatch.setattr("extensions.ext_set_secretkey.dify_config.SECRET_KEY", secret_key)
|
||||
monkeypatch.setattr("configs.secret_key.storage", storage)
|
||||
app = Flask(__name__)
|
||||
app.config["SECRET_KEY"] = secret_key
|
||||
|
||||
ext_set_secretkey.init_app(app)
|
||||
|
||||
assert app.secret_key == secret_key
|
||||
assert app.config["SECRET_KEY"] == secret_key
|
||||
assert storage.saved_files == []
|
||||
|
||||
|
||||
def test_init_app_generates_and_persists_secret_key_when_missing(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
storage = InMemoryStorage()
|
||||
monkeypatch.setattr("extensions.ext_set_secretkey.dify_config.SECRET_KEY", "")
|
||||
monkeypatch.setattr("configs.secret_key.storage", storage)
|
||||
app = Flask(__name__)
|
||||
app.config["SECRET_KEY"] = ""
|
||||
|
||||
ext_set_secretkey.init_app(app)
|
||||
|
||||
persisted_key = storage.files[".dify_secret_key"].decode("utf-8").strip()
|
||||
assert persisted_key
|
||||
assert storage.saved_files == [(".dify_secret_key", f"{persisted_key}\n".encode())]
|
||||
assert persisted_key == ext_set_secretkey.dify_config.SECRET_KEY
|
||||
assert persisted_key == app.config["SECRET_KEY"]
|
||||
assert persisted_key == app.secret_key
|
||||
|
||||
|
||||
def test_init_app_reuses_persisted_secret_key_when_missing(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
persisted_key = "persisted-secret-key"
|
||||
storage = InMemoryStorage({".dify_secret_key": f"{persisted_key}\n".encode()})
|
||||
monkeypatch.setattr("extensions.ext_set_secretkey.dify_config.SECRET_KEY", "")
|
||||
monkeypatch.setattr("configs.secret_key.storage", storage)
|
||||
app = Flask(__name__)
|
||||
app.config["SECRET_KEY"] = ""
|
||||
|
||||
ext_set_secretkey.init_app(app)
|
||||
|
||||
assert persisted_key == ext_set_secretkey.dify_config.SECRET_KEY
|
||||
assert persisted_key == app.config["SECRET_KEY"]
|
||||
assert persisted_key == app.secret_key
|
||||
assert storage.saved_files == []
|
||||
@ -143,28 +143,13 @@ class TestPassportService:
|
||||
assert str(exc_info.value) == "401 Unauthorized: Token has expired."
|
||||
|
||||
# Configuration tests
|
||||
def test_should_handle_empty_secret_key(self):
|
||||
"""Test behavior when SECRET_KEY is empty"""
|
||||
def test_should_use_configured_secret_key_without_policy_validation(self):
|
||||
"""Test that policy decisions are owned by config, not PassportService."""
|
||||
with patch("libs.passport.dify_config") as mock_config:
|
||||
mock_config.SECRET_KEY = ""
|
||||
mock_config.SECRET_KEY = "configured"
|
||||
service = PassportService()
|
||||
|
||||
# Empty secret key should still work but is insecure
|
||||
payload = {"test": "data"}
|
||||
token = service.issue(payload)
|
||||
decoded = service.verify(token)
|
||||
assert decoded == payload
|
||||
|
||||
def test_should_handle_none_secret_key(self):
|
||||
"""Test behavior when SECRET_KEY is None"""
|
||||
with patch("libs.passport.dify_config") as mock_config:
|
||||
mock_config.SECRET_KEY = None
|
||||
service = PassportService()
|
||||
|
||||
payload = {"test": "data"}
|
||||
# JWT library will raise TypeError when secret is None
|
||||
with pytest.raises((TypeError, jwt.exceptions.InvalidKeyError)):
|
||||
service.issue(payload)
|
||||
assert service.sk == "configured"
|
||||
|
||||
# Boundary condition tests
|
||||
def test_should_handle_large_payload(self, passport_service):
|
||||
|
||||
@ -28,7 +28,8 @@ LANG=C.UTF-8
|
||||
LC_ALL=C.UTF-8
|
||||
PYTHONIOENCODING=utf-8
|
||||
UV_CACHE_DIR=/tmp/.uv-cache
|
||||
SECRET_KEY=sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U
|
||||
# Leave empty to auto-generate a persistent key in the storage directory.
|
||||
SECRET_KEY=
|
||||
INIT_PASSWORD=
|
||||
DEPLOY_ENV=PRODUCTION
|
||||
CHECK_UPDATE_URL=https://updates.dify.ai
|
||||
|
||||
@ -87,7 +87,7 @@ The root `.env.example` file contains the essential startup settings. Optional a
|
||||
1. **Server Configuration**:
|
||||
|
||||
- `LOG_LEVEL`, `DEBUG`, `FLASK_DEBUG`: Logging and debug settings.
|
||||
- `SECRET_KEY`: A key for encrypting session cookies and other sensitive data.
|
||||
- `SECRET_KEY`: A key for signing sessions, JWTs, and file URLs. Leave it empty to let Dify generate a persistent key in the storage directory, or set a unique value yourself.
|
||||
|
||||
1. **Database Configuration**:
|
||||
|
||||
|
||||
@ -36,5 +36,6 @@ TIDB_PUBLIC_KEY=dify
|
||||
TIDB_PRIVATE_KEY=dify
|
||||
VIKINGDB_ACCESS_KEY=your-ak
|
||||
VIKINGDB_SECRET_KEY=your-sk
|
||||
SECRET_KEY=sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U
|
||||
# Leave empty to auto-generate a persistent key in the storage directory.
|
||||
SECRET_KEY=
|
||||
INIT_PASSWORD=
|
||||
|
||||
Loading…
Reference in New Issue
Block a user