huace-aigc-auth-client 1.1.8__tar.gz → 1.1.9__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.
- {huace_aigc_auth_client-1.1.8/huace_aigc_auth_client.egg-info → huace_aigc_auth_client-1.1.9}/PKG-INFO +69 -97
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/README.md +68 -96
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/huace_aigc_auth_client/__init__.py +10 -2
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/huace_aigc_auth_client/legacy_adapter.py +6 -23
- huace_aigc_auth_client-1.1.9/huace_aigc_auth_client/webhook_flask.py +218 -0
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9/huace_aigc_auth_client.egg-info}/PKG-INFO +69 -97
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/huace_aigc_auth_client.egg-info/SOURCES.txt +1 -0
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/pyproject.toml +1 -1
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/LICENSE +0 -0
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/MANIFEST.in +0 -0
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/QUICK_START.txt +0 -0
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/huace_aigc_auth_client/sdk.py +0 -0
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/huace_aigc_auth_client/webhook.py +0 -0
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/huace_aigc_auth_client.egg-info/dependency_links.txt +0 -0
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/huace_aigc_auth_client.egg-info/requires.txt +0 -0
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/huace_aigc_auth_client.egg-info/top_level.txt +0 -0
- {huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/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.
|
|
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
|
-
# 可选:鉴权服务地址(默认为生产环境)- 测试环境鉴权地址: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
|
|
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:
|
|
@@ -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
|
|
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
|
|
427
|
-
"""
|
|
428
|
-
user = self.db.
|
|
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
|
|
435
|
-
"""
|
|
436
|
-
|
|
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.
|
|
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
|
|
508
|
+
async def handle_auth_webhook(request_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
474
509
|
"""
|
|
475
|
-
|
|
510
|
+
独立的 webhook 处理函数(用于 SDK 集成)
|
|
476
511
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
Returns:
|
|
481
|
-
dict: 处理结果
|
|
512
|
+
这个函数不需要 db 参数,会在内部创建数据库会话。
|
|
513
|
+
适用于通过 SDK 的 register_webhook_router 注册。
|
|
482
514
|
"""
|
|
483
|
-
from
|
|
515
|
+
from app.db.session import get_db
|
|
484
516
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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=
|
|
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:
|
{huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/huace_aigc_auth_client/__init__.py
RENAMED
|
@@ -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
|
"""异步获取用户(子类必须实现)
|
|
@@ -309,6 +291,7 @@ class LegacySystemAdapter(ABC):
|
|
|
309
291
|
Returns:
|
|
310
292
|
Dict: 处理结果
|
|
311
293
|
"""
|
|
294
|
+
logger.info(f"Handling webhook event: {event} with data: {data}")
|
|
312
295
|
if event == "user.created" or event == "user.updated" or event == "user.login":
|
|
313
296
|
# 转换数据格式
|
|
314
297
|
legacy_data = self.transform_auth_to_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.
|
|
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
|
-
# 可选:鉴权服务地址(默认为生产环境)- 测试环境鉴权地址: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
|
|
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:
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{huace_aigc_auth_client-1.1.8 → huace_aigc_auth_client-1.1.9}/huace_aigc_auth_client/webhook.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|