Tech Story
As a platform engineer, I want expired and revoked refresh tokens periodically purged from the database so that the refresh_tokens table does not grow unboundedly and sensitive (even if hashed) token records are removed promptly after expiry.
Context
Refresh tokens are never deleted — only marked revoked = true or left with a past expiresAt. Over time this table accumulates every token ever issued. Beyond storage growth, retaining revoked/expired records longer than necessary increases the blast radius of a DB leak.
Acceptance Criteria
Technical Elaboration
- Create
TokenCleanupService in the auth module decorated with @Injectable()
- Use
@Cron() from @nestjs/schedule (already installed) reading cron expression from ConfigService
- Query:
DELETE FROM refresh_tokens WHERE revoked = true OR expires_at < NOW()
- Wrap in try/catch and log both success (rows deleted) and failure without rethrowing (job failure should not crash the process)
- Register in
AuthModule providers
Notes
ScheduleModule is already conditionally loaded in AppModule (skipped in test env) — no changes needed there
- Consider adding a similar cleanup for
password_resets table (used/expired rows)
Tech Story
As a platform engineer, I want expired and revoked refresh tokens periodically purged from the database so that the
refresh_tokenstable does not grow unboundedly and sensitive (even if hashed) token records are removed promptly after expiry.Context
Refresh tokens are never deleted — only marked
revoked = trueor left with a pastexpiresAt. Over time this table accumulates every token ever issued. Beyond storage growth, retaining revoked/expired records longer than necessary increases the blast radius of a DB leak.Acceptance Criteria
refresh_tokensrows whererevoked = trueORexpiresAt < NOW()NODE_ENV === 'test')REFRESH_TOKEN_CLEANUP_CRONenv var (default:0 3 * * *— 3am daily)Technical Elaboration
TokenCleanupServicein theauthmodule decorated with@Injectable()@Cron()from@nestjs/schedule(already installed) reading cron expression fromConfigServiceDELETE FROM refresh_tokens WHERE revoked = true OR expires_at < NOW()AuthModuleprovidersNotes
ScheduleModuleis already conditionally loaded inAppModule(skipped in test env) — no changes needed therepassword_resetstable (used/expired rows)