From 5ee219caba0111ac9a1c3118a8b7a7462c6ad3a7 Mon Sep 17 00:00:00 2001 From: DiheChen Date: Sun, 8 Nov 2026 23:38:38 +0800 Subject: [PATCH] fix: confirm the room ticket again after joining so a revocation during the join drops the connection --- internal/app/identity/identity.go | 11 +++++- internal/app/identity/identity_test.go | 48 ++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/internal/app/identity/identity.go b/internal/app/identity/identity.go index 19b0fbf..e2577f4 100644 --- a/internal/app/identity/identity.go +++ b/internal/app/identity/identity.go @@ -133,7 +133,16 @@ func (s *Service) EnterRoom(ctx context.Context, roomID, ticket string) (*realti if holder.Bot { return nil, ErrBotSession } - return s.gateway.Join(logctx.WithMember(logctx.WithRoom(ctx, r.Info.ID), holder.Member.ID), r.Info.ID, holder.Member, holder.SessionTokenHash) + session, err := s.gateway.Join(logctx.WithMember(logctx.WithRoom(ctx, r.Info.ID), holder.Member.ID), r.Info.ID, holder.Member, holder.SessionTokenHash) + if err != nil { + return nil, err + } + // 作废时先删除票据、再通知断开:会话加入网关之前发出的通知它收不到,所以加入之后再确认一次票据。 + if _, err := s.VerifyTicket(ctx, ticket); err != nil { + s.gateway.Leave(session) + return nil, err + } + return session, nil } func NewAPIKey() (string, port.NewAPIKey) { diff --git a/internal/app/identity/identity_test.go b/internal/app/identity/identity_test.go index 4cc631e..6143228 100644 --- a/internal/app/identity/identity_test.go +++ b/internal/app/identity/identity_test.go @@ -9,6 +9,7 @@ import ( "log/slog" "regexp" "testing" + "testing/synctest" "github.com/dihemoe/alisten/internal/app/port" "github.com/dihemoe/alisten/internal/app/port/porttest" @@ -170,6 +171,53 @@ func TestIssueTicketRejectsDeletedSession(t *testing.T) { } } +// 第一次查询照常,之后的查询都认为票据已经作废。 +type revokedAfterFirstLookup struct { + port.Tickets + lookups int +} + +func (r *revokedAfterFirstLookup) TicketHolder(ctx context.Context, tokenHash []byte) (port.TicketHolder, error) { + r.lookups++ + if r.lookups > 1 { + return port.TicketHolder{}, port.ErrTicketNotFound + } + return r.Tickets.TicketHolder(ctx, tokenHash) +} + +func TestEnterRoomLeavesWhenTicketIsRevokedWhileJoining(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + store := porttest.New() + node := porttest.NewRedis().Node("a") + host, gateway := realtime.New(store, store, nil, porttest.NewPreparer(), realtime.Cluster{Instance: "a", Leases: node, Liveness: node, Bus: node}, slog.New(slog.DiscardHandler)) + ctx, stop := context.WithCancel(t.Context()) + wait, err := realtime.Start(ctx, host, gateway) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + _ = host.Shutdown(context.Background()) + _ = gateway.Shutdown(context.Background()) + stop() + wait() + }) + s := New(store, store, &revokedAfterFirstLookup{Tickets: store}, gateway) + alice := createMember(t, s) + id := createRoom(t, store, "") + ticket, err := s.IssueTicket(t.Context(), alice.Member.ID, alice.MemberToken, id, "") + if err != nil { + t.Fatal(err) + } + + if _, err := s.EnterRoom(t.Context(), id, ticket); !errors.Is(err, ErrInvalidTicket) { + t.Errorf("加入期间票据被作废,错误 = %v,期望 ErrInvalidTicket", err) + } + if n := gateway.Sessions(); n != 0 { + t.Errorf("网关上残留 %d 个会话,期望 0", n) + } + }) +} + type loggedSubscriptions struct { port.Bus logger *slog.Logger