huace-aigc-auth-client 1.1.7__py3-none-any.whl → 1.1.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -67,11 +67,76 @@ from .legacy_adapter import (
67
67
  create_sync_config,
68
68
  create_default_field_mappings,
69
69
  )
70
+ # fastapi 相关功能是可选的,如果未安装 fastapi 则跳过
71
+ try:
72
+ from .webhook import (
73
+ register_webhook_router,
74
+ verify_webhook_signature,
75
+ )
76
+ _fastapi_available = True
77
+ except ImportError:
78
+ _fastapi_available = False
79
+ # 提供占位符,避免 __all__ 导出时出错
80
+ register_webhook_router = None
81
+ verify_webhook_signature = None
82
+
83
+ # Flask 相关功能是可选的,如果未安装 flask 则跳过
84
+ try:
85
+ from .webhook_flask import (
86
+ create_flask_webhook_blueprint,
87
+ register_flask_webhook_routes,
88
+ )
89
+ _flask_available = True
90
+ except ImportError:
91
+ _flask_available = False
92
+ # 提供占位符,避免 __all__ 导出时出错
93
+ create_flask_webhook_blueprint = None
94
+ register_flask_webhook_routes = None
95
+
96
+ def setLogger(log):
97
+ """
98
+ 统一设置所有模块的 logger
99
+
100
+ Args:
101
+ log: logging.Logger 实例
102
+
103
+ 使用示例:
104
+ import logging
105
+ from huace_aigc_auth_client import setLogger
106
+
107
+ logger = logging.getLogger("my_app")
108
+ logger.setLevel(logging.INFO)
109
+
110
+ # 设置 SDK 所有模块使用该 logger
111
+ setLogger(logger)
112
+ """
113
+ try:
114
+ from .sdk import setLogger as sdk_setLogger
115
+ sdk_setLogger(log)
116
+ except Exception as e:
117
+ print(f"Failed to set logger for sdk module: {e}")
118
+
119
+ try:
120
+ from .legacy_adapter import setLogger as legacy_setLogger
121
+ legacy_setLogger(log)
122
+ except Exception as e:
123
+ print(f"Failed to set logger for legacy_adapter module: {e}")
124
+
125
+ if _fastapi_available:
126
+ try:
127
+ from .webhook import setLogger as webhook_setLogger
128
+ webhook_setLogger(log)
129
+ except Exception as e:
130
+ print(f"Failed to set logger for webhook module: {e}")
131
+
132
+ # 只在 flask 可用时设置 flask 模块的 logger
133
+ if _flask_available:
134
+ try:
135
+ from .webhook_flask import setLogger as webhook_flask_setLogger
136
+ webhook_flask_setLogger(log)
137
+ except Exception as e:
138
+ print(f"Failed to set logger for webhook_flask module: {e}")
70
139
 
71
- from .webhook import (
72
- register_webhook_router,
73
- verify_webhook_signature,
74
- )
75
140
 
