···112 return nil
113}
1140000000000000000000115// GetAuthRequestInfo retrieves authentication request data by state
116func (s *OAuthStore) GetAuthRequestInfo(ctx context.Context, state string) (*oauth.AuthRequestData, error) {
117 var requestDataJSON string
···112 return nil
113}
114115+// DeleteOldSessionsForDID removes all sessions for a DID except the specified session to keep
116+// This is used during OAuth callback to clean up stale sessions with expired refresh tokens
117+func (s *OAuthStore) DeleteOldSessionsForDID(ctx context.Context, did string, keepSessionID string) error {
118+ result, err := s.db.ExecContext(ctx, `
119+ DELETE FROM oauth_sessions WHERE account_did = ? AND session_id != ?
120+ `, did, keepSessionID)
121+122+ if err != nil {
123+ return fmt.Errorf("failed to delete old sessions for DID: %w", err)
124+ }
125+126+ deleted, _ := result.RowsAffected()
127+ if deleted > 0 {
128+ slog.Info("Deleted old OAuth sessions for DID", "count", deleted, "did", did, "kept", keepSessionID)
129+ }
130+131+ return nil
132+}
133+134// GetAuthRequestInfo retrieves authentication request data by state
135func (s *OAuthStore) GetAuthRequestInfo(ctx context.Context, state string) (*oauth.AuthRequestData, error) {
136 var requestDataJSON string
+18
pkg/auth/oauth/server.go
···122123 slog.Debug("OAuth callback successful", "did", did, "sessionID", sessionID)
1240000000000000000125 // Invalidate cached session (if any) since we have a new session with new tokens
00126 if s.refresher != nil {
127 s.refresher.InvalidateSession(did)
128 slog.Debug("Invalidated cached session after creating new session", "did", did)
···122123 slog.Debug("OAuth callback successful", "did", did, "sessionID", sessionID)
124125+ // Clean up old OAuth sessions for this DID BEFORE invalidating cache
126+ // This prevents accumulation of stale sessions with expired refresh tokens
127+ // Order matters: delete from DB first, then invalidate cache, so when cache reloads
128+ // it will only find the new session
129+ type sessionCleaner interface {
130+ DeleteOldSessionsForDID(ctx context.Context, did string, keepSessionID string) error
131+ }
132+ if cleaner, ok := s.app.clientApp.Store.(sessionCleaner); ok {
133+ if err := cleaner.DeleteOldSessionsForDID(r.Context(), did, sessionID); err != nil {
134+ slog.Warn("Failed to clean up old OAuth sessions", "did", did, "error", err)
135+ // Non-fatal - log and continue
136+ } else {
137+ slog.Debug("Cleaned up old OAuth sessions", "did", did, "kept", sessionID)
138+ }
139+ }
140+141 // Invalidate cached session (if any) since we have a new session with new tokens
142+ // This happens AFTER deleting old sessions from database, ensuring the cache
143+ // will load the correct session when it's next accessed
144 if s.refresher != nil {
145 s.refresher.InvalidateSession(did)
146 slog.Debug("Invalidated cached session after creating new session", "did", did)