feat: apikey 加密存储解密使用 + 全端点请求日志

apikey 加密:
- create_service: 存入前使用 env.password_encode() 加密
- get_service_by_id: 读取后使用 env.password_decode() 解密
- get_all_services: 批量返回时解密每条 apikey
- test_service_connection: 直接查库也做解密

请求日志 ([INFO]):
- get_all_services, create_service, delete_service, get_service_by_id
- test_service_connection, create_session, send_message_to_service
- get_session_messages, get_active_sessions, get_recent_sessions
- get_session_by_id, get_setting, save_setting

生产环境可通过调高日志级别屏蔽 [INFO] 输出,测试阶段保留便于调试。
This commit is contained in:
yumoqing 2026-04-27 16:16:46 +08:00
parent 7f2f5728d6
commit 876c6a857c

View File

@ -63,6 +63,7 @@ async def get_all_services(orgid: str) -> List[Dict]:
Returns: Returns:
List of service dictionaries belonging to the specified organization List of service dictionaries belonging to the specified organization
""" """
print(f"[INFO] get_all_services: orgid={orgid}")
try: try:
# Query services table with orgid filter using sqlor-database-module # Query services table with orgid filter using sqlor-database-module
db = DBPools() db = DBPools()
@ -73,9 +74,13 @@ async def get_all_services(orgid: str) -> List[Dict]:
recs = await sor.sqlExe(sql_template, {'orgid': orgid}) recs = await sor.sqlExe(sql_template, {'orgid': orgid})
# Convert datetime objects to ISO format strings for JSON serialization # Convert datetime objects to ISO format strings for JSON serialization
# and decode encrypted apikey
result = [] result = []
for rec in recs: for rec in recs:
result.append(dict(rec)) d = dict(rec)
if d.get('apikey'):
d['apikey'] = env.password_decode(d['apikey'])
result.append(d)
return result return result
@ -96,6 +101,7 @@ async def create_service(name: str, url: str, orgid: str, description: str = "",
Returns: Returns:
The created service ID The created service ID
""" """
print(f"[INFO] create_service: name={name}, url={url}, orgid={orgid}, description={description}, apikey={'***' if apikey else '(empty)'}")
try: try:
# Validate service URL # Validate service URL
if not await validate_service_url(url): if not await validate_service_url(url):
@ -103,9 +109,12 @@ async def create_service(name: str, url: str, orgid: str, description: str = "",
service_id = str(uuid.uuid4()) service_id = str(uuid.uuid4())
# Encrypt apikey before storing
env = ServerEnv()
stored_apikey = env.password_encode(apikey) if apikey else ""
# Save to database using sqlor-database-module # Save to database using sqlor-database-module
db = DBPools() db = DBPools()
env = ServerEnv()
dbname = env.get_module_dbname('hermes-web-cli') dbname = env.get_module_dbname('hermes-web-cli')
async with db.sqlorContext(dbname) as sor: async with db.sqlorContext(dbname) as sor:
sql_template = SERVICES_CRUD['operations']['create']['sql_template'] sql_template = SERVICES_CRUD['operations']['create']['sql_template']
@ -115,7 +124,7 @@ async def create_service(name: str, url: str, orgid: str, description: str = "",
'name': name, 'name': name,
'service_url': url, 'service_url': url,
'description': description, 'description': description,
'apikey': apikey, 'apikey': stored_apikey,
'status': 'active' 'status': 'active'
}) })
@ -135,6 +144,7 @@ async def delete_service(service_id: str, orgid: str) -> bool:
Returns: Returns:
True if deleted successfully, False otherwise True if deleted successfully, False otherwise
""" """
print(f"[INFO] delete_service: service_id={service_id}, orgid={orgid}")
try: try:
# Verify service belongs to current org before deletion # Verify service belongs to current org before deletion
service = await get_service_by_id(service_id, orgid) service = await get_service_by_id(service_id, orgid)
@ -182,6 +192,7 @@ async def get_service_by_id(service_id: str, orgid: str) -> Optional[Dict]:
Returns: Returns:
Service dictionary if found and owned by org, None otherwise Service dictionary if found and owned by org, None otherwise
""" """
print(f"[INFO] get_service_by_id: service_id={service_id}, orgid={orgid}")
try: try:
# Query database directly with orgid filter for security # Query database directly with orgid filter for security
db = DBPools() db = DBPools()
@ -195,7 +206,11 @@ async def get_service_by_id(service_id: str, orgid: str) -> Optional[Dict]:
}) })
if len(recs) > 0: if len(recs) > 0:
return dict(recs[0]) r = dict(recs[0])
# Decode encrypted apikey
if r.get('apikey'):
r['apikey'] = env.password_decode(r['apikey'])
return r
return None return None
@ -214,6 +229,7 @@ async def test_service_connection(service_id: str, orgid: str = "") -> Tuple[boo
Returns: Returns:
Tuple[bool, str]: (is_connected, status_message) Tuple[bool, str]: (is_connected, status_message)
""" """
print(f"[INFO] test_service_connection: service_id={service_id}, orgid={orgid}")
try: try:
# Get service configuration from database # Get service configuration from database
db = DBPools() db = DBPools()
@ -240,13 +256,18 @@ async def test_service_connection(service_id: str, orgid: str = "") -> Tuple[boo
service = dict(recs[0]) service = dict(recs[0])
url = service["service_url"] url = service["service_url"]
# Decode encrypted apikey
apikey = service.get("apikey", "") apikey = service.get("apikey", "")
if apikey:
apikey = env.password_decode(apikey)
# Prepare headers # Prepare headers
headers = {} headers = {}
if apikey: if apikey:
headers["Authorization"] = f"Bearer {apikey}" headers["Authorization"] = f"Bearer {apikey}"
print(f"[INFO] test_service_connection: testing {url}")
# Test the /health endpoint or similar # Test the /health endpoint or similar
timeout = aiohttp.ClientTimeout(total=10) timeout = aiohttp.ClientTimeout(total=10)
async with aiohttp.ClientSession(timeout=timeout) as session: async with aiohttp.ClientSession(timeout=timeout) as session:
@ -265,6 +286,7 @@ async def test_service_connection(service_id: str, orgid: str = "") -> Tuple[boo
# Session management # Session management
async def create_session(service_id: str, user_id: str, orgid: str, user_message: str = "") -> str: async def create_session(service_id: str, user_id: str, orgid: str, user_message: str = "") -> str:
"""Create a new session with a Hermes service.""" """Create a new session with a Hermes service."""
print(f"[INFO] create_session: service_id={service_id}, user_id={user_id}, orgid={orgid}, user_message={'(empty)' if not user_message else '(has message)'}")
try: try:
# Get service configuration (verify it belongs to current org) # Get service configuration (verify it belongs to current org)
service = await get_service_by_id(service_id, orgid) service = await get_service_by_id(service_id, orgid)
@ -336,6 +358,7 @@ async def send_message_to_service(service_id: str, session_id: str, message: str
Returns: Returns:
Response from the service Response from the service
""" """
print(f"[INFO] send_message_to_service: service_id={service_id}, session_id={session_id}, user_id={user_id}, orgid={orgid}, message_len={len(message)}")
try: try:
# Verify session belongs to current user before sending message # Verify session belongs to current user before sending message
session = await get_session_by_id(session_id, user_id) session = await get_session_by_id(session_id, user_id)
@ -385,6 +408,7 @@ async def get_session_messages(session_id: str, user_id: str, orgid: str) -> Lis
Returns: Returns:
List of message dictionaries List of message dictionaries
""" """
print(f"[INFO] get_session_messages: session_id={session_id}, user_id={user_id}, orgid={orgid}")
try: try:
# Verify session belongs to current user before getting messages # Verify session belongs to current user before getting messages
session = await get_session_by_id(session_id, user_id) session = await get_session_by_id(session_id, user_id)
@ -451,6 +475,7 @@ async def get_active_sessions(user_id: str) -> List[Dict]:
Returns: Returns:
List of active session dictionaries List of active session dictionaries
""" """
print(f"[INFO] get_active_sessions: user_id={user_id}")
try: try:
# Query the sessions table for active sessions belonging to current user using sqlor-database-module # Query the sessions table for active sessions belonging to current user using sqlor-database-module
db = DBPools() db = DBPools()
@ -481,6 +506,7 @@ async def get_recent_sessions(user_id: str, limit: int = 5) -> List[Dict]:
Returns: Returns:
List of recent session dictionaries List of recent session dictionaries
""" """
print(f"[INFO] get_recent_sessions: user_id={user_id}, limit={limit}")
try: try:
# Query the sessions table for recent sessions belonging to current user using sqlor-database-module # Query the sessions table for recent sessions belonging to current user using sqlor-database-module
db = DBPools() db = DBPools()
@ -511,6 +537,7 @@ async def get_session_by_id(session_id: str, user_id: str) -> Optional[Dict]:
Returns: Returns:
Session dictionary if found and owned by user, None otherwise Session dictionary if found and owned by user, None otherwise
""" """
print(f"[INFO] get_session_by_id: session_id={session_id}, user_id={user_id}")
try: try:
# Query database directly with user_id filter for security # Query database directly with user_id filter for security
db = DBPools() db = DBPools()
@ -554,6 +581,7 @@ async def get_setting(user_id: str) -> Dict:
Returns: Returns:
User settings dictionary User settings dictionary
""" """
print(f"[INFO] get_setting: user_id={user_id}")
import json import json
default_settings = { default_settings = {
@ -612,6 +640,7 @@ async def save_setting(section: str, key: str, value, user_id: str) -> bool:
Returns: Returns:
True if saved successfully, False otherwise True if saved successfully, False otherwise
""" """
print(f"[INFO] save_setting: user_id={user_id}, section={section}, key={key}")
import json import json
# Load existing settings or start with defaults # Load existing settings or start with defaults