76
141
  __all__ = [
77
142
  # 核心类
@@ -94,8 +159,13 @@ __all__ = [
94
159
  "SyncResult",
95
160
  "create_sync_config",
96
161
  "create_default_field_mappings",
97
- # Webhook 接收
162
+ # Webhook 接收 (FastAPI)
98
163
  "register_webhook_router",
99
164
  "verify_webhook_signature",
165
+ # Webhook 接收 (Flask)
166
+ "create_flask_webhook_blueprint",
167
+ "register_flask_webhook_routes",
168
+ # Logger 设置
169
+ "setLogger",
100
170
  ]
101
- __version__ = "1.1.7"
171
+ __version__ = "1.1.13"
@@ -20,7 +20,9 @@ from enum import Enum
20
20
  from abc import ABC, abstractmethod
21
21
 
22
22
  logger = logging.getLogger(__name__)
23
-
23
+ def setLogger(log):
24
+ global logger
25
+ logger = log
24
26
 
25
27
  class PasswordMode(Enum):
26
28
  """密码处理模式"""
@@ -101,11 +103,11 @@ class LegacySystemAdapter(ABC):
101
103
  旧系统适配器抽象基类
102
104
 
103
105
  接入系统需要继承此类并实现以下方法:
104
- - get_user_by_unique_field: 通过唯一字段获取用户
105
- - create_user: 创建用户
106
- - update_user: 更新用户(可选)
107
- - get_all_users: 获取所有用户(用于初始化同步)
108
- - handle_webhook: 处理 webhook 通知(可选,异步方法)
106
+ - get_user_by_username_async
107
+ - _create_user_async
108
+ - _update_user_async
109
+ - _delete_user_async
110
+ - get_all_users_async
109
111
  """
110
112
 
111
113
  def __init__(self, sync_config: SyncConfig, auth_client=None):
@@ -119,24 +121,6 @@ class LegacySystemAdapter(ABC):
119
121
  self.config = sync_config
120
122
  self.auth_client = auth_client
121
123
 
122
- @abstractmethod
123
- def get_user_by_unique_field(self, value: Any) -> Optional[LegacyUserData]:
124
- """通过唯一字段获取旧系统用户"""
125
- pass
126
-
127
- @abstractmethod
128
- def create_user(self, user_data: Dict[str, Any]) -> Optional[Any]:
129
- """在旧系统创建用户,返回用户ID"""
130
- pass
131
-
132
- def update_user(self, unique_value: Any, user_data: Dict[str, Any]) -> bool:
133
- """更新旧系统用户(可选实现)"""
134
- return False
135
-
136
- def get_all_users(self) -> List[LegacyUserData]:
137
- """获取所有旧系统用户(用于初始化同步)"""
138
- return []
139
-
140
124
  @abstractmethod
141
125
  async def get_user_by_username_async(self, username: str) -> Optional[LegacyUserData]:
142
126
  """异步获取用户(子类必须实现)
@@ -147,6 +131,7 @@ class LegacySystemAdapter(ABC):
147
131
  Returns:
148
132
  Optional[LegacyUserData]: 用户数据,不存在返回 None
149
133
  """
134
+ logger.info(f"Fetching user by username asynchronously: {username}")
150
135
  raise NotImplementedError("Subclass must implement get_user_by_username_async method")
151
136
 
152
137
  @abstractmethod
@@ -159,6 +144,7 @@ class LegacySystemAdapter(ABC):
159
144
  Returns:
160
145
  Optional[Any]: 创建的用户 ID 或其他标识
161
146
  """
147
+ logger.info(f"Creating user with data: {user_data}")
162
148
  raise NotImplementedError("Subclass must implement _create_user_async method")
163
149
 
164
150
  @abstractmethod
@@ -172,6 +158,7 @@ class LegacySystemAdapter(ABC):
172
158
  Returns:
173
159
  bool: 更新成功返回 True
174
160
  """
161
+ logger.info(f"Updating user: {username} with data: {user_data}")
175
162
  raise NotImplementedError("Subclass must implement _update_user_async method")
176
163
 
177
164
  @abstractmethod
@@ -184,6 +171,7 @@ class LegacySystemAdapter(ABC):
184
171
  Returns:
185
172
  bool: 删除成功返回 True
186
173
  """
174
+ logger.info(f"Deleting user: {username}")
187
175
  raise NotImplementedError("Subclass must implement _delete_user_async method")
188
176
 
189
177
  @abstractmethod
@@ -193,6 +181,7 @@ class LegacySystemAdapter(ABC):
193
181
  Returns:
194
182
  List[LegacyUserData]: 所有用户数据列表
195
183
  """
184
+ logger.info("Fetching all users from legacy system asynchronously")
196
185
  raise NotImplementedError("Subclass must implement get_all_users_async method")
197
186
 
198
187
  async def upsert_user_async(self, user_data: Dict[str, Any], auth_data: Dict[str, Any] = None) -> Dict[str, Any]:
@@ -208,6 +197,7 @@ class LegacySystemAdapter(ABC):
208
197
  """
209
198
  username = user_data.get("username")
210
199
  if not username:
200
+ logger.error("username is required for upsert operation")
211
201
  raise ValueError("username is required for upsert operation")
212
202
 
213
203
  # 检查用户是否存在
@@ -216,6 +206,7 @@ class LegacySystemAdapter(ABC):
216
206
  if existing:
217
207
  # 用户存在,执行更新
218
208
  if not auth_data or auth_data.get("updatedFields") is None or len(auth_data.get("updatedFields")) == 0:
209
+ logger.info(f"No updatedFields provided for user: {username}, skipping update")
219
210
  # 如果没有提供 auth_data 或 updatedFields,则不更新
220
211
  return {"created": False, "user_id": existing.get("id")}
221
212
  await self._update_user_async(username, user_data)
@@ -225,6 +216,41 @@ class LegacySystemAdapter(ABC):
225
216
  user_id = await self._create_user_async(user_data)
226
217
  return {"created": True, "user_id": user_id}
227
218
 
219
+ async def sync_user_to_auth(self, legacy_user: LegacyUserData) -> Dict[str, Any]:
220
+ """同步单个旧系统用户到 aigc-auth(默认实现)
221
+
222
+ 子类可以选择性覆盖此方法以自定义同步逻辑。
223
+
224
+ Args:
225
+ legacy_user: 旧系统用户数据
226
+ Returns:
227
+ Dict: 同步结果
228
+ """
229
+ if not self.auth_client:
230
+ logger.error("auth_client is required for sync_user_to_auth")
231
+ raise ValueError("auth_client is required for sync_user_to_auth. Please provide it in constructor.")
232
+
233
+ logger.info(f"Syncing legacy user to auth: {legacy_user.to_dict()}")
234
+ auth_data = self.transform_legacy_to_auth(legacy_user)
235
+
236
+ # 获取密码(支持新的元组返回格式)
237
+ password_result = self.get_password_for_sync(legacy_user)
238
+ if isinstance(password_result, tuple):
239
+ password, is_hashed = password_result
240
+ else:
241
+ password, is_hashed = password_result, False
242
+
243
+ # 根据是否已加密选择不同的字段
244
+ if is_hashed:
245
+ auth_data["password_hashed"] = password
246
+ else:
247
+ auth_data["password"] = password
248
+
249
+ result = await self.auth_client.sync_user_to_auth(auth_data)
250
+ logger.info(f"Sync result for user {legacy_user.get('username')}: {result}")
251
+
252
+ return result
253
+
228
254
  async def batch_sync_to_auth(self) -> Dict[str, Any]:
229
255
  """批量同步旧系统用户到 aigc-auth(默认实现)
230
256
 
@@ -234,8 +260,10 @@ class LegacySystemAdapter(ABC):
234
260
  Dict: 同步结果统计
235
261
  """
236
262
  if not self.auth_client:
263
+ logger.error("auth_client is required for batch_sync_to_auth")
237
264
  raise ValueError("auth_client is required for batch_sync_to_auth. Please provide it in constructor.")
238
265
 
266
+ logger.info("Starting batch sync from legacy system to aigc-auth")
239
267
  users = await self.get_all_users_async()
240
268
 
241
269
  results = {
@@ -248,22 +276,8 @@ class LegacySystemAdapter(ABC):
248
276
 
249
277
  for user in users:
250
278
  try:
251
- auth_data = self.transform_legacy_to_auth(user)
252
-
253
- # 获取密码(支持新的元组返回格式)
254
- password_result = self.get_password_for_sync(user)
255
- if isinstance(password_result, tuple):
256
- password, is_hashed = password_result
257
- else:
258
- password, is_hashed = password_result, False
259
-
260
- # 根据是否已加密选择不同的字段
261
- if is_hashed:
262
- auth_data["password_hashed"] = password
263
- else:
264
- auth_data["password"] = password
265
-
266
- result = self.auth_client.sync_user_to_auth(auth_data)
279
+ result = await self.sync_user_to_auth(user)
280
+ logger.info(f"Sync result for user {user.get('username')}: {result}")
267
281
 
268
282
  if result.get("success"):
269
283
  if result.get("created"):
@@ -277,6 +291,7 @@ class LegacySystemAdapter(ABC):
277
291
  "error": result.get("message")
278
292
  })
