diff --git a/internal/adapter/out/postgres/tickets.go b/internal/adapter/out/postgres/tickets.go index 3e2339f..386a046 100644 --- a/internal/adapter/out/postgres/tickets.go +++ b/internal/adapter/out/postgres/tickets.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" "github.com/dihemoe/alisten/internal/adapter/out/postgres/internal/query" "github.com/dihemoe/alisten/internal/app/port" @@ -30,7 +31,7 @@ func (s *Store) CreateTicket(ctx context.Context, t port.NewTicket) error { return q.CreateTicket(ctx, query.CreateTicketParams{TokenHash: t.TokenHash, RoomID: t.RoomID, MemberID: t.MemberID, SessionTokenHash: t.SessionTokenHash}) }) if err != nil { - return fmt.Errorf("签发房间 %s 的票据:%w", t.RoomID, err) + return fmt.Errorf("签发房间 %s 的票据:%w", t.RoomID, sessionError(err)) } return nil } @@ -50,3 +51,13 @@ func (s *Store) TicketHolder(ctx context.Context, tokenHash []byte) (port.Ticket SessionTokenHash: row.SessionTokenHash, }, nil } + +const foreignKeyViolation = "23503" + +func sessionError(err error) error { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) && pgErr.Code == foreignKeyViolation && pgErr.ConstraintName == "room_tickets_session_token_hash_fkey" { + return port.ErrSessionNotFound + } + return err +} diff --git a/internal/adapter/out/postgres/tickets_test.go b/internal/adapter/out/postgres/tickets_test.go index ed321bb..3a18e09 100644 --- a/internal/adapter/out/postgres/tickets_test.go +++ b/internal/adapter/out/postgres/tickets_test.go @@ -39,6 +39,20 @@ func TestTicketHolder(t *testing.T) { } } +func TestCreateTicketRejectsDeletedSession(t *testing.T) { + store, _ := newStore(t) + alice := createMember(t, store, "alice", "甲") + id := createRoom(t, store, port.NewRoom{Kind: room.Ephemeral, Name: "临时"}) + if err := store.DeleteSession(t.Context(), tokenHash("alice")); err != nil { + t.Fatal(err) + } + + err := store.CreateTicket(t.Context(), port.NewTicket{TokenHash: tokenHash("t1"), RoomID: id, MemberID: alice.ID, SessionTokenHash: tokenHash("alice")}) + if !errors.Is(err, port.ErrSessionNotFound) { + t.Errorf("会话已经删除时 CreateTicket = %v,期望 ErrSessionNotFound", err) + } +} + func TestTicketsBelongToSessions(t *testing.T) { store, _ := newStore(t) alice := createMember(t, store, "alice-1", "甲") diff --git a/internal/app/identity/identity.go b/internal/app/identity/identity.go index ed97ad4..19b0fbf 100644 --- a/internal/app/identity/identity.go +++ b/internal/app/identity/identity.go @@ -96,6 +96,10 @@ func (s *Service) IssueTicket(ctx context.Context, memberID, memberToken, roomID // 刚才是按旧密码校验的;客户端重新输入密码后,按新密码校验。 return "", ErrWrongPassword } + if errors.Is(err, port.ErrSessionNotFound) { + // 会话在鉴权之后被删除(例如另一台设备退出了其他设备),此时成员 token 确实已经失效。 + return "", ErrInvalidMemberToken + } if err != nil { return "", err } diff --git a/internal/app/identity/identity_test.go b/internal/app/identity/identity_test.go index d97a1d0..4cc631e 100644 --- a/internal/app/identity/identity_test.go +++ b/internal/app/identity/identity_test.go @@ -157,6 +157,19 @@ func TestTicketBelongsToIssuingSession(t *testing.T) { } } +func TestIssueTicketRejectsDeletedSession(t *testing.T) { + s, store := newService(t) + alice := createMember(t, s) + id := createRoom(t, store, "") + if err := store.DeleteSession(t.Context(), hashSecret(alice.MemberToken)); err != nil { + t.Fatal(err) + } + + if _, err := s.IssueTicket(t.Context(), alice.Member.ID, alice.MemberToken, id, ""); !errors.Is(err, ErrInvalidMemberToken) { + t.Errorf("会话在签发之前被删除,错误 = %v,期望 ErrInvalidMemberToken", err) + } +} + type loggedSubscriptions struct { port.Bus logger *slog.Logger diff --git a/internal/app/port/porttest/store.go b/internal/app/port/porttest/store.go index e0800e7..f17371b 100644 --- a/internal/app/port/porttest/store.go +++ b/internal/app/port/porttest/store.go @@ -257,7 +257,7 @@ func (s *Store) CreateTicket(_ context.Context, t port.NewTicket) error { return errors.New("成员不存在") } if _, ok := s.sessions[string(t.SessionTokenHash)]; t.SessionTokenHash != nil && !ok { - return errors.New("会话不存在") + return port.ErrSessionNotFound } if _, ok := s.tickets[string(t.TokenHash)]; ok { return errors.New("票据哈希重复") diff --git a/internal/app/port/ticket.go b/internal/app/port/ticket.go index 11f62f7..b1e1ef6 100644 --- a/internal/app/port/ticket.go +++ b/internal/app/port/ticket.go @@ -34,7 +34,7 @@ type TicketHolder struct { type Tickets interface { // 房间的密码哈希已经不是 t.PasswordHash 时返回 ErrPasswordChanged:密码在校验之后被修改过, - // 设置或修改密码时的作废已经执行,此时签发的票据不会再被作废。 + // 设置或修改密码时的作废已经执行,此时签发的票据不会再被作废。t.SessionTokenHash 的会话已经不存在时返回 ErrSessionNotFound。 CreateTicket(ctx context.Context, t NewTicket) error TicketHolder(ctx context.Context, tokenHash []byte) (TicketHolder, error) }