From 65d155f74f94acaf74540b55f74ad36583b5d8eb Mon Sep 17 00:00:00 2001 From: Evan Jarrett Date: Tue, 4 Nov 2025 23:26:57 -0600 Subject: [PATCH] try and invalidate sessions --- pkg/appview/db/oauth_store.go | 19 +++++++++++++++++++ pkg/auth/oauth/server.go | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/pkg/appview/db/oauth_store.go b/pkg/appview/db/oauth_store.go index fcfc262..3997f6a 100644 --- a/pkg/appview/db/oauth_store.go +++ b/pkg/appview/db/oauth_store.go @@ -112,6 +112,25 @@ func (s *OAuthStore) DeleteSessionsForDID(ctx context.Context, did string) error return nil } +// DeleteOldSessionsForDID removes all sessions for a DID except the specified session to keep +// This is used during OAuth callback to clean up stale sessions with expired refresh tokens +func (s *OAuthStore) DeleteOldSessionsForDID(ctx context.Context, did string, keepSessionID string) error { + result, err := s.db.ExecContext(ctx, ` + DELETE FROM oauth_sessions WHERE account_did = ? AND session_id != ? + `, did, keepSessionID) + + if err != nil { + return fmt.Errorf("failed to delete old sessions for DID: %w", err) + } + + deleted, _ := result.RowsAffected() + if deleted > 0 { + slog.Info("Deleted old OAuth sessions for DID", "count", deleted, "did", did, "kept", keepSessionID) + } + + return nil +} + // GetAuthRequestInfo retrieves authentication request data by state func (s *OAuthStore) GetAuthRequestInfo(ctx context.Context, state string) (*oauth.AuthRequestData, error) { var requestDataJSON string diff --git a/pkg/auth/oauth/server.go b/pkg/auth/oauth/server.go index 5c4f8c1..df97c42 100644 --- a/pkg/auth/oauth/server.go +++ b/pkg/auth/oauth/server.go @@ -122,7 +122,25 @@ func (s *Server) ServeCallback(w http.ResponseWriter, r *http.Request) { slog.Debug("OAuth callback successful", "did", did, "sessionID", sessionID) + // Clean up old OAuth sessions for this DID BEFORE invalidating cache + // This prevents accumulation of stale sessions with expired refresh tokens + // Order matters: delete from DB first, then invalidate cache, so when cache reloads + // it will only find the new session + type sessionCleaner interface { + DeleteOldSessionsForDID(ctx context.Context, did string, keepSessionID string) error + } + if cleaner, ok := s.app.clientApp.Store.(sessionCleaner); ok { + if err := cleaner.DeleteOldSessionsForDID(r.Context(), did, sessionID); err != nil { + slog.Warn("Failed to clean up old OAuth sessions", "did", did, "error", err) + // Non-fatal - log and continue + } else { + slog.Debug("Cleaned up old OAuth sessions", "did", did, "kept", sessionID) + } + } + // Invalidate cached session (if any) since we have a new session with new tokens + // This happens AFTER deleting old sessions from database, ensuring the cache + // will load the correct session when it's next accessed if s.refresher != nil { s.refresher.InvalidateSession(did) slog.Debug("Invalidated cached session after creating new session", "did", did)