279
293
  except Exception as e:
294
+ logger.error(f"Error syncing user {user.get('username')}: {e}")
280
295
  results["failed"] += 1
281
296
  results["errors"].append({
282
297
  "user": user.get("username"),
@@ -298,19 +313,24 @@ class LegacySystemAdapter(ABC):
298
313
  Returns:
299
314
  Dict: 处理结果
300
315
  """
316
+ logger.info(f"Handling webhook event: {event} with data: {data}")
301
317
  if event == "user.created" or event == "user.updated" or event == "user.login":
302
318
  # 转换数据格式
303
319
  legacy_data = self.transform_auth_to_legacy(data)
304
320
 
305
321
  # 获取密码
306
- password_result = self.get_password_for_sync()
322
+ password_result = self.get_password_for_sync(legacy_data)
307
323
  if isinstance(password_result, tuple):
308
324
  password, is_hashed = password_result
309
325
  else:
310
326
  password, is_hashed = password_result, False
311
- legacy_data["password"] = password
327
+ if is_hashed:
328
+ legacy_data["password_hashed"] = password
329
+ else:
330
+ legacy_data["password"] = password
312
331
 
313
332
  # 创建或更新用户
333
+ logger.info(f"Handling {event} event for user: {legacy_data}")
314
334
  result = await self.upsert_user_async(legacy_data)
315
335
 
316
336
  return {
@@ -321,18 +341,23 @@ class LegacySystemAdapter(ABC):
321
341
  }
322
342
 
323
343
  elif event == "user.deleted":
344
+ logger.info("Handling user.deleted event")
324
345
  # 禁用用户而不是删除
325
346
  username = data.get("username")
326
347
  if not username:
348
+ logger.error("username is required for user.deleted event")
327
349
  logger.error("username is required for user.deleted event")
328
350
  return {"success": False, "message": "username is required for user.deleted event"}
351
+ logger.info(f"Disabling user: {username}")
329
352
  await self._delete_user_async(username)
330
353
 
331
354
  return {"success": True, "message": "User disabled"}
332
355
 
333
356
  elif event == "user.init_sync_auth":
357
+ logger.info("Handling user.init_sync_auth event")
334
358
  # 初始化同步:批量同步旧系统用户到 aigc-auth
335
359
  if not self.auth_client:
360
+ logger.error("auth_client is required for init_sync_auth event")
336
361
  return {"success": False, "message": "auth_client is required for init_sync_auth event. Please provide it in constructor."}
337
362
 
338
363
  results = await self.batch_sync_to_auth()
@@ -362,7 +387,7 @@ class LegacySystemAdapter(ABC):
362
387
 
363
388
  if auth_value is not None:
364
389
  result[mapping.legacy_field] = auth_value
365
-
390
+ logger.info(f"Transformed auth user({auth_user}) to legacy format: {result}")
366
391
  return result
367
392
 
368
393
  def transform_legacy_to_auth(self, legacy_user: LegacyUserData) -> Dict[str, Any]:
@@ -382,7 +407,7 @@ class LegacySystemAdapter(ABC):
382
407
 
383
408
  if legacy_value is not None:
384
409
  result[mapping.auth_field] = legacy_value
385
-
410
+ logger.info(f"Transformed legacy user({legacy_user}) to auth format: {result}")
386
411
  return result
387
412
 
388
413
  def get_password_for_sync(self, legacy_user: Optional[LegacyUserData] = None) -> tuple:
@@ -397,7 +422,14 @@ class LegacySystemAdapter(ABC):
397
422
  return (self.config.unified_password, False)
398
423
  elif self.config.password_mode == PasswordMode.CUSTOM_MAPPING:
399
424
  if self.config.password_mapper and legacy_user:
400
- password = self.config.password_mapper(legacy_user.data)
425
+ # 兼容 dict 和 LegacyUserData 两种类型
426
+ if isinstance(legacy_user, dict):
427
+ user_data = legacy_user
428
+ elif isinstance(legacy_user, LegacyUserData):
429
+ user_data = legacy_user.data
430
+ else:
431
+ user_data = legacy_user
432
+ password = self.config.password_mapper(user_data)
401
433
  return (password, self.config.password_is_hashed)
402
434
  return (self.config.unified_password, False)
403
435
  return (self.config.unified_password, False)
@@ -19,6 +19,9 @@ from typing import Optional, List, Dict, Any, Callable, Tuple
19
19
  from dataclasses import dataclass
20
20
 
21
21
  logger = logging.getLogger(__name__)
22
+ def setLogger(log):
23
+ global logger
24
+ logger = log
22
25
 
23
26
  # 尝试加载 .env 文件
24
27
  try:
@@ -13,7 +13,9 @@ from typing import Callable, Awaitable, Dict, Any, Optional
13
13
  from fastapi import APIRouter, Request, HTTPException
14
14
 
15
15
  logger = logging.getLogger(__name__)
16
-
16
+ def setLogger(log):
17
+ global logger
18
+ logger = log
17
19
 
18
20
  def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
19
21
  """
@@ -0,0 +1,220 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Flask Webhook 接口
4
+
5
+ 提供 Flask 版本的 webhook 接收功能,用于接收 aigc-auth 的用户变更通知。
6
+ 适用于使用 Flask 框架的项目。
7
+ """
8
+
9
+ import os
10
+ import hmac
11
+ import hashlib
12
+ import logging
13
+ from typing import Callable, Dict, Any, Optional
14
+ from flask import Blueprint, request, jsonify
15
+
16
+ logger = logging.getLogger(__name__)
17
+ def setLogger(log):
18
+ global logger
19
+ logger = log
20
+
21
+ def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
22
+ """
23
+ 验证 webhook 签名
24
+
25
+ Args:
26
+ payload: 请求体(bytes)
27
+ signature: 签名字符串
28
+ secret: 密钥
29
+
30
+ Returns:
31
+ bool: 签名是否有效
32
+ """
33
+ if not secret:
34
+ # 如果未配置密钥,跳过验证(开发环境)
35
+ logger.warning("Webhook secret not configured, skipping signature verification")
36
+ return True
37
+
38
+ if not signature:
39
+ return False
40
+
41
+ expected = hmac.new(
42
+ secret.encode('utf-8'),
43
+ payload,
44
+ hashlib.sha256
45
+ ).hexdigest()
46
+
47
+ return hmac.compare_digest(expected, signature)
48
+
49
+
50
+ def create_flask_webhook_blueprint(
51
+ handler: Callable[[Dict[str, Any]], Dict[str, Any]],
52
+ secret_env_key: str = "AIGC_AUTH_WEBHOOK_SECRET",
53
+ url_prefix: str = "/api/webhook",
54
+ blueprint_name: str = "aigc_auth_webhook"
55
+ ) -> Blueprint:
56
+ """
57
+ 创建 Flask Webhook Blueprint
58
+
59
+ Args:
60
+ handler: 处理函数,接收 webhook 数据并返回结果
61
+ 函数签名: def handler(data: Dict[str, Any]) -> Dict[str, Any]
62
+ - 如果是同步函数,直接调用
63
+ - 如果是异步函数,会使用 asyncio.run 包装
64
+ secret_env_key: webhook 密钥的环境变量名
65
+ url_prefix: URL 前缀,默认为 "/api/webhook"
66
+ blueprint_name: Blueprint 名称,默认为 "aigc_auth_webhook"
67
+
68
+ Returns:
69
+ Blueprint: Flask Blueprint 实例
70
+
71
+ 使用示例:
72
+ from huace_aigc_auth_client.webhook_flask import create_flask_webhook_blueprint
73
+
74
+ # 方式1:使用适配器的 handle_webhook 方法
75
+ from your_app.adapters import YourAdapter
76
+
77
+ sync_config = create_sync_config(...)
78
+ auth_client = AigcAuthClient(...)
79
+ adapter = YourAdapter(sync_config, auth_client)
80
+
81
+ async def webhook_handler(data: dict) -> dict:
82
+ event = data.get("event")
83
+ event_data = data.get("data", {})
84
+ return await adapter.handle_webhook(event, event_data)
85
+
86
+ webhook_bp = create_flask_webhook_blueprint(
87
+ handler=webhook_handler,
88
+ url_prefix="/api/webhook"
89
+ )
90
+
91
+ # 方式2:自定义处理逻辑
92
+ def custom_handler(data: dict) -> dict:
93
+ event = data.get("event")
94
+ if event == "user.created":
95
+ # 自定义处理逻辑
96
+ user_data = data.get("data", {})
97
+ # ... 处理用户创建事件
98
+ return {"status": "success", "created": True}
99
+ return {"status": "ok"}
100
+
101
+ webhook_bp = create_flask_webhook_blueprint(handler=custom_handler)
102
+
103
+ # 注册到 Flask app
104
+ app.register_blueprint(webhook_bp)
105
+
106
+ # Webhook 端点: POST /api/webhook/auth
107
+ """
108
+ webhook_bp = Blueprint(blueprint_name, __name__, url_prefix=url_prefix)
109
+
110
+ @webhook_bp.route('/auth', methods=['POST'])
111
+ def receive_auth_webhook():
112
+ """
113
+ 接收 aigc-auth 用户变更通知
114
+
115
+ 当 aigc-auth 中创建或更新用户时,会发送 webhook 通知到此端点。
116
+
117
+ 支持的事件:
118
+ - user.created: 用户创建
119
+ - user.updated: 用户更新
120
+ - user.deleted: 用户删除
121
+ - user.login: 用户登录
122
+ - user.init_sync_auth: 初始化同步请求
123
+ """
124
+ # 获取请求体
125
+ body = request.get_data()
126
+
127
+ # 验证签名
128
+ signature = request.headers.get("X-Webhook-Signature", "")
129
+ secret = os.getenv(secret_env_key, "")
130
+
131
+ if not verify_webhook_signature(body, signature, secret):
132
+ logger.warning("Webhook 签名验证失败")
133
+ return jsonify({"code": 401, "message": "Invalid signature"}), 401
134
+
135
+ # 解析请求数据
136
+ try:
137
+ data = request.get_json()
138
+ except Exception as e:
139
+ logger.error(f"解析 webhook 数据失败: {e}")
140
+ return jsonify({"code": 400, "message": "Invalid JSON payload"}), 400
141
+
142
+ # 记录日志
143
+ event = data.get("event", "unknown")
144
+ logger.info(f"收到 webhook 事件: {event}")
145
+
146
+ # 调用用户提供的处理函数
147
+ try:
148
+ import asyncio
149
+ import inspect
150
+
151
+ # 检查 handler 是否为协程函数
152
+ if inspect.iscoroutinefunction(handler):
153
+ result = asyncio.run(handler(data))
154
+ else:
155
+ result = handler(data)
156
+
157
+ return jsonify({"code": 0, "message": "success", "data": result})
158
+
159
+ except Exception as e:
160
+ logger.exception(f"处理 webhook 失败: {e}")
161
+ return jsonify({"code": 500, "message": str(e)}), 500
162
+
163
+ return webhook_bp
164
+
165
+
166
+ def register_flask_webhook_routes(
167
+ app,
168
+ handler: Callable[[Dict[str, Any]], Dict[str, Any]],
169
+ secret_env_key: str = "AIGC_AUTH_WEBHOOK_SECRET",
170
+ url_prefix: str = "/api/webhook",
171
+ blueprint_name: str = "aigc_auth_webhook"
172
+ ):
173
+ """
174
+ 注册 webhook 路由到 Flask 应用
175
+
176
+ 这是一个便捷函数,封装了创建 blueprint 和注册的过程。
177
+
178
+ Args:
179
+ app: Flask 应用实例
180
+ handler: 处理函数,接收 webhook 数据并返回结果
181
+ secret_env_key: webhook 密钥的环境变量名
182
+ url_prefix: URL 前缀,默认为 "/api/webhook"
183
+ blueprint_name: Blueprint 名称
184
+
185
+ 使用示例:
186
+ from huace_aigc_auth_client.webhook_flask import register_flask_webhook_routes
187
+
188
+ app = Flask(__name__)
189
+
190
+ # 创建适配器
191
+ adapter = YourAdapter(sync_config, auth_client)
192
+
193
+ # 定义处理函数
194
+ async def webhook_handler(data: dict) -> dict:
195
+ event = data.get("event")
196
+ event_data = data.get("data", {})
197
+ return await adapter.handle_webhook(event, event_data)
198
+
199
+ # 注册 webhook 路由
200
+ register_flask_webhook_routes(
201
+ app,
202
+ handler=webhook_handler,
203
+ url_prefix="/custom/webhook" # 自定义前缀
204
+ )
205
+
206
+ # Webhook 端点: POST /custom/webhook/auth
207
+ """
208
+ webhook_bp = create_flask_webhook_blueprint(
209
+ handler=handler,
210
+ secret_env_key=secret_env_key,
211
+ url_prefix=url_prefix,
212
+ blueprint_name=blueprint_name
213
+ )
214
+ app.register_blueprint(webhook_bp)
215
+ logger.info(f"Webhook 路由已注册: {url_prefix}/auth")
216
+
217
+
218
+ # 向后兼容的别名
219
+ create_webhook_blueprint = create_flask_webhook_blueprint
220
+ register_webhook_routes = register_flask_webhook_routes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: huace-aigc-auth-client
3
- Version: 1.1.7
3
+ Version: 1.1.13
4
4
  Summary: 华策AIGC Auth Client - 提供 Token 验证、用户信息获取、权限检查、旧系统接入等功能
5
5
  Author-email: Huace <support@huace.com>
6
6
  License: MIT
@@ -44,8 +44,7 @@ pip install huace-aigc-auth-client
44
44
  # 必填:应用 ID 和密钥(在鉴权中心创建应用后获取)
45
45
  AIGC_AUTH_APP_ID=your_app_id
46
46
  AIGC_AUTH_APP_SECRET=your_app_secret
47
-
48
- # 可选:鉴权服务地址(默认为生产环境)- 测试环境鉴权地址:https://auth-test.aigc.huacemedia.com/aigc-auth/api/v1
47
+ # 鉴权服务地址(默认为生产环境)- 测试环境鉴权地址:http://auth.aigc-test.huacemedia.com/aigc-auth/api/v1
49
48
  AIGC_AUTH_BASE_URL=https://aigc-auth.huacemedia.com/aigc-auth/api/v1
50
49
  ```
51
50
 
@@ -420,8 +419,8 @@ from huace_aigc_auth_client import LegacySystemAdapter, LegacyUserData
420
419
  class MyLegacyAdapter(LegacySystemAdapter):
421
420
  """实现与旧系统用户表的交互"""
422
421
 
423
- def __init__(self, db, sync_config):
424
- super().__init__(sync_config)
422
+ def __init__(self, db, sync_config, auth_client=None):
423
+ super().__init__(sync_config, auth_client)
425
424
  self.db = db
426
425
 
427
426
  def get_user_by_unique_field(self, username: str):
@@ -435,8 +434,23 @@ class MyLegacyAdapter(LegacySystemAdapter):
435
434
  "email": user.email,
436
435
  # ... 其他字段
437
436
  })
437
+
438
+ async def get_user_by_username_async(self, username: str) -> Optional[LegacyUserData]:
439
+ """异步通过用户名获取旧系统用户"""
440
+ user = await self.db.execute(
441
+ select(User).where(User.username == username)
442
+ )
443
+ user = user.scalars().first()
444
+ if not user:
445
+ return None
446
+ return LegacyUserData({
447
+ "id": user.id,
448
+ "username": user.username,
449
+ "email": user.email,
450
+ # ... 其他字段
451
+ })
438
452
 
439
- def create_user(self, user_data: dict):
453
+ async def _create_user_async(self, user_data: Dict[str, Any]) -> Optional[Any]:
440
454
  """在旧系统创建用户"""
441
455
  user = User(
442
456
  username=user_data["username"],
@@ -445,20 +459,39 @@ class MyLegacyAdapter(LegacySystemAdapter):
445
459
  # ... 其他字段
446
460
  )
447
461
  self.db.add(user)
448
- self.db.commit()
462
+ await self.db.commit()
449
463
  return user.id
450
464
 
451
- def update_user(self, username: str, user_data: dict):
452
- """更新旧系统用户"""
453
- user = self.db.query(User).filter(User.username == username).first()
465
+ async def _update_user_async(self, username: str, user_data: Dict[str, Any]) -> bool:
466
+ """异步更新旧系统用户"""
467
+ user = await self.db.execute(
468
+ select(User).where(User.username == username)
469
+ )
470
+ user = user.scalars().first()
454
471
  if user:
455
472
  for key, value in user_data.items():
456
473
  setattr(user, key, value)
457
- self.db.commit()
474
+ await self.db.commit()
475
+ return True
476
+ return False
477
+
478
+ async def _delete_user_async(self, username: str) -> bool:
479
+ """异步删除旧系统用户"""
480
+ user = await self.db.execute(
481
+ select(User).where(User.username == username)
482
+ )
483
+ user = user.scalars().first()
484
+ if user:
485
+ await self.db.delete(user)
486
+ # 或者软删除:user.is_active = False
487
+ await self.db.commit()
488
+ return True
489
+ return False
458
490
 
459
- def get_all_users(self):
460
- """获取所有用户(用于初始化同步)"""
461
- users = self.db.query(User).all()
491
+ async def get_all_users_async(self) -> List[LegacyUserData]:
492
+ """异步获取所有用户(用于初始化同步)"""
493
+ result = await self.db.execute(select(User))
494
+ users = result.scalars().all()
462
495
  return [LegacyUserData({"username": u.username, ...}) for u in users]
463
496
  ```
464
497
 
@@ -468,7 +501,7 @@ class MyLegacyAdapter(LegacySystemAdapter):
468
501
  from huace_aigc_auth_client import AigcAuthClient, UserSyncService
469
502
 
470
503
  client = AigcAuthClient()
471
- adapter = MyLegacyAdapter(db, sync_config)
504
+ adapter = MyLegacyAdapter(db, sync_config, client)
472
505
  sync_service = UserSyncService(client, adapter)
473
506
 
474
507
  # 在获取用户信息后调用同步
@@ -479,7 +512,7 @@ async def auth_middleware(request, call_next):
479
512
  # 登录成功后,同步用户到旧系统
480
513
  if hasattr(request.state, "user_info"):
481
514
  user_info = request.state.user_info
482
- sync_service.sync_on_login(user_info)
515
+ await sync_service.sync_on_login_async(user_info)
483
516
 
484
517
  return response
485
518
  ```
@@ -493,47 +526,35 @@ from fastapi import APIRouter
493
526
  from huace_aigc_auth_client import register_webhook_router
494
527
 
495
528
  api_router = APIRouter()
529
+ client = AigcAuthClient()
530
+ adapter = MyLegacyAdapter(db, sync_config, client)
496
531
 
497
532
  # 定义 webhook 处理函数
498
- async def handle_webhook(data: dict) -> dict:
533
+ async def handle_auth_webhook(request_data: Dict[str, Any]) -> Dict[str, Any]:
499
534
  """
500
- 处理来自 aigc-auth webhook 通知
535
+ 独立的 webhook 处理函数(用于 SDK 集成)
501
536
 
502
- Args:
503
- data: webhook 数据,包含 event 和 data 字段
504
-
505
- Returns:
506
- dict: 处理结果
537
+ 这个函数不需要 db 参数,会在内部创建数据库会话。
538
+ 适用于通过 SDK register_webhook_router 注册。
507
539
  """
508
- from your_app.db import get_db_session
540
+ from app.db.session import get_db
509
541
 
510
- event = data.get("event")
511
- user_data = data.get("data", {})
512
-
513
- # 创建数据库会话
514
- async with get_db_session() as db:
515
- adapter = MyLegacyAdapter(db, sync_config)
516
-
517
- if event == "user.created":
518
- # 处理用户创建事件
519
- if not adapter.get_user_by_unique_field(user_data["username"]):
520
- legacy_data = adapter.transform_auth_to_legacy(user_data)
521
- legacy_data["password"] = sync_config.unified_password
522
- adapter.create_user(legacy_data)
523
- await db.commit()
524
-
525
- elif event == "user.updated":
526
- # 处理用户更新事件
527
- legacy_data = adapter.transform_auth_to_legacy(user_data)
528
- adapter.update_user(user_data["username"], legacy_data)
529
- await db.commit()
530
-
531
- return {"status": "ok", "event": event}
542
+ async for db in get_db():
543
+ try:
544
+ event = request_data.get("event")
545
+ data = request_data.get("data", {})
546
+ logger.info(f"Received webhook event: {event} with data: {data}")
547
+
548
+ # 调用适配器的 handle_webhook 方法
549
+ return await adapter.handle_webhook(event, data)
550
+ except Exception as e:
551
+ logger.exception(f"Failed to handle webhook: {e}")
552
+ raise
532
553
 
533
554
  # 注册 webhook 路由(自动处理签名验证)
534
555
  register_webhook_router(
535
556
  api_router,
536
- handler=handle_webhook,
557
+ handler=handle_auth_webhook,
537
558
  prefix="/webhook", # 可选,默认 "/webhook"
538
559
  secret_env_key="aigc-auth-webhook-secret", # 可选,在 auth 后台配置
539
560
  tags=["Webhook"] # 可选,默认 ["Webhook"]
@@ -542,55 +563,6 @@ register_webhook_router(
542
563
  # webhook 端点将自动创建在: /webhook/auth
543
564
  ```
544
565
 
545
- **传统方式:手动实现 Webhook 端点**
546
-
547
- ```python
548
- import hmac
549
- import hashlib
550
- import os
551
-
552
- @app.post("/api/v1/webhook/auth")
553
- async def receive_auth_webhook(request: Request, db: Session = Depends(get_db)):
554
- """接收 aigc-auth 的用户变更通知"""
555
- # 获取原始请求体
556
- body = await request.body()
557
-
558
- # 验证签名
559
- signature = request.headers.get("X-Webhook-Signature", "")
560
- secret = os.getenv("AIGC_AUTH_WEBHOOK_SECRET", "")
561
-
562
- if secret:
563
- expected = hmac.new(
564
- secret.encode('utf-8'),
565
- body,
566
- hashlib.sha256
567
- ).hexdigest()
568
-
569
- if not hmac.compare_digest(expected, signature):
570
- raise HTTPException(status_code=401, detail="Invalid signature")
571
-
572
- # 解析数据
573
- data = await request.json()
574
- event = data.get("event")
575
- user_data = data.get("data", {})
576
-
577
- if event == "user.created":
578
- adapter = MyLegacyAdapter(db, sync_config)
579
- if not adapter.get_user_by_unique_field(user_data["username"]):
580
- legacy_data = adapter.transform_auth_to_legacy(user_data)
581
- legacy_data["password"] = sync_config.unified_password
582
- adapter.create_user(legacy_data)
583
- db.commit()
584
-
585
- elif event == "user.updated":
586
- adapter = MyLegacyAdapter(db, sync_config)
587
- legacy_data = adapter.transform_auth_to_legacy(user_data)
588
- adapter.update_user(user_data["username"], legacy_data)
589
- db.commit()
590
-
591
- return {"success": True}
592
- ```
593
-
594
566
  **Webhook 签名验证说明**
595
567
 
596
568
  SDK 的 `register_webhook_router` 会自动处理签名验证:
@@ -604,7 +576,7 @@ SDK 的 `register_webhook_router` 会自动处理签名验证:
604
576
  ```python
605
577
  # 一次性脚本:将旧系统用户同步到 aigc-auth
606
578
  async def init_sync():
607
- adapter = MyLegacyAdapter(db, sync_config)
579
+ adapter = MyLegacyAdapter(db, sync_config, client)
608
580
  users = adapter.get_all_users()
609
581
 
610
582
  for user in users:
@@ -0,0 +1,10 @@
1
+ huace_aigc_auth_client/__init__.py,sha256=Pjau66hZz0MeL7WWf-G4nGWkjw-4UazjSy2f8n5jdGM,4540
2
+ huace_aigc_auth_client/legacy_adapter.py,sha256=t-1QekRecn0B_gQ5r-ksX0hhuqUTBB2lvIUiNT4SowI,23781
3
+ huace_aigc_auth_client/sdk.py,sha256=ypClZfQm4Ux4db8XDP51I5Cuk1Uc9F2VPgECpXXphkQ,23272
4
+ huace_aigc_auth_client/webhook.py,sha256=XQZYEbMoqIdqZWCGSTcedeDKJpDbUVSq5g08g-6Qucg,4124
5
+ huace_aigc_auth_client/webhook_flask.py,sha256=Iosu4dBtRhQZM_ytn-bn82MpVsyOiV28FBnt7Tfh31U,7225
6
+ huace_aigc_auth_client-1.1.13.dist-info/licenses/LICENSE,sha256=z7dgC7KljhBLNvKjN15391nMj3aLt0gbud8-Yf1F8EQ,1063
7
+ huace_aigc_auth_client-1.1.13.dist-info/METADATA,sha256=XspgJA5ZklRRqN7zRy37eXffkcWINrLMT31mmVtEzmo,22971
8
+ huace_aigc_auth_client-1.1.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ huace_aigc_auth_client-1.1.13.dist-info/top_level.txt,sha256=kbv0nQ6PQ0JVneWPH7O2AbtlJnP7AjvFJ6JjM6ZEBxo,23
10
+ huace_aigc_auth_client-1.1.13.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- huace_aigc_auth_client/__init__.py,sha256=WIRO-YlPbw_hXU7WxmyEI2Be_BvatQijtlKTSlSrr4k,2332
2
- huace_aigc_auth_client/legacy_adapter.py,sha256=eNoyaCtTMdeNfzDlB66Fu-6WAfGYGk3DioIBAAZOL6A,21871
3
- huace_aigc_auth_client/sdk.py,sha256=ha1e_MSzLCsIMrdgcKSh-V5hTVR1rqqrIMWcDmtVMKs,23217
4
- huace_aigc_auth_client/webhook.py,sha256=m-mjXNNqrUClrNpOHRBzty0XKFDBuZp4e_PgLd2t0aA,4070
5
- huace_aigc_auth_client-1.1.7.dist-info/licenses/LICENSE,sha256=z7dgC7KljhBLNvKjN15391nMj3aLt0gbud8-Yf1F8EQ,1063
6
- huace_aigc_auth_client-1.1.7.dist-info/METADATA,sha256=UPpY9MyNjsxOSd4XWbGT89VMBGwGG8EOgrvBourbLlk,23486
7
- huace_aigc_auth_client-1.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- huace_aigc_auth_client-1.1.7.dist-info/top_level.txt,sha256=kbv0nQ6PQ0JVneWPH7O2AbtlJnP7AjvFJ6JjM6ZEBxo,23
9
- huace_aigc_auth_client-1.1.7.dist-info/RECORD,,