From da91fabbedc1bc7b30a81ee8b4801572538ad1fc Mon Sep 17 00:00:00 2001 From: Kaushik Date: Thu, 21 Jul 2022 19:54:05 +0530 Subject: [PATCH] axios interceptors, middleware change, logger formatting, CORS --- axios.js | 44 +++++++++++++++++++++++ boilerplates/controller.js | 4 +-- controllers/auth.js | 10 +++--- index.js | 6 +++- middleware/authCheck.js | 8 ++++- routes/auth.js | 2 -- typedefs.js | 4 +++ utils/logger.js | 73 +++++++++++++++++++++++++++++--------- 8 files changed, 123 insertions(+), 28 deletions(-) diff --git a/axios.js b/axios.js index 72a3c2c..bca1ca5 100644 --- a/axios.js +++ b/axios.js @@ -1,6 +1,7 @@ const axios = require('axios'); const { baseAPIURL, accountsAPIURL } = require("./constants"); +const logger = require('./utils/logger')(module); const authInstance = axios.default.create({ baseURL: accountsAPIURL, @@ -19,6 +20,49 @@ const axiosInstance = axios.default.create({ }, }); +axiosInstance.interceptors.request.use(request => { + logger.info("API call", { + url: request.url, + params: request.params, + }); + return request; +}) + +axiosInstance.interceptors.response.use( + (response) => response, + (error) => { + if (error.response) { + // Server has responded + logger.error( + "API: Error", { + response: { + status: error.response.status, + statusText: error.response.statusText, + data: error.response.data + } + }); + } else if (error.request) { + // The request was made but no response was received + logger.error( + "API: No response", { + request: { + url: error.request?.url, + params: error.request?.params, + } + }); + } else { + // Something happened in setting up the request that triggered an Error + logger.error( + "API: Request error", { + error: { + message: error.message, + } + }); + } + return Promise.reject(error); + } +) + module.exports = { authInstance, axiosInstance, diff --git a/boilerplates/controller.js b/boilerplates/controller.js index 8df8d36..bbbb795 100644 --- a/boilerplates/controller.js +++ b/boilerplates/controller.js @@ -1,5 +1,5 @@ require('dotenv').config(); -const { logger } = require("../utils/logger"); +const logger = require("../utils/logger")(module); const typedefs = require("../typedefs"); @@ -11,7 +11,7 @@ const __controller_func = async (req, res) => { try { } catch (error) { - logger.error(error); + logger.error('Error', { error }); return res.status(500).send({ message: "Server Error. Try again." }); } } diff --git a/controllers/auth.js b/controllers/auth.js index 2251547..84f1a23 100644 --- a/controllers/auth.js +++ b/controllers/auth.js @@ -5,7 +5,7 @@ const typedefs = require("../typedefs"); const { scopes, stateKey, accountsAPIURL } = require('../constants'); const generateRandString = require('../utils/generateRandString'); -const { logger } = require('../utils/logger'); +const logger = require('../utils/logger')(module); /** * Stateful redirect to Spotify login with credentials @@ -29,7 +29,7 @@ const login = (_req, res) => { }).toString() ); } catch (error) { - logger.error(error); + logger.error('Error', { error }); return res.status(500).send({ message: "Server Error. Try again." }); } } @@ -49,7 +49,7 @@ const callback = async (req, res) => { logger.error('state mismatch'); return res.redirect(409, '/'); } else if (error) { - logger.error('callback error', { error }); + logger.error('callback error', { authError: error }); return res.status(401).send(`Error: ${error}`); } else { // get auth tokens @@ -82,7 +82,7 @@ const callback = async (req, res) => { } } } catch (error) { - logger.error(error); + logger.error('Error', { error }); return res.status(500).send({ message: "Server Error. Try again." }); } } @@ -117,7 +117,7 @@ const refresh = async (req, res) => { res.status(response.status).send('Error: Refresh token flow failed.'); } } catch (error) { - logger.error(error); + logger.error('Error', { error }); return res.status(500).send({ message: "Server Error. Try again." }); } }; diff --git a/index.js b/index.js index 23c849c..768ce7a 100644 --- a/index.js +++ b/index.js @@ -14,7 +14,11 @@ app.use( }) ); -app.use(cors()); +const corsOptions = { + origin: process.env.NODE_ENV === 'development' ? 'localhost:' + process.env.PORT : process.env.LIVE_URL, +} + +app.use(cors(corsOptions)); app.use(cookieParser()); app.use(express.json()); diff --git a/middleware/authCheck.js b/middleware/authCheck.js index 4b73af9..e4525b4 100644 --- a/middleware/authCheck.js +++ b/middleware/authCheck.js @@ -1,16 +1,22 @@ const typedefs = require("../typedefs"); +const logger = require("../utils/logger")(module); /** * middleware to test if authenticated + * + * TODO: not checking if tokens are valid * @param {typedefs.Req} req * @param {typedefs.Res} res * @param {typedefs.Next} next */ const isAuthenticated = (req, res, next) => { if (req.session.refreshToken && req.session.accessToken) { - req.authHeader = `Bearer ${req.session.access_token}`; + // TODO: find a better way to set bearer token + req.authHeader = { 'Authorization': `Bearer ${req.session.accessToken}` }; next() } else { + const delSession = req.session.destroy(); + logger.info("Session destroyed.", { sessionID: delSession.id }); res.status(401).redirect("/"); } } diff --git a/routes/auth.js b/routes/auth.js index e6de74b..2f0fa63 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -5,13 +5,11 @@ const validator = require("../validators"); router.get( "/login", - validator.validate, login ); router.get( "/callback", - validator.validate, callback ); diff --git a/typedefs.js b/typedefs.js index cfe5e8b..e494f32 100644 --- a/typedefs.js +++ b/typedefs.js @@ -1,7 +1,11 @@ /** + * @typedef {import('module')} Module + * * @typedef {import('express').Request} Req * @typedef {import('express').Response} Res * @typedef {import('express').NextFunction} Next + * + * @typedef {import('winston').Logger} Logger */ exports.unused = {}; \ No newline at end of file diff --git a/utils/logger.js b/utils/logger.js index fc0c728..b46439b 100644 --- a/utils/logger.js +++ b/utils/logger.js @@ -1,21 +1,60 @@ require('dotenv').config(); -const { createLogger, transports, config, format } = require('winston'); -const { combine, timestamp, json } = format; +const path = require("path"); -const logger = createLogger({ - levels: config.npm.levels, - format: combine( - timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - json() - ), - transports: [ - process.env.NODE_ENV !== 'production' ? - new transports.Console() : - new transports.File({ filename: __dirname + '/../logs/common.log' }), - new transports.File({ filename: __dirname + '/../logs/error.log', level: 'error' }), - ] +const { createLogger, transports, config, format } = require('winston'); +const { combine, label, timestamp, printf } = format; + +const typedefs = require("../typedefs"); + +const getLabel = (callingModule) => { + const parts = callingModule.filename.split(path.sep); + return path.join(parts[parts.length - 2], parts.pop()); +}; + +const logMetaReplacer = (key, value) => { + if (key === "error") { + return value.name + ': ' + value.message; + } + return value; +} + +const metaFormat = (meta) => { + if (Object.keys(meta).length > 0) + return '\n' + JSON.stringify(meta, logMetaReplacer) + '\n'; + return '\n'; +} + +const logFormat = printf(({ level, message, label, timestamp, ...meta }) => { + if (meta.error) { + for (const key in meta.error) { + if (typeof key !== "symbol" && key !== "message" && key !== "name") { + delete meta.error[key] + } + } + } + return `${timestamp} [${label}] ${level}: ${message}${metaFormat(meta)}`; }); -module.exports = { - logger -}; \ No newline at end of file +/** + * Creates a curried function, and call it with the module in use to get logs with filename + * @param {typedefs.Module} callingModule The module from which the logger is called + * @returns {typedefs.Logger} + */ +const logger = (callingModule) => { + return createLogger({ + levels: config.npm.levels, + format: combine( + label({ label: getLabel(callingModule) }), + timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + logFormat, + ), + transports: [ + process.env.NODE_ENV !== 'production' ? + new transports.Console() : + new transports.File({ filename: __dirname + '/../logs/common.log' }), + new transports.File({ filename: __dirname + '/../logs/error.log', level: 'error' }), + ] + }); +} + +module.exports = logger; \ No newline at end of file