diff --git a/src/interceptors/audit-log.interceptor.ts b/src/interceptors/audit-log.interceptor.ts index 2ded26ec1..56ecd9eec 100644 --- a/src/interceptors/audit-log.interceptor.ts +++ b/src/interceptors/audit-log.interceptor.ts @@ -1,5 +1,4 @@ import { - BadGatewayException, CallHandler, ExecutionContext, Injectable, @@ -11,8 +10,7 @@ import type { Prisma } from '@prisma/client'; import { getClientIp } from 'request-ip'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; -import { UAParser } from 'ua-parser-js'; -import { GROUP_NOT_FOUND } from '../errors/errors.constants'; +import UAParser from 'ua-parser-js'; import { STAART_AUDIT_LOG_DATA } from '../modules/audit-logs/audit-log.constants'; import { UserRequest } from '../modules/auth/auth.interface'; import { WebhooksService } from '../modules/webhooks/webhooks.service'; @@ -42,14 +40,19 @@ export class AuditLogger implements NestInterceptor { if (typeof auditLog === 'string') auditLog = [auditLog]; const request = context.switchToHttp().getRequest() as UserRequest; const groupId = parseInt(request.params.groupId); - if (isNaN(groupId)) throw new BadGatewayException(GROUP_NOT_FOUND); const ip = getClientIp(request); const location = await this.geolocationService.getLocation(ip); const userAgent = request.get('user-agent'); const ua = new UAParser(userAgent); - for await (const event of auditLog) { + for await (const rawEvent of auditLog) { + let event = rawEvent; + if (request.user.id && request.user.type === 'user') + event = event.replace('{userId}', request.user.id.toString()); + if (groupId) + event = event.replace('{groupId}', groupId.toString()); const data: Prisma.AuditLogCreateInput = { event, + rawEvent, city: location?.city?.names?.en, region: location?.subdivisions?.pop()?.names?.en, timezone: location?.location?.time_zone, @@ -66,9 +69,11 @@ export class AuditLogger implements NestInterceptor { }; if (request.user.id && request.user.type === 'user') data.user = { connect: { id: request.user.id } }; + if (request.user.id && request.user.type === 'api-key') + data.apiKey = { connect: { id: request.user.id } }; if (groupId) data.group = { connect: { id: groupId } }; await this.prisma.auditLog.create({ data }); - this.webhooksService.triggerWebhook(groupId, event); + if (groupId) this.webhooksService.triggerWebhook(groupId, event); } } })() diff --git a/src/modules/audit-logs/audit-logs-group.controller.ts b/src/modules/audit-logs/audit-logs-group.controller.ts new file mode 100644 index 000000000..dc87c9f2c --- /dev/null +++ b/src/modules/audit-logs/audit-logs-group.controller.ts @@ -0,0 +1,34 @@ +import { Controller, Get, Param, ParseIntPipe, Query } from '@nestjs/common'; +import { AuditLog } from '@prisma/client'; +import { CursorPipe } from '../../pipes/cursor.pipe'; +import { OptionalIntPipe } from '../../pipes/optional-int.pipe'; +import { OrderByPipe } from '../../pipes/order-by.pipe'; +import { WherePipe } from '../../pipes/where.pipe'; +import { Expose } from '../../providers/prisma/prisma.interface'; +import { Scopes } from '../auth/scope.decorator'; +import { AuditLogsService } from './audit-logs.service'; + +@Controller('groups/:groupId/audit-logs') +export class AuditLogGroupController { + constructor(private auditLogsService: AuditLogsService) {} + + /** Get audit logs for a group */ + @Get() + @Scopes('group-{groupId}:read-audit-log-*') + async getAll( + @Param('groupId', ParseIntPipe) groupId: number, + @Query('skip', OptionalIntPipe) skip?: number, + @Query('take', OptionalIntPipe) take?: number, + @Query('cursor', CursorPipe) cursor?: Record, + @Query('where', WherePipe) where?: Record, + @Query('orderBy', OrderByPipe) orderBy?: Record, + ): Promise[]> { + return this.auditLogsService.getAuditLogs({ + skip, + take, + orderBy, + cursor, + where: { ...where, group: { id: groupId } }, + }); + } +} diff --git a/src/modules/audit-logs/audit-logs-user.controller.ts b/src/modules/audit-logs/audit-logs-user.controller.ts new file mode 100644 index 000000000..b948b7ee8 --- /dev/null +++ b/src/modules/audit-logs/audit-logs-user.controller.ts @@ -0,0 +1,34 @@ +import { Controller, Get, Param, ParseIntPipe, Query } from '@nestjs/common'; +import { AuditLog } from '@prisma/client'; +import { CursorPipe } from '../../pipes/cursor.pipe'; +import { OptionalIntPipe } from '../../pipes/optional-int.pipe'; +import { OrderByPipe } from '../../pipes/order-by.pipe'; +import { WherePipe } from '../../pipes/where.pipe'; +import { Expose } from '../../providers/prisma/prisma.interface'; +import { Scopes } from '../auth/scope.decorator'; +import { AuditLogsService } from './audit-logs.service'; + +@Controller('users/:userId/audit-logs') +export class AuditLogUserController { + constructor(private auditLogsService: AuditLogsService) {} + + /** Get audit logs for a user */ + @Get() + @Scopes('user-{userId}:read-audit-log-*') + async getAll( + @Param('userId', ParseIntPipe) userId: number, + @Query('skip', OptionalIntPipe) skip?: number, + @Query('take', OptionalIntPipe) take?: number, + @Query('cursor', CursorPipe) cursor?: Record, + @Query('where', WherePipe) where?: Record, + @Query('orderBy', OrderByPipe) orderBy?: Record, + ): Promise[]> { + return this.auditLogsService.getAuditLogs({ + skip, + take, + orderBy, + cursor, + where: { ...where, user: { id: userId } }, + }); + } +} diff --git a/src/modules/audit-logs/audit-logs.controller.ts b/src/modules/audit-logs/audit-logs.controller.ts index 39498e20b..2b0ecc1ff 100644 --- a/src/modules/audit-logs/audit-logs.controller.ts +++ b/src/modules/audit-logs/audit-logs.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Param, ParseIntPipe, Query } from '@nestjs/common'; +import { Controller, Get, Query } from '@nestjs/common'; import { AuditLog } from '@prisma/client'; import { CursorPipe } from '../../pipes/cursor.pipe'; import { OptionalIntPipe } from '../../pipes/optional-int.pipe'; @@ -8,21 +8,21 @@ import { Expose } from '../../providers/prisma/prisma.interface'; import { Scopes } from '../auth/scope.decorator'; import { AuditLogsService } from './audit-logs.service'; -@Controller('groups/:groupId/audit-logs') +@Controller('audit-logs') export class AuditLogController { constructor(private auditLogsService: AuditLogsService) {} + /** Get audit logs for a group */ @Get() - @Scopes('group-{groupId}:read-audit-log-*') + @Scopes('audit-log-*:read-info') async getAll( - @Param('groupId', ParseIntPipe) groupId: number, @Query('skip', OptionalIntPipe) skip?: number, @Query('take', OptionalIntPipe) take?: number, @Query('cursor', CursorPipe) cursor?: Record, @Query('where', WherePipe) where?: Record, @Query('orderBy', OrderByPipe) orderBy?: Record, ): Promise[]> { - return this.auditLogsService.getAuditLogs(groupId, { + return this.auditLogsService.getAuditLogs({ skip, take, orderBy, @@ -30,13 +30,4 @@ export class AuditLogController { where, }); } - - @Get(':id') - @Scopes('group-{groupId}:read-audit-log-{id}') - async get( - @Param('groupId', ParseIntPipe) groupId: number, - @Param('id', ParseIntPipe) id: number, - ): Promise> { - return this.auditLogsService.getAuditLog(groupId, Number(id)); - } } diff --git a/src/modules/audit-logs/audit-logs.module.ts b/src/modules/audit-logs/audit-logs.module.ts index 9b42588a9..969bdd25b 100644 --- a/src/modules/audit-logs/audit-logs.module.ts +++ b/src/modules/audit-logs/audit-logs.module.ts @@ -1,11 +1,17 @@ import { Module } from '@nestjs/common'; import { PrismaModule } from '../../providers/prisma/prisma.module'; import { AuditLogController } from './audit-logs.controller'; +import { AuditLogGroupController } from './audit-logs-group.controller'; +import { AuditLogUserController } from './audit-logs-user.controller'; import { AuditLogsService } from './audit-logs.service'; @Module({ imports: [PrismaModule], - controllers: [AuditLogController], + controllers: [ + AuditLogController, + AuditLogGroupController, + AuditLogUserController, + ], providers: [AuditLogsService], }) export class AuditLogsModule {} diff --git a/src/modules/audit-logs/audit-logs.service.ts b/src/modules/audit-logs/audit-logs.service.ts index e1a52cb3c..8f2b53400 100644 --- a/src/modules/audit-logs/audit-logs.service.ts +++ b/src/modules/audit-logs/audit-logs.service.ts @@ -1,11 +1,6 @@ -import { - Injectable, - NotFoundException, - UnauthorizedException, -} from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import type { Prisma } from '@prisma/client'; import { AuditLog } from '@prisma/client'; -import { UNAUTHORIZED_RESOURCE } from '../../errors/errors.constants'; import { Expose } from '../../providers/prisma/prisma.interface'; import { PrismaService } from '../../providers/prisma/prisma.service'; @@ -13,34 +8,26 @@ import { PrismaService } from '../../providers/prisma/prisma.service'; export class AuditLogsService { constructor(private prisma: PrismaService) {} - async getAuditLogs( - groupId: number, - params: { - skip?: number; - take?: number; - cursor?: Prisma.AuditLogWhereUniqueInput; - where?: Prisma.AuditLogWhereInput; - orderBy?: Prisma.AuditLogOrderByInput; - }, - ): Promise[]> { + async getAuditLogs(params: { + skip?: number; + take?: number; + cursor?: Prisma.AuditLogWhereUniqueInput; + where?: Prisma.AuditLogWhereInput; + orderBy?: Prisma.AuditLogOrderByInput; + }): Promise[]> { const { skip, take, cursor, where, orderBy } = params; - const AuditLog = await this.prisma.auditLog.findMany({ - skip, - take, - cursor, - where: { ...where, group: { id: groupId } }, - orderBy, - }); - return AuditLog.map((group) => this.prisma.expose(group)); - } - - async getAuditLog(groupId: number, id: number): Promise> { - const AuditLog = await this.prisma.auditLog.findUnique({ - where: { id }, - }); - if (!AuditLog) throw new NotFoundException(UNAUTHORIZED_RESOURCE); - if (AuditLog.groupId !== groupId) - throw new UnauthorizedException(UNAUTHORIZED_RESOURCE); - return this.prisma.expose(AuditLog); + try { + const AuditLog = await this.prisma.auditLog.findMany({ + skip, + take, + cursor, + where, + orderBy, + include: { group: true, user: true }, + }); + return AuditLog.map((group) => this.prisma.expose(group)); + } catch (error) { + return []; + } } }