v1.6: Platform Gateway — Amazon/JD/Taobao/PDD/TikTok 5平台API集成 + start_provision_v2.sh

This commit is contained in:
Chatwoot AI Agent Dev
2026-06-05 04:30:28 +00:00
parent 351c9b82fb
commit 989e21d1f6
19 changed files with 1760 additions and 15 deletions
+68
View File
@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
"""AES-256-GCM credential encryption.
The 32-byte key is loaded from ``GATEWAY_AES_KEY`` (base64, 32 bytes raw).
Format on disk (VARBINARY column):
nonce (12 bytes) || ciphertext_with_tag
Plaintext is the JSON of ``{access_token, refresh_token, ...}`` per channel.
"""
from __future__ import annotations
import base64
import json
import logging
import os
from typing import Any
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
log = logging.getLogger("chathub.gateway.crypto")
def _key() -> bytes:
raw = os.environ.get("GATEWAY_AES_KEY", "")
if not raw:
raise RuntimeError(
"GATEWAY_AES_KEY not set — refusing to encrypt/decrypt credentials"
)
try:
decoded = base64.b64decode(raw, validate=True)
except Exception as e:
raise RuntimeError(f"GATEWAY_AES_KEY not valid base64: {e}") from None
if len(decoded) != 32:
raise RuntimeError(
f"GATEWAY_AES_KEY must decode to 32 bytes, got {len(decoded)}"
)
return decoded
def encrypt(plaintext_obj: dict | str) -> bytes:
"""Encrypt a dict (or string) under AES-256-GCM. Returns nonce||ct."""
plaintext = (
plaintext_obj
if isinstance(plaintext_obj, str)
else json.dumps(plaintext_obj, ensure_ascii=False, sort_keys=True)
)
nonce = os.urandom(12)
return nonce + AESGCM(_key()).encrypt(nonce, plaintext.encode("utf-8"), None)
def decrypt(blob: bytes) -> dict:
"""Decrypt a nonce||ct blob back to a dict."""
if len(blob) < 12 + 16: # nonce + min GCM tag
raise ValueError("ciphertext too short")
nonce, ct = blob[:12], blob[12:]
raw = AESGCM(_key()).decrypt(nonce, ct, None)
return json.loads(raw.decode("utf-8"))
def is_configured() -> bool:
"""Check whether a usable key is present. Used by callers to short-circuit."""
try:
_key()
return True
except RuntimeError:
return False