Skip to content
This repository has been archived by the owner on Apr 19, 2023. It is now read-only.

Commit

Permalink
✨ Add audit logs to users/groups
Browse files Browse the repository at this point in the history
  • Loading branch information
AnandChowdhary committed Jan 9, 2021
1 parent 0c0ade5 commit ec40a98
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 55 deletions.
17 changes: 11 additions & 6 deletions src/interceptors/audit-log.interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
BadGatewayException,
CallHandler,
ExecutionContext,
Injectable,
Expand All @@ -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';
Expand Down Expand Up @@ -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,
Expand All @@ -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);
}
}
})()
Expand Down
34 changes: 34 additions & 0 deletions src/modules/audit-logs/audit-logs-group.controller.ts
Original file line number Diff line number Diff line change
@@ -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<string, number | string>,
@Query('where', WherePipe) where?: Record<string, number | string>,
@Query('orderBy', OrderByPipe) orderBy?: Record<string, 'asc' | 'desc'>,
): Promise<Expose<AuditLog>[]> {
return this.auditLogsService.getAuditLogs({
skip,
take,
orderBy,
cursor,
where: { ...where, group: { id: groupId } },
});
}
}
34 changes: 34 additions & 0 deletions src/modules/audit-logs/audit-logs-user.controller.ts
Original file line number Diff line number Diff line change
@@ -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<string, number | string>,
@Query('where', WherePipe) where?: Record<string, number | string>,
@Query('orderBy', OrderByPipe) orderBy?: Record<string, 'asc' | 'desc'>,
): Promise<Expose<AuditLog>[]> {
return this.auditLogsService.getAuditLogs({
skip,
take,
orderBy,
cursor,
where: { ...where, user: { id: userId } },
});
}
}
19 changes: 5 additions & 14 deletions src/modules/audit-logs/audit-logs.controller.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -8,35 +8,26 @@ 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<string, number | string>,
@Query('where', WherePipe) where?: Record<string, number | string>,
@Query('orderBy', OrderByPipe) orderBy?: Record<string, 'asc' | 'desc'>,
): Promise<Expose<AuditLog>[]> {
return this.auditLogsService.getAuditLogs(groupId, {
return this.auditLogsService.getAuditLogs({
skip,
take,
orderBy,
cursor,
where,
});
}

@Get(':id')
@Scopes('group-{groupId}:read-audit-log-{id}')
async get(
@Param('groupId', ParseIntPipe) groupId: number,
@Param('id', ParseIntPipe) id: number,
): Promise<Expose<AuditLog>> {
return this.auditLogsService.getAuditLog(groupId, Number(id));
}
}
8 changes: 7 additions & 1 deletion src/modules/audit-logs/audit-logs.module.ts
Original file line number Diff line number Diff line change
@@ -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 {}
55 changes: 21 additions & 34 deletions src/modules/audit-logs/audit-logs.service.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,33 @@
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';

@Injectable()
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<Expose<AuditLog>[]> {
async getAuditLogs(params: {
skip?: number;
take?: number;
cursor?: Prisma.AuditLogWhereUniqueInput;
where?: Prisma.AuditLogWhereInput;
orderBy?: Prisma.AuditLogOrderByInput;
}): Promise<Expose<AuditLog>[]> {
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<AuditLog>(group));
}

async getAuditLog(groupId: number, id: number): Promise<Expose<AuditLog>> {
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>(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<AuditLog>(group));
} catch (error) {
return [];
}
}
}

0 comments on commit ec40a98

Please sign in to comment.