Skip to content

Instantly share code, notes, and snippets.

@neonexus
Last active December 23, 2022 03:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neonexus/641f01bcaafa45fb55277f52c65d05d7 to your computer and use it in GitHub Desktop.
Save neonexus/641f01bcaafa45fb55277f52c65d05d7 to your computer and use it in GitHub Desktop.
Sails config files to capture request logs. Replace "_" in filenames with "/". See https://github.com/neonexus/sails-react-bootstrap-webpack for a working example.
const stringify = require('json-stringify-safe');
module.exports = {
friendlyName: 'Finalize request log',
description: 'Used by custom responses to log final data points about requests.',
inputs: {
req: {
type: 'ref',
description: 'The current incoming request (req).',
required: true
},
res: {
type: 'ref',
description: 'The current outgoing response (res).',
required: true
},
body: {
type: 'ref',
description: 'The body of the response.',
required: true
}
},
exits: {
success: {}
},
fn: async function(inputs, exits){
if (inputs.req.requestId) {
let out = _.merge({}, inputs.body);
let headers = _.merge({}, inputs.res._headers); // copy the object
const bleep = '*******';
if (!sails.config.logSensitiveData) {
if (out._csrf) {
out._csrf = bleep;
}
if (out.token) {
out.token = bleep;
}
if (out.access_token) {
out.access_token = bleep;
}
if (out.refresh_token) {
out.refresh_token = bleep;
}
}
if (_.isObject(out)) {
out = stringify(out);
}
const diff = process.hrtime(inputs.req._customStartTime);
const time = diff[0] * 1e3 + diff[1] * 1e-6;
const totalTime = time.toFixed(4) + 'ms';
let log = {
responseCode: inputs.res.statusCode,
responseBody: out,
responseHeaders: stringify(headers),
responseTime: totalTime
};
// add links to users / clients / accounts to the log here
/*
if (inputs.req.account) {
log.account = inputs.req.account;
}
*/
sails.models.requestlog.update(inputs.req.requestId, log, (err) => {
if (err) {
console.log(err);
}
});
}
// All done.
return exits.success();
}
};
module.exports = {
friendlyName: 'Keep Model Safe',
description: 'Enforce custom .toJSON() is called recursively.',
sync: true, // function is not async
inputs: {
data: {
type: 'ref',
required: true
}
},
exits: {
success: {}
},
fn: (inputs, exits) => {
const dataCopy = _.merge({}, inputs.data);
// Force all objects to use their .toJSON() functions, if they have them.
// This prevents accidental leaking of sensitive data, by utilizing customToJSON on model defitions.
(function findTheJson(data) {
_.forEach(data, (val, key) => {
if (_.isObject(val)) {
return findTheJson(val);
}
// detect if there is a .toJSON(), and uses it
if (val && val.toJSON && typeof val.toJSON === 'function') {
// detect if it's a moment.js object, and uses .format() instead of .toJSON()
if (val.toDate && typeof val.toDate === 'function' && typeof val.format === 'function') {
data[key] = val.format();
} else {
data[key] = val.toJSON();
}
}
});
})(dataCopy);
return exits.success(dataCopy);
}
};
const stringify = require('json-stringify-safe');
module.exports = (sails) => {
return {
routes: {
before: {
'*': (req, res, next) => {
if (req.method !== 'HEAD' && req.path !== '/__getcookie' && req.path !== '/') {
const bleep = '*******';
let body = _.merge({}, req.body),
query = _.merge({}, req.query),
headers = _.merge({}, req.headers); // copy the object
if (!sails.config.logSensitiveData) {
// don't log plain-text passwords
if (body.password) {
body.password = bleep;
}
if (body.password2) {
body.password2 = bleep;
}
if (body.currentPassword) {
body.currentPassword = bleep;
}
if (body.newPassword) {
body.newPassword = bleep;
}
if (body.newPassword2) {
body.newPassword2 = bleep;
}
if (body.pass) {
body.pass = bleep;
}
if (query.securityToken) {
query.securityToken = bleep;
}
if (headers.securityToken) {
headers.securityToken = bleep;
}
}
if (_.isObject(body)) {
body = stringify(body);
}
sails.models.requestlog.create({
direction: 'inbound',
method: req.method,
host: req.hostname || req.host || 'unknown',
path: req.path,
headers: headers,
getParams: query,
body: body
}).meta({fetch: true}).exec(async (err, newRequest) => {
if (err) {
console.log(err);
return next(); // don't stop the traffic if there is a problem
}
req.requestId = newRequest.id;
req._customStartTime = process.hrtime();
return next();
});
} else {
return next();
}
}
}
}
};
};
module.exports = {
primaryKey: 'id',
attributes: {
id: {
type: 'number',
autoIncrement: true
},
direction: {
type: 'string',
isIn: [
'inbound',
'outbound'
],
required: true,
columnType: 'varchar(8)'
},
method: {
type: 'string',
isIn: [
'GET',
'POST',
'PUT',
'DELETE',
'PATCH'
],
required: true,
columnType: 'varchar(6)'
},
host: {
type: 'string',
required: true,
columnType: 'varchar(191)'
},
path: {
type: 'string',
required: true,
columnType: 'varchar(191)'
},
headers: {
type: 'json'
},
getParams: {
type: 'json'
},
body: {
type: 'string',
columnType: 'longtext',
allowNull: true
},
responseCode: {
type: 'number',
allowNull: true,
columnType: 'int(4) unsigned'
},
responseBody: {
type: 'string',
columnType: 'longtext',
allowNull: true
},
responseHeaders: {
type: 'string',
columnType: 'longtext',
allowNull: true
},
responseTime: {
type: 'string',
allowNull: true
},
createdAt: {
type: 'ref',
columnType: 'datetime',
autoCreatedAt: true
},
updatedAt: false
},
customToJSON: () => {
// Below is an example of how to prevent accidental password hash / verification key leaking.
// This only removes it from the final object sent to an API request.
return _.omit(this, [
'password',
'verificationKey'
]);
}
};
module.exports = async function badRequest(msg){
const res = this.res;
const req = this.req;
if (!msg) {
msg = 'Could not understand request';
}
const out = {
success: false,
errors: msg
};
res.status(400).json(out);
await sails.helpers.finalizeRequestLog(req, res, out);
};
module.exports = async function created(data){
const res = this.res;
const req = this.req;
if (!data) {
data = {};
}
if (typeof data === 'string') {
data = {message: data};
}
data = sails.helpers.keepModelsSafe(data);
const out = _.merge({success: true}, data);
res.status(201).json(out);
await sails.helpers.finalizeRequestLog(req, res, out);
};
module.exports = async function forbidden(msg){
const res = this.res;
const req = this.req;
if (!msg) {
msg = 'You are not permitted to perform this action.';
}
const out = {
success: false,
errors: msg
};
res.status(403).json(out);
await sails.helpers.finalizeRequestLog(req, res, out);
};
module.exports = async function notFound(msg){
const req = this.req;
const res = this.res;
if (!msg) {
msg = 'Could not locate requested item';
}
const out = {
success: false,
errors: msg
};
res.status(404).json(out);
await sails.helpers.finalizeRequestLog(req, res, out);
};
module.exports = async function sendOK(data) {
const res = this.res;
const req = this.req;
if (!data) {
data = {};
}
if (typeof data === 'string') {
data = {message: data};
}
data = sails.helpers.keepModelsSafe(data);
const out = _.merge({success: true}, data);
res.status(200);
res.json(out);
await sails.helpers.finalizeRequestLog(req, res, out);
};
module.exports = async function serverError(msg){
const req = this.req;
const res = this.res;
if (!msg) {
msg = 'Unknown server error occurred';
}
const out = {
success: false,
errors: msg
};
res.status(500).json(out);
await sails.helpers.finalizeRequestLog(req, res, out);
};
module.exports = {
logSensitiveData: false // Making this true will log sensitive information in request logs.
};

Sails Request Logging

This is a set of files to configure Sails.js v1, to log all requests / responses in the database.

Replace underscores "_" in filenames with forward slashses "/".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment