fix(auth): список сессий без мусора ротаций — фильтр isReplaced, перенос UA/IP, чистка старых

- listUserSessions: + isReplaced = false — иначе каждый рефреш токена добавлял
  в список устройств «новую сессию» (цепочка ротации видна только для детекта кражи);
- rotateFamilySession: userAgent/ipAddress переносятся из исходной сессии —
  rotated-строки больше не «Неизвестное устройство»;
- новый worker session-cleanup (тик раз в сутки + прогон при старте): удаляет
  replaced-сессии старше 72ч (grace-окно детекта кражи) и истёкшие.
This commit is contained in:
2026-09-22 09:01:59 +03:00
parent 13db5be030
commit 8d11ad3b33
4 changed files with 69 additions and 2 deletions

View File

@@ -330,7 +330,9 @@ export class AuthService {
appRole: user.appRole
}, remember, session.familyId);
// Атомарная ротация: replaced + insert новой сессии в одной транзакции
// Атомарная ротация: replaced + insert новой сессии в одной транзакции.
// userAgent/ipAddress переносим из исходной сессии — иначе rotated-строки
// отображались бы в списке устройств как «Неизвестное устройство».
const newSession = await storage.rotateUserSession(session.id, {
userId: user.id,
refreshTokenHash: hashToken(tokens.refreshToken),
@@ -339,6 +341,8 @@ export class AuthService {
parentRefreshTokenHash: session.refreshTokenHash,
expiresAt,
remember,
ipAddress: session.ipAddress,
userAgent: session.userAgent,
});
// Access-токен перевыпускаем с sid — для отзыва устройства в моменте