|
@@ -31,6 +31,7 @@ from app.services.web_auth_roles import (
|
|
|
resolve_web_role_for_user,
|
|
resolve_web_role_for_user,
|
|
|
user_has_bound_roles,
|
|
user_has_bound_roles,
|
|
|
)
|
|
)
|
|
|
|
|
+from app.services.mp_webview_ticket import TICKET_TTL_SEC, consume_ticket, issue_ticket
|
|
|
from app.crud.crm.quote import view_by_share_code
|
|
from app.crud.crm.quote import view_by_share_code
|
|
|
|
|
|
|
|
router = APIRouter(prefix="", tags=["公共接口"])
|
|
router = APIRouter(prefix="", tags=["公共接口"])
|
|
@@ -207,6 +208,79 @@ def refresh_token(body: dict, db: Session = Depends(get_db)):
|
|
|
return error_response(401, "refreshToken已过期")
|
|
return error_response(401, "refreshToken已过期")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+@router.post("/web/auth/mp-webview-ticket", response_model=dict)
|
|
|
|
|
+def mp_webview_ticket(request: Request, db: Session = Depends(get_db)):
|
|
|
|
|
+ """小程序内嵌 WebView:签发一次性短时 ticket(需 Bearer)。"""
|
|
|
|
|
+ authorization = request.headers.get("Authorization", "")
|
|
|
|
|
+ if not authorization.startswith("Bearer "):
|
|
|
|
|
+ return error_response(401, "未授权")
|
|
|
|
|
+ token = authorization.replace("Bearer ", "").strip()
|
|
|
|
|
+ try:
|
|
|
|
|
+ current_user = get_current_user_by_token(token=token, db=db)
|
|
|
|
|
+ payload = decode_token(token)
|
|
|
|
|
+ except HTTPException:
|
|
|
|
|
+ return error_response(401, "Token无效或已过期")
|
|
|
|
|
+ session_id = str(payload.get("sid") or "").strip()
|
|
|
|
|
+ if not session_id:
|
|
|
|
|
+ return error_response(401, "Token无效或已过期")
|
|
|
|
|
+ try:
|
|
|
|
|
+ ticket = issue_ticket(
|
|
|
|
|
+ user_id=str(current_user.get("user_id") or ""),
|
|
|
|
|
+ session_id=session_id,
|
|
|
|
|
+ refresh_jti=str(payload.get("jti") or ""),
|
|
|
|
|
+ )
|
|
|
|
|
+ except RuntimeError:
|
|
|
|
|
+ return error_response(503, "暂时无法打开内嵌页,请稍后重试")
|
|
|
|
|
+ return success_response({"ticket": ticket, "expiresIn": TICKET_TTL_SEC})
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@router.post("/web/auth/mp-webview-exchange", response_model=dict)
|
|
|
|
|
+def mp_webview_exchange(body: dict, db: Session = Depends(get_db)):
|
|
|
|
|
+ """小程序内嵌 WebView:用 ticket 兑换 Web JWT(不吊销原 refresh)。"""
|
|
|
|
|
+ ticket = str((body or {}).get("ticket") or "").strip()
|
|
|
|
|
+ if not ticket:
|
|
|
|
|
+ return error_response(400, "ticket不能为空")
|
|
|
|
|
+ try:
|
|
|
|
|
+ data = consume_ticket(ticket)
|
|
|
|
|
+ except RuntimeError:
|
|
|
|
|
+ return error_response(503, "暂时无法打开内嵌页,请稍后重试")
|
|
|
|
|
+ if not data:
|
|
|
|
|
+ return error_response(401, "ticket无效或已过期")
|
|
|
|
|
+ user_id = str(data.get("userId") or "").strip()
|
|
|
|
|
+ session_id = str(data.get("sessionId") or "").strip()
|
|
|
|
|
+ if not user_id or not session_id:
|
|
|
|
|
+ return error_response(401, "ticket无效或已过期")
|
|
|
|
|
+ if not is_session_valid(session_id):
|
|
|
|
|
+ return error_response(401, "登录已失效,请重新登录")
|
|
|
|
|
+ user = user_crud.get(db, user_id)
|
|
|
|
|
+ if not user or getattr(user, "del_flag", 0) != 0:
|
|
|
|
|
+ return error_response(401, "用户不存在或已删除")
|
|
|
|
|
+ if user.status != "1":
|
|
|
|
|
+ return error_response(403, "账号已被停用,请联系管理员")
|
|
|
|
|
+ web_role = resolve_web_role_for_user(db, user.id, user.tenant_no)
|
|
|
|
|
+ access_jti = generate_uuid()
|
|
|
|
|
+ refresh_jti = generate_uuid()
|
|
|
|
|
+ access_token = create_access_token(
|
|
|
|
|
+ data=_jwt_user_payload(user, web_role),
|
|
|
|
|
+ session_id=session_id,
|
|
|
|
|
+ token_jti=access_jti,
|
|
|
|
|
+ )
|
|
|
|
|
+ refresh_token_val = create_refresh_token(
|
|
|
|
|
+ data={"sub": user.id, "username": user.user_no, "role": web_role},
|
|
|
|
|
+ session_id=session_id,
|
|
|
|
|
+ refresh_jti=refresh_jti,
|
|
|
|
|
+ )
|
|
|
|
|
+ store_refresh_token(refresh_jti=refresh_jti, user_id=user.id)
|
|
|
|
|
+ return success_response(
|
|
|
|
|
+ {
|
|
|
|
|
+ "token": access_token,
|
|
|
|
|
+ "refreshToken": refresh_token_val,
|
|
|
|
|
+ "expiresIn": settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
|
|
|
|
|
+ "userInfo": _build_user_info_payload(user, web_role),
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
@router.post("/web/auth/logout", response_model=dict)
|
|
@router.post("/web/auth/logout", response_model=dict)
|
|
|
def logout(request: Request):
|
|
def logout(request: Request):
|
|
|
"""登出:吊销 Redis 会话(若已配置 REDIS_HOST)。无 Token 或已失效时仍返回成功(幂等)。"""
|
|
"""登出:吊销 Redis 会话(若已配置 REDIS_HOST)。无 Token 或已失效时仍返回成功(幂等)。"""
|