feat(server): remove inactive sessions (#9121)

* feat(server): remove inactive sessions

* add rudimentary unit test

---------

Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
This commit is contained in:
Jason Rasmussen
2024-04-27 16:45:16 -04:00
committed by GitHub
parent 953896a35a
commit 034c928d9e
12 changed files with 82 additions and 3 deletions

View File

@@ -72,6 +72,7 @@ describe(JobService.name, () => {
{ name: JobName.CLEAN_OLD_AUDIT_LOGS },
{ name: JobName.USER_SYNC_USAGE },
{ name: JobName.QUEUE_FACIAL_RECOGNITION, data: { force: false } },
{ name: JobName.CLEAN_OLD_SESSION_TOKENS },
]);
});
});

View File

@@ -207,6 +207,7 @@ export class JobService {
{ name: JobName.CLEAN_OLD_AUDIT_LOGS },
{ name: JobName.USER_SYNC_USAGE },
{ name: JobName.QUEUE_FACIAL_RECOGNITION, data: { force: false } },
{ name: JobName.CLEAN_OLD_SESSION_TOKENS },
]);
}

View File

@@ -8,6 +8,7 @@ import { LibraryService } from 'src/services/library.service';
import { MediaService } from 'src/services/media.service';
import { MetadataService } from 'src/services/metadata.service';
import { PersonService } from 'src/services/person.service';
import { SessionService } from 'src/services/session.service';
import { SmartInfoService } from 'src/services/smart-info.service';
import { StorageTemplateService } from 'src/services/storage-template.service';
import { StorageService } from 'src/services/storage.service';
@@ -27,6 +28,7 @@ export class MicroservicesService {
private metadataService: MetadataService,
private personService: PersonService,
private smartInfoService: SmartInfoService,
private sessionService: SessionService,
private storageTemplateService: StorageTemplateService,
private storageService: StorageService,
private userService: UserService,
@@ -42,6 +44,7 @@ export class MicroservicesService {
[JobName.ASSET_DELETION_CHECK]: () => this.assetService.handleAssetDeletionCheck(),
[JobName.DELETE_FILES]: (data: IDeleteFilesJob) => this.storageService.handleDeleteFiles(data),
[JobName.CLEAN_OLD_AUDIT_LOGS]: () => this.auditService.handleCleanup(),
[JobName.CLEAN_OLD_SESSION_TOKENS]: () => this.sessionService.handleCleanup(),
[JobName.USER_DELETE_CHECK]: () => this.userService.handleUserDeleteCheck(),
[JobName.USER_DELETION]: (data) => this.userService.handleUserDelete(data),
[JobName.USER_SYNC_USAGE]: () => this.userService.handleUserSyncUsage(),

View File

@@ -1,3 +1,5 @@
import { UserEntity } from 'src/entities/user.entity';
import { JobStatus } from 'src/interfaces/job.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ISessionRepository } from 'src/interfaces/session.interface';
import { SessionService } from 'src/services/session.service';
@@ -26,6 +28,32 @@ describe('SessionService', () => {
expect(sut).toBeDefined();
});
describe('handleCleanup', () => {
it('should return skipped if nothing is to be deleted', async () => {
sessionMock.search.mockResolvedValue([]);
await expect(sut.handleCleanup()).resolves.toEqual(JobStatus.SKIPPED);
expect(sessionMock.search).toHaveBeenCalled();
});
it('should delete sessions', async () => {
sessionMock.search.mockResolvedValue([
{
createdAt: new Date('1970-01-01T00:00:00.00Z'),
updatedAt: new Date('1970-01-02T00:00:00.00Z'),
deviceOS: '',
deviceType: '',
id: '123',
token: '420',
user: {} as UserEntity,
userId: '42',
},
]);
await expect(sut.handleCleanup()).resolves.toEqual(JobStatus.SUCCESS);
expect(sessionMock.delete).toHaveBeenCalledWith('123');
});
});
describe('getAll', () => {
it('should get the devices', async () => {
sessionMock.getByUserId.mockResolvedValue([sessionStub.valid, sessionStub.inactive]);

View File

@@ -1,8 +1,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { DateTime } from 'luxon';
import { AccessCore, Permission } from 'src/cores/access.core';
import { AuthDto } from 'src/dtos/auth.dto';
import { SessionResponseDto, mapSession } from 'src/dtos/session.dto';
import { IAccessRepository } from 'src/interfaces/access.interface';
import { JobStatus } from 'src/interfaces/job.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ISessionRepository } from 'src/interfaces/session.interface';
@@ -19,6 +21,25 @@ export class SessionService {
this.access = AccessCore.create(accessRepository);
}
async handleCleanup() {
const sessions = await this.sessionRepository.search({
updatedBefore: DateTime.now().minus({ days: 90 }).toJSDate(),
});
if (sessions.length === 0) {
return JobStatus.SKIPPED;
}
for (const session of sessions) {
await this.sessionRepository.delete(session.id);
this.logger.verbose(`Deleted expired session token: ${session.deviceOS}/${session.deviceType}`);
}
this.logger.log(`Deleted ${sessions.length} expired session tokens`);
return JobStatus.SUCCESS;
}
async getAll(auth: AuthDto): Promise<SessionResponseDto[]> {
const sessions = await this.sessionRepository.getByUserId(auth.user.id);
return sessions.map((session) => mapSession(session, auth.session?.id));