From af08f0f9c60301efb349292300da5fbfabe827cb Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 29 Dec 2025 03:40:36 -0800 Subject: [PATCH] =?UTF-8?q?fix(mpc-system):=20=E4=BF=AE=E5=A4=8D=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E9=82=80=E8=AF=B7=E7=A0=81=E5=8A=A0=E5=85=A5=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E6=97=B6=20invalid=20token=20=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: 通过邀请码查询会话后加入时报 "13 INTERNAL: invalid token" 原因: GetSessionByInviteCode API 没有返回 join_token 修复: - account-service: GetSessionByInviteCode 在查询时生成新的 wildcard join token - account-service: CoManagedHTTPHandler 添加 jwtService 依赖注入 - service-party-app: validateInviteCode 返回 join_token - service-party-app: Join.tsx 保存并使用 joinToken 和 partyId - service-party-app: preload.ts joinSession 使用正确的参数格式 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../adapters/input/http/co_managed_handler.go | 31 +++++++++++-- .../services/account/cmd/server/main.go | 4 +- .../service-party-app/electron/main.ts | 4 +- .../electron/modules/account-client.ts | 3 ++ .../service-party-app/electron/preload.ts | 4 +- .../service-party-app/src/pages/Join.tsx | 45 ++++++++++++++++--- 6 files changed, 78 insertions(+), 13 deletions(-) diff --git a/backend/mpc-system/services/account/adapters/input/http/co_managed_handler.go b/backend/mpc-system/services/account/adapters/input/http/co_managed_handler.go index 801ae200..1a1b3ed7 100644 --- a/backend/mpc-system/services/account/adapters/input/http/co_managed_handler.go +++ b/backend/mpc-system/services/account/adapters/input/http/co_managed_handler.go @@ -12,6 +12,7 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" grpcclient "github.com/rwadurian/mpc-system/services/account/adapters/output/grpc" + "github.com/rwadurian/mpc-system/pkg/jwt" "github.com/rwadurian/mpc-system/pkg/logger" "go.uber.org/zap" ) @@ -20,7 +21,8 @@ import ( // This is a completely independent handler that does not affect existing functionality type CoManagedHTTPHandler struct { sessionCoordinatorClient *grpcclient.SessionCoordinatorClient - db *sql.DB // Database connection for invite_code lookups + db *sql.DB // Database connection for invite_code lookups + jwtService *jwt.JWTService // JWT service for generating join tokens } // NewCoManagedHTTPHandler creates a new CoManagedHTTPHandler @@ -37,10 +39,12 @@ func NewCoManagedHTTPHandler( func NewCoManagedHTTPHandlerWithDB( sessionCoordinatorClient *grpcclient.SessionCoordinatorClient, db *sql.DB, + jwtService *jwt.JWTService, ) *CoManagedHTTPHandler { return &CoManagedHTTPHandler{ sessionCoordinatorClient: sessionCoordinatorClient, db: db, + jwtService: jwtService, } } @@ -380,7 +384,7 @@ func (h *CoManagedHTTPHandler) GetSessionByInviteCode(c *gin.Context) { return } - // Get wildcard join token from session coordinator + // Get session status from session coordinator statusResp, err := h.sessionCoordinatorClient.GetSessionStatus(ctx, sessionID) if err != nil { logger.Error("Failed to get session status from coordinator", @@ -398,10 +402,30 @@ func (h *CoManagedHTTPHandler) GetSessionByInviteCode(c *gin.Context) { return } + // Generate a wildcard join token for this session + // This allows any participant to join using this token + var joinToken string + if h.jwtService != nil { + sessionUUID, err := uuid.Parse(sessionID) + if err == nil { + // Token valid until session expires + tokenExpiry := time.Until(expiresAt) + if tokenExpiry > 0 { + joinToken, err = h.jwtService.GenerateJoinToken(sessionUUID, "*", tokenExpiry) + if err != nil { + logger.Warn("Failed to generate join token", + zap.String("session_id", sessionID), + zap.Error(err)) + } + } + } + } + logger.Info("Found session for invite_code", zap.String("invite_code", inviteCode), zap.String("session_id", sessionID), - zap.String("wallet_name", walletName)) + zap.String("wallet_name", walletName), + zap.Bool("has_join_token", joinToken != "")) c.JSON(http.StatusOK, gin.H{ "session_id": sessionID, @@ -412,6 +436,7 @@ func (h *CoManagedHTTPHandler) GetSessionByInviteCode(c *gin.Context) { "completed_parties": statusResp.CompletedParties, "total_parties": statusResp.TotalParties, "expires_at": expiresAt.UnixMilli(), + "join_token": joinToken, }) } diff --git a/backend/mpc-system/services/account/cmd/server/main.go b/backend/mpc-system/services/account/cmd/server/main.go index 21fbd047..86ac11ac 100644 --- a/backend/mpc-system/services/account/cmd/server/main.go +++ b/backend/mpc-system/services/account/cmd/server/main.go @@ -303,8 +303,8 @@ func startHTTPServer( }) // Create co-managed wallet handler (independent from existing functionality) - // Uses database connection for invite_code lookups - coManagedHandler := httphandler.NewCoManagedHTTPHandlerWithDB(sessionCoordinatorClient, db) + // Uses database connection for invite_code lookups and JWT service for generating join tokens + coManagedHandler := httphandler.NewCoManagedHTTPHandlerWithDB(sessionCoordinatorClient, db, jwtService) // Configure authentication middleware // Skip paths that don't require authentication diff --git a/backend/mpc-system/services/service-party-app/electron/main.ts b/backend/mpc-system/services/service-party-app/electron/main.ts index e879c74f..de652270 100644 --- a/backend/mpc-system/services/service-party-app/electron/main.ts +++ b/backend/mpc-system/services/service-party-app/electron/main.ts @@ -235,8 +235,10 @@ function setupIpcHandlers() { n: result?.threshold_n, }, status: result?.status, - currentParticipants: result?.joined_parties || 0, + currentParticipants: result?.completed_parties || result?.joined_parties || 0, + totalParticipants: result?.total_parties || result?.threshold_n || 0, }, + joinToken: result?.join_token, }; } catch (error) { return { success: false, error: (error as Error).message }; diff --git a/backend/mpc-system/services/service-party-app/electron/modules/account-client.ts b/backend/mpc-system/services/service-party-app/electron/modules/account-client.ts index e10a6ae0..26e35867 100644 --- a/backend/mpc-system/services/service-party-app/electron/modules/account-client.ts +++ b/backend/mpc-system/services/service-party-app/electron/modules/account-client.ts @@ -82,6 +82,9 @@ export interface GetSessionByInviteCodeResponse { invite_code: string; expires_at: number; joined_parties: number; + completed_parties?: number; + total_parties?: number; + join_token?: string; } // Sign 会话相关 diff --git a/backend/mpc-system/services/service-party-app/electron/preload.ts b/backend/mpc-system/services/service-party-app/electron/preload.ts index ea9d24bd..4924d16b 100644 --- a/backend/mpc-system/services/service-party-app/electron/preload.ts +++ b/backend/mpc-system/services/service-party-app/electron/preload.ts @@ -16,8 +16,8 @@ contextBridge.exposeInMainWorld('electronAPI', { initiatorName: string; }) => ipcRenderer.invoke('grpc:createSession', params), - joinSession: (sessionId: string, participantName: string) => - ipcRenderer.invoke('grpc:joinSession', { sessionId, participantName }), + joinSession: (params: { sessionId: string; partyId: string; joinToken: string }) => + ipcRenderer.invoke('grpc:joinSession', params), validateInviteCode: (code: string) => ipcRenderer.invoke('grpc:validateInviteCode', { code }), diff --git a/backend/mpc-system/services/service-party-app/src/pages/Join.tsx b/backend/mpc-system/services/service-party-app/src/pages/Join.tsx index 37c1474b..9e37edb4 100644 --- a/backend/mpc-system/services/service-party-app/src/pages/Join.tsx +++ b/backend/mpc-system/services/service-party-app/src/pages/Join.tsx @@ -9,6 +9,14 @@ interface SessionInfo { initiator: string; createdAt: string; currentParticipants: number; + totalParticipants?: number; +} + +interface ValidateResult { + success: boolean; + error?: string; + sessionInfo?: SessionInfo; + joinToken?: string; } export default function Join() { @@ -20,6 +28,8 @@ export default function Join() { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [sessionInfo, setSessionInfo] = useState(null); + const [joinToken, setJoinToken] = useState(null); + const [partyId, setPartyId] = useState(null); const [step, setStep] = useState<'input' | 'confirm' | 'joining'>('input'); useEffect(() => { @@ -38,10 +48,22 @@ export default function Join() { setError(null); try { + // 获取当前 partyId + const partyResult = await window.electronAPI.grpc.getPartyId(); + if (!partyResult.success || !partyResult.partyId) { + setError('请先连接到消息路由器'); + setIsLoading(false); + return; + } + setPartyId(partyResult.partyId); + // 解析邀请码获取会话信息 - const result = await window.electronAPI.grpc.validateInviteCode(codeToValidate); + const result: ValidateResult = await window.electronAPI.grpc.validateInviteCode(codeToValidate); if (result.success && result.sessionInfo) { setSessionInfo(result.sessionInfo); + if (result.joinToken) { + setJoinToken(result.joinToken); + } setStep('confirm'); } else { setError(result.error || '无效的邀请码'); @@ -59,15 +81,26 @@ export default function Join() { return; } + if (!partyId) { + setError('未获取到 Party ID,请重试'); + return; + } + + if (!joinToken) { + setError('未获取到加入令牌,请重新验证邀请码'); + return; + } + setStep('joining'); setIsLoading(true); setError(null); try { - const result = await window.electronAPI.grpc.joinSession( - sessionInfo.sessionId, - participantName.trim() - ); + const result = await window.electronAPI.grpc.joinSession({ + sessionId: sessionInfo.sessionId, + partyId: partyId, + joinToken: joinToken, + }); if (result.success) { navigate(`/session/${sessionInfo.sessionId}`); @@ -190,6 +223,8 @@ export default function Join() { onClick={() => { setStep('input'); setSessionInfo(null); + setJoinToken(null); + setPartyId(null); setError(null); }} disabled={isLoading}