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