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

@@ -353,7 +353,9 @@ export class UsersStorage {
});
}
// Активные сессии пользователя (для управления устройствами)
// Активные сессии пользователя (для управления устройствами).
// isReplaced = false: rotated-строки цепочки ротации в список устройств НЕ
// попадают — иначе каждый рефреш токена добавлял в UI «новое устройство».
async listUserSessions(userId: number): Promise<UserSession[]> {
return db
.select()
@@ -361,6 +363,7 @@ export class UsersStorage {
.where(and(
eq(userSessions.userId, userId),
eq(userSessions.isRevoked, false),
eq(userSessions.isReplaced, false),
gte(userSessions.expiresAt, new Date()),
))
.orderBy(desc(userSessions.createdAt))