Route.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. const nodePath = require('path');
  2. const JWT = require('jsonwebtoken');
  3. const db = require('knex')({
  4. client: process.env.DB_CLIENT,
  5. connection: {
  6. host: process.env.DB_HOST,
  7. user: process.env.DB_USER,
  8. password: process.env.DB_PASSWORD,
  9. database: process.env.DB_DATABASE,
  10. filename: nodePath.join(__dirname, '..', '..', '..', 'database.sqlite')
  11. },
  12. postProcessResponse: result => {
  13. /*
  14. Fun fact: Depending on the database used by the user and given that I don't want
  15. to force a specific database for everyone because of the nature of this project,
  16. some things like different data types for booleans need to be considered like in
  17. the implementation below where sqlite returns 1 and 0 instead of true and false.
  18. */
  19. const booleanFields = [
  20. 'enabled',
  21. 'enableDownload',
  22. 'isAdmin'
  23. ];
  24. const processResponse = row => {
  25. Object.keys(row).forEach(key => {
  26. if (booleanFields.includes(key)) {
  27. row[key] = row[key] === 1 ? true : false;
  28. }
  29. });
  30. return row;
  31. };
  32. if (Array.isArray(result)) return result.map(row => processResponse(row));
  33. if (typeof result === 'object') return processResponse(result);
  34. return result;
  35. },
  36. useNullAsDefault: process.env.DB_CLIENT === 'sqlite3' ? true : false
  37. });
  38. const moment = require('moment');
  39. const log = require('../utils/Log');
  40. const bcrypt = require('bcrypt');
  41. class Route {
  42. constructor(path, method, options) {
  43. if (!path) throw new Error('Every route needs a URL associated with it.');
  44. if (!method) throw new Error('Every route needs its method specified.');
  45. this.path = path;
  46. this.method = method;
  47. this.options = options || {};
  48. }
  49. authorize(req, res) {
  50. if (this.options.bypassAuth) return this.run(req, res, db);
  51. if (req.headers.apiKey) return this.authorizeApiKey(req, res, req.headers.apiKey);
  52. if (!req.headers.authorization) return res.status(401).json({ message: 'No authorization header provided' });
  53. const token = req.headers.authorization.split(' ')[1];
  54. if (!token) return res.status(401).json({ message: 'No authorization header provided' });
  55. return JWT.verify(token, process.env.SECRET, async (error, decoded) => {
  56. if (error) {
  57. log.error(error);
  58. return res.status(401).json({ message: 'Invalid token' });
  59. }
  60. const id = decoded ? decoded.sub : '';
  61. const iat = decoded ? decoded.iat : '';
  62. const user = await db.table('users').where({ id }).first();
  63. if (!user) return res.status(401).json({ message: 'Invalid authorization' });
  64. if (iat && iat < moment(user.passwordEditedAt).format('x')) return res.status(401).json({ message: 'Token expired' });
  65. if (!user.enabled) return res.status(401).json({ message: 'This account has been disabled' });
  66. if (this.options.adminOnly && !user.isAdmin) return res.status(401).json({ message: 'Invalid authorization' });
  67. return this.run(req, res, db, user);
  68. });
  69. }
  70. authorizeApiKey(req, res, apiKey) {
  71. if (this.options.noApiKey) return res.status(401).json({ message: 'Api Key not allowed for this resource' });
  72. /*
  73. Need to read more into how api keys work before proceeding any further
  74. const comparePassword = await bcrypt.compare(password, user.password);
  75. if (!comparePassword) return res.status(401).json({ message: 'Invalid authorization.' });
  76. */
  77. }
  78. run(req, res, db) { // eslint-disable-line no-unused-vars
  79. return;
  80. }
  81. error(res, error) {
  82. log.error(error);
  83. return res.status(500).json({ message: 'There was a problem parsing the request' });
  84. }
  85. }
  86. module.exports = Route;