Skip to content

Commit

Permalink
:feat(sign out): user should be able to sign out
Browse files Browse the repository at this point in the history
  - blacklist tokens and drop them as user sign out

[Finishes #166789871]
  • Loading branch information
manzif committed Jul 9, 2019
1 parent 7ee9714 commit e8865fe
Show file tree
Hide file tree
Showing 16 changed files with 261 additions and 6 deletions.
8 changes: 8 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ DATABASE_USERNAME_DEV="your-database-username"
DATABASE_PASSWORD="your-database-password"
DATABASE_PASSWORD_TEST="your-postgres-password-test"
SECRET_JWT_KEY="your-own-secret-key"
DATABASE_USERNAME_TEST="your-database-username-for-test"
SENDGRID_API_KEY="insert your send grid token"
SECRET_JWT_KEY='write your secret key'
SECRET_JWT_KEY="your-own-secret-key"
SECRET_KEY="your-database-password"
DATABASE_PASSWORD_TEST="password"
DATABASE_USERNAME_TEST="postgres"
HEROKU_DATABASE_USERNAME="heroku-database-username"
HEROKU_SECRET_KEY="heroku-database-password"
HEROKU_HOST="heroku-host"
HEROKU_DATABASE="heroku-database-name"
SendGridKey=""

7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
"dependencies": {
"@hapi/joi": "^15.1.0",
"@sendgrid/mail": "^6.4.0",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-register": "^6.26.0",
"bcrypt": "^3.0.6",
"body-parser": "^1.19.0",
"dotenv": "^6.2.0",
Expand All @@ -34,6 +39,7 @@
"methods": "^1.1.2",
"morgan": "^1.9.1",
"nodemailer": "^6.2.1",
"node-cron": "^2.0.3",
"nyc": "^14.1.1",
"pg": "^7.11.0",
"pg-hstore": "^2.3.3",
Expand Down Expand Up @@ -61,6 +67,7 @@
"eslint-plugin-jsx-a11y": "^6.2.3",
"mocha": "^6.1.4",
"mocha-lcov-reporter": "^1.3.0",
"node-mocks-http": "^1.7.6",
"nodemon": "^1.19.1"
}
}
19 changes: 18 additions & 1 deletion src/controllers/userControllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import sendVerificationEmail from '../helpers/sendVerificationEmail';

dotenv.config();

const { Users } = model;
const { Users, BlacklistTokens } = model;

/**
* user controller
Expand Down Expand Up @@ -38,6 +38,7 @@ class UserManager {
message: 'Thank you for registration, You should check your email for verification',
});
} catch (error) {
console.log(error);
return res.status(409).json({
message: 'user with the same email already exist'
});
Expand Down Expand Up @@ -208,5 +209,21 @@ class UserManager {
});
}
}

/**
*
* @param {obkect} req
* @param {object} res
* @returns {message} User logged out
*/
static async logout(req, res) {
const { token } = req.headers;
const tokenPayload = await processToken.verifyToken(token);
const { exp } = tokenPayload;
await BlacklistTokens.create({ token, expires: exp * 1000 });
return res.status(200).json({
message: 'User logged out'
});
}
}
export default UserManager;
11 changes: 11 additions & 0 deletions src/db/migrations/20190704064417-create-blacklist-tokens.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

module.exports = {
up: function(queryInterface, Sequelize) {
return Promise.resolve()
},

down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('BlacklistTokens');
}
};
11 changes: 11 additions & 0 deletions src/db/migrations/20190708104045-create-blacklist-tokens.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

module.exports = {
up: function(queryInterface, Sequelize) {
return Promise.resolve()
},

down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('BlacklistTokens');
}
};
11 changes: 11 additions & 0 deletions src/db/migrations/20190708142302-create-blacklist-token.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

module.exports = {
up: function(queryInterface, Sequelize) {
return Promise.resolve()
},

down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('BlacklistTokens');
}
};
11 changes: 11 additions & 0 deletions src/db/migrations/20190708144428-create-blacklist-tokens.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

module.exports = {
up: function(queryInterface, Sequelize) {
return Promise.resolve()
},

down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('BlacklistTokens');
}
};
30 changes: 30 additions & 0 deletions src/db/migrations/20190708151534-create-blacklist-tokens.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('BlacklistTokens', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
token: {
type: Sequelize.TEXT
},
expires: {
type: Sequelize.BIGINT
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('BlacklistTokens');
}
};
11 changes: 11 additions & 0 deletions src/db/models/blacklisttokens.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const BlacklistTokens = sequelize.define('BlacklistTokens', {
token: DataTypes.TEXT,
expires: DataTypes.BIGINT
}, {});
BlacklistTokens.associate = function(models) {
// associations can be defined here
};
return BlacklistTokens;
};
16 changes: 16 additions & 0 deletions src/helpers/deleteBlacklistTokens.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'regenerator-runtime';
import Sequelize from 'sequelize';
import db from '../db/models';

const { BlacklistTokens } = db;
const { Op } = Sequelize;

module.exports = async () => {
await BlacklistTokens.destroy({
where: {
expires: {
[Op.lte]: Date.now()
}
}
});
};
4 changes: 2 additions & 2 deletions src/helpers/processToken.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class processToken {
* @returns {object} token
*/
static async signToken(payload) {
const token = await jwt.sign(payload, process.env.SECRET_JWT_KEY, { expiresIn: '24h' });
const token = await jwt.sign(payload, process.env.SECRET_JWT_KEY, { expiresIn: 60 });
return token;
}

Expand All @@ -22,7 +22,7 @@ class processToken {
* @returns {object} verified token
*/
static async verifyToken(token) {
const verifyToken = await jwt.verify(token, process.env.SECRET_JWT_KEY, { expiresIn: '24h' });
const verifyToken = await jwt.verify(token, process.env.SECRET_JWT_KEY, { expiresIn: 60 });
return verifyToken;
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import express from 'express';
import logger from 'morgan';
import bodyParser from 'body-parser';
import swaggerUI from 'swagger-ui-express';
import cron from 'node-cron';
import swaggerJSDoc from '../swagger.json';
import routes from './routes/api/index';
import config from './db/config/envirnoment';
import deleteBlacklist from './helpers/deleteBlacklistTokens';

const app = express(); // setup express application

Expand All @@ -15,7 +17,10 @@ app.use(logger('dev')); // log requests to the console
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(routes);

// cron fro deleting blacklist tokens
cron.schedule('* * * * *', () => {
deleteBlacklist();
});
// Access swagger ui documentation on this route
app.use('/', swaggerUI.serve, swaggerUI.setup(swaggerJSDoc));

Expand Down
28 changes: 28 additions & 0 deletions src/middlewares/logout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

import models from '../db/models';

const { BlacklistTokens } = models;
/**
* logout
*/
class logout {
/**
*
* @param {object} req
* @param {object} res
* @param {object} next
* @returns {message} user already logged out
*/
static async logoutToken(req, res, next) {
const head = req.headers.token;
const findToken = await BlacklistTokens.findOne({
where: { token: head }
});
if (findToken) {
return res.status(400).json({ message: 'user already logged out' });
}
next();
}
}

export default logout;
4 changes: 4 additions & 0 deletions src/routes/api/users.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import express from 'express';
import userController from '../../controllers/userControllers';
import SignupValidation from '../../middlewares/signup';
import logout from '../../middlewares/logout';

const { logoutToken } = logout;

const router = express.Router();

Expand All @@ -9,5 +12,6 @@ router.post('/users/login', userController.login);
router.post('/user/forgot-password', userController.forgotPassword);
router.post('/user/reset-password/:userToken', userController.resetPassword);
router.post('/users', SignupValidation.signupvalidator, userController.registerUser);
router.post('/users/logout', logoutToken, userController.logout);

export default router;
37 changes: 37 additions & 0 deletions swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@
"type": "string"
}
}
},
"logout": {
"required": [
"token"
],
"properties": {
"token": {
"type": "text"
}
}
}
},
"paths": {
Expand Down Expand Up @@ -229,6 +239,33 @@
}
}
}
},
"/api/users/logout":{
"post": {
"tags": [
"users"
],
"description": "Log out a user",
"parameters": [
{
"name":"token",
"in": "header",
"description" :"Token",
"required":true
}
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "User logged out",
"schema": {
"$ref": "#/definitions/logout"
}
}
}
}
}
}
}
Loading

0 comments on commit e8865fe

Please sign in to comment.