leonardo: get-session 按 cookie 串行,避免并发刷新令牌被判重用

This commit is contained in:
2026-08-10 20:18:32 +08:00
parent 15602b8033
commit 5403acc4c9
+29 -1
View File
@@ -66,10 +66,14 @@ type Client struct {
// persists it; keeping it here means an unpersisted rotation still works for
// the rest of the process's life.
rotated map[string]string
// refreshing serialises get-session per cookie. Two concurrent refreshes hand
// Cognito the same refresh token twice and its reuse detection revokes the
// whole session — the account then answers 401 forever.
refreshing map[string]*sync.Mutex
}
func NewClient(proxy string) *Client {
return &Client{proxy: strings.TrimSpace(proxy), sessions: map[string]*Session{}, rotated: map[string]string{}}
return &Client{proxy: strings.TrimSpace(proxy), sessions: map[string]*Session{}, rotated: map[string]string{}, refreshing: map[string]*sync.Mutex{}}
}
func (c *Client) SetProxy(proxy string) {
@@ -193,6 +197,18 @@ func (c *Client) GetSession(ctx context.Context, cookie string) (*Session, error
}
c.mu.Unlock()
// Only one refresh per cookie at a time; the others wait and then re-use the
// token it minted.
gate := c.refreshGate(cookie)
gate.Lock()
defer gate.Unlock()
c.mu.Lock()
if cs, ok := c.sessions[cookie]; ok && cs.ExpiresAt-60 > time.Now().Unix() {
c.mu.Unlock()
return cs, nil
}
c.mu.Unlock()
// Use the freshest known value (an earlier response may have rotated the
// better-auth cookie cache) rather than the possibly stale stored cookie.
send := cookie
@@ -327,6 +343,18 @@ func (c *Client) GetSession(ctx context.Context, cookie string) (*Session, error
return sess, nil
}
// refreshGate returns the per-cookie lock that serialises get-session refreshes.
func (c *Client) refreshGate(cookie string) *sync.Mutex {
c.mu.Lock()
defer c.mu.Unlock()
gate, ok := c.refreshing[cookie]
if !ok {
gate = &sync.Mutex{}
c.refreshing[cookie] = gate
}
return gate
}
// warmSession calls cross-origin-cookie, whose response refreshes better-auth's
// cookie cache and CF_Access_Token. It returns the Set-Cookie headers; a
// checkpoint answer (403/429) is an error, since get-session would then be cold.