diff --git a/app.js b/app.js index 6acc8da..ba877b8 100644 --- a/app.js +++ b/app.js @@ -10,7 +10,7 @@ const usersRouter = require('./routes/api/users'); const middleware = require('./utils/middleware'); const logger = require('./utils/logger'); -const loginRouter = require('./routes/api/login'); +const loginRouter = require('./routes/api/auth'); const softwaresRouter = require('./routes/api/softwares'); const app = express(); @@ -59,7 +59,7 @@ app.use( // Routes app.use(rootRouter); app.use('/api/users', usersRouter); -app.use('/api/login', loginRouter); +app.use('/api/auth', loginRouter); app.use('/api/softwares', softwaresRouter); app.use(middleware.unknownEndPoint); diff --git a/controllers/api/loginController.js b/controllers/api/authController.js similarity index 59% rename from controllers/api/loginController.js rename to controllers/api/authController.js index 9474dff..36aa0a1 100644 --- a/controllers/api/loginController.js +++ b/controllers/api/authController.js @@ -2,8 +2,9 @@ const jwt = require('jsonwebtoken'); const bcrypt = require('bcrypt'); const User = require('../../models/user'); const config = require('../../utils/config'); +const { getReqAuthToken, verifyAuthToken } = require('../../utils/jwtUtils'); -const postLogin = async (req, res) => { +const postAuth = async (req, res) => { const { body } = req; // Return the user before update so can view last login. @@ -14,9 +15,10 @@ const postLogin = async (req, res) => { }, ); - const passwordCorrect = user === null - ? false - : await bcrypt.compare(body.password, user.passwordHash); + const passwordCorrect = + user === null + ? false + : await bcrypt.compare(body.password, user.passwordHash); if (!(user && passwordCorrect)) { return res.status('401').json({ @@ -39,6 +41,22 @@ const postLogin = async (req, res) => { }); }; +const getAuth = async (req, res) => { + const token = getReqAuthToken(req); + const decodedToken = await verifyAuthToken(token); + + if (!decodedToken) { + return res.status(401).json({ + error: 'Missing or Invalid Token', + }); + } + + return res.status(200).json({ + ...decodedToken, + }); +}; + module.exports = { - postLogin, + postAuth, + getAuth, }; diff --git a/requests/api/auth/get_auth.rest b/requests/api/auth/get_auth.rest new file mode 100644 index 0000000..6bac84f --- /dev/null +++ b/requests/api/auth/get_auth.rest @@ -0,0 +1,2 @@ +GET http://localhost:3001/api/auth/ +Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InNhbXBsZSIsImlkIjoiNWY4ZGEzODcwODlmZmY0ZGIwNmJjMGFmIiwiaWF0IjoxNjA0MTUxOTk5fQ.Wh_KEZVodkyPtZMzrAck1Z1mhIoLu2XoEcCH6G6kMAI \ No newline at end of file diff --git a/requests/api/login/post_login.rest b/requests/api/auth/post_auth.rest similarity index 71% rename from requests/api/login/post_login.rest rename to requests/api/auth/post_auth.rest index c668eb7..5f6416c 100644 --- a/requests/api/login/post_login.rest +++ b/requests/api/auth/post_auth.rest @@ -1,4 +1,4 @@ -POST http://localhost:3001/api/login/ +POST http://localhost:3001/api/auth/ Content-Type: application/json { diff --git a/routes/api/auth.js b/routes/api/auth.js new file mode 100644 index 0000000..c6cc96d --- /dev/null +++ b/routes/api/auth.js @@ -0,0 +1,8 @@ +const authRouter = require('express').Router(); +const authController = require('../../controllers/api/authController'); + +authRouter.post('/', authController.postAuth); + +authRouter.get('/', authController.getAuth); + +module.exports = authRouter; diff --git a/routes/api/login.js b/routes/api/login.js deleted file mode 100644 index 01f2e4d..0000000 --- a/routes/api/login.js +++ /dev/null @@ -1,6 +0,0 @@ -const loginRouter = require('express').Router(); -const loginController = require('../../controllers/api/loginController'); - -loginRouter.post('/', loginController.postLogin); - -module.exports = loginRouter; diff --git a/tests/api/login/login.test.js b/tests/api/auth/auth.test.js similarity index 92% rename from tests/api/login/login.test.js rename to tests/api/auth/auth.test.js index fb4397c..d061dc3 100644 --- a/tests/api/login/login.test.js +++ b/tests/api/auth/auth.test.js @@ -11,8 +11,8 @@ beforeEach(async () => { await initialiseADefaultUserInDb(); }); -describe('Login Controller', () => { - describe('POST request to /api/login/', () => { +describe('Auth Controller', () => { + describe('POST request to /api/auth/', () => { test('When the username is incorrect, return status code 401 and json with error Invalid username and/or password message', async () => { const loginUser = { username: 'Sampl', @@ -20,7 +20,7 @@ describe('Login Controller', () => { }; const response = await api - .post('/api/login') + .post('/api/auth') .send(loginUser) .expect(401) .expect('Content-Type', /application\/json/); @@ -35,7 +35,7 @@ describe('Login Controller', () => { }; const response = await api - .post('/api/login') + .post('/api/auth') .send(loginUser) .expect(401) .expect('Content-Type', /application\/json/); @@ -50,7 +50,7 @@ describe('Login Controller', () => { }; const response = await api - .post('/api/login') + .post('/api/auth') .send(loginUser) .expect(401) .expect('Content-Type', /application\/json/); @@ -65,7 +65,7 @@ describe('Login Controller', () => { }; const response = await api - .post('/api/login') + .post('/api/auth') .send(loginUser) .expect(200) .expect('Content-Type', /application\/json/); diff --git a/utils/jwtUtils.js b/utils/jwtUtils.js new file mode 100644 index 0000000..557aeb1 --- /dev/null +++ b/utils/jwtUtils.js @@ -0,0 +1,37 @@ +const jwt = require('jsonwebtoken'); +const config = require('./config'); +const User = require('../models/user'); + +const getReqAuthToken = (req) => { + const authorization = req.get('Authorization'); + + if (authorization && authorization.toLowerCase().startsWith('bearer ')) { + return authorization.substring(7); + } + + return null; +}; + +/** + * Verify the input jwt token. + * Returns null if invalid/missing token or the user id in the payload is not found in database. + * If the token is valid and the user id is valid, returns the decoded token. + * @param {String} authToken The jwt user token + */ +const verifyAuthToken = async (authToken) => { + const decodedAuthToken = !authToken + ? null + : jwt.verify(authToken, config.JWT_SECRET); + + // Return null, it is an invalid token or the user id is not found in database. + if (!authToken || !(await User.findById(decodedAuthToken.id))) { + return null; + } + + return decodedAuthToken; +}; + +module.exports = { + getReqAuthToken, + verifyAuthToken, +}; diff --git a/utils/middleware.js b/utils/middleware.js index 04ce962..e04c340 100644 --- a/utils/middleware.js +++ b/utils/middleware.js @@ -1,35 +1,23 @@ /** * The custom middlewares */ -const jwt = require('jsonwebtoken'); const logger = require('./logger'); -const config = require('./config'); -const User = require('../models/user'); - -const getTokenFrom = (req) => { - const authorization = req.get('Authorization'); - - if (authorization && authorization.toLowerCase().startsWith('bearer ')) { - return authorization.substring(7); - } - - return null; -}; +const { getReqAuthToken, verifyAuthToken } = require('./jwtUtils'); /** * Middleware to protect API endpoints through token validation. - * Returns json error message with status code 401 if token is missing or invalid. - * If token is valid, add decodedToken Object Property to req. + * Returns json error message with status code 401 if token is missing, invalid + * or user id in payload is invalid. + * If token is valid and user id is valid, add decodedToken Object Property to req. * @param {Object} req request Object * @param {Object} res response Object * @param {Function} next Function which can be called to pass controls to the next handler */ const tokenValidation = async (req, res, next) => { - const token = getTokenFrom(req); - const decodedToken = !token ? null : jwt.verify(token, config.JWT_SECRET); + const token = getReqAuthToken(req); + const decodedToken = await verifyAuthToken(token); - // Only registered users can post to softwares API endpoint - if (!token || !decodedToken.id || !(await User.findById(decodedToken.id))) { + if (!decodedToken) { return res.status(401).json({ error: 'Missing or Invalid Token', });