(ctx context.Context, req *common.NormalizedRequest, ap *AuthPayload)
| 120 | } |
| 121 | |
| 122 | func (s *DatabaseStrategy) Authenticate(ctx context.Context, req *common.NormalizedRequest, ap *AuthPayload) (*common.User, error) { |
| 123 | if ap.Secret == nil { |
| 124 | s.recordAuthFailureMetric(req, "missing_secret") |
| 125 | return nil, common.NewErrAuthUnauthorized("database", "no secret provided") |
| 126 | } |
| 127 | |
| 128 | apiKey := ap.Secret.Value |
| 129 | if apiKey == "" { |
| 130 | s.recordAuthFailureMetric(req, "empty_secret") |
| 131 | return nil, common.NewErrAuthUnauthorized("database", "empty API key") |
| 132 | } |
| 133 | |
| 134 | // Check positive cache first if available |
| 135 | if s.cache != nil { |
| 136 | if cachedUser, found := s.cache.Get(apiKey); found { |
| 137 | s.logger.Debug().Str("apiKey", apiKey).Msg("API key found in cache") |
| 138 | return cachedUser, nil |
| 139 | } |
| 140 | s.logger.Debug().Str("apiKey", apiKey).Msg("API key not found in cache") |
| 141 | } |
| 142 | |
| 143 | // Negative cache: short-circuit known invalid/disabled keys |
| 144 | if s.negCache != nil { |
| 145 | if _, found := s.negCache.Get(apiKey); found { |
| 146 | s.logger.Debug().Str("apiKey", apiKey).Msg("API key found in negative cache") |
| 147 | s.recordAuthFailureMetric(req, "cached_unknown_api_key") |
| 148 | return nil, common.NewErrAuthUnauthorized("database", "invalid API key") |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | // Fail-open fast path. When the connector is in a known-down state and |
| 153 | // fail-open is configured, serve the emergency user immediately without |
| 154 | // going through singleflight + connector.Get + Error log + metric. This |
| 155 | // is what eliminates per-request pressure during a sustained outage |
| 156 | // (see DatabaseStrategy struct comment for the incident reference). |
| 157 | // One caller per connectorDownProbeInterval still goes through the real |
| 158 | // DB path so we eventually notice recovery; everyone else fast-paths. |
| 159 | if u := s.tryFastFailOpen(); u != nil { |
| 160 | s.recordAuthFailureMetric(req, "db_fail_open_fast_path") |
| 161 | return u, nil |
| 162 | } |
| 163 | |
| 164 | // Use singleflight to deduplicate concurrent misses per key |
| 165 | type authFetchResult struct { |
| 166 | user *common.User |
| 167 | err error |
| 168 | neg bool |
| 169 | skipCache bool |
| 170 | } |
| 171 | v, sfErr, _ := s.sf.Do(apiKey, func() (interface{}, error) { |
| 172 | rangeKey := "*" |
| 173 | lookupCtx := ctx |
| 174 | if s.cfg != nil && s.cfg.MaxWait.Duration() > 0 { |
| 175 | var cancel context.CancelFunc |
| 176 | lookupCtx, cancel = context.WithTimeout(ctx, s.cfg.MaxWait.Duration()) |
| 177 | defer cancel() |
| 178 | } |
| 179 | valueBytes, err := s.getWithRetries(lookupCtx, data.ConnectorMainIndex, apiKey, rangeKey) |
nothing calls this directly
no test coverage detected