fix: reject a room ticket as an invalid member token when its session was deleted meanwhile

This commit is contained in:
DiheChen committed 2026-11-08 23:35:50 +08:00
1 parent 4e2c24bdca
commit 4f6858b17c
6 files changed
+45 -3

No files matched your search

+12 -1
View File
@@ -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
}
@@ -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", "甲")
+4
View File
@@ -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
}
+13
View File
@@ -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
+1 -1
View File
@@ -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("票据哈希重复")
+1 -1
View File
@@ -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)
}