feat: secure admin user management
This commit is contained in:
@@ -1,18 +1,25 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LoggingService } from './logging.service';
|
||||
|
||||
describe('LoggingService', () => {
|
||||
let service: LoggingService;
|
||||
it('can persist an event through the caller transaction manager', async () => {
|
||||
const defaultRepository = { save: jest.fn() };
|
||||
const transactionRepository = { save: jest.fn() };
|
||||
const manager = {
|
||||
getRepository: jest.fn(() => transactionRepository),
|
||||
} as any;
|
||||
const service = new LoggingService(defaultRepository as any);
|
||||
const event = {
|
||||
event: 'admin_user_profile_update' as const,
|
||||
details: 'targetUserId=2',
|
||||
userId: 1,
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [LoggingService],
|
||||
}).compile();
|
||||
await service.info(event, manager);
|
||||
|
||||
service = module.get<LoggingService>(LoggingService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
expect(transactionRepository.save).toHaveBeenCalledWith({
|
||||
...event,
|
||||
level: 'INFO',
|
||||
});
|
||||
expect(defaultRepository.save).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { EntityManager, Repository } from 'typeorm';
|
||||
import { CreateLogDTO } from './dto/create-log.dto';
|
||||
import { LogEntry } from './entities/log-entry.entity';
|
||||
import { LOGEVENT } from './model/logging-event.type';
|
||||
@@ -23,22 +23,25 @@ export class LoggingService {
|
||||
});
|
||||
}
|
||||
|
||||
async info({
|
||||
event,
|
||||
details,
|
||||
userId,
|
||||
}: {
|
||||
event: LOGEVENT;
|
||||
details: string;
|
||||
userId: number;
|
||||
}) {
|
||||
async info(
|
||||
{
|
||||
event,
|
||||
details,
|
||||
userId,
|
||||
}: {
|
||||
event: LOGEVENT;
|
||||
details: string;
|
||||
userId: number;
|
||||
},
|
||||
manager?: EntityManager,
|
||||
) {
|
||||
const e: CreateLogDTO = {
|
||||
event,
|
||||
details,
|
||||
userId,
|
||||
level: 'INFO',
|
||||
};
|
||||
await this.repository.save(e);
|
||||
await (manager?.getRepository(LogEntry) ?? this.repository).save(e);
|
||||
}
|
||||
|
||||
async error({
|
||||
|
||||
@@ -15,6 +15,11 @@ export type LOGEVENT =
|
||||
| 'transaction_create_fail'
|
||||
| 'transaction_reverse'
|
||||
| 'player_creation'
|
||||
| 'admin_user_profile_update'
|
||||
| 'admin_user_role_update'
|
||||
| 'admin_user_status_update'
|
||||
| 'admin_player_assign'
|
||||
| 'admin_player_unlink'
|
||||
| 'team_create';
|
||||
|
||||
export type LOGLEVEL = 'FATAL' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE';
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddPlayerLookupIndexes1785517200000 implements MigrationInterface {
|
||||
name = 'AddPlayerLookupIndexes1785517200000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE INDEX "IDX_player_team_id" ON "player" ("teamId")',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'CREATE INDEX "IDX_player_user_id" ON "player" ("userId")',
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX "IDX_player_user_id"');
|
||||
await queryRunner.query('DROP INDEX "IDX_player_team_id"');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { getMetadataArgsStorage } from 'typeorm';
|
||||
import { Player } from '../../players/entities/player.entity';
|
||||
import { AddPlayerLookupIndexes1785517200000 } from './1785517200000-AddPlayerLookupIndexes';
|
||||
|
||||
describe('AddPlayerLookupIndexes1785517200000', () => {
|
||||
it('adds reversible indexes for team and user foreign-key lookups', async () => {
|
||||
const queryRunner = { query: jest.fn() } as any;
|
||||
const migration = new AddPlayerLookupIndexes1785517200000();
|
||||
|
||||
await migration.up(queryRunner);
|
||||
expect(queryRunner.query.mock.calls.map(([sql]) => sql)).toEqual([
|
||||
'CREATE INDEX "IDX_player_team_id" ON "player" ("teamId")',
|
||||
'CREATE INDEX "IDX_player_user_id" ON "player" ("userId")',
|
||||
]);
|
||||
|
||||
queryRunner.query.mockClear();
|
||||
await migration.down(queryRunner);
|
||||
expect(queryRunner.query.mock.calls.map(([sql]) => sql)).toEqual([
|
||||
'DROP INDEX "IDX_player_user_id"',
|
||||
'DROP INDEX "IDX_player_team_id"',
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps entity index metadata aligned with the migration', () => {
|
||||
const playerIndexes = getMetadataArgsStorage()
|
||||
.indices.filter((index) => index.target === Player)
|
||||
.map((index) => index.name);
|
||||
|
||||
expect(playerIndexes).toEqual(
|
||||
expect.arrayContaining(['IDX_player_team_id', 'IDX_player_user_id']),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user