server/services/audit_service/service.js

'use strict';

/* eslint-disable security/detect-object-injection */

/**
 * Module dependencies, required for ALL Twyr' modules
 * @ignore
 */

/**
 * Module dependencies, required for this module
 * @ignore
 */
const TwyrBaseService = require('twyr-base-service').TwyrBaseService;

const TwyrBaseError = require('twyr-base-error').TwyrBaseError;
const TwyrSrvcError = require('twyr-service-error').TwyrServiceError;

/**
 * @class   AuditService
 * @extends {TwyrBaseService}
 * @classdesc The Twyr Web Application Server Auditing Service.
 *
 * @description
 * Allows the rest of the Twyr Modules to publish audit trails.
 *
 */
class AuditService extends TwyrBaseService {
	// #region Constructor
	constructor(parent, loader) {
		super(parent, loader);
	}
	// #endregion

	// #region API
	/**
	 * @async
	 * @function
	 * @instance
	 * @memberof AuditService
	 * @name     publish
	 *
	 * @param    {Object} auditPayload - The payload to be cleaned and published to the message queue.
	 *
	 * @returns  {null} Nothing.
	 *
	 * @summary  Publishes the audit trail to the messaging queue, and then deletes the cached details.
	 */
	async publish(auditPayload) {
		if(!auditPayload) return;
		try {
			this._cleanBeforePublish(auditPayload);
			if(!Object.keys(auditPayload).length) return;

			if(auditPayload.error) {
				this.$dependencies.LoggerService.error(`Error Servicing Request ${auditPayload.id} - ${auditPayload['request-meta']['url']}:`, auditPayload);
				await this.$dependencies.PubsubService.publish('twyr-audit', 'TWYR.AUDIT.ERROR', JSON.stringify(auditPayload));
			}
			else {
				if(twyrEnv === 'development') this.$dependencies.LoggerService.debug(`Serviced Request ${auditPayload.id} - ${auditPayload['request-meta']['url']}:`, auditPayload);
				await this.$dependencies.PubsubService.publish('twyr-audit', 'TWYR.AUDIT.LOG', JSON.stringify(auditPayload));
			}
		}
		catch(err) {
			throw new TwyrSrvcError(`${this.name}::_publishAudit error`, err);
		}
	}
	// #endregion

	// #region Private Methods
	/**
	 * @async
	 * @function
	 * @instance
	 * @memberof AuditService
	 * @name     _cleanBeforePublish
	 *
	 * @param    {Object} auditDetails - The data for the request/response cycle.
	 * @param    {Object} [value] - The audit trail information.
	 *
	 * @returns  {null} Nothing.
	 *
	 * @summary  Deletes any empty keys in the audit trail data.
	 */
	_cleanBeforePublish(auditDetails, value) {
		if(!auditDetails && value) auditDetails = { 'payload': value };
		if(!auditDetails) return null;

		if(!Object.keys(auditDetails).length)
			return null;

		Object.keys(auditDetails).forEach((key) => {
			if(!auditDetails[key]) {
				delete auditDetails[key];
				return;
			}

			if(['password', 'image', 'random'].indexOf(key.toLowerCase()) >= 0) {
				auditDetails[key] = '****';
				return;
			}

			if(key.startsWith('_')) {
				delete auditDetails[key];
				return;
			}

			if(auditDetails[key] instanceof TwyrBaseError) {
				auditDetails[key] = auditDetails[key].toString();
				return;
			}

			if(auditDetails[key] instanceof Error) {
				auditDetails[key] = new TwyrBaseError(`Wrapped for Auditing`, auditDetails[key]).toString();
				return;
			}

			if(typeof auditDetails[key] === 'object') {
				auditDetails[key] = this._cleanBeforePublish(auditDetails[key]);

				if(!auditDetails[key]) {
					delete auditDetails[key];
					return;
				}

				if(!Object.keys(auditDetails[key]).length) {
					delete auditDetails[key];
					return;
				}
			}
		});

		if(!Object.keys(auditDetails).length)
			return null;

		return auditDetails;
	}
	// #endregion

	// #region Properties
	/**
	 * @override
	 */
	get Interface() {
		return {
			'publish': this.publish.bind(this)
		};
	}

	/**
	 * @override
	 */
	get dependencies() {
		return ['ConfigurationService', 'LoggerService', 'PubsubService'].concat(super.dependencies);
	}

	/**
	 * @override
	 */
	get basePath() {
		return __dirname;
	}
	// #endregion
}

exports.service = AuditService;