Skip to content

Commit

Permalink
feat(authentication): user sign-up and authentication
Browse files Browse the repository at this point in the history
 - update user tests to test for token generation
 - hash user password
 - authenticate user
 - save user data to the database
 - add unit test for user login
 - add user login endpoint
 - document login endpoint
 - add build script to package.json

[Finishes #166789868]
  • Loading branch information
adafia committed Jul 5, 2019
1 parent 863d8e8 commit 936d1ed
Show file tree
Hide file tree
Showing 13 changed files with 231 additions and 49 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,6 @@ package-lock.json

#editorconfig
.editorconfig

#dist
dist
3 changes: 2 additions & 1 deletion .nycrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"exclude": [
"tests",
"src/db/"
"src/db/",
"src/index.js"
]
}
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ However, for the purpose of testing and configurations, one endpoint has been cr

> create database to postgresql
```
> createdb authorsHavenDb
> createdb authorshavendb
```
remember to change the password in config.json file for development
remember to change the password in config.js file for development

> create your config file
```
Expand Down
3 changes: 2 additions & 1 deletion example.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
DATABASE_USERNAME_DEV="your-database-username"
SECRET_KEY="your-database-password"
DATABASE_PASSWORD="your-database-password"
SECRET_JWT_KEY="your-own-secret-key"
HEROKU_DATABASE_USERNAME="heroku-database-username"
HEROKU_SECRET_KEY="heroku-database-password"
HEROKU_HOST="heroku-host"
Expand Down
18 changes: 11 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"coverage": "npm test && nyc report --reporter=text-lcov | coveralls",
"drop_tables": "sequelize db:migrate:undo:all",
"create_tables": "sequelize db:migrate",
"start": "babel-node ./src/index.js",
"start": "node dist/index.js",
"build": "babel src --out-dir dist",
"dev": "nodemon --exec babel-node ./src/index.js"
},
"engines": {
Expand All @@ -18,32 +19,35 @@
"author": "Andela Simulations Programme",
"license": "MIT",
"dependencies": {
"bcrypt": "^3.0.6",
"body-parser": "^1.19.0",
"dotenv": "^6.2.0",
"ejs": "^2.6.1",
"errorhandler": "^1.5.0",
"express": "^4.17.1",
"express-session": "^1.15.6",
"jsonwebtoken": "^8.5.1",
"method-override": "^2.3.10",
"methods": "^1.1.2",
"morgan": "^1.9.1",
"nyc": "^14.1.1",
"pg": "^7.11.0",
"pg-hstore": "^2.3.3",
"regenerator-runtime": "^0.13.2",
"request": "^2.87.0",
"sequelize": "^5.8.12",
"sequelize-cli": "^5.5.0",
"body-parser": "^1.19.0",
"underscore": "^1.9.1",
"swagger-jsdoc": "^1.3.0",
"swagger-ui-express": "^4.0.2",
"underscore": "^1.9.1"
},
"devDependencies": {
"mocha": "^6.1.4",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-polyfill": "^6.26.0",
"babel-register": "^6.26.0",
"babel-preset-env": "^1.7.0"
},
"devDependencies": {
"mocha": "^6.1.4",
"babel-preset-env": "^1.7.0",
"chai": "^4.2.0",
"chai-http": "^4.3.0",
"coveralls": "^3.0.4",
Expand Down
61 changes: 58 additions & 3 deletions src/controllers/userControllers.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import model from '../db/models/index';

const { Users } = model;
Expand All @@ -14,17 +16,25 @@ class UserManager {
*/
static async registerUser(req, res) {
try {
const { username, email, password } = req.body;
const {
username, email, password, bio, image
} = req.body;
const user = {
username, email, hash: password
username, email, hash: password, bio, image: null
};

const payload = { username, email };
const token = await jwt.sign(payload, process.env.SECRET_JWT_KEY, { expiresIn: '24h' });

await Users.create(user);
return res.status(201).json({
message: 'user registered succesfully',
user: {
username,
email,
token,
username,
bio,
image
}
});
} catch (error) {
Expand All @@ -33,5 +43,50 @@ class UserManager {
});
}
}

/**
*
* @param {object} req
* @param {object} res
* @returns {Object} user object
*/
static async login(req, res) {
try {
const findUser = await Users.findOne({
where: { email: req.body.email }
});

if (findUser) {
const { username, email, hash } = findUser.dataValues;
const userData = { username, email, hash };

if (bcrypt.compareSync(req.body.password, userData.hash)) {
const payload = {
username: userData.username,
email: userData.email
};
const token = jwt.sign(payload, process.env.SECRET_JWT_KEY, { expiresIn: '24h' });
return res.status(200).json({
message: 'login succesfull',
user: {
token,
email: payload.email,
username: payload.username
}
});
}
return res.status(401).json({
message: 'incorrect password'
});
}
return res.status(404).json({
message: `user with email: ${req.body.email} does not exist`
});
} catch (error) {
return res.status(500).json({
message: 'internal server error! please try again later'
});
}
}
}
export default UserManager;
7 changes: 3 additions & 4 deletions src/db/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ const config = {
},
test: {
username: envConfig.db_username_test,
password: envConfig.db_password,
password: envConfig.db_password_test,
database: 'ah_db_test',
host: '127.0.0.1',
dialect: 'postgres',
operatorsAliases: false
host: 'localhost',
dialect: 'postgres'
},
production: {
username: envConfig.db_username_pro,
Expand Down
2 changes: 1 addition & 1 deletion src/db/config/envirnoment.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ envConfig.db_password = process.env.DATABASE_PASSWORD;
envConfig.port = process.env.PORT || 7000;

envConfig.db_username_test = process.env.DATABASE_USERNAME_TEST;
envConfig.db_password_test = process.env.DATABASE_PASSWORD;
envConfig.db_password_test = process.env.DATABASE_PASSWORD_TEST;

envConfig.db_username_pro = process.env.HEROKU_DATABASE_USERNAME;
envConfig.db_password_pro = process.env.HEROKU_SECRET_KEY;
Expand Down
15 changes: 14 additions & 1 deletion src/db/models/users.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import bcrypt from 'bcrypt';

export default (sequelize, DataTypes) => {
const Users = sequelize.define('Users', {
username: {
Expand Down Expand Up @@ -30,7 +32,18 @@ export default (sequelize, DataTypes) => {
}
}],
hash: DataTypes.STRING
}, {});
}, {
hooks: {
beforeCreate: async (user) => {
user.hash = await bcrypt.hashSync(user.hash, 8);
},
},
instanceMethods: {
validatePassword: async function(hash) {
return await bcrypt.compareSync(hash, this.password);
}
}
});
Users.associate = () => {
};
return Users;
Expand Down
10 changes: 6 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'regenerator-runtime';
import express from 'express';
import logger from 'morgan';
import bodyParser from 'body-parser';
Expand All @@ -8,16 +9,17 @@ import config from './db/config/envirnoment';

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

// Access swagger ui documentation on this route
app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(swaggerJSDoc));

app.use(logger('dev')); // log requests to the console

// Parse incoming requests data
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(routes);
app.get('*', (req, res) => res.status(200).send({

// Access swagger ui documentation on this route
app.use('/', swaggerUI.serve, swaggerUI.setup(swaggerJSDoc));

app.use('/*', (req, res) => res.status(200).send({
message: 'Welcome to the default Authors Haven API route',
}));

Expand Down
1 change: 1 addition & 0 deletions src/routes/api/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ import userController from '../../controllers/userControllers';
const router = express.Router();

router.post('/users', userController.registerUser);
router.post('/users/login', userController.login);

export default router;
55 changes: 54 additions & 1 deletion swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"version": "1.0.0",
"description": "Authors Haven Documentation with Swagger"
},
"host": "ah-lobos-backend-swagger.herokuapp.com",
"host": "ah-lobos-staging.herokuapp.com",
"basePath": "/",
"tags": [
{
Expand Down Expand Up @@ -34,6 +34,24 @@
"type": "string"
}
}
},
"login": {
"required": [
"username",
"email",
"password"
],
"properties": {
"username": {
"type": "string"
},
"email": {
"type": "string"
},
"password": {
"type": "string"
}
}
}
},
"paths": {
Expand Down Expand Up @@ -65,6 +83,41 @@
}
}
}
},
"/api/users/login": {
"post": {
"tags": ["users"],
"description": "Login a user",
"parameters": [
{
"name": "body",
"in": "body",
"description": "User login",
"require": true,
"schema": {
"$ref": "#/definitions/login"
}
}
],
"produces": ["application/json"],
"responses": {
"200": {
"description": "login succesfull",
"schema": {
"$ref": "#/definitions/login"
}
},
"404": {
"description": "user with email does not exist"
},
"401": {
"description": "incorrect password"
},
"500": {
"description": "internal server error! please try again later"
}
}
}
}
}
}
Loading

0 comments on commit 936d1ed

Please sign in to comment.