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 "/".
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. | |
}; |