Sitemap

Audit logs on Strapi v4.6.x

3 min readFeb 21, 2023

At the time I am writing this article, this option is only available in the “GOLD PLAN” version. I couldn’t assess how it was implemented, but certainly in a more elegant way which I’m going to present now.

Premise:

Be able to audit changes to some system tables, identifying the user who made the change, and what was changed.

Solution:

Store auditing logs inside a new table, using middleware and Strapi lifecycle hooks.

Steps:

  1. Create new collection type "AuditLog"
  2. Create new collection type "Article" (this is only to use on this tutorial)
  3. Create a helper with the array of UIDs to audit
  4. Create the main lifecycle file
  5. Create the lifecycle file inside the API
  6. Create the middleware
  7. Configure Strapi with the new middleware

Create new collection type “AuditLog”

Fields:

  • uid — Long Text
  • params — JSON
  • result — JSON
  • user — JSON
  • actionType — Long Text
  • changedId — Short Text
Press enter or click to view image in full size

Create new collection type “Article”

Fields:

  • name — Short Text

Create a helper with the array of UIDs to audit

This is a helper with a simple array of UIDs to avoid Strapi audit every request to the backend API.

./src/misc/uidAutitList.js
module.exports = [
'api::article.article',
]

Create the main lifecycle file

Every collection needs their own "lifecycle.js" file, and here I'm centralizing the logic of all files that we need manually input inside "./src/api/<collectionName>/content-types/<collectionName>/lifecycles.js" path

./src/misc/lifecycle.js

const arrUidToAudit = require('./uidAuditList')

module.exports = {
afterCreate(event) {
const { result, params, model } = event;
if (arrUidToAudit.includes(model.uid)) {
strapi.entityService.create('api::audit-log.audit-log', {
data: {
uid: model.uid,
params: params,
result: result,
actionType: 'ADD'
}
})
}
},
afterUpdate(event) {
const { result, params, model } = event;
if (arrUidToAudit.includes(model.uid)) {
strapi.entityService.create('api::audit-log.audit-log', {
data: {
uid: model.uid,
params: params,
result: result,
actionType: 'UPDATE'
}
})
}
},
beforeDelete(event, ctx) {
const { result, params, model } = event;
params.populate = { createdBy: true, updatedBy: true }
},
async afterDelete(event) {
const { result, model } = event;
if (arrUidToAudit.includes(model.uid)) {
await strapi.db.query('api::audit-log.audit-log').update({
where: { uid: model.uid, changedId: result.id},
data: {
result: result
}
});
}
}
}

The "afterDelete" lifecycle will update the audit item created on the middleware (see next steps) with more informations about the deleted record.

Create the lifecycle file inside the API

We need to place this file inside each collection that we want to enable lifecycle auditing.

./src/api/article/content-types/article/lifecycles.js
const lifecycles = require("../../../../misc/lifecycles");

module.exports = lifecycles

Create the middleware

This step was necessary as the “beforeDelete” and “afterDelete” lifecycle methods do not contain any information about who made the request to delete the item.

So I intercepted all “DELETE” requests to get the JWT token user information, as you can see in the script below:

./src/middlewares/audit.js
// path: /middlewares/audit.js

const jwt_decode = require("jwt-decode");
const arrUidToAudit = require('../misc/uidAuditList')

module.exports = () => {
return async (ctx, next) => {
const { request } = ctx

if (request.method === 'DELETE') {
const urlArr = request.url.split('/')
const uid = urlArr[3]
const id = urlArr[4]

//
// filter uid's to log
if (arrUidToAudit.includes(uid)) {
const tokenSemBearer = ctx?.request?.header?.authorization?.replace("Bearer ", "");
let decoded = jwt_decode(tokenSemBearer);

const userId = decoded.id;

const user = await strapi.db.query('plugin::users-permissions.user').findOne({
select: '*',
where: { id: userId },
});

await strapi.entityService.create('api::audit-log.audit-log', {
data: {
uid: uid,
changedId: id,
params: {id: id},
user: user,
actionType: 'DELETE'
}
})
}
}

await next();
};
};

Configure Strapi with the new middleware

Default configuration for application middewares.

./config/middlewares.js
module.exports = [
'strapi::errors',
'strapi::security',
'strapi::cors',
'strapi::poweredBy',
'strapi::logger',
'strapi::query',
'strapi::body',
'strapi::session',
'strapi::favicon',
'strapi::public',
'global::audit', // here's the new middleware
];

Next steps

  • check multi add/delete lifecycle
  • wrap it on a plugin

--

--

Responses (3)