huace-aigc-auth-client 1.1.7__py3-none-any.whl → 1.1.9__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.
- huace_aigc_auth_client/__init__.py +10 -2
- huace_aigc_auth_client/legacy_adapter.py +25 -25
- huace_aigc_auth_client/webhook_flask.py +218 -0
- {huace_aigc_auth_client-1.1.7.dist-info → huace_aigc_auth_client-1.1.9.dist-info}/METADATA +69 -97
- huace_aigc_auth_client-1.1.9.dist-info/RECORD +10 -0
- huace_aigc_auth_client-1.1.7.dist-info/RECORD +0 -9
- {huace_aigc_auth_client-1.1.7.dist-info → huace_aigc_auth_client-1.1.9.dist-info}/WHEEL +0 -0
- {huace_aigc_auth_client-1.1.7.dist-info → huace_aigc_auth_client-1.1.9.dist-info}/licenses/LICENSE +0 -0
- {huace_aigc_auth_client-1.1.7.dist-info → huace_aigc_auth_client-1.1.9.dist-info}/top_level.txt +0 -0
|
@@ -73,6 +73,11 @@ from .webhook import (
|
|
|
73
73
|
verify_webhook_signature,
|
|
74
74
|
)
|
|
75
75
|
|
|
76
|
+
from .webhook_flask import (
|
|
77
|
+
create_flask_webhook_blueprint,
|
|
78
|
+
register_flask_webhook_routes,
|
|
79
|
+
)
|
|
80
|
+
|
|
76
81
|
__all__ = [
|
|
77
82
|
# 核心类
|
|
78
83
|
"AigcAuthClient",
|
|
@@ -94,8 +99,11 @@ __all__ = [
|
|
|
94
99
|
"SyncResult",
|
|
95
100
|
"create_sync_config",
|
|
96
101
|
"create_default_field_mappings",
|
|
97
|
-
# Webhook 接收
|
|
102
|
+
# Webhook 接收 (FastAPI)
|
|
98
103
|
"register_webhook_router",
|
|
99
104
|
"verify_webhook_signature",
|
|
105
|
+
# Webhook 接收 (Flask)
|
|
106
|
+
"create_flask_webhook_blueprint",
|
|
107
|
+
"register_flask_webhook_routes",
|
|
100
108
|
]
|
|
101
|
-
__version__ = "1.1.
|
|
109
|
+
__version__ = "1.1.9"
|
|
@@ -101,11 +101,11 @@ class LegacySystemAdapter(ABC):
|
|
|
101
101
|
旧系统适配器抽象基类
|
|
102
102
|
|
|
103
103
|
接入系统需要继承此类并实现以下方法:
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
108
|
-
-
|
|
104
|
+
- get_user_by_username_async
|
|
105
|
+
- _create_user_async
|
|
106
|
+
- _update_user_async
|
|
107
|
+
- _delete_user_async
|
|
108
|
+
- get_all_users_async
|
|
109
109
|
"""
|
|
110
110
|
|
|
111
111
|
def __init__(self, sync_config: SyncConfig, auth_client=None):
|
|
@@ -119,24 +119,6 @@ class LegacySystemAdapter(ABC):
|
|
|
119
119
|
self.config = sync_config
|
|
120
120
|
self.auth_client = auth_client
|
|
121
121
|
|
|
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
122
|
@abstractmethod
|
|
141
123
|
async def get_user_by_username_async(self, username: str) -> Optional[LegacyUserData]:
|
|
142
124
|
"""异步获取用户(子类必须实现)
|
|
@@ -147,6 +129,7 @@ class LegacySystemAdapter(ABC):
|
|
|
147
129
|
Returns:
|
|
148
130
|
Optional[LegacyUserData]: 用户数据,不存在返回 None
|
|
149
131
|
"""
|
|
132
|
+
logger.info(f"Fetching user by username asynchronously: {username}")
|
|
150
133
|
raise NotImplementedError("Subclass must implement get_user_by_username_async method")
|
|
151
134
|
|
|
152
135
|
@abstractmethod
|
|
@@ -159,6 +142,7 @@ class LegacySystemAdapter(ABC):
|
|
|
159
142
|
Returns:
|
|
160
143
|
Optional[Any]: 创建的用户 ID 或其他标识
|
|
161
144
|
"""
|
|
145
|
+
logger.info(f"Creating user with data: {user_data}")
|
|
162
146
|
raise NotImplementedError("Subclass must implement _create_user_async method")
|
|
163
147
|
|
|
164
148
|
@abstractmethod
|
|
@@ -172,6 +156,7 @@ class LegacySystemAdapter(ABC):
|
|
|
172
156
|
Returns:
|
|
173
157
|
bool: 更新成功返回 True
|
|
174
158
|
"""
|
|
159
|
+
logger.info(f"Updating user: {username} with data: {user_data}")
|
|
175
160
|
raise NotImplementedError("Subclass must implement _update_user_async method")
|
|
176
161
|
|
|
177
162
|
@abstractmethod
|
|
@@ -184,6 +169,7 @@ class LegacySystemAdapter(ABC):
|
|
|
184
169
|
Returns:
|
|
185
170
|
bool: 删除成功返回 True
|
|
186
171
|
"""
|
|
172
|
+
logger.info(f"Deleting user: {username}")
|
|
187
173
|
raise NotImplementedError("Subclass must implement _delete_user_async method")
|
|
188
174
|
|
|
189
175
|
@abstractmethod
|
|
@@ -193,6 +179,7 @@ class LegacySystemAdapter(ABC):
|
|
|
193
179
|
Returns:
|
|
194
180
|
List[LegacyUserData]: 所有用户数据列表
|
|
195
181
|
"""
|
|
182
|
+
logger.info("Fetching all users from legacy system asynchronously")
|
|
196
183
|
raise NotImplementedError("Subclass must implement get_all_users_async method")
|
|
197
184
|
|
|
198
185
|
async def upsert_user_async(self, user_data: Dict[str, Any], auth_data: Dict[str, Any] = None) -> Dict[str, Any]:
|
|
@@ -208,6 +195,7 @@ class LegacySystemAdapter(ABC):
|
|
|
208
195
|
"""
|
|
209
196
|
username = user_data.get("username")
|
|
210
197
|
if not username:
|
|
198
|
+
logger.error("username is required for upsert operation")
|
|
211
199
|
raise ValueError("username is required for upsert operation")
|
|
212
200
|
|
|
213
201
|
# 检查用户是否存在
|
|
@@ -216,6 +204,7 @@ class LegacySystemAdapter(ABC):
|
|
|
216
204
|
if existing:
|
|
217
205
|
# 用户存在,执行更新
|
|
218
206
|
if not auth_data or auth_data.get("updatedFields") is None or len(auth_data.get("updatedFields")) == 0:
|
|
207
|
+
logger.info(f"No updatedFields provided for user: {username}, skipping update")
|
|
219
208
|
# 如果没有提供 auth_data 或 updatedFields,则不更新
|
|
220
209
|
return {"created": False, "user_id": existing.get("id")}
|
|
221
210
|
await self._update_user_async(username, user_data)
|
|
@@ -234,8 +223,10 @@ class LegacySystemAdapter(ABC):
|
|
|
234
223
|
Dict: 同步结果统计
|
|
235
224
|
"""
|
|
236
225
|
if not self.auth_client:
|
|
226
|
+
logger.error("auth_client is required for batch_sync_to_auth")
|
|
237
227
|
raise ValueError("auth_client is required for batch_sync_to_auth. Please provide it in constructor.")
|
|
238
228
|
|
|
229
|
+
logger.info("Starting batch sync from legacy system to aigc-auth")
|
|
239
230
|
users = await self.get_all_users_async()
|
|
240
231
|
|
|
241
232
|
results = {
|
|
@@ -264,6 +255,7 @@ class LegacySystemAdapter(ABC):
|
|
|
264
255
|
auth_data["password"] = password
|
|
265
256
|
|
|
266
257
|
result = self.auth_client.sync_user_to_auth(auth_data)
|
|
258
|
+
logger.info(f"Sync result for user {user.get('username')}: {result}")
|
|
267
259
|
|
|
268
260
|
if result.get("success"):
|
|
269
261
|
if result.get("created"):
|
|
@@ -277,6 +269,7 @@ class LegacySystemAdapter(ABC):
|
|
|
277
269
|
"error": result.get("message")
|
|
278
270
|
})
|
|
279
271
|
except Exception as e:
|
|
272
|
+
logger.error(f"Error syncing user {user.get('username')}: {e}")
|
|
280
273
|
results["failed"] += 1
|
|
281
274
|
results["errors"].append({
|
|
282
275
|
"user": user.get("username"),
|
|
@@ -298,6 +291,7 @@ class LegacySystemAdapter(ABC):
|
|
|
298
291
|
Returns:
|
|
299
292
|
Dict: 处理结果
|
|
300
293
|
"""
|
|
294
|
+
logger.info(f"Handling webhook event: {event} with data: {data}")
|
|
301
295
|
if event == "user.created" or event == "user.updated" or event == "user.login":
|
|
302
296
|
# 转换数据格式
|
|
303
297
|
legacy_data = self.transform_auth_to_legacy(data)
|
|
@@ -311,6 +305,7 @@ class LegacySystemAdapter(ABC):
|
|
|
311
305
|
legacy_data["password"] = password
|
|
312
306
|
|
|
313
307
|
# 创建或更新用户
|
|
308
|
+
logger.info(f"Handling {event} event for user: {legacy_data}")
|
|
314
309
|
result = await self.upsert_user_async(legacy_data)
|
|
315
310
|
|
|
316
311
|
return {
|
|
@@ -321,18 +316,23 @@ class LegacySystemAdapter(ABC):
|
|
|
321
316
|
}
|
|
322
317
|
|
|
323
318
|
elif event == "user.deleted":
|
|
319
|
+
logger.info("Handling user.deleted event")
|
|
324
320
|
# 禁用用户而不是删除
|
|
325
321
|
username = data.get("username")
|
|
326
322
|
if not username:
|
|
323
|
+
logger.error("username is required for user.deleted event")
|
|
327
324
|
logger.error("username is required for user.deleted event")
|
|
328
325
|
return {"success": False, "message": "username is required for user.deleted event"}
|
|
326
|
+
logger.info(f"Disabling user: {username}")
|
|
329
327
|
await self._delete_user_async(username)
|
|
330
328
|
|
|
331
329
|
return {"success": True, "message": "User disabled"}
|
|
332
330
|
|
|
333
331
|
elif event == "user.init_sync_auth":
|
|
332
|
+
logger.info("Handling user.init_sync_auth event")
|
|
334
333
|
# 初始化同步:批量同步旧系统用户到 aigc-auth
|
|
335
334
|
if not self.auth_client:
|
|
335
|
+
logger.error("auth_client is required for init_sync_auth event")
|
|
336
336
|
return {"success": False, "message": "auth_client is required for init_sync_auth event. Please provide it in constructor."}
|
|
337
337
|
|
|
338
338
|
results = await self.batch_sync_to_auth()
|
|
@@ -362,7 +362,7 @@ class LegacySystemAdapter(ABC):
|
|
|
362
362
|
|
|
363
363
|
if auth_value is not None:
|
|
364
364
|
result[mapping.legacy_field] = auth_value
|
|
365
|
-
|
|
365
|
+
logger.info(f"Transformed auth user({auth_user}) to legacy format: {result}")
|
|
366
366
|
return result
|
|
367
367
|
|
|
368
368
|
def transform_legacy_to_auth(self, legacy_user: LegacyUserData) -> Dict[str, Any]:
|
|
@@ -382,7 +382,7 @@ class LegacySystemAdapter(ABC):
|
|
|
382
382
|
|
|
383
383
|
if legacy_value is not None:
|
|
384
384
|
result[mapping.auth_field] = legacy_value
|
|
385
|
-
|
|
385
|
+
logger.info(f"Transformed legacy user({legacy_user}) to auth format: {result}")
|
|
386
386
|
return result
|
|
387
387
|
|
|
388
388
|
def get_password_for_sync(self, legacy_user: Optional[LegacyUserData] = None) -> tuple:
|
|
@@ -0,0 +1,218 @@
|
|
|
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
|
+
|
|
18
|
+
|
|
19
|
+
def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
|
|
20
|
+
"""
|
|
21
|
+
验证 webhook 签名
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
payload: 请求体(bytes)
|
|
25
|
+
signature: 签名字符串
|
|
26
|
+
secret: 密钥
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
bool: 签名是否有效
|
|
30
|
+
"""
|
|
31
|
+
if not secret:
|
|
32
|
+
# 如果未配置密钥,跳过验证(开发环境)
|
|
33
|
+
logger.warning("Webhook secret not configured, skipping signature verification")
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
if not signature:
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
expected = hmac.new(
|
|
40
|
+
secret.encode('utf-8'),
|
|
41
|
+
payload,
|
|
42
|
+
hashlib.sha256
|
|
43
|
+
).hexdigest()
|
|
44
|
+
|
|
45
|
+
return hmac.compare_digest(expected, signature)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def create_flask_webhook_blueprint(
|
|
49
|
+
handler: Callable[[Dict[str, Any]], Dict[str, Any]],
|
|
50
|
+
secret_env_key: str = "AIGC_AUTH_WEBHOOK_SECRET",
|
|
51
|
+
url_prefix: str = "/api/webhook",
|
|
52
|
+
blueprint_name: str = "aigc_auth_webhook"
|
|
53
|
+
) -> Blueprint:
|
|
54
|
+
"""
|
|
55
|
+
创建 Flask Webhook Blueprint
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
handler: 处理函数,接收 webhook 数据并返回结果
|
|
59
|
+
函数签名: def handler(data: Dict[str, Any]) -> Dict[str, Any]
|
|
60
|
+
- 如果是同步函数,直接调用
|
|
61
|
+
- 如果是异步函数,会使用 asyncio.run 包装
|
|
62
|
+
secret_env_key: webhook 密钥的环境变量名
|
|
63
|
+
url_prefix: URL 前缀,默认为 "/api/webhook"
|
|
64
|
+
blueprint_name: Blueprint 名称,默认为 "aigc_auth_webhook"
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Blueprint: Flask Blueprint 实例
|
|
68
|
+
|
|
69
|
+
使用示例:
|
|
70
|
+
from huace_aigc_auth_client.webhook_flask import create_flask_webhook_blueprint
|
|
71
|
+
|
|
72
|
+
# 方式1:使用适配器的 handle_webhook 方法
|
|
73
|
+
from your_app.adapters import YourAdapter
|
|
74
|
+
|
|
75
|
+
sync_config = create_sync_config(...)
|
|
76
|
+
auth_client = AigcAuthClient(...)
|
|
77
|
+
adapter = YourAdapter(sync_config, auth_client)
|
|
78
|
+
|
|
79
|
+
async def webhook_handler(data: dict) -> dict:
|
|
80
|
+
event = data.get("event")
|
|
81
|
+
event_data = data.get("data", {})
|
|
82
|
+
return await adapter.handle_webhook(event, event_data)
|
|
83
|
+
|
|
84
|
+
webhook_bp = create_flask_webhook_blueprint(
|
|
85
|
+
handler=webhook_handler,
|
|
86
|
+
url_prefix="/api/webhook"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# 方式2:自定义处理逻辑
|
|
90
|
+
def custom_handler(data: dict) -> dict:
|
|
91
|
+
event = data.get("event")
|
|
92
|
+
if event == "user.created":
|
|
93
|
+
# 自定义处理逻辑
|
|
94
|
+
user_data = data.get("data", {})
|
|
95
|
+
# ... 处理用户创建事件
|
|
96
|
+
return {"status": "success", "created": True}
|
|
97
|
+
return {"status": "ok"}
|
|
98
|
+
|
|
99
|
+
webhook_bp = create_flask_webhook_blueprint(handler=custom_handler)
|
|
100
|
+
|
|
101
|
+
# 注册到 Flask app
|
|
102
|
+
app.register_blueprint(webhook_bp)
|
|
103
|
+
|
|
104
|
+
# Webhook 端点: POST /api/webhook/auth
|
|
105
|
+
"""
|
|
106
|
+
webhook_bp = Blueprint(blueprint_name, __name__, url_prefix=url_prefix)
|
|
107
|
+
|
|
108
|
+
@webhook_bp.route('/auth', methods=['POST'])
|
|
109
|
+
def receive_auth_webhook():
|
|
110
|
+
"""
|
|
111
|
+
接收 aigc-auth 用户变更通知
|
|
112
|
+
|
|
113
|
+
当 aigc-auth 中创建或更新用户时,会发送 webhook 通知到此端点。
|
|
114
|
+
|
|
115
|
+
支持的事件:
|
|
116
|
+
- user.created: 用户创建
|
|
117
|
+
- user.updated: 用户更新
|
|
118
|
+
- user.deleted: 用户删除
|
|
119
|
+
- user.login: 用户登录
|
|
120
|
+
- user.init_sync_auth: 初始化同步请求
|
|
121
|
+
"""
|
|
122
|
+
# 获取请求体
|
|
123
|
+
body = request.get_data()
|
|
124
|
+
|
|
125
|
+
# 验证签名
|
|
126
|
+
signature = request.headers.get("X-Webhook-Signature", "")
|
|
127
|
+
secret = os.getenv(secret_env_key, "")
|
|
128
|
+
|
|
129
|
+
if not verify_webhook_signature(body, signature, secret):
|
|
130
|
+
logger.warning("Webhook 签名验证失败")
|
|
131
|
+
return jsonify({"code": 401, "message": "Invalid signature"}), 401
|
|
132
|
+
|
|
133
|
+
# 解析请求数据
|
|
134
|
+
try:
|
|
135
|
+
data = request.get_json()
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error(f"解析 webhook 数据失败: {e}")
|
|
138
|
+
return jsonify({"code": 400, "message": "Invalid JSON payload"}), 400
|
|
139
|
+
|
|
140
|
+
# 记录日志
|
|
141
|
+
event = data.get("event", "unknown")
|
|
142
|
+
logger.info(f"收到 webhook 事件: {event}")
|
|
143
|
+
|
|
144
|
+
# 调用用户提供的处理函数
|
|
145
|
+
try:
|
|
146
|
+
import asyncio
|
|
147
|
+
import inspect
|
|
148
|
+
|
|
149
|
+
# 检查 handler 是否为协程函数
|
|
150
|
+
if inspect.iscoroutinefunction(handler):
|
|
151
|
+
result = asyncio.run(handler(data))
|
|
152
|
+
else:
|
|
153
|
+
result = handler(data)
|
|
154
|
+
|
|
155
|
+
return jsonify({"code": 0, "message": "success", "data": result})
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
logger.exception(f"处理 webhook 失败: {e}")
|
|
159
|
+
return jsonify({"code": 500, "message": str(e)}), 500
|
|
160
|
+
|
|
161
|
+
return webhook_bp
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def register_flask_webhook_routes(
|
|
165
|
+
app,
|
|
166
|
+
handler: Callable[[Dict[str, Any]], Dict[str, Any]],
|
|
167
|
+
secret_env_key: str = "AIGC_AUTH_WEBHOOK_SECRET",
|
|
168
|
+
url_prefix: str = "/api/webhook",
|
|
169
|
+
blueprint_name: str = "aigc_auth_webhook"
|
|
170
|
+
):
|
|
171
|
+
"""
|
|
172
|
+
注册 webhook 路由到 Flask 应用
|
|
173
|
+
|
|
174
|
+
这是一个便捷函数,封装了创建 blueprint 和注册的过程。
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
app: Flask 应用实例
|
|
178
|
+
handler: 处理函数,接收 webhook 数据并返回结果
|
|
179
|
+
secret_env_key: webhook 密钥的环境变量名
|
|
180
|
+
url_prefix: URL 前缀,默认为 "/api/webhook"
|
|
181
|
+
blueprint_name: Blueprint 名称
|
|
182
|
+
|
|
183
|
+
使用示例:
|
|
184
|
+
from huace_aigc_auth_client.webhook_flask import register_flask_webhook_routes
|
|
185
|
+
|
|
186
|
+
app = Flask(__name__)
|
|
187
|
+
|
|
188
|
+
# 创建适配器
|
|
189
|
+
adapter = YourAdapter(sync_config, auth_client)
|
|
190
|
+
|
|
191
|
+
# 定义处理函数
|
|
192
|
+
async def webhook_handler(data: dict) -> dict:
|
|
193
|
+
event = data.get("event")
|
|
194
|
+
event_data = data.get("data", {})
|
|
195
|
+
return await adapter.handle_webhook(event, event_data)
|
|
196
|
+
|
|
197
|
+
# 注册 webhook 路由
|
|
198
|
+
register_flask_webhook_routes(
|
|
199
|
+
app,
|
|
200
|
+
handler=webhook_handler,
|
|
201
|
+
url_prefix="/custom/webhook" # 自定义前缀
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Webhook 端点: POST /custom/webhook/auth
|
|
205
|
+
"""
|
|
206
|
+
webhook_bp = create_flask_webhook_blueprint(
|
|
207
|
+
handler=handler,
|
|
208
|
+
secret_env_key=secret_env_key,
|
|
209
|
+
url_prefix=url_prefix,
|
|
210
|
+
blueprint_name=blueprint_name
|
|
211
|
+
)
|
|
212
|
+
app.register_blueprint(webhook_bp)
|
|
213
|
+
logger.info(f"Webhook 路由已注册: {url_prefix}/auth")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# 向后兼容的别名
|
|
217
|
+
create_webhook_blueprint = create_flask_webhook_blueprint
|
|
218
|
+
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.
|
|
3
|
+
Version: 1.1.9
|
|
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
|
|
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
|
|
452
|
-
"""
|
|
453
|
-
user = self.db.
|
|
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
|
|
460
|
-
"""
|
|
461
|
-
|
|
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.
|
|
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
|
|
533
|
+
async def handle_auth_webhook(request_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
499
534
|
"""
|
|
500
|
-
|
|
535
|
+
独立的 webhook 处理函数(用于 SDK 集成)
|
|
501
536
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
Returns:
|
|
506
|
-
dict: 处理结果
|
|
537
|
+
这个函数不需要 db 参数,会在内部创建数据库会话。
|
|
538
|
+
适用于通过 SDK 的 register_webhook_router 注册。
|
|
507
539
|
"""
|
|
508
|
-
from
|
|
540
|
+
from app.db.session import get_db
|
|
509
541
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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=
|
|
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=U48dzde3qmEDmuwqCiOGpKrEFSJbt_vC-Fjt1PLNEeQ,2549
|
|
2
|
+
huace_aigc_auth_client/legacy_adapter.py,sha256=YV7sOFhCr4SDfDUHrJMEy4JBjUgapDfzyvZK82elhQk,22538
|
|
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/webhook_flask.py,sha256=JiIsAr6INjGNKVzamyHDWVl7tzdCPOR1WguMLAmt6iI,7171
|
|
6
|
+
huace_aigc_auth_client-1.1.9.dist-info/licenses/LICENSE,sha256=z7dgC7KljhBLNvKjN15391nMj3aLt0gbud8-Yf1F8EQ,1063
|
|
7
|
+
huace_aigc_auth_client-1.1.9.dist-info/METADATA,sha256=ARbnsmp4fQKwSTFDXKA8qgEKxXX10L4yEBGokiFR8aU,22970
|
|
8
|
+
huace_aigc_auth_client-1.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
huace_aigc_auth_client-1.1.9.dist-info/top_level.txt,sha256=kbv0nQ6PQ0JVneWPH7O2AbtlJnP7AjvFJ6JjM6ZEBxo,23
|
|
10
|
+
huace_aigc_auth_client-1.1.9.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,,
|
|
File without changes
|
{huace_aigc_auth_client-1.1.7.dist-info → huace_aigc_auth_client-1.1.9.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{huace_aigc_auth_client-1.1.7.dist-info → huace_aigc_auth_client-1.1.9.dist-info}/top_level.txt
RENAMED
|
File without changes
|