Overview
AuthService.refreshTokens() verifies if (user.status !== UserStatus.ACTIVE) throw new UnauthorizedException('User is not active'), but AuthService.login() performs no status check at all — it goes straight from the caller to generateTokens(user) and updateRefreshTokenHash. A suspended, banned, or soft-deleted account can therefore obtain a fresh access and refresh token pair by logging in again. The refresh-path check then rejects the next refresh, so the account gets one full access-token lifetime of authenticated access after every login attempt, indefinitely. The asymmetry between the two paths is the bug: the invariant is enforced in one place and not the other.
Specifications
Features:
- Account status is validated on every credential-to-token exchange.
- The status check is centralized so it cannot be omitted from a new entry point.
Tasks:
- Add the
UserStatus.ACTIVE check to login() before token generation.
- Extract the check into a single
assertUserMayAuthenticate(user) helper called by login, refreshTokens, and the social login paths in src/auth/strategies/.
- Return a distinct, non-enumerable error message for suspended accounts consistent with the existing responses.
- Add tests covering login for suspended, banned, and pending-verification accounts.
Impacted Files:
src/auth/auth.service.ts
src/auth/strategies/
Acceptance Criteria
- A suspended user cannot obtain tokens through login.
- All authentication entry points route through one status check.
- Tests cover each non-active status.
Overview
AuthService.refreshTokens()verifiesif (user.status !== UserStatus.ACTIVE) throw new UnauthorizedException('User is not active'), butAuthService.login()performs no status check at all — it goes straight from the caller togenerateTokens(user)andupdateRefreshTokenHash. A suspended, banned, or soft-deleted account can therefore obtain a fresh access and refresh token pair by logging in again. The refresh-path check then rejects the next refresh, so the account gets one full access-token lifetime of authenticated access after every login attempt, indefinitely. The asymmetry between the two paths is the bug: the invariant is enforced in one place and not the other.Specifications
Features:
Tasks:
UserStatus.ACTIVEcheck tologin()before token generation.assertUserMayAuthenticate(user)helper called bylogin,refreshTokens, and the social login paths insrc/auth/strategies/.Impacted Files:
src/auth/auth.service.tssrc/auth/strategies/Acceptance Criteria