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:
parent
7f2f5728d6
commit
876c6a857c
@ -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)
|
||||||
@ -174,14 +184,15 @@ async def delete_service(service_id: str, orgid: str) -> bool:
|
|||||||
|
|
||||||
async def get_service_by_id(service_id: str, orgid: str) -> Optional[Dict]:
|
async def get_service_by_id(service_id: str, orgid: str) -> Optional[Dict]:
|
||||||
"""Get service configuration by ID (only if owned by specified organization).
|
"""Get service configuration by ID (only if owned by specified organization).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
service_id: The ID of the service to retrieve
|
service_id: The ID of the service to retrieve
|
||||||
orgid: The ID of the organization requesting the service
|
orgid: The ID of the organization requesting the service
|
||||||
|
|
||||||
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()
|
||||||
@ -193,12 +204,16 @@ async def get_service_by_id(service_id: str, orgid: str) -> Optional[Dict]:
|
|||||||
'service_id': service_id,
|
'service_id': service_id,
|
||||||
'orgid': orgid
|
'orgid': orgid
|
||||||
})
|
})
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error getting service: {str(e)}")
|
print(f"Error getting service: {str(e)}")
|
||||||
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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user