diff --git a/api/main_endpoints/routes/Advertisement.js b/api/main_endpoints/routes/Advertisement.js index 09bb7bb75..0339e481d 100644 --- a/api/main_endpoints/routes/Advertisement.js +++ b/api/main_endpoints/routes/Advertisement.js @@ -1,14 +1,12 @@ const express = require('express'); const router = express.Router(); const { OK, BAD_REQUEST, FORBIDDEN, UNAUTHORIZED, NOT_FOUND } = require('../../util/constants').STATUS_CODES; -const { - decodeToken, - checkIfTokenSent, -} = require('../util/token-functions.js'); +const { decodeToken } = require('../util/token-functions.js'); const logger = require('../../util/logger'); const Advertisement = require('../models/Advertisement'); const AuditLog = require('../models/AuditLog.js'); const AuditLogActions = require('../util/auditLogActions.js'); +const membershipState = require('../../util/constants.js').MEMBERSHIP_STATE; router.get('/', async (req, res) => { const count = await Advertisement.countDocuments(); @@ -26,10 +24,9 @@ router.get('/', async (req, res) => { router.get('/getAllAdvertisements', async (req, res) => { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!await decodeToken(req)) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req, membershipState.OFFICER); + if (decoded.status !== OK) { + return res.sendStatus(decoded.status); } Advertisement.find() .sort({ createdAt: -1 }) @@ -41,13 +38,9 @@ router.get('/getAllAdvertisements', async (req, res) => { }); router.post('/createAdvertisement', async (req, res) => { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } - - const user = await decodeToken(req); - if (!user) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req, membershipState.OFFICER); + if (decoded.status !== OK) { + return res.sendStatus(decoded.status); } const newAd = new Advertisement({ @@ -58,7 +51,7 @@ router.post('/createAdvertisement', async (req, res) => { try { const createdAd = await Advertisement.create(newAd); AuditLog.create({ - userId: user._id, + userId: decoded.token._id, action: AuditLogActions.CREATE_AD, details: { message: createdAd.message, @@ -75,15 +68,9 @@ router.post('/createAdvertisement', async (req, res) => { }); router.post('/deleteAdvertisement', async (req, res) => { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!await decodeToken(req)) { - return res.sendStatus(UNAUTHORIZED); - } - - const user = await decodeToken(req); - if (!user) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req, membershipState.OFFICER); + if (decoded.status !== OK) { + return res.sendStatus(decoded.status); } try { @@ -94,7 +81,7 @@ router.post('/deleteAdvertisement', async (req, res) => { } AuditLog.create({ - userId: user._id, + userId: decoded.token._id, action: AuditLogActions.DELETE_AD, details: { deletedAd: { diff --git a/api/main_endpoints/routes/AuditLog.js b/api/main_endpoints/routes/AuditLog.js index edf306ab3..05b6b429b 100644 --- a/api/main_endpoints/routes/AuditLog.js +++ b/api/main_endpoints/routes/AuditLog.js @@ -3,25 +3,18 @@ const router = express.Router(); const AuditLog = require('../models/AuditLog'); const { OK, UNAUTHORIZED, SERVER_ERROR } = require('../../util/constants').STATUS_CODES; -const { OFFICER } = require('../../util/constants.js').MEMBERSHIP_STATE; +const membershipState = require('../../util/constants.js').MEMBERSHIP_STATE; -const { checkIfTokenSent, checkIfTokenValid, decodeTokenFromBodyOrQuery } = require('../util/token-functions.js'); +const { decodeToken } = require('../util/token-functions.js'); const logger = require('../../util/logger'); const User = require('../models/User.js'); let { clients } = require('../util/AuditLog.js'); router.get('/getAuditLogs', async (req, res) => { - if (!checkIfTokenSent(req)) { - logger.warn('/getAuditLogs was requested without a token'); - return res.sendStatus(UNAUTHORIZED); - } - - const isValid = checkIfTokenValid(req, OFFICER); - - if (!isValid) { - logger.warn('/getAuditLogs was requested with an invalid or unauthorized token'); - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req, membershipState.OFFICER); + if (decoded.status !== OK) { + return res.sendStatus(decoded.status); } const itemsPerPage = 50; @@ -73,9 +66,9 @@ router.get('/getAuditLogs', async (req, res) => { }); router.get('/listen', async (req, res) => { - const decoded = await decodeTokenFromBodyOrQuery(req); - if (!Object.keys(decoded) || decoded.accessLevel < OFFICER) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req, membershipState.OFFICER); + if (decoded.status !== OK) { + return res.sendStatus(decoded.status); } const headers = { diff --git a/api/main_endpoints/routes/Auth.js b/api/main_endpoints/routes/Auth.js index 98f945c12..74c55053a 100644 --- a/api/main_endpoints/routes/Auth.js +++ b/api/main_endpoints/routes/Auth.js @@ -11,11 +11,7 @@ const PasswordReset = require('../models/PasswordReset.js'); const logger = require('../../util/logger'); const { registerUser, testPasswordStrength } = require('../util/userHelpers'); const { verifyCaptcha } = require('../util/captcha'); -const { - checkIfTokenSent, - checkIfTokenValid, - decodeToken -} = require('../util/token-functions'); +const { decodeToken } = require('../util/token-functions'); const jwt = require('jsonwebtoken'); const { OK, @@ -60,10 +56,9 @@ router.post('/register', async (req, res) => { }); router.post('/resendVerificationEmail', async (req, res) => { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!checkIfTokenValid(req, membershipState.OFFICER)) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req, membershipState.OFFICER); + if (decoded.status !== OK) { + return res.sendStatus(decoded.status); } const maybeUser = await userWithEmailExists(req.body.email); if (!maybeUser) { @@ -239,16 +234,12 @@ router.post('/login', function(req, res) { // Verifies the users session if they have an active jwtToken. // Used on the inital load of root '/' // Returns the name and accesslevel of the user w/ the given access token -router.post('/verify', function(req, res) { - if (!checkIfTokenSent(req)) { - return res.status(UNAUTHORIZED).json({}); - } - const token = decodeToken(req); - if (token === null || Object.keys(token).length === 0) { - res.status(UNAUTHORIZED).json({}); - } else { - res.status(OK).json(token); +router.post('/verify', async function(req, res) { + const decoded = await decodeToken(req); + if (decoded.status !== OK) { + return res.sendStatus(decoded.status); } + res.status(OK).json(decoded.token); }); router.post('/generateHashedId', async (req, res) => { diff --git a/api/main_endpoints/routes/Cleezy.js b/api/main_endpoints/routes/Cleezy.js index 8c2091446..6d9ee8089 100644 --- a/api/main_endpoints/routes/Cleezy.js +++ b/api/main_endpoints/routes/Cleezy.js @@ -1,10 +1,7 @@ const express = require('express'); const axios = require('axios'); const router = express.Router(); -const { - decodeToken, - checkIfTokenSent, -} = require('../util/token-functions.js'); +const { decodeToken } = require('../util/token-functions.js'); const { OK, UNAUTHORIZED, @@ -28,10 +25,9 @@ router.get('/list', async (req, res) => { }); } const { page = 0, search, sortColumn = 'created_at', sortOrder = 'DESC'} = req.query; - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!await decodeToken(req)) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req); + if (decoded.status !== OK) { + return res.sendStatus(decoded.status); } try { const returnData = await cleezyHelpers.searchCleezyUrls({ page, search, sortColumn, sortOrder }); @@ -47,10 +43,9 @@ router.get('/list', async (req, res) => { }); router.post('/createUrl', async (req, res) => { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!await decodeToken(req)) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req); + if (decoded.status !== OK) { + return res.sendStatus(decoded.status); } const { url, alias, expiresAt } = req.body; let jsonbody = { url, alias: alias || null }; @@ -68,10 +63,9 @@ router.post('/createUrl', async (req, res) => { }); router.post('/deleteUrl', async (req, res) => { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!await decodeToken(req)) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req); + if (decoded.status !== OK) { + return res.sendStatus(decoded.status); } const { alias } = req.body; axios diff --git a/api/main_endpoints/routes/LedSign.js b/api/main_endpoints/routes/LedSign.js index bf68e1cff..b517029bf 100644 --- a/api/main_endpoints/routes/LedSign.js +++ b/api/main_endpoints/routes/LedSign.js @@ -5,10 +5,7 @@ const { SERVER_ERROR, UNAUTHORIZED } = require('../../util/constants').STATUS_CODES; -const { - decodeToken, - checkIfTokenSent -} = require('../util/token-functions.js'); +const { decodeToken } = require('../util/token-functions.js'); const logger = require('../../util/logger'); const { updateSign, healthCheck, turnOffSign } = require('../util/LedSign.js'); const AuditLogActions = require('../util/auditLogActions.js'); @@ -37,14 +34,10 @@ router.get('/healthCheck', async (req, res) => { }); router.post('/updateSignText', async (req, res) => { - if (!checkIfTokenSent(req)) { - logger.warn('/updateSignText was requested without a token'); - return res.sendStatus(UNAUTHORIZED); - } - const user = await decodeToken(req); // Store the user here - if (!user || Object.keys(user) === 0) { + const decoded = await decodeToken(req); + if (decoded.status !== OK) { logger.warn('/updateSignText was requested with an invalid token'); - return res.sendStatus(UNAUTHORIZED); + return res.sendStatus(decoded.status); } if (!LED_SIGN.ENABLED && !runningInTest) { logger.warn('led sign is disabled, returning 200 by default'); @@ -65,7 +58,7 @@ router.post('/updateSignText', async (req, res) => { } await AuditLog.create({ - userId: user._id, + userId: decoded.token._id, action: AuditLogActions.UPDATE_SIGN, details: { newSignText: req.body.text, diff --git a/api/main_endpoints/routes/Messages.js b/api/main_endpoints/routes/Messages.js index 033cb3133..fa98e37b0 100644 --- a/api/main_endpoints/routes/Messages.js +++ b/api/main_endpoints/routes/Messages.js @@ -11,7 +11,7 @@ const bodyParser = require('body-parser'); const User = require('../models/User.js'); const logger = require('../../util/logger'); const client = require('prom-client'); -const { decodeToken, decodeTokenFromBodyOrQuery } = require('../util/token-functions.js'); +const { decodeToken } = require('../util/token-functions.js'); const { MetricsHandler, register } = require('../../util/metrics.js'); @@ -80,11 +80,11 @@ router.post('/send', async (req, res) => { } // Assume user passed a non null/undefined token - const userObj = decodeToken(req); + const userObj = await decodeToken(req); if (!userObj) { return res.sendStatus(UNAUTHORIZED); } - nameToUse = userObj.firstName; + nameToUse = userObj.token.firstName; try { writeMessage(id, `${message}`, `${nameToUse}:`); return res.json({ status: 'Message sent' }); @@ -152,11 +152,11 @@ router.get('/listen', async (req, res) => { let filterQuery = {}; // filter to find user in the database if (token) { - let userObj = decodeTokenFromBodyOrQuery(req); - if (!Object.keys(userObj)) { + const userObj = await decodeToken(req); + if (!userObj.token) { return res.sendStatus(UNAUTHORIZED); } - filterQuery._id = userObj._id; + filterQuery._id = userObj.token._id; } else { filterQuery.apiKey = apiKey; } diff --git a/api/main_endpoints/routes/OfficeAccessCard.js b/api/main_endpoints/routes/OfficeAccessCard.js index 31531e7f0..4215b2dd4 100644 --- a/api/main_endpoints/routes/OfficeAccessCard.js +++ b/api/main_endpoints/routes/OfficeAccessCard.js @@ -6,7 +6,7 @@ const { OK, FORBIDDEN, } = require('../../util/constants').STATUS_CODES; -const { OFFICER } = require('../../util/constants').MEMBERSHIP_STATE; +const membershipState = require('../../util/constants').MEMBERSHIP_STATE; const express = require('express'); const router = express.Router(); const bodyParser = require('body-parser'); @@ -14,12 +14,7 @@ const OfficeAccessCard = require('../models/OfficeAccessCard.js'); const logger = require('../../util/logger'); const { officeAccessCard = {} } = require('../../config/config.json'); const { API_KEY = 'NOTHING_REALLY' } = officeAccessCard; -const { - decodeTokenFromBodyOrQuery, - decodeToken, - checkIfTokenSent, - checkIfTokenValid -} = require('../util/token-functions.js'); +const { decodeToken } = require('../util/token-functions.js'); const ROWS_PER_PAGE = 25; const { checkIfCardExists, @@ -142,9 +137,9 @@ router.get('/verify', async (req, res) => { }); router.post('/delete', async (req, res) => { - const decoded = decodeToken(req); - if (!decoded) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req); + if (decoded.status !== OK) { + return res.sendStatus(decoded.status); } const { alias } = req.body; @@ -173,7 +168,7 @@ router.post('/delete', async (req, res) => { statusCode: OK, }); AuditLog.create({ - userId: decoded._id, + userId: decoded.token._id, action: AuditLogActions.DELETE_CARD, details: { alias } }); @@ -189,10 +184,9 @@ router.post('/delete', async (req, res) => { }); router.post('/getAllCards', async (req, res) => { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!checkIfTokenValid(req)) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req); + if (decoded.status !== OK) { + return res.sendStatus(decoded.status); } const skip = Math.max(Number(req.body.page) || 0, 0) * ROWS_PER_PAGE; @@ -216,9 +210,9 @@ router.post('/getAllCards', async (req, res) => { }); router.get('/listen', async (req, res) => { - const decoded = await decodeTokenFromBodyOrQuery(req); - if (!Object.keys(decoded) || decoded.accessLevel < OFFICER) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req, membershipState.OFFICER); + if (decoded.status !== OK) { + return res.sendStatus(decoded.status); } const headers = { diff --git a/api/main_endpoints/routes/Printer.js b/api/main_endpoints/routes/Printer.js index a0621b7e8..7a35f4261 100644 --- a/api/main_endpoints/routes/Printer.js +++ b/api/main_endpoints/routes/Printer.js @@ -8,10 +8,7 @@ const path = require('path'); const { MetricsHandler, register } = require('../../util/metrics.js'); const { cleanUpChunks, cleanUpExpiredChunks, recordPrintingFolderSize } = require('../util/Printer.js'); -const { - decodeToken, - checkIfTokenSent, -} = require('../util/token-functions.js'); +const { decodeToken } = require('../util/token-functions.js'); const { OK, UNAUTHORIZED, @@ -74,15 +71,10 @@ router.get('/healthCheck', async (req, res) => { }); router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => { - if (!checkIfTokenSent(req)) { - logger.warn('/sendPrintRequest was requested without a token'); - return res.sendStatus(UNAUTHORIZED); - } - - const decodedToken = await decodeToken(req); - if (!decodedToken || Object.keys(decodedToken) === 0) { + const decoded = await decodeToken(req); + if (decoded.status !== OK) { logger.warn('/sendPrintRequest was requested with an invalid token'); - return res.sendStatus(UNAUTHORIZED); + return res.sendStatus(decoded.status); } if (!PRINTING.ENABLED) { logger.warn('Printing is disabled, returning 200 and dummy print id to mock the printing server'); diff --git a/api/main_endpoints/routes/ShortcutSearch.js b/api/main_endpoints/routes/ShortcutSearch.js index e2e854c70..70d8649c4 100644 --- a/api/main_endpoints/routes/ShortcutSearch.js +++ b/api/main_endpoints/routes/ShortcutSearch.js @@ -3,10 +3,7 @@ const express = require('express'); const router = express.Router(); const User = require('../models/User.js'); -const { - checkIfTokenSent, - checkIfTokenValid, -} = require('../util/token-functions'); +const { decodeToken } = require('../util/token-functions'); const { OK, UNAUTHORIZED, @@ -23,10 +20,9 @@ const MAX_RESULT = 5; // Search for all members using either first name, last name or email // Search for all cleezy urls using either alias or url router.post('/', async function(req, res) { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!checkIfTokenValid(req, membershipState.OFFICER)) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req, membershipState.OFFICER); + if (!decoded) { + return res.sendStatus(decoded.status); } if (!req.body.query) { diff --git a/api/main_endpoints/routes/User.js b/api/main_endpoints/routes/User.js index a9c0d37b5..576a74ce4 100644 --- a/api/main_endpoints/routes/User.js +++ b/api/main_endpoints/routes/User.js @@ -9,11 +9,7 @@ const { getMemberExpirationDate, hashPassword, } = require('../util/userHelpers'); -const { - checkIfTokenSent, - checkIfTokenValid, - decodeToken, -} = require('../util/token-functions'); +const { decodeToken } = require('../util/token-functions'); const { OK, BAD_REQUEST, @@ -36,13 +32,11 @@ const ROWS_PER_PAGE = 20; // Delete a member router.post('/delete', async (req, res) => { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!checkIfTokenValid(req)) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req); + if (!decoded) { + return res.sendStatus(decoded.status); } - const decoded = decodeToken(req); const targetUser = await User.findById(req.body._id); if (!targetUser) { return res.sendStatus(NOT_FOUND); @@ -78,12 +72,12 @@ router.post('/delete', async (req, res) => { }); // Search for a member -router.post('/search', function(req, res) { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!checkIfTokenValid(req, membershipState.ALUMNI)) { - return res.sendStatus(UNAUTHORIZED); +router.post('/search', async function(req, res) { + const decoded = await decodeToken(req, membershipState.ALUMNI); + if (!decoded) { + return res.sendStatus(decoded.status); } + User.findOne({ email: req.body.email }, function(error, result) { if (error) { res.status(BAD_REQUEST).send({ message: 'Bad Request.' }); @@ -121,11 +115,11 @@ router.post('/search', function(req, res) { // Search for all members router.post('/users', async function(req, res) { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!checkIfTokenValid(req)) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req); + if (!decoded) { + return res.sendStatus(decoded.status); } + let maybeOr = {}; if (req.body.query) { maybeOr = { @@ -164,17 +158,15 @@ router.post('/users', async function(req, res) { // Edit/Update a member record router.post('/edit', async (req, res) => { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!checkIfTokenValid(req)) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req); + if (!decoded) { + return res.sendStatus(decoded.status); } if (!req.body._id) { return res.sendStatus(BAD_REQUEST); } - let decoded = decodeToken(req); if (decoded.accessLevel < membershipState.OFFICER) { if (req.body.email && req.body.email != decoded.email) { return res @@ -234,9 +226,9 @@ router.post('/edit', async (req, res) => { // create audit log for password change AuditLog.create({ - userId: decoded._id, + userId: decoded.token._id, action: AuditLogActions.CHANGE_PW, - details: { email: existingUser.email, userId: decoded._id }, + details: { email: existingUser.email, userId: decoded.token._id }, }).catch(logger.error); } else { @@ -272,7 +264,7 @@ router.post('/edit', async (req, res) => { } AuditLog.create({ - userId: decoded._id, // person who did modification + userId: decoded.token._id, // person who did modification action: AuditLogActions.UPDATE_USER, documentId: user._id, details: { @@ -289,11 +281,10 @@ router.post('/edit', async (req, res) => { }); }); -router.post('/getPagesPrintedCount', (req, res) => { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!checkIfTokenValid(req)) { - return res.sendStatus(UNAUTHORIZED); +router.post('/getPagesPrintedCount', async (req, res) => { + const decoded = await decodeToken(req); + if (!decoded) { + return res.sendStatus(decoded.status); } User.findOne({ email: req.body.email }, function(error, result) { if (error) { @@ -317,15 +308,13 @@ router.post('/getPagesPrintedCount', (req, res) => { }); router.post('/getUserById', async (req, res) => { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!checkIfTokenValid(req)) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req); + if (!decoded) { + return res.sendStatus(decoded.status); } // If not officer, only allow reading of own account - let decoded = decodeToken(req); if (decoded.accessLevel < membershipState.OFFICER) { - if (req.body.userID && req.body.userID !== decoded._id) { + if (req.body.userID && req.body.userID !== decoded.token._id) { return res .status(FORBIDDEN) .json({ message: 'you must be an officer or admin to read other users\' data' }); @@ -392,11 +381,10 @@ router.post('/getUserDataByEmail', (req, res) => { }); // Search for all members with verified emails and subscribed -router.post('/usersSubscribedAndVerified', function(req, res) { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!checkIfTokenValid(req)) { - return res.sendStatus(UNAUTHORIZED); +router.post('/usersSubscribedAndVerified', async function(req, res) { + const decoded = await decodeToken(req); + if (!decoded) { + return res.sendStatus(decoded.status); } User.find({ emailVerified: true, emailOptIn: true }) .then((users) => { @@ -418,11 +406,10 @@ router.post('/usersSubscribedAndVerified', function(req, res) { }); // Search for all members with verified emails, subscribed, and not banned or pending -router.post('/usersValidVerifiedAndSubscribed', function(req, res) { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!checkIfTokenValid(req, membershipState.OFFICER)) { - return res.sendStatus(UNAUTHORIZED); +router.post('/usersValidVerifiedAndSubscribed', async function(req, res) { + const decoded = await decodeToken(req); + if (!decoded) { + return res.sendStatus(decoded.status); } User.find({ emailVerified: true, @@ -446,13 +433,11 @@ router.post('/usersValidVerifiedAndSubscribed', function(req, res) { // Generate an API key for the Messages API if the user does not have an API key; otherwise, return the existing API key router.post('/apikey', async (req, res) => { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } - if (!checkIfTokenValid(req)) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req); + if (!decoded) { + return res.sendStatus(decoded.status); } - let { _id } = decodeToken(req); + let { _id } = decoded.token; User.findOne({_id}) .then((user) => { @@ -482,10 +467,9 @@ router.post('/apikey', async (req, res) => { // Finds number of those signups who've paid for annual plan // Assumes members who have paid have been assigned an expiration date router.get('/getNewPaidMembersThisSemester', async (req, res) => { - if (!checkIfTokenSent(req)) { - return res.sendStatus(FORBIDDEN); - } else if (!checkIfTokenValid(req, membershipState.OFFICER)) { - return res.sendStatus(UNAUTHORIZED); + const decoded = await decodeToken(req); + if (!decoded) { + return res.sendStatus(decoded.status); } const today = new Date(); diff --git a/api/main_endpoints/util/token-functions.js b/api/main_endpoints/util/token-functions.js index 7d7fb43d4..910d0b49d 100644 --- a/api/main_endpoints/util/token-functions.js +++ b/api/main_endpoints/util/token-functions.js @@ -5,75 +5,44 @@ const membershipState = require('../../util/constants').MEMBERSHIP_STATE; require('./passport')(passport); - -/** - * Check if the request body contains a token - * @param {object} request the HTTP request from the client - * @returns {boolean} if the token exists in the request body - */ -function checkIfTokenSent(request) { - try { - return !!request.headers.authorization; - } catch(_) { - return false; - } -} +const { SceStatusOrToken } = require('../../util/token-verification.js'); +const { UNAUTHORIZED, OK, FORBIDDEN } = require('../../util/constants').STATUS_CODES; +const logger = require('../../util/logger'); /** * @param {object} request the HTTP request from the client */ -function decodeToken(request){ - try { - let decodedResponse = {}; - if (!request.headers.authorization || !request.headers.authorization.length) { - return decodedResponse; - } - const token = request.headers.authorization.split('Bearer ')[1]; - const userToken = token.replace(/^JWT\s/, ''); - jwt.verify(userToken, secretKey, function(error, decoded) { - if (!error && decoded) { - decodedResponse = decoded; +function decodeToken(request, accessLevel = membershipState.NON_MEMBER) { + return new Promise((resolve) => { + try { + let decodedResponse = new SceStatusOrToken(); + let token = null; + if (request.headers.authorization && request.headers.authorization.length) { + token = request.headers.authorization.split('Bearer ')[1]; + } else if (request.query.token) { + token = request.query.token; + } else { + decodedResponse.status = UNAUTHORIZED; + return resolve(decodedResponse); } - }); - return decodedResponse; - } catch (_) { - return null; - } -} - -/** -* @param {object} request the HTTP request from the client -*/ -function decodeTokenFromBodyOrQuery(request){ - const token = request.body.token || request.query.token; - const userToken = token.replace(/^JWT\s/, ''); - let decodedResponse = {}; - jwt.verify(userToken, secretKey, function(error, decoded) { - if (!error && decoded) { - decodedResponse = decoded; + const userToken = token.replace(/^JWT\s/, ''); + jwt.verify(userToken, secretKey, function(error, decoded) { + if (!error && decoded) { + decodedResponse.status = decoded.accessLevel >= accessLevel ? OK : FORBIDDEN; + decodedResponse.token = decoded; + return resolve(decodedResponse); + } + decodedResponse.status = FORBIDDEN; + return resolve(decodedResponse); + }); + } catch (err) { + logger.error('unable to decode token', err); + decodedResponse.status = UNAUTHORIZED; + return resolve(decodedResponse); } }); - return decodedResponse; -} - -/** - * Checks if the request token is valid and returns either a valid response - * or undefined - * @param {object} request the HTTP request from the client - * @param {number} accessLevel the minimum access level to consider the token valid - * @param {boolean} returnDecoded optional parameter to return the decoded - * response to the user - * @returns {boolean} whether the user token is valid or not - */ -function checkIfTokenValid(request, accessLevel = membershipState.NON_MEMBER) { - let decoded = decodeToken(request); - let response = decoded && decoded.accessLevel >= accessLevel; - return response; } module.exports = { - checkIfTokenSent, - checkIfTokenValid, decodeToken, - decodeTokenFromBodyOrQuery }; diff --git a/api/util/token-verification.js b/api/util/token-verification.js index 7f13ca9b9..a37016446 100644 --- a/api/util/token-verification.js +++ b/api/util/token-verification.js @@ -8,4 +8,12 @@ const { DISCORD_COREV4_KEY } = require('../config/config.json'); function checkDiscordKey(apiKey) { return apiKey === DISCORD_COREV4_KEY; } -module.exports = { checkDiscordKey }; + +class SceStatusOrToken { + constructor() { + this.token = null; + this.status = null; + } +} + +module.exports = { checkDiscordKey, SceStatusOrToken }; diff --git a/test/api/Auth.js b/test/api/Auth.js index 7c67f565a..b418d5435 100644 --- a/test/api/Auth.js +++ b/test/api/Auth.js @@ -180,7 +180,7 @@ describe('Auth', () => { } }; - const decodedPayload = decodeToken(mockRequest); + const decodedPayload = await decodeToken(mockRequest); const expectedPayload = { firstName: 'Test', lastName: 'User', diff --git a/test/api/TokenFunctions.js b/test/api/TokenFunctions.js index 194f6c403..13bd60c06 100644 --- a/test/api/TokenFunctions.js +++ b/test/api/TokenFunctions.js @@ -4,6 +4,8 @@ const sinon = require('sinon'); const chai = require('chai'); const expect = chai.expect; const proxyquire = require('proxyquire'); +const { OK, FORBIDDEN, UNAUTHORIZED } = require('../../api/util/constants').STATUS_CODES; +const membershipState = require('../../api/util/constants').MEMBERSHIP_STATE; const requestWithToken = { headers: { @@ -31,32 +33,41 @@ describe('TokenFunctions', () => { }); done(); }); - describe('checkIfTokenSent', () => { - it('Should return true if a token field exists in the request', done => { - expect(tokenFunctions.checkIfTokenSent(requestWithToken)).to.equal(true); - done(); + describe('decodeToken', () => { + it('Should resolve with UNAUTHORIZED if no token is sent', done => { + tokenFunctions.decodeToken(requestWithoutToken) + .then(decodedResponse => { + expect(decodedResponse.status).to.equal(UNAUTHORIZED); + done(); + }); }); - it('Should return false if a token field does ' + - 'not exist in the request', done => { - expect(tokenFunctions.checkIfTokenSent(requestWithoutToken)) - .to.equal(false); - done(); - }); - }); - - describe('checkIfTokenValid', () => { - it('Should return the decoded response ', done => { - jwtStub.yields(false, requestWithToken.body); - expect(tokenFunctions.checkIfTokenValid(requestWithToken)) - .to.equal(true); - done(); - }); - it('Should return false if a token field ' + - 'does not exist in the request', done => { - jwtStub.yields(true, false); - expect(tokenFunctions.checkIfTokenValid(requestWithToken)) - .to.equal(false); - done(); + it('Should resolve with FORBIDDEN if token is invalid', done => { + jwtStub.yields(new Error('invalid token'), null); + tokenFunctions.decodeToken(requestWithToken) + .then(decodedResponse => { + expect(decodedResponse.status).to.equal(FORBIDDEN); + done(); + }); }); + it('Should resolve with FORBIDDEN if access level is insufficient', + done => { + jwtStub.yields(null, { accessLevel: membershipState.MEMBER }); + tokenFunctions.decodeToken(requestWithToken, membershipState.OFFICER) + .then(decodedResponse => { + expect(decodedResponse.status).to.equal(FORBIDDEN); + done(); + }); + }); + it('Should resolve with OK and the decoded token if token is valid and access level is sufficient', + done => { + const decodedToken = { accessLevel: membershipState.OFFICER, firstName: 'Test' }; + jwtStub.yields(null, decodedToken); + tokenFunctions.decodeToken(requestWithToken, membershipState.OFFICER) + .then(decodedResponse => { + expect(decodedResponse.status).to.equal(OK); + expect(decodedResponse.token).to.equal(decodedToken); + done(); + }); + }); }); }); diff --git a/test/util/mocks/TokenValidFunctions.js b/test/util/mocks/TokenValidFunctions.js index f3d1e36f9..e236e6004 100644 --- a/test/util/mocks/TokenValidFunctions.js +++ b/test/util/mocks/TokenValidFunctions.js @@ -1,15 +1,14 @@ const TokenFunctions = require( '../../../api/main_endpoints/util/token-functions'); const sinon = require('sinon'); +const { OK, FORBIDDEN } = require('../../../api/util/constants').STATUS_CODES; -let checkifTokenValidMock = null; let decodeTokenValidMock = null; /** * Initialize the stub to be used in other functions. */ function initializeTokenMock() { - checkifTokenValidMock = sinon.stub(TokenFunctions, 'checkIfTokenValid'); decodeTokenValidMock = sinon.stub(TokenFunctions, 'decodeToken'); } @@ -17,7 +16,6 @@ function initializeTokenMock() { * Restore sinon's stub, function returned to its original state */ function restoreTokenMock() { - checkifTokenValidMock.restore(); decodeTokenValidMock.restore(); } @@ -25,7 +23,6 @@ function restoreTokenMock() { * Reset sinon-stub's call, reset onCall-function back to the beginning */ function resetTokenMock() { - checkifTokenValidMock.reset(); decodeTokenValidMock.reset(); } @@ -41,12 +38,7 @@ function setTokenStatus( returnValue, data = {}, ) { - checkifTokenValidMock.returns(returnValue); - if (returnValue) { - decodeTokenValidMock.returns(data); - } else { - decodeTokenValidMock.returns(null); - } + decodeTokenValidMock.returns(returnValue ? Promise.resolve({ status: OK, token: data }) : Promise.resolve({ status: FORBIDDEN, token: null })); } module.exports = {