feat(auth): access 24ч, jti против гонок ротации, транзакционная ротация, отзыв сессий по sid
This commit is contained in:
30
server/utils/sessionRevocation.ts
Normal file
30
server/utils/sessionRevocation.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { db } from '../db';
|
||||
import { userSessions } from '@shared/schema';
|
||||
|
||||
/**
|
||||
* Проверка отзыва сессии (user_sessions.is_revoked) для access-токенов с claim sid.
|
||||
* In-memory кэш ~30 сек: неизменный ответ не дёргает БД на каждый запрос,
|
||||
* отозванная сессия умирает в течение ~30 секунд на всех инстансах приложения.
|
||||
*/
|
||||
const CACHE_TTL_MS = 30_000;
|
||||
const cache = new Map<number, { revoked: boolean; ts: number }>();
|
||||
|
||||
export async function isSessionRevoked(sid: number): Promise<boolean> {
|
||||
const hit = cache.get(sid);
|
||||
if (hit && Date.now() - hit.ts < CACHE_TTL_MS) return hit.revoked;
|
||||
const [row] = await db
|
||||
.select({ isRevoked: userSessions.isRevoked })
|
||||
.from(userSessions)
|
||||
.where(eq(userSessions.id, sid));
|
||||
// Неизвестная/удалённая сессия считается отозванной
|
||||
const revoked = row ? row.isRevoked : true;
|
||||
cache.set(sid, { revoked, ts: Date.now() });
|
||||
return revoked;
|
||||
}
|
||||
|
||||
/** Сброс кэша после явного отзыва (logout, отзыв устройства, смена пароля) */
|
||||
export function invalidateSessionRevocationCache(sid?: number): void {
|
||||
if (sid === undefined) cache.clear();
|
||||
else cache.delete(sid);
|
||||
}
|
||||
Reference in New Issue
Block a user