huace-aigc-auth-client 1.1.8__tar.gz → 1.1.10__tar.gz

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.
Files changed (17) hide show
  1. {huace_aigc_auth_client-1.1.8/huace_aigc_auth_client.egg-info → huace_aigc_auth_client-1.1.10}/PKG-INFO +69 -97
  2. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10}/README.md +68 -96
  3. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10}/huace_aigc_auth_client/__init__.py +10 -2
  4. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10}/huace_aigc_auth_client/legacy_adapter.py +47 -41
  5. huace_aigc_auth_client-1.1.10/huace_aigc_auth_client/webhook_flask.py +218 -0
  6. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10/huace_aigc_auth_client.egg-info}/PKG-INFO +69 -97
  7. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10}/huace_aigc_auth_client.egg-info/SOURCES.txt +1 -0
  8. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10}/pyproject.toml +1 -1
  9. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10}/LICENSE +0 -0
  10. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10}/MANIFEST.in +0 -0
  11. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10}/QUICK_START.txt +0 -0
  12. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10}/huace_aigc_auth_client/sdk.py +0 -0
  13. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10}/huace_aigc_auth_client/webhook.py +0 -0
  14. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10}/huace_aigc_auth_client.egg-info/dependency_links.txt +0 -0
  15. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10}/huace_aigc_auth_client.egg-info/requires.txt +0 -0
  16. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10}/huace_aigc_auth_client.egg-info/top_level.txt +0 -0
  17. {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: huace-aigc-auth-client
3
- Version: 1.1.8
3
+ Version: 1.1.10
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
- # 可选:鉴权服务地址(默认为生产环境)- 测试环境鉴权地址:http://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:
@@ -19,8 +19,7 @@ pip install huace-aigc-auth-client
19
19
  # 必填:应用 ID 和密钥(在鉴权中心创建应用后获取)
20
20
  AIGC_AUTH_APP_ID=your_app_id
21
21
  AIGC_AUTH_APP_SECRET=your_app_secret
22
-
23
- # 可选:鉴权服务地址(默认为生产环境)- 测试环境鉴权地址:http://auth-test.aigc.huacemedia.com/aigc-auth/api/v1
22
+ # 鉴权服务地址(默认为生产环境)- 测试环境鉴权地址:http://auth.aigc-test.huacemedia.com/aigc-auth/api/v1
24
23
  AIGC_AUTH_BASE_URL=https://aigc-auth.huacemedia.com/aigc-auth/api/v1
25
24
  ```
26
25
 
@@ -395,8 +394,8 @@ from huace_aigc_auth_client import LegacySystemAdapter, LegacyUserData
395
394
  class MyLegacyAdapter(LegacySystemAdapter):
396
395
  """实现与旧系统用户表的交互"""
397
396
 
398
- def __init__(self, db, sync_config):
399
- super().__init__(sync_config)
397
+ def __init__(self, db, sync_config, auth_client=None):
398
+ super().__init__(sync_config, auth_client)
400
399
  self.db = db
401
400
 
402
401
  def get_user_by_unique_field(self, username: str):
@@ -410,8 +409,23 @@ class MyLegacyAdapter(LegacySystemAdapter):
410
409
  "email": user.email,
411
410
  # ... 其他字段
412
411
  })
412
+
413
+ async def get_user_by_username_async(self, username: str) -> Optional[LegacyUserData]:
414
+ """异步通过用户名获取旧系统用户"""
415
+ user = await self.db.execute(
416
+ select(User).where(User.username == username)
417
+ )
418
+ user = user.scalars().first()
419
+ if not user:
420
+ return None
421
+ return LegacyUserData({
422
+ "id": user.id,
423
+ "username": user.username,
424
+ "email": user.email,
425
+ # ... 其他字段
426
+ })
413
427
 
414
- def create_user(self, user_data: dict):
428
+ async def _create_user_async(self, user_data: Dict[str, Any]) -> Optional[Any]:
415
429
  """在旧系统创建用户"""
416
430
  user = User(
417
431
  username=user_data["username"],
@@ -420,20 +434,39 @@ class MyLegacyAdapter(LegacySystemAdapter):
420
434
  # ... 其他字段
421
435
  )
422
436
  self.db.add(user)
423
- self.db.commit()
437
+ await self.db.commit()
424
438
  return user.id
425
439
 
426
- def update_user(self, username: str, user_data: dict):
427
- """更新旧系统用户"""
428
- user = self.db.query(User).filter(User.username == username).first()
440
+ async def _update_user_async(self, username: str, user_data: Dict[str, Any]) -> bool:
441
+ """异步更新旧系统用户"""
442
+ user = await self.db.execute(
443
+ select(User).where(User.username == username)
444
+ )
445
+ user = user.scalars().first()
429
446
  if user:
430
447
  for key, value in user_data.items():
431
448
  setattr(user, key, value)
432
- self.db.commit()
449
+ await self.db.commit()
450
+ return True
451
+ return False
452
+
453
+ async def _delete_user_async(self, username: str) -> bool:
454
+ """异步删除旧系统用户"""
455
+ user = await self.db.execute(
456
+ select(User).where(User.username == username)
457
+ )
458
+ user = user.scalars().first()
459
+ if user:
460
+ await self.db.delete(user)
461
+ # 或者软删除:user.is_active = False
462
+ await self.db.commit()
463
+ return True
464
+ return False
433
465
 
434
- def get_all_users(self):
435
- """获取所有用户(用于初始化同步)"""
436
- users = self.db.query(User).all()
466
+ async def get_all_users_async(self) -> List[LegacyUserData]:
467
+ """异步获取所有用户(用于初始化同步)"""
468
+ result = await self.db.execute(select(User))
469
+ users = result.scalars().all()
437
470
  return [LegacyUserData({"username": u.username, ...}) for u in users]
438
471
  ```
439
472
 
@@ -443,7 +476,7 @@ class MyLegacyAdapter(LegacySystemAdapter):
443
476
  from huace_aigc_auth_client import AigcAuthClient, UserSyncService
444
477
 
445
478
  client = AigcAuthClient()
446
- adapter = MyLegacyAdapter(db, sync_config)
479
+ adapter = MyLegacyAdapter(db, sync_config, client)
447
480
  sync_service = UserSyncService(client, adapter)
448
481
 
449
482
  # 在获取用户信息后调用同步
@@ -454,7 +487,7 @@ async def auth_middleware(request, call_next):
454
487
  # 登录成功后,同步用户到旧系统
455
488
  if hasattr(request.state, "user_info"):
456
489
  user_info = request.state.user_info
457
- sync_service.sync_on_login(user_info)
490
+ await sync_service.sync_on_login_async(user_info)
458
491
 
459
492
  return response
460
493
  ```
@@ -468,47 +501,35 @@ from fastapi import APIRouter
468
501
  from huace_aigc_auth_client import register_webhook_router
469
502
 
470
503
  api_router = APIRouter()
504
+ client = AigcAuthClient()
505
+ adapter = MyLegacyAdapter(db, sync_config, client)
471
506
 
472
507
  # 定义 webhook 处理函数
473
- async def handle_webhook(data: dict) -> dict:
508
+ async def handle_auth_webhook(request_data: Dict[str, Any]) -> Dict[str, Any]:
474
509
  """
475
- 处理来自 aigc-auth webhook 通知
510
+ 独立的 webhook 处理函数(用于 SDK 集成)
476
511
 
477
- Args:
478
- data: webhook 数据,包含 event 和 data 字段
479
-
480
- Returns:
481
- dict: 处理结果
512
+ 这个函数不需要 db 参数,会在内部创建数据库会话。
513
+ 适用于通过 SDK register_webhook_router 注册。
482
514
  """
483
- from your_app.db import get_db_session
515
+ from app.db.session import get_db
484
516
 
485
- event = data.get("event")
486
- user_data = data.get("data", {})
487
-
488
- # 创建数据库会话
489
- async with get_db_session() as db:
490
- adapter = MyLegacyAdapter(db, sync_config)
491
-
492
- if event == "user.created":
493
- # 处理用户创建事件
494
- if not adapter.get_user_by_unique_field(user_data["username"]):
495
- legacy_data = adapter.transform_auth_to_legacy(user_data)
496
- legacy_data["password"] = sync_config.unified_password
497
- adapter.create_user(legacy_data)
498
- await db.commit()
499
-
500
- elif event == "user.updated":
501
- # 处理用户更新事件
502
- legacy_data = adapter.transform_auth_to_legacy(user_data)
503
- adapter.update_user(user_data["username"], legacy_data)
504
- await db.commit()
505
-
506
- return {"status": "ok", "event": event}
517
+ async for db in get_db():
518
+ try:
519
+ event = request_data.get("event")
520
+ data = request_data.get("data", {})
521
+ logger.info(f"Received webhook event: {event} with data: {data}")
522
+
523
+ # 调用适配器的 handle_webhook 方法
524
+ return await adapter.handle_webhook(event, data)
525
+ except Exception as e:
526
+ logger.exception(f"Failed to handle webhook: {e}")
527
+ raise
507
528
 
508
529
  # 注册 webhook 路由(自动处理签名验证)
509
530
  register_webhook_router(
510
531
  api_router,
511
- handler=handle_webhook,
532
+ handler=handle_auth_webhook,
512
533
  prefix="/webhook", # 可选,默认 "/webhook"
513
534
  secret_env_key="aigc-auth-webhook-secret", # 可选,在 auth 后台配置
514
535
  tags=["Webhook"] # 可选,默认 ["Webhook"]
@@ -517,55 +538,6 @@ register_webhook_router(
517
538
  # webhook 端点将自动创建在: /webhook/auth
518
539
  ```
519
540
 
520
- **传统方式:手动实现 Webhook 端点**
521
-
522
- ```python
523
- import hmac
524
- import hashlib
525
- import os
526
-
527
- @app.post("/api/v1/webhook/auth")
528
- async def receive_auth_webhook(request: Request, db: Session = Depends(get_db)):
529
- """接收 aigc-auth 的用户变更通知"""
530
- # 获取原始请求体
531
- body = await request.body()
532
-
533
- # 验证签名
534
- signature = request.headers.get("X-Webhook-Signature", "")
535
- secret = os.getenv("AIGC_AUTH_WEBHOOK_SECRET", "")
536
-
537
- if secret:
538
- expected = hmac.new(
539
- secret.encode('utf-8'),
540
- body,
541
- hashlib.sha256
542
- ).hexdigest()
543
-
544
- if not hmac.compare_digest(expected, signature):
545
- raise HTTPException(status_code=401, detail="Invalid signature")
546
-
547
- # 解析数据
548
- data = await request.json()
549
- event = data.get("event")
550
- user_data = data.get("data", {})
551
-
552
- if event == "user.created":
553
- adapter = MyLegacyAdapter(db, sync_config)
554
- if not adapter.get_user_by_unique_field(user_data["username"]):
555
- legacy_data = adapter.transform_auth_to_legacy(user_data)
556
- legacy_data["password"] = sync_config.unified_password
557
- adapter.create_user(legacy_data)
558
- db.commit()
559
-
560
- elif event == "user.updated":
561
- adapter = MyLegacyAdapter(db, sync_config)
562
- legacy_data = adapter.transform_auth_to_legacy(user_data)
563
- adapter.update_user(user_data["username"], legacy_data)
564
- db.commit()
565
-
566
- return {"success": True}
567
- ```
568
-
569
541
  **Webhook 签名验证说明**
570
542
 
571
543
  SDK 的 `register_webhook_router` 会自动处理签名验证:
@@ -579,7 +551,7 @@ SDK 的 `register_webhook_router` 会自动处理签名验证:
579
551
  ```python
580
552
  # 一次性脚本:将旧系统用户同步到 aigc-auth
581
553
  async def init_sync():
582
- adapter = MyLegacyAdapter(db, sync_config)
554
+ adapter = MyLegacyAdapter(db, sync_config, client)
583
555
  users = adapter.get_all_users()
584
556
 
585
557
  for user in users:
@@ -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.8"
109
+ __version__ = "1.1.10"
@@ -101,11 +101,11 @@ class LegacySystemAdapter(ABC):
101
101
  旧系统适配器抽象基类
102
102
 
103
103
  接入系统需要继承此类并实现以下方法:
104
- - get_user_by_unique_field: 通过唯一字段获取用户
105
- - create_user: 创建用户
106
- - update_user: 更新用户(可选)
107
- - get_all_users: 获取所有用户(用于初始化同步)
108
- - handle_webhook: 处理 webhook 通知(可选,异步方法)
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
  """异步获取用户(子类必须实现)
@@ -232,6 +214,41 @@ class LegacySystemAdapter(ABC):
232
214
  user_id = await self._create_user_async(user_data)
233
215
  return {"created": True, "user_id": user_id}
234
216
 
217
+ async def sync_user_to_auth(self, legacy_user: LegacyUserData) -> Dict[str, Any]:
218
+ """同步单个旧系统用户到 aigc-auth(默认实现)
219
+
220
+ 子类可以选择性覆盖此方法以自定义同步逻辑。
221
+
222
+ Args:
223
+ legacy_user: 旧系统用户数据
224
+ Returns:
225
+ Dict: 同步结果
226
+ """
227
+ if not self.auth_client:
228
+ logger.error("auth_client is required for sync_user_to_auth")
229
+ raise ValueError("auth_client is required for sync_user_to_auth. Please provide it in constructor.")
230
+
231
+ logger.info(f"Syncing legacy user to auth: {legacy_user.to_dict()}")
232
+ auth_data = self.transform_legacy_to_auth(legacy_user)
233
+
234
+ # 获取密码(支持新的元组返回格式)
235
+ password_result = self.get_password_for_sync(legacy_user)
236
+ if isinstance(password_result, tuple):
237
+ password, is_hashed = password_result
238
+ else:
239
+ password, is_hashed = password_result, False
240
+
241
+ # 根据是否已加密选择不同的字段
242
+ if is_hashed:
243
+ auth_data["password_hashed"] = password
244
+ else:
245
+ auth_data["password"] = password
246
+
247
+ result = await self.auth_client.sync_user_to_auth(auth_data)
248
+ logger.info(f"Sync result for user {legacy_user.get('username')}: {result}")
249
+
250
+ return result
251
+
235
252
  async def batch_sync_to_auth(self) -> Dict[str, Any]:
236
253
  """批量同步旧系统用户到 aigc-auth(默认实现)
237
254
 
@@ -257,22 +274,7 @@ class LegacySystemAdapter(ABC):
257
274
 
258
275
  for user in users:
259
276
  try:
260
- auth_data = self.transform_legacy_to_auth(user)
261
-
262
- # 获取密码(支持新的元组返回格式)
263
- password_result = self.get_password_for_sync(user)
264
- if isinstance(password_result, tuple):
265
- password, is_hashed = password_result
266
- else:
267
- password, is_hashed = password_result, False
268
-
269
- # 根据是否已加密选择不同的字段
270
- if is_hashed:
271
- auth_data["password_hashed"] = password
272
- else:
273
- auth_data["password"] = password
274
-
275
- result = self.auth_client.sync_user_to_auth(auth_data)
277
+ result = await self.sync_user_to_auth(user)
276
278
  logger.info(f"Sync result for user {user.get('username')}: {result}")
277
279
 
278
280
  if result.get("success"):
@@ -309,17 +311,21 @@ class LegacySystemAdapter(ABC):
309
311
  Returns:
310
312
  Dict: 处理结果
311
313
  """
314
+ logger.info(f"Handling webhook event: {event} with data: {data}")
312
315
  if event == "user.created" or event == "user.updated" or event == "user.login":
313
316
  # 转换数据格式
314
317
  legacy_data = self.transform_auth_to_legacy(data)
315
318
 
316
319
  # 获取密码
317
- password_result = self.get_password_for_sync()
320
+ password_result = self.get_password_for_sync(legacy_data)
318
321
  if isinstance(password_result, tuple):
319
322
  password, is_hashed = password_result
320
323
  else:
321
324
  password, is_hashed = password_result, False
322
- legacy_data["password"] = password
325
+ if is_hashed:
326
+ legacy_data["password_hashed"] = password
327
+ else:
328
+ legacy_data["password"] = password
323
329
 
324
330
  # 创建或更新用户
325
331
  logger.info(f"Handling {event} event for user: {legacy_data}")
@@ -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.8
3
+ Version: 1.1.10
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
- # 可选:鉴权服务地址(默认为生产环境)- 测试环境鉴权地址:http://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:
@@ -7,6 +7,7 @@ huace_aigc_auth_client/__init__.py
7
7
  huace_aigc_auth_client/legacy_adapter.py
8
8
  huace_aigc_auth_client/sdk.py
9
9
  huace_aigc_auth_client/webhook.py
10
+ huace_aigc_auth_client/webhook_flask.py
10
11
  huace_aigc_auth_client.egg-info/PKG-INFO
11
12
  huace_aigc_auth_client.egg-info/SOURCES.txt
12
13
  huace_aigc_auth_client.egg-info/dependency_links.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "huace-aigc-auth-client"
7
- version = "1.1.8"
7
+ version = "1.1.10"
8
8
  description = "华策AIGC Auth Client - 提供 Token 验证、用户信息获取、权限检查、旧系统接入等功能"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.7"