mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
feat: delete stale user sessions (#8738)
This commit is contained in:
parent
d2daae5857
commit
c6519cd95d
@ -2,6 +2,7 @@ import type { IUnleashStores } from '../types/stores';
|
|||||||
import type { IUnleashConfig } from '../types/option';
|
import type { IUnleashConfig } from '../types/option';
|
||||||
import type { Logger } from '../logger';
|
import type { Logger } from '../logger';
|
||||||
import type { ISession, ISessionStore } from '../types/stores/session-store';
|
import type { ISession, ISessionStore } from '../types/stores/session-store';
|
||||||
|
import { compareDesc } from 'date-fns';
|
||||||
|
|
||||||
export default class SessionService {
|
export default class SessionService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
@ -32,6 +33,26 @@ export default class SessionService {
|
|||||||
return this.sessionStore.deleteSessionsForUser(userId);
|
return this.sessionStore.deleteSessionsForUser(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteStaleSessionsForUser(
|
||||||
|
userId: number,
|
||||||
|
maxSessions: number,
|
||||||
|
): Promise<void> {
|
||||||
|
let userSessions: ISession[] = [];
|
||||||
|
try {
|
||||||
|
// this method may throw errors when no session
|
||||||
|
userSessions = await this.sessionStore.getSessionsForUser(userId);
|
||||||
|
} catch (e) {}
|
||||||
|
const newestFirst = userSessions.sort((a, b) =>
|
||||||
|
compareDesc(a.createdAt, b.createdAt),
|
||||||
|
);
|
||||||
|
const sessionsToDelete = newestFirst.slice(maxSessions);
|
||||||
|
await Promise.all(
|
||||||
|
sessionsToDelete.map((session) =>
|
||||||
|
this.sessionStore.delete(session.sid),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async deleteSession(sid: string): Promise<void> {
|
async deleteSession(sid: string): Promise<void> {
|
||||||
return this.sessionStore.delete(sid);
|
return this.sessionStore.delete(sid);
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ import type { TokenUserSchema } from '../openapi/spec/token-user-schema';
|
|||||||
import PasswordMismatch from '../error/password-mismatch';
|
import PasswordMismatch from '../error/password-mismatch';
|
||||||
import type EventService from '../features/events/event-service';
|
import type EventService from '../features/events/event-service';
|
||||||
|
|
||||||
import { SYSTEM_USER, SYSTEM_USER_AUDIT } from '../types';
|
import { type IFlagResolver, SYSTEM_USER, SYSTEM_USER_AUDIT } from '../types';
|
||||||
import { PasswordPreviouslyUsedError } from '../error/password-previously-used';
|
import { PasswordPreviouslyUsedError } from '../error/password-previously-used';
|
||||||
import { RateLimitError } from '../error/rate-limit-error';
|
import { RateLimitError } from '../error/rate-limit-error';
|
||||||
import type EventEmitter from 'events';
|
import type EventEmitter from 'events';
|
||||||
@ -90,6 +90,8 @@ class UserService {
|
|||||||
|
|
||||||
private settingService: SettingService;
|
private settingService: SettingService;
|
||||||
|
|
||||||
|
private flagResolver: IFlagResolver;
|
||||||
|
|
||||||
private passwordResetTimeouts: { [key: string]: NodeJS.Timeout } = {};
|
private passwordResetTimeouts: { [key: string]: NodeJS.Timeout } = {};
|
||||||
|
|
||||||
private baseUriPath: string;
|
private baseUriPath: string;
|
||||||
@ -103,9 +105,14 @@ class UserService {
|
|||||||
getLogger,
|
getLogger,
|
||||||
authentication,
|
authentication,
|
||||||
eventBus,
|
eventBus,
|
||||||
|
flagResolver,
|
||||||
}: Pick<
|
}: Pick<
|
||||||
IUnleashConfig,
|
IUnleashConfig,
|
||||||
'getLogger' | 'authentication' | 'server' | 'eventBus'
|
| 'getLogger'
|
||||||
|
| 'authentication'
|
||||||
|
| 'server'
|
||||||
|
| 'eventBus'
|
||||||
|
| 'flagResolver'
|
||||||
>,
|
>,
|
||||||
services: {
|
services: {
|
||||||
accessService: AccessService;
|
accessService: AccessService;
|
||||||
@ -125,6 +132,7 @@ class UserService {
|
|||||||
this.emailService = services.emailService;
|
this.emailService = services.emailService;
|
||||||
this.sessionService = services.sessionService;
|
this.sessionService = services.sessionService;
|
||||||
this.settingService = services.settingService;
|
this.settingService = services.settingService;
|
||||||
|
this.flagResolver = flagResolver;
|
||||||
|
|
||||||
process.nextTick(() => this.initAdminUser(authentication));
|
process.nextTick(() => this.initAdminUser(authentication));
|
||||||
|
|
||||||
@ -400,6 +408,19 @@ class UserService {
|
|||||||
const match = await bcrypt.compare(password, passwordHash);
|
const match = await bcrypt.compare(password, passwordHash);
|
||||||
if (match) {
|
if (match) {
|
||||||
const loginOrder = await this.store.successfullyLogin(user);
|
const loginOrder = await this.store.successfullyLogin(user);
|
||||||
|
const deleteStaleUserSessions = this.flagResolver.getVariant(
|
||||||
|
'deleteStaleUserSessions',
|
||||||
|
);
|
||||||
|
if (deleteStaleUserSessions.feature_enabled) {
|
||||||
|
const allowedSessions = Number(
|
||||||
|
deleteStaleUserSessions.payload?.value || 30,
|
||||||
|
);
|
||||||
|
// subtract current user session that will be created
|
||||||
|
await this.sessionService.deleteStaleSessionsForUser(
|
||||||
|
user.id,
|
||||||
|
Math.max(allowedSessions - 1, 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
this.eventBus.emit(USER_LOGIN, { loginOrder });
|
this.eventBus.emit(USER_LOGIN, { loginOrder });
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,8 @@ export type IFlagKey =
|
|||||||
| 'productivityReportEmail'
|
| 'productivityReportEmail'
|
||||||
| 'enterprise-payg'
|
| 'enterprise-payg'
|
||||||
| 'simplifyProjectOverview'
|
| 'simplifyProjectOverview'
|
||||||
| 'flagOverviewRedesign';
|
| 'flagOverviewRedesign'
|
||||||
|
| 'deleteStaleUserSessions';
|
||||||
|
|
||||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||||
|
|
||||||
|
@ -112,3 +112,18 @@ test('Can delete session by sid', async () => {
|
|||||||
sessionService.getSession('abc123'),
|
sessionService.getSession('abc123'),
|
||||||
).rejects.toThrow(NotFoundError);
|
).rejects.toThrow(NotFoundError);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Can delete stale sessions', async () => {
|
||||||
|
await sessionService.insertSession(newSession);
|
||||||
|
await sessionService.insertSession({ ...newSession, sid: 'new' });
|
||||||
|
|
||||||
|
const sessionsToKeep = 1;
|
||||||
|
await sessionService.deleteStaleSessionsForUser(
|
||||||
|
newSession.sess.user.id,
|
||||||
|
sessionsToKeep,
|
||||||
|
);
|
||||||
|
|
||||||
|
const sessions = await sessionService.getSessionsForUser(1);
|
||||||
|
expect(sessions.length).toBe(1);
|
||||||
|
expect(sessions[0].sid).toBe('new');
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user