feat(msync): 用户令牌签发端点(UserTokenIssueView)+ Market 返回 Source/ServiceName/ServiceOnline

This commit is contained in:
TermiNexus committed 2026-08-14 00:33:49 +08:00
1 parent 00340f818f
commit a30e27e4bc
3 files changed
+57

No files matched your search

+5
View File
@@ -72,6 +72,11 @@ def _AppFlat(app, tenant_installed=None, user_record=None, company_installed=Non
'AuthRequired': app.AuthRequired,
'MenuRootUUID': UUIDObj(app.MenuRootUUID).str if app.MenuRootUUID else '',
'AppSort': app.AppSort or 0,
# 远端注入(MSYNC):来源/提供方服务/在线状态(前端"服务离线"提示用)
'Source': app.Source or 'local',
'ServiceName': app.ServiceName or '',
'ServiceDomain': app.ServiceDomain or '',
'ServiceOnline': app.ServiceOnline if app.ServiceOnline is not None else 1,
# 前端入口(真应用注册协议:安装后"打开应用"跳转目标)
'FrontendRoute': app.FrontendRoute or '',
'FrontendType': app.FrontendType or 'vue',
+51
View File
@@ -74,6 +74,17 @@ def _GetCompanyBytes(request):
return None
def _ResolveRoleUUID(request):
"""当前角色 UUID bytes(Cookie CurrentRoleUUID)"""
role_uuid_str = request.COOKIES.get('CurrentRoleUUID', '')
if not role_uuid_str:
return None
try:
return UUIDObj(role_uuid_str).bytes
except Exception:
return None
def _ParseUUIDList(values):
"""解析公司 UUID 列表(str/bytes 混合 → bytes 列表),过滤无效"""
result = []
@@ -532,3 +543,43 @@ class AppConfigCopyView(ELTAuthenticatedAPIView):
if not ok:
return Response({'code': 1, 'msg': err}, status=400)
return Response({'code': 0, 'msg': f'已复制 {copied} 项配置到 {len(targets)} 家公司', 'data': {'Copied': copied}})
class UserTokenIssueView(ELTAuthenticatedAPIView):
"""签发用户级短期令牌——员工 iframe 访问子服务器远端 App 时透传用户身份
POST /API/MSync/UserToken/Issue
Body: { AudienceService: 目标服务名(子服务器 MSYNC 注册名) }
返回: { AccessToken, ExpiresAt(ts), AudienceService }
令牌由主服务器 MASTER_SECRET 签发(HMAC-SHA256,typ=user,TTL 10min),
附带当前用户/角色/公司/租户上下文;子服务器通过
/api/msync/user/token/introspect/ 自省获取用户身份。
"""
def post(self, request):
audience = (request.data.get('AudienceService') or '').strip()
if not audience:
return Response({'code': 1, 'msg': '缺少目标服务名 AudienceService'}, status=400)
user_uuid = _GetUserBytes(request)
if not user_uuid:
return Response({'code': 401, 'msg': '无法识别当前用户'}, status=401)
try:
from gvsdsdk.msync.auth import IssueUserToken
token, expires = IssueUserToken(
user_uuid,
RoleUUID=_ResolveRoleUUID(request),
CompanyUUID=_GetCompanyBytes(request),
TenantUUID=_GetTenantBytes(request),
AudienceService=audience,
)
except Exception as e:
logger.error(f'用户令牌签发异常: {e}')
return Response({'code': 500, 'msg': '用户令牌签发失败'}, status=500)
if not token:
return Response({'code': 500, 'msg': '用户令牌签发失败'}, status=500)
return Response({'code': 0, 'data': {
'AccessToken': token,
'ExpiresAt': expires,
'AudienceService': audience,
}})
+1
View File
@@ -159,6 +159,7 @@ urlpatterns = [
path('API/App/Pending', gerp_views.AppPendingView.as_view(), name='app_pending'),
path('API/App/Config', gerp_views.AppConfigView.as_view(), name='app_config'),
path('API/App/Config/Copy', gerp_views.AppConfigCopyView.as_view(), name='app_config_copy'),
path('API/MSync/UserToken/Issue', gerp_views.UserTokenIssueView.as_view(), name='msync_user_token_issue'),
# 官网社区(discuss.gvsds.com 前端调用)
path('API/Community/', include('gvsds.apps.community.urls')),