Thread ctx through auth middleware DB lookups
Backend CI / Test (push) Has been cancelled
Backend CI / Contract Tests (push) Has been cancelled
Backend CI / Build (push) Has been cancelled
Backend CI / Lint (push) Has been cancelled
Backend CI / Secret Scanning (push) Has been cancelled

The auth middleware's m.db.Preload + m.db.First calls were running without
ctx, so on cache miss the resulting SQL queries appeared as orphan
gorm.Query / gorm.Row spans in Jaeger. Now they nest under the parent
HTTP request span like every other repo call.

This was the last orphaned-SQL source on the request hot path. Combined
with the seven service migrations, every authenticated API call now
produces a fully-nested flame graph: HTTP → auth-token-lookup (cache hit)
or HTTP → auth-token-SQL (cache miss) → service → service-SQL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-04-25 16:36:47 -05:00
parent e881d37de0
commit d9b5f85c3d
+9 -8
View File
@@ -104,7 +104,7 @@ func (m *AuthMiddleware) TokenAuth() echo.MiddlewareFunc {
} }
// Cache miss - look up token in database // Cache miss - look up token in database
user, authToken, err := m.getUserFromDatabaseWithToken(token) user, authToken, err := m.getUserFromDatabaseWithToken(c.Request().Context(), token)
if err != nil { if err != nil {
log.Debug().Err(err).Str("token", truncateToken(token)).Msg("Token authentication failed") log.Debug().Err(err).Str("token", truncateToken(token)).Msg("Token authentication failed")
return apperrors.Unauthorized("error.invalid_token") return apperrors.Unauthorized("error.invalid_token")
@@ -148,7 +148,7 @@ func (m *AuthMiddleware) OptionalTokenAuth() echo.MiddlewareFunc {
} }
// Try database // Try database
user, authToken, err := m.getUserFromDatabaseWithToken(token) user, authToken, err := m.getUserFromDatabaseWithToken(c.Request().Context(), token)
if err == nil && !m.isTokenExpired(authToken.Created) { if err == nil && !m.isTokenExpired(authToken.Created) {
m.cacheTokenInfo(c.Request().Context(), token, user.ID, authToken.Created) m.cacheTokenInfo(c.Request().Context(), token, user.ID, authToken.Created)
c.Set(AuthUserKey, user) c.Set(AuthUserKey, user)
@@ -224,7 +224,7 @@ func (m *AuthMiddleware) getUserFromCache(ctx context.Context, token string) (*m
// In-memory cache miss — fetch from database // In-memory cache miss — fetch from database
var user models.User var user models.User
if err := m.db.First(&user, userID).Error; err != nil { if err := m.db.WithContext(ctx).First(&user, userID).Error; err != nil {
// User was deleted - invalidate caches // User was deleted - invalidate caches
m.cache.InvalidateAuthToken(ctx, token) m.cache.InvalidateAuthToken(ctx, token)
return nil, err return nil, err
@@ -242,10 +242,11 @@ func (m *AuthMiddleware) getUserFromCache(ctx context.Context, token string) (*m
} }
// getUserFromDatabaseWithToken looks up the token in the database and returns // getUserFromDatabaseWithToken looks up the token in the database and returns
// both the user and the auth token record (for expiry checking). // both the user and the auth token record (for expiry checking). The ctx is
func (m *AuthMiddleware) getUserFromDatabaseWithToken(token string) (*models.User, *models.AuthToken, error) { // threaded into the GORM session so the SQL span attaches to the request trace.
func (m *AuthMiddleware) getUserFromDatabaseWithToken(ctx context.Context, token string) (*models.User, *models.AuthToken, error) {
var authToken models.AuthToken var authToken models.AuthToken
if err := m.db.Preload("User").Where("key = ?", token).First(&authToken).Error; err != nil { if err := m.db.WithContext(ctx).Preload("User").Where("key = ?", token).First(&authToken).Error; err != nil {
return nil, nil, fmt.Errorf("token not found") return nil, nil, fmt.Errorf("token not found")
} }
@@ -262,8 +263,8 @@ func (m *AuthMiddleware) getUserFromDatabaseWithToken(token string) (*models.Use
// getUserFromDatabase looks up the token in the database and caches the // getUserFromDatabase looks up the token in the database and caches the
// resulting user record in memory. // resulting user record in memory.
// Deprecated: Use getUserFromDatabaseWithToken for new code paths that need expiry checking. // Deprecated: Use getUserFromDatabaseWithToken for new code paths that need expiry checking.
func (m *AuthMiddleware) getUserFromDatabase(token string) (*models.User, error) { func (m *AuthMiddleware) getUserFromDatabase(ctx context.Context, token string) (*models.User, error) {
user, _, err := m.getUserFromDatabaseWithToken(token) user, _, err := m.getUserFromDatabaseWithToken(ctx, token)
return user, err return user, err
} }