fix: Throttle audit logging for expired API token use
Every request presenting an expired token wrote a token.expired_use audit row, and RateLimitTokens only applies once a session exists, so a rejected token was never rate-limited. A looping job with one expired token could write an unbounded number of audit rows, drowning the real audit trail. services.ShouldLogExpiredTokenUse now dedupes to at most one token.expired_use record per token per minute, mirroring the throttle TouchAPIToken already uses for last-used. It lives in services rather than auth because the storage concern belongs beside the token's other storage-backed state. The first use per window is still recorded, which is what makes a forgotten job visible.
This commit is contained in:
@@ -135,9 +135,13 @@ func sessionFromToken(c *gin.Context) (*Session, bool) {
|
||||
tok, err := services.ResolveAPIToken(raw)
|
||||
if errors.Is(err, services.ErrTokenExpired) {
|
||||
// Recorded rather than only refused: an expired token still being
|
||||
// presented is how a forgotten CI job becomes visible.
|
||||
services.LogEvent(tok.InstanceID, "token.expired_use", tok.Name, "", "",
|
||||
fmt.Sprintf("expired token '%s' was used", tok.Name))
|
||||
// presented is how a forgotten CI job becomes visible. Throttled to
|
||||
// once per token per minute, or a looping job writes an unbounded
|
||||
// stream of audit rows instead of one.
|
||||
if services.ShouldLogExpiredTokenUse(tok.TokenID) {
|
||||
services.LogEvent(tok.InstanceID, "token.expired_use", tok.Name, "", "",
|
||||
fmt.Sprintf("expired token '%s' was used", tok.Name))
|
||||
}
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "token expired", "code": "token_expired"})
|
||||
return nil, false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user