huace-aigc-auth-client 1.1.0__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.
@@ -0,0 +1,93 @@
1
+ """
2
+ AIGC Auth Python SDK
3
+
4
+ 使用方法:
5
+ from sdk import AigcAuthClient, require_auth
6
+
7
+ # 创建客户端
8
+ client = AigcAuthClient(app_id="your_app_id", app_secret="your_app_secret")
9
+
10
+ # 验证 token
11
+ result = client.verify_token(token)
12
+
13
+ # 获取用户信息
14
+ user_info = client.get_user_info(token)
15
+
16
+ # FastAPI 中间件使用
17
+ @app.get("/protected")
18
+ @require_auth(client)
19
+ def protected_route(user_info: dict):
20
+ return {"user": user_info}
21
+
22
+ 旧系统接入:
23
+ from sdk import AigcAuthClient
24
+ from sdk.legacy_adapter import (
25
+ LegacySystemAdapter,
26
+ SyncConfig,
27
+ UserSyncService,
28
+ FieldMapping,
29
+ create_sync_config,
30
+ create_default_field_mappings
31
+ )
32
+
33
+ # 创建自定义字段映射(根据接入系统的用户表定制)
34
+ field_mappings = create_default_field_mappings()
35
+ # 或自定义: field_mappings = [FieldMapping(...), ...]
36
+
37
+ sync_config = create_sync_config(
38
+ field_mappings=field_mappings,
39
+ webhook_url="https://your-domain.com/webhook"
40
+ )
41
+
42
+ # 实现适配器并创建同步服务
43
+ adapter = YourLegacyAdapter(sync_config)
44
+ sync_service = UserSyncService(client, adapter)
45
+ """
46
+
47
+ from .sdk import (
48
+ AigcAuthClient,
49
+ require_auth,
50
+ AuthMiddleware,
51
+ UserInfo,
52
+ TokenVerifyResult,
53
+ AigcAuthError,
54
+ create_fastapi_auth_dependency
55
+ )
56
+
57
+ from .legacy_adapter import (
58
+ LegacySystemAdapter,
59
+ LegacyUserData,
60
+ SyncConfig,
61
+ SyncDirection,
62
+ PasswordMode,
63
+ FieldMapping,
64
+ UserSyncService,
65
+ WebhookSender,
66
+ SyncResult,
67
+ create_sync_config,
68
+ create_default_field_mappings,
69
+ )
70
+
71
+ __all__ = [
72
+ # 核心类
73
+ "AigcAuthClient",
74
+ "require_auth",
75
+ "AuthMiddleware",
76
+ "UserInfo",
77
+ "TokenVerifyResult",
78
+ "AigcAuthError",
79
+ "create_fastapi_auth_dependency",
80
+ # 旧系统接入
81
+ "LegacySystemAdapter",
82
+ "LegacyUserData",
83
+ "SyncConfig",
84
+ "SyncDirection",
85
+ "PasswordMode",
86
+ "FieldMapping",
87
+ "UserSyncService",
88
+ "WebhookSender",
89
+ "SyncResult",
90
+ "create_sync_config",
91
+ "create_default_field_mappings",
92
+ ]
93
+ __version__ = "1.1.0"
@@ -0,0 +1,483 @@
1
+ """
2
+ AIGC Auth Legacy System Adapter
3
+
4
+ 提供旧系统接入支持,包括:
5
+ 1. 字段映射配置
6
+ 2. 用户数据同步
7
+ 3. 密码处理策略
8
+ 4. Webhook 推送支持
9
+ """
10
+
11
+ import os
12
+ import hmac
13
+ import hashlib
14
+ import json
15
+ import logging
16
+ import requests
17
+ from typing import Optional, List, Dict, Any, Callable, TypeVar, Generic
18
+ from dataclasses import dataclass, field
19
+ from enum import Enum
20
+ from abc import ABC, abstractmethod
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class PasswordMode(Enum):
26
+ """密码处理模式"""
27
+ UNIFIED = "unified" # 统一初始密码
28
+ CUSTOM_MAPPING = "custom_mapping" # 自定义映射函数
29
+
30
+
31
+ class SyncDirection(Enum):
32
+ """同步方向"""
33
+ AUTH_TO_LEGACY = "auth_to_legacy" # aigc-auth → 旧系统
34
+ LEGACY_TO_AUTH = "legacy_to_auth" # 旧系统 → aigc-auth
35
+ BIDIRECTIONAL = "bidirectional" # 双向同步
36
+
37
+
38
+ @dataclass
39
+ class FieldMapping:
40
+ """字段映射配置"""
41
+ auth_field: str # aigc-auth 字段名
42
+ legacy_field: str # 旧系统字段名
43
+ transform_to_legacy: Optional[Callable[[Any], Any]] = None # auth → legacy 转换函数
44
+ transform_to_auth: Optional[Callable[[Any], Any]] = None # legacy → auth 转换函数
45
+ required: bool = False # 是否必填
46
+ default_value: Any = None # 默认值
47
+
48
+
49
+ @dataclass
50
+ class SyncConfig:
51
+ """同步配置"""
52
+ # 基本配置
53
+ enabled: bool = True
54
+ direction: SyncDirection = SyncDirection.AUTH_TO_LEGACY
55
+
56
+ # 字段映射
57
+ field_mappings: List[FieldMapping] = field(default_factory=list)
58
+
59
+ # 唯一标识字段(用于匹配用户)
60
+ unique_field: str = "username"
61
+
62
+ # 密码处理
63
+ password_mode: PasswordMode = PasswordMode.UNIFIED
64
+ unified_password: str = "Abc@123456" # 统一初始密码
65
+ password_mapper: Optional[Callable[[str], str]] = None # 自定义密码映射函数
66
+
67
+ # Webhook 配置
68
+ webhook_enabled: bool = False
69
+ webhook_url: Optional[str] = None
70
+ webhook_secret: Optional[str] = None
71
+ webhook_retry_count: int = 3
72
+ webhook_timeout: int = 10
73
+
74
+
75
+ @dataclass
76
+ class LegacyUserData:
77
+ """旧系统用户数据"""
78
+ data: Dict[str, Any]
79
+
80
+ def get(self, key: str, default: Any = None) -> Any:
81
+ return self.data.get(key, default)
82
+
83
+ def to_dict(self) -> Dict[str, Any]:
84
+ return self.data.copy()
85
+
86
+
87
+ @dataclass
88
+ class SyncResult:
89
+ """同步结果"""
90
+ success: bool
91
+ user_id: Optional[int] = None
92
+ auth_user_id: Optional[int] = None
93
+ legacy_user_id: Optional[Any] = None
94
+ message: str = ""
95
+ errors: List[str] = field(default_factory=list)
96
+
97
+
98
+ class LegacySystemAdapter(ABC):
99
+ """
100
+ 旧系统适配器抽象基类
101
+
102
+ 接入系统需要继承此类并实现以下方法:
103
+ - get_user_by_unique_field: 通过唯一字段获取用户
104
+ - create_user: 创建用户
105
+ - update_user: 更新用户(可选)
106
+ - get_all_users: 获取所有用户(用于初始化同步)
107
+ """
108
+
109
+ def __init__(self, sync_config: SyncConfig):
110
+ self.config = sync_config
111
+
112
+ @abstractmethod
113
+ def get_user_by_unique_field(self, value: Any) -> Optional[LegacyUserData]:
114
+ """通过唯一字段获取旧系统用户"""
115
+ pass
116
+
117
+ @abstractmethod
118
+ def create_user(self, user_data: Dict[str, Any]) -> Optional[Any]:
119
+ """在旧系统创建用户,返回用户ID"""
120
+ pass
121
+
122
+ def update_user(self, unique_value: Any, user_data: Dict[str, Any]) -> bool:
123
+ """更新旧系统用户(可选实现)"""
124
+ return False
125
+
126
+ def get_all_users(self) -> List[LegacyUserData]:
127
+ """获取所有旧系统用户(用于初始化同步)"""
128
+ return []
129
+
130
+ def transform_auth_to_legacy(self, auth_user: Dict[str, Any]) -> Dict[str, Any]:
131
+ """将 aigc-auth 用户数据转换为旧系统格式"""
132
+ result = {}
133
+
134
+ for mapping in self.config.field_mappings:
135
+ auth_value = auth_user.get(mapping.auth_field)
136
+
137
+ if auth_value is None:
138
+ if mapping.required and mapping.default_value is None:
139
+ raise ValueError(f"Required field '{mapping.auth_field}' is missing")
140
+ auth_value = mapping.default_value
141
+
142
+ if mapping.transform_to_legacy:
143
+ auth_value = mapping.transform_to_legacy(auth_value)
144
+
145
+ if auth_value is not None:
146
+ result[mapping.legacy_field] = auth_value
147
+
148
+ return result
149
+
150
+ def transform_legacy_to_auth(self, legacy_user: LegacyUserData) -> Dict[str, Any]:
151
+ """将旧系统用户数据转换为 aigc-auth 格式"""
152
+ result = {}
153
+
154
+ for mapping in self.config.field_mappings:
155
+ legacy_value = legacy_user.get(mapping.legacy_field)
156
+
157
+ if legacy_value is None:
158
+ if mapping.required and mapping.default_value is None:
159
+ raise ValueError(f"Required field '{mapping.legacy_field}' is missing")
160
+ legacy_value = mapping.default_value
161
+
162
+ if mapping.transform_to_auth:
163
+ legacy_value = mapping.transform_to_auth(legacy_value)
164
+
165
+ if legacy_value is not None:
166
+ result[mapping.auth_field] = legacy_value
167
+
168
+ return result
169
+
170
+ def get_password_for_sync(self, legacy_user: Optional[LegacyUserData] = None) -> str:
171
+ """获取同步时使用的密码"""
172
+ if self.config.password_mode == PasswordMode.UNIFIED:
173
+ return self.config.unified_password
174
+ elif self.config.password_mode == PasswordMode.CUSTOM_MAPPING:
175
+ if self.config.password_mapper and legacy_user:
176
+ return self.config.password_mapper(legacy_user.data)
177
+ return self.config.unified_password
178
+ return self.config.unified_password
179
+
180
+
181
+ class WebhookSender:
182
+ """Webhook 发送器"""
183
+
184
+ def __init__(self, config: SyncConfig):
185
+ self.config = config
186
+
187
+ def generate_signature(self, payload: str) -> str:
188
+ """生成 HMAC-SHA256 签名"""
189
+ if not self.config.webhook_secret:
190
+ return ""
191
+
192
+ return hmac.new(
193
+ self.config.webhook_secret.encode('utf-8'),
194
+ payload.encode('utf-8'),
195
+ hashlib.sha256
196
+ ).hexdigest()
197
+
198
+ def send(self, event_type: str, data: Dict[str, Any]) -> bool:
199
+ """发送 webhook 通知"""
200
+ if not self.config.webhook_enabled or not self.config.webhook_url:
201
+ return False
202
+
203
+ payload = json.dumps({
204
+ "event": event_type,
205
+ "data": data
206
+ }, ensure_ascii=False)
207
+
208
+ signature = self.generate_signature(payload)
209
+
210
+ headers = {
211
+ "Content-Type": "application/json",
212
+ "X-Webhook-Signature": signature,
213
+ "X-Webhook-Event": event_type
214
+ }
215
+
216
+ for attempt in range(self.config.webhook_retry_count):
217
+ try:
218
+ response = requests.post(
219
+ self.config.webhook_url,
220
+ data=payload,
221
+ headers=headers,
222
+ timeout=self.config.webhook_timeout
223
+ )
224
+
225
+ if response.status_code == 200:
226
+ logger.info(f"Webhook sent successfully: {event_type}")
227
+ return True
228
+ else:
229
+ logger.warning(f"Webhook failed with status {response.status_code}: {response.text}")
230
+
231
+ except requests.RequestException as e:
232
+ logger.error(f"Webhook request failed (attempt {attempt + 1}): {e}")
233
+
234
+ return False
235
+
236
+
237
+ class UserSyncService:
238
+ """
239
+ 用户同步服务
240
+
241
+ 提供以下功能:
242
+ 1. 登录时自动同步(auth → legacy)
243
+ 2. 初始化批量同步(legacy → auth)
244
+ 3. Webhook 增量推送
245
+ """
246
+
247
+ def __init__(
248
+ self,
249
+ auth_client, # AigcAuthClient
250
+ legacy_adapter: LegacySystemAdapter
251
+ ):
252
+ self.auth_client = auth_client
253
+ self.adapter = legacy_adapter
254
+ self.config = legacy_adapter.config
255
+ self.webhook_sender = WebhookSender(self.config)
256
+
257
+ def sync_on_login(self, auth_user_info) -> SyncResult:
258
+ """
259
+ 登录时同步用户到旧系统
260
+
261
+ 当用户通过 aigc-auth 登录成功后调用,
262
+ 如果旧系统没有该用户则自动创建。
263
+
264
+ Args:
265
+ auth_user_info: aigc-auth 返回的 UserInfo 对象
266
+
267
+ Returns:
268
+ SyncResult: 同步结果
269
+ """
270
+ if not self.config.enabled:
271
+ return SyncResult(success=True, message="Sync disabled")
272
+
273
+ if self.config.direction == SyncDirection.LEGACY_TO_AUTH:
274
+ return SyncResult(success=True, message="Direction is legacy_to_auth, skip")
275
+
276
+ try:
277
+ # 获取唯一标识值
278
+ unique_value = getattr(auth_user_info, self.config.unique_field, None)
279
+ if not unique_value:
280
+ return SyncResult(
281
+ success=False,
282
+ message=f"Unique field '{self.config.unique_field}' not found in auth user"
283
+ )
284
+
285
+ # 检查旧系统是否已有该用户
286
+ legacy_user = self.adapter.get_user_by_unique_field(unique_value)
287
+
288
+ if legacy_user:
289
+ # 用户已存在,可选更新
290
+ if self.config.direction == SyncDirection.BIDIRECTIONAL:
291
+ auth_data = self._user_info_to_dict(auth_user_info)
292
+ legacy_data = self.adapter.transform_auth_to_legacy(auth_data)
293
+ self.adapter.update_user(unique_value, legacy_data)
294
+
295
+ return SyncResult(
296
+ success=True,
297
+ auth_user_id=auth_user_info.id,
298
+ legacy_user_id=legacy_user.get("id"),
299
+ message="User already exists in legacy system"
300
+ )
301
+
302
+ # 创建新用户
303
+ auth_data = self._user_info_to_dict(auth_user_info)
304
+ legacy_data = self.adapter.transform_auth_to_legacy(auth_data)
305
+
306
+ # 添加密码
307
+ legacy_data["password"] = self.adapter.get_password_for_sync()
308
+
309
+ legacy_user_id = self.adapter.create_user(legacy_data)
310
+
311
+ if legacy_user_id:
312
+ return SyncResult(
313
+ success=True,
314
+ auth_user_id=auth_user_info.id,
315
+ legacy_user_id=legacy_user_id,
316
+ message="User synced to legacy system"
317
+ )
318
+ else:
319
+ return SyncResult(
320
+ success=False,
321
+ auth_user_id=auth_user_info.id,
322
+ message="Failed to create user in legacy system"
323
+ )
324
+
325
+ except Exception as e:
326
+ logger.exception("Error syncing user on login")
327
+ return SyncResult(success=False, message=str(e), errors=[str(e)])
328
+
329
+ def _user_info_to_dict(self, user_info) -> Dict[str, Any]:
330
+ """将 UserInfo 对象转换为字典"""
331
+ return {
332
+ "id": user_info.id,
333
+ "username": user_info.username,
334
+ "nickname": user_info.nickname,
335
+ "email": user_info.email,
336
+ "phone": user_info.phone,
337
+ "avatar": user_info.avatar,
338
+ "roles": user_info.roles,
339
+ "permissions": user_info.permissions,
340
+ "department": user_info.department,
341
+ "company": user_info.company,
342
+ "is_admin": user_info.is_admin
343
+ }
344
+
345
+ def batch_sync_to_auth(
346
+ self,
347
+ users: List[LegacyUserData],
348
+ on_progress: Optional[Callable[[int, int], None]] = None
349
+ ) -> Dict[str, Any]:
350
+ """
351
+ 批量同步旧系统用户到 aigc-auth
352
+
353
+ Args:
354
+ users: 旧系统用户列表
355
+ on_progress: 进度回调函数 (current, total)
356
+
357
+ Returns:
358
+ Dict: 同步结果统计
359
+ """
360
+ results = {
361
+ "total": len(users),
362
+ "success": 0,
363
+ "failed": 0,
364
+ "skipped": 0,
365
+ "errors": []
366
+ }
367
+
368
+ for i, user in enumerate(users):
369
+ try:
370
+ auth_data = self.adapter.transform_legacy_to_auth(user)
371
+
372
+ # 添加密码
373
+ auth_data["password"] = self.adapter.get_password_for_sync(user)
374
+
375
+ # 调用 SDK 同步接口
376
+ result = self.auth_client.sync_user_to_auth(auth_data)
377
+
378
+ if result.get("success"):
379
+ if result.get("created"):
380
+ results["success"] += 1
381
+ else:
382
+ results["skipped"] += 1
383
+ else:
384
+ results["failed"] += 1
385
+ results["errors"].append({
386
+ "user": user.get(self.config.unique_field),
387
+ "error": result.get("message")
388
+ })
389
+
390
+ except Exception as e:
391
+ results["failed"] += 1
392
+ results["errors"].append({
393
+ "user": user.get(self.config.unique_field),
394
+ "error": str(e)
395
+ })
396
+
397
+ if on_progress:
398
+ on_progress(i + 1, len(users))
399
+
400
+ return results
401
+
402
+ def send_user_created_webhook(self, auth_user_data: Dict[str, Any]) -> bool:
403
+ """发送用户创建 webhook"""
404
+ return self.webhook_sender.send("user.created", auth_user_data)
405
+
406
+ def send_user_updated_webhook(self, auth_user_data: Dict[str, Any]) -> bool:
407
+ """发送用户更新 webhook"""
408
+ return self.webhook_sender.send("user.updated", auth_user_data)
409
+
410
+
411
+ # ============ 预设字段映射 ============
412
+
413
+ def create_default_field_mappings() -> List[FieldMapping]:
414
+ """创建默认字段映射配置(通用基础映射)"""
415
+ return [
416
+ FieldMapping(
417
+ auth_field="username",
418
+ legacy_field="username",
419
+ required=True
420
+ ),
421
+ FieldMapping(
422
+ auth_field="email",
423
+ legacy_field="email"
424
+ ),
425
+ FieldMapping(
426
+ auth_field="nickname",
427
+ legacy_field="nickname"
428
+ ),
429
+ FieldMapping(
430
+ auth_field="phone",
431
+ legacy_field="phone"
432
+ ),
433
+ FieldMapping(
434
+ auth_field="avatar",
435
+ legacy_field="avatar"
436
+ ),
437
+ FieldMapping(
438
+ auth_field="company",
439
+ legacy_field="company"
440
+ ),
441
+ FieldMapping(
442
+ auth_field="department",
443
+ legacy_field="department"
444
+ ),
445
+ ]
446
+
447
+
448
+ # ============ 便捷函数 ============
449
+
450
+ def create_sync_config(
451
+ field_mappings: List[FieldMapping] = None,
452
+ password_mode: PasswordMode = PasswordMode.UNIFIED,
453
+ unified_password: str = "Abc@123456",
454
+ webhook_url: Optional[str] = None,
455
+ webhook_secret: Optional[str] = None,
456
+ direction: SyncDirection = SyncDirection.AUTH_TO_LEGACY,
457
+ **kwargs
458
+ ) -> SyncConfig:
459
+ """
460
+ 创建同步配置的便捷函数
461
+
462
+ Args:
463
+ field_mappings: 字段映射列表,默认使用通用映射
464
+ password_mode: 密码处理模式
465
+ unified_password: 统一初始密码
466
+ webhook_url: Webhook 接收地址
467
+ webhook_secret: Webhook 签名密钥
468
+ direction: 同步方向
469
+
470
+ Returns:
471
+ SyncConfig: 同步配置对象
472
+ """
473
+ return SyncConfig(
474
+ enabled=True,
475
+ direction=direction,
476
+ field_mappings=field_mappings or create_default_field_mappings(),
477
+ password_mode=password_mode,
478
+ unified_password=unified_password,
479
+ webhook_enabled=bool(webhook_url),
480
+ webhook_url=webhook_url,
481
+ webhook_secret=webhook_secret,
482
+ **kwargs
483
+ )