fix(mpc-system): 修复通过邀请码加入会话时 invalid token 错误
问题: 通过邀请码查询会话后加入时报 "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 <noreply@anthropic.com>
This commit is contained in:
parent
21985abde5
commit
af08f0f9c6
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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 会话相关
|
||||
|
|
|
|||
|
|
@ -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 }),
|
||||
|
|
|
|||
|
|
@ -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<string | null>(null);
|
||||
const [sessionInfo, setSessionInfo] = useState<SessionInfo | null>(null);
|
||||
const [joinToken, setJoinToken] = useState<string | null>(null);
|
||||
const [partyId, setPartyId] = useState<string | null>(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}
|
||||
|
|
|
|||
Loading…
Reference in New Issue