Skip to content

Commit 5ba9cf5

Browse files
fix(dashboard-api): invalidate auth cache on team membership changes (#2288)
* fix(dashboard-api): invalidate auth cache on team membership changes Evict the cached user-team auth entry when a member is added or removed, so the change takes effect immediately instead of waiting for TTL expiry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): normalize teamID in cache key to prevent case mismatch The teamID from the X-Supabase-Team header could differ in casing (e.g. uppercase UUID) from uuid.UUID.String() used during invalidation, causing cache keys to not match and invalidation to silently fail. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1798413 commit 5ba9cf5

File tree

2 files changed

+16
-1
lines changed

2 files changed

+16
-1
lines changed

packages/auth/pkg/auth/service.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"net/http"
8+
"strings"
89

910
"github.com/gin-gonic/gin"
1011
"github.com/google/uuid"
@@ -155,7 +156,7 @@ func (s *AuthService[T]) ValidateSupabaseTeam(ctx context.Context, ginCtx *gin.C
155156
}
156157
}
157158

158-
cacheKey := fmt.Sprintf("%s-%s", userID.String(), teamID)
159+
cacheKey := supabaseTeamCacheKey(userID, teamID)
159160

160161
result, err := s.teamCache.GetOrSet(ctx, cacheKey, func(ctx context.Context, _ string) (T, error) {
161162
return s.store.GetTeamByIDAndUserID(ctx, userID, teamID)
@@ -197,6 +198,12 @@ func (s *AuthService[T]) ValidateSupabaseTeam(ctx context.Context, ginCtx *gin.C
197198
return result, nil
198199
}
199200

201+
// InvalidateTeamMemberCache removes the cached auth entry for a specific user-team pair.
202+
// This should be called when team membership changes (member added or removed).
203+
func (s *AuthService[T]) InvalidateTeamMemberCache(userID uuid.UUID, teamID string) {
204+
s.teamCache.Invalidate(supabaseTeamCacheKey(userID, teamID))
205+
}
206+
200207
// InvalidateTeamCache queries the team's API key hashes and removes their cached entries.
201208
func (s *AuthService[T]) InvalidateTeamCache(ctx context.Context, teamID uuid.UUID) error {
202209
hashes, err := s.store.GetTeamAPIKeyHashes(ctx, teamID)
@@ -211,6 +218,10 @@ func (s *AuthService[T]) InvalidateTeamCache(ctx context.Context, teamID uuid.UU
211218
return nil
212219
}
213220

221+
func supabaseTeamCacheKey(userID uuid.UUID, teamID string) string {
222+
return fmt.Sprintf("%s-%s", userID.String(), strings.ToLower(teamID))
223+
}
224+
214225
// Close stops the underlying cache's background refresh goroutines.
215226
func (s *AuthService[T]) Close(ctx context.Context) error {
216227
return s.teamCache.Close(ctx)

packages/dashboard-api/internal/handlers/team_members.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ func (s *APIStore) PostTeamsTeamIDMembers(c *gin.Context, teamID api.TeamID) {
107107
return
108108
}
109109

110+
s.authService.InvalidateTeamMemberCache(user.ID, teamInfo.Team.ID.String())
111+
110112
c.Status(http.StatusCreated)
111113
}
112114

@@ -187,5 +189,7 @@ func (s *APIStore) DeleteTeamsTeamIDMembersUserId(c *gin.Context, teamID api.Tea
187189
return
188190
}
189191

192+
s.authService.InvalidateTeamMemberCache(userId, teamInfo.Team.ID.String())
193+
190194
c.Status(http.StatusNoContent)
191195
}

0 commit comments

Comments
 (0)