icynet-auth-server/src/modules/objects/audit/audit.service.ts

222 lines
5.2 KiB
TypeScript

import { Inject, Injectable } from '@nestjs/common';
import { Request } from 'express';
import {
FindManyOptions,
FindOptionsWhere,
ILike,
In,
Repository,
} from 'typeorm';
import { User } from '../user/user.entity';
import { AuditLog } from './audit.entity';
import { AuditAction } from './audit.enum';
import { Lookup, lookup } from 'geoip-lite';
import { Details, parse } from 'express-useragent';
import { FormUtilityService } from 'src/modules/utility/services/form-utility.service';
import { AuditSearchClause, UserLoginEntry } from './audit.interfaces';
const PLUCK_LOCATION = ['country', 'city', 'timezone', 'll'];
const PLUCK_USER_AGENT = ['browser', 'version', 'os', 'platform'];
const AUTOFLAG = [
AuditAction.MALICIOUS_REQUEST,
AuditAction.THROTTLE,
AuditAction.DEACTIVATION_REQUEST,
AuditAction.DATA_DOWNLOAD_REQUEST,
];
@Injectable()
export class AuditService {
constructor(
@Inject('AUDIT_REPOSITORY')
private readonly audit: Repository<AuditLog>,
private readonly form: FormUtilityService,
) {}
public async insertAudit(
action: AuditAction,
comment?: string,
user?: User,
ip?: string,
ua?: string,
) {
const audit = new AuditLog();
audit.action = action as string;
audit.content = comment;
audit.actor_ip = ip;
audit.actor_ua = ua;
audit.actor = user;
if (AUTOFLAG.includes(action)) {
audit.flagged = true;
// TODO: email administrator
}
await this.updateAudit(audit);
return audit;
}
public async auditRequest(
req: Request,
type: AuditAction,
comment?: string,
user?: User,
) {
return this.insertAudit(
type,
comment,
user || req.user || null,
req.ip,
req.header('user-agent'),
);
}
public getIPLocation(ip: string) {
return lookup(ip);
}
public getUserAgentInfo(ua: string) {
return parse(ua);
}
public async searchForAuditCount(search: AuditSearchClause) {
return this.audit.count(this.buildAuditSearch(search));
}
public async searchForAudit(
limit = 50,
offset = 0,
search: AuditSearchClause,
): Promise<
[
(AuditLog & {
location?: Partial<Lookup>;
user_agent?: Partial<Details>;
})[],
number,
]
> {
const [list, num] = await this.audit.findAndCount({
...this.buildAuditSearch(search),
take: limit,
skip: offset,
order: { created_at: 'DESC' },
relations: ['actor'],
});
return [
list.map((entry) => ({
...entry,
location: entry.actor_ip
? this.form.pluckObject(
this.getIPLocation(entry.actor_ip),
PLUCK_LOCATION,
)
: null,
user_agent: entry.actor_ua
? this.form.pluckObject(
this.getUserAgentInfo(entry.actor_ua),
PLUCK_USER_AGENT,
)
: null,
actor: this.form.stripObject(entry.actor, ['password']),
})),
num,
];
}
public async getUserLogins(
user: User,
sessid?: string,
): Promise<UserLoginEntry[]> {
const userLogins: UserLoginEntry[] = [];
const auditEntries = await this.audit.find({
where: { actor: { id: user.id }, action: AuditAction.LOGIN },
order: { created_at: 'DESC' },
take: 10,
});
auditEntries.forEach((entry) => {
userLogins.push({
login_at: entry.created_at,
current: sessid === entry.content,
location: entry.actor_ip
? this.form.pluckObject(
this.getIPLocation(entry.actor_ip),
PLUCK_LOCATION,
)
: null,
user_agent: entry.actor_ua
? this.form.pluckObject(
this.getUserAgentInfo(entry.actor_ua),
PLUCK_USER_AGENT,
)
: null,
});
});
return userLogins;
}
public async getUserAccountCreation(user: User) {
const auditEntry = await this.audit.findOne({
where: { actor: { id: user.id }, action: AuditAction.REGISTRATION },
});
if (!auditEntry) {
return null;
}
return {
created_at: auditEntry.created_at,
ip: auditEntry.actor_ip,
location: auditEntry.actor_ip
? this.form.pluckObject(
this.getIPLocation(auditEntry.actor_ip),
PLUCK_LOCATION,
)
: null,
user_agent: auditEntry.actor_ua
? this.form.pluckObject(
this.getUserAgentInfo(auditEntry.actor_ua),
PLUCK_USER_AGENT,
)
: null,
};
}
public async updateAudit(audit: AuditLog): Promise<void> {
await this.audit.save(audit);
}
private buildAuditSearch(
search: AuditSearchClause,
): FindManyOptions<AuditLog> {
const obj: FindOptionsWhere<AuditLog> = {};
if (search.actions) {
obj.action = In((search.actions as string).split(','));
}
if (search.content) {
obj.content = ILike(`%${search.content}%`);
}
if (search.ip) {
obj.actor_ip = ILike(`%${search.ip}%`);
}
if (search.ua) {
obj.actor_ua = ILike(`%${search.ua}%`);
}
if (search.user) {
obj.actor = { uuid: search.user };
}
if (search.flagged) {
obj.flagged = search.flagged;
}
return { where: obj };
}
}