From 9449b271f54607f05a06d87547de4685db3a409a Mon Sep 17 00:00:00 2001 From: Matthew Knight Date: Tue, 17 Feb 2026 16:20:24 -0800 Subject: [PATCH] Add periodic cleanup for expired email tokens Fixes #34 Co-Authored-By: Claude Opus 4.6 --- cmd/server/main.go | 1 + internal/auth/tokens.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/cmd/server/main.go b/cmd/server/main.go index 313a976..ae49668 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -62,6 +62,7 @@ func main() { defer cancel() go sessionStore.Cleanup(ctx, 30*time.Minute) + go authService.CleanupExpiredTokens(ctx, 1*time.Hour) publicRouter := publichandlers.NewRouter(publichandlers.Dependencies{ DB: db, diff --git a/internal/auth/tokens.go b/internal/auth/tokens.go index ec8a438..a4f0c59 100644 --- a/internal/auth/tokens.go +++ b/internal/auth/tokens.go @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/mattnite/forgejo-tickets/internal/models" + "github.com/rs/zerolog/log" "golang.org/x/crypto/bcrypt" ) @@ -85,6 +86,23 @@ func (s *Service) redeemToken(ctx context.Context, plainToken string, tokenType return &user, nil } +// CleanupExpiredTokens periodically deletes expired and used email tokens. +func (s *Service) CleanupExpiredTokens(ctx context.Context, interval time.Duration) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + result := s.db.Where("expires_at <= ? OR used_at IS NOT NULL", time.Now()).Delete(&models.EmailToken{}) + if result.Error != nil { + log.Error().Err(result.Error).Msg("email token cleanup error") + } + } + } +} + func hashToken(plainToken string) string { h := sha256.Sum256([]byte(plainToken)) return hex.EncodeToString(h[:])