Skip to content

Commit

Permalink
Add type to token payloads
Browse files Browse the repository at this point in the history
closes #28
  • Loading branch information
hagopj13 committed Sep 14, 2020
1 parent 3eb644e commit eb5de2c
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 23 deletions.
4 changes: 4 additions & 0 deletions src/config/passport.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
const config = require('./config');
const { tokenTypes } = require('./tokens');
const { User } = require('../models');

const jwtOptions = {
Expand All @@ -9,6 +10,9 @@ const jwtOptions = {

const jwtVerify = async (payload, done) => {
try {
if (payload.type !== tokenTypes.ACCESS) {
throw new Error('Invalid token type');
}
const user = await User.findById(payload.sub);
if (!user) {
return done(null, false);
Expand Down
9 changes: 5 additions & 4 deletions src/services/token.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ const { tokenTypes } = require('../config/tokens');
* @param {string} [secret]
* @returns {string}
*/
const generateToken = (userId, expires, secret = config.jwt.secret) => {
const generateToken = (userId, expires, type, secret = config.jwt.secret) => {
const payload = {
sub: userId,
iat: moment().unix(),
exp: expires.unix(),
type,
};
return jwt.sign(payload, secret);
};
Expand Down Expand Up @@ -65,10 +66,10 @@ const verifyToken = async (token, type) => {
*/
const generateAuthTokens = async (user) => {
const accessTokenExpires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const accessToken = generateToken(user.id, accessTokenExpires);
const accessToken = generateToken(user.id, accessTokenExpires, tokenTypes.ACCESS);

const refreshTokenExpires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = generateToken(user.id, refreshTokenExpires);
const refreshToken = generateToken(user.id, refreshTokenExpires, tokenTypes.REFRESH);
await saveToken(refreshToken, user.id, refreshTokenExpires, tokenTypes.REFRESH);

return {
Expand All @@ -94,7 +95,7 @@ const generateResetPasswordToken = async (email) => {
throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email');
}
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = generateToken(user.id, expires);
const resetPasswordToken = generateToken(user.id, expires, tokenTypes.RESET_PASSWORD);
await saveToken(resetPasswordToken, user.id, expires, tokenTypes.RESET_PASSWORD);
return resetPasswordToken;
};
Expand Down
5 changes: 3 additions & 2 deletions tests/fixtures/token.fixture.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
const moment = require('moment');
const config = require('../../src/config/config');
const { tokenTypes } = require('../../src/config/tokens');
const tokenService = require('../../src/services/token.service');
const { userOne, admin } = require('./user.fixture');

const accessTokenExpires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const userOneAccessToken = tokenService.generateToken(userOne._id, accessTokenExpires);
const adminAccessToken = tokenService.generateToken(admin._id, accessTokenExpires);
const userOneAccessToken = tokenService.generateToken(userOne._id, accessTokenExpires, tokenTypes.ACCESS);
const adminAccessToken = tokenService.generateToken(admin._id, accessTokenExpires, tokenTypes.ACCESS);

module.exports = {
userOneAccessToken,
Expand Down
49 changes: 32 additions & 17 deletions tests/integration/auth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ describe('Auth routes', () => {
test('should return 204 if refresh token is valid', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = tokenService.generateToken(userOne._id, expires);
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH);

await request(app).post('/v1/auth/logout').send({ refreshToken }).expect(httpStatus.NO_CONTENT);
Expand All @@ -143,15 +143,15 @@ describe('Auth routes', () => {
test('should return 404 error if refresh token is not found in the database', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = tokenService.generateToken(userOne._id, expires);
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);

await request(app).post('/v1/auth/logout').send({ refreshToken }).expect(httpStatus.NOT_FOUND);
});

test('should return 404 error if refresh token is blacklisted', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = tokenService.generateToken(userOne._id, expires);
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH, true);

await request(app).post('/v1/auth/logout').send({ refreshToken }).expect(httpStatus.NOT_FOUND);
Expand All @@ -162,7 +162,7 @@ describe('Auth routes', () => {
test('should return 200 and new auth tokens if refresh token is valid', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = tokenService.generateToken(userOne._id, expires);
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH);

const res = await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.OK);
Expand All @@ -186,7 +186,7 @@ describe('Auth routes', () => {
test('should return 401 error if refresh token is signed using an invalid secret', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = tokenService.generateToken(userOne._id, expires, 'invalidSecret');
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH, 'invalidSecret');
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH);

await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.UNAUTHORIZED);
Expand All @@ -195,15 +195,15 @@ describe('Auth routes', () => {
test('should return 401 error if refresh token is not found in the database', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = tokenService.generateToken(userOne._id, expires);
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);

await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.UNAUTHORIZED);
});

test('should return 401 error if refresh token is blacklisted', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = tokenService.generateToken(userOne._id, expires);
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH, true);

await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.UNAUTHORIZED);
Expand All @@ -220,7 +220,7 @@ describe('Auth routes', () => {

test('should return 401 error if user is not found', async () => {
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = tokenService.generateToken(userOne._id, expires);
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH);

await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.UNAUTHORIZED);
Expand Down Expand Up @@ -259,7 +259,7 @@ describe('Auth routes', () => {
test('should return 204 and reset the password', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires);
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);

await request(app)
Expand All @@ -285,7 +285,7 @@ describe('Auth routes', () => {
test('should return 401 if reset password token is blacklisted', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires);
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD, true);

await request(app)
Expand All @@ -298,7 +298,7 @@ describe('Auth routes', () => {
test('should return 401 if reset password token is expired', async () => {
await insertUsers([userOne]);
const expires = moment().subtract(1, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires);
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);

await request(app)
Expand All @@ -310,7 +310,7 @@ describe('Auth routes', () => {

test('should return 401 if user is not found', async () => {
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires);
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);

await request(app)
Expand All @@ -323,7 +323,7 @@ describe('Auth routes', () => {
test('should return 400 if password is missing or invalid', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires);
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);

await request(app).post('/v1/auth/reset-password').query({ token: resetPasswordToken }).expect(httpStatus.BAD_REQUEST);
Expand Down Expand Up @@ -387,10 +387,25 @@ describe('Auth middleware', () => {
);
});

test('should call next with unauthorized error if the token is not an access token', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${refreshToken}` } });
const next = jest.fn();

await auth()(req, httpMocks.createResponse(), next);

expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
);
});

test('should call next with unauthorized error if access token is generated with an invalid secret', async () => {
await insertUsers([userOne]);
const tokenExpires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const accessToken = tokenService.generateToken(userOne._id, tokenExpires, 'invalidSecret');
const expires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const accessToken = tokenService.generateToken(userOne._id, expires, tokenTypes.ACCESS, 'invalidSecret');
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${accessToken}` } });
const next = jest.fn();

Expand All @@ -404,8 +419,8 @@ describe('Auth middleware', () => {

test('should call next with unauthorized error if access token is expired', async () => {
await insertUsers([userOne]);
const tokenExpires = moment().subtract(1, 'minutes');
const accessToken = tokenService.generateToken(userOne._id, tokenExpires);
const expires = moment().subtract(1, 'minutes');
const accessToken = tokenService.generateToken(userOne._id, expires, tokenTypes.ACCESS);
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${accessToken}` } });
const next = jest.fn();

Expand Down

0 comments on commit eb5de2c

Please sign in to comment.