Skip to content
This repository has been archived by the owner on Jul 16, 2022. It is now read-only.

Commit

Permalink
Merge pull request #25 from newdevtech/feature/166473183/user-authent…
Browse files Browse the repository at this point in the history
…ication

#166473183 Add auth functionality
  • Loading branch information
ebenezerdon committed Jun 5, 2019
2 parents 1470afd + 50ba2c7 commit 0f6f5cf
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 61 deletions.
1 change: 1 addition & 0 deletions .codeclimate.yml
@@ -1,2 +1,3 @@
exclude_patterns:
- 'src/database'
- '**/*.spec.js'
158 changes: 158 additions & 0 deletions src/app/user/_tests_/user.spec.js
@@ -0,0 +1,158 @@
import chai, { expect } from 'chai';
import chaiHttp from 'chai-http';
import models from '../../../database/models';
import server from '../../../index';

chai.use(chaiHttp);

const baseUrl = '/api/v1/users';

let uniqueUser;
const userRequestObject = {
username: 'buttercup',
email: 'buttercup@puffmail.com',
firstName: 'butter',
password: 'superbuttercup',
};

describe('User Test Suite', () => {
before(async () => {
await models.sequelize.sync({ force: true });
const requestObject = {
...userRequestObject,
...{
username: 'uniqueUser',
email: 'unique@email.com',
},
};
uniqueUser = (await chai
.request(server)
.post(`${baseUrl}/auth/signup`)
.send(requestObject)).body.user;
});

describe('Input Validation', () => {
it('should not signup a user with missing required field', async () => {
const requestObject = {
...userRequestObject,
...{ username: undefined },
};
const response = await chai
.request(server)
.post(`${baseUrl}/auth/signup`)
.send(requestObject);
const errorMessage = 'username is required';
expect(response.body.error).to.equal(errorMessage);
expect(response.status).to.equal(400);
});

it('should not signup a user with empty param', async () => {
const requestObject = { ...userRequestObject, ...{ username: '' } };
const response = await chai
.request(server)
.post(`${baseUrl}/auth/signup`)
.send(requestObject);
const errorMessage = 'username must not be empty';
expect(response.body.error).to.equal(errorMessage);
expect(response.status).to.equal(400);
});

it('should not signup a user with invalid email', async () => {
const requestObject = { ...userRequestObject, ...{ email: 'invalid' } };
const response = await chai
.request(server)
.post(`${baseUrl}/auth/signup`)
.send(requestObject);
const errorMessage = 'Please enter a valid email';
expect(response.body.error).to.equal(errorMessage);
expect(response.status).to.equal(400);
});

it('should not signup a user with invalid input type', async () => {
const requestObject = { ...userRequestObject };
requestObject.firstName = ['butter', 'cup'];
const response = await chai
.request(server)
.post(`${baseUrl}/auth/signup`)
.send(requestObject);
const errorMessage = '[firstName] must be of type: string';
expect(response.body.error).to.equal(errorMessage);
expect(response.status).to.equal(400);
});

it('should not signup a user if password is less than 8', async () => {
const requestObject = { ...userRequestObject, ...{ password: 'bugs' } };
const response = await chai
.request(server)
.post(`${baseUrl}/auth/signup`)
.send(requestObject);
const errorMessage = 'Your password must be at least 8 characters';
expect(response.body.error).to.equal(errorMessage);
expect(response.status).to.equal(400);
});

it('should not signup a user if username is not unique', async () => {
const requestObject = { ...userRequestObject };
requestObject.username = uniqueUser.username;
const response = await chai
.request(server)
.post(`${baseUrl}/auth/signup`)
.send(requestObject);
const errorMessage = 'username already in use';
expect(response.body.error).to.equal(errorMessage);
expect(response.status).to.equal(409);
});

it('should not signup a user if email is not unique', async () => {
const requestObject = { ...userRequestObject };
requestObject.email = uniqueUser.email;
const response = await chai
.request(server)
.post(`${baseUrl}/auth/signup`)
.send(requestObject);
const errorMessage = 'email already in use';
expect(response.body.error).to.equal(errorMessage);
expect(response.status).to.equal(409);
});

it('should signup a user and return token if valid inputs', async () => {
const response = await chai
.request(server)
.post(`${baseUrl}/auth/signup`)
.send(userRequestObject);
expect(response.body.message).to.equal('Registration Successful!');
expect(response.body.token.length).to.be.greaterThan(0);
expect(response.status).to.equal(201);
});
});

describe('Login', () => {
it('should not login a user with invalid credentials', async () => {
const requestObject = {
usernameOrEmail: uniqueUser.username,
password: 'invalid-password',
};
const response = await chai
.request(server)
.post(`${baseUrl}/auth/login`)
.send(requestObject);
const errorMessage = 'Invalid credentials';
expect(response.body.error).to.equal(errorMessage);
expect(response.status).to.equal(401);
});

it('should return token if credentials are valid', async () => {
const requestObject = {
usernameOrEmail: uniqueUser.username,
password: userRequestObject.password,
};
const response = await chai
.request(server)
.post(`${baseUrl}/auth/login`)
.send(requestObject);
expect(response.body.message).to.equal('Login Successful!');
expect(response.body.token.length).to.be.greaterThan(0);
expect(response.status).to.equal(201);
});
});
});
19 changes: 8 additions & 11 deletions src/common/helpers/paramValidator.js
Expand Up @@ -7,7 +7,7 @@ const requiredParamsValidator = (body, requiredParams, next) => {
if (!Object.keys(body).includes(param)) {
errorArray.push(`${param} is required`);
}
if (param.trim().length < 1) {
if (Object.keys(body).includes(param) && body[param].length < 1) {
errorArray.push(`${param} must not be empty`);
}
});
Expand Down Expand Up @@ -57,17 +57,14 @@ const typeValidator = (params, type, next) => {
const uniqueParamValidator = async (param, model, next) => {
const paramKey = Object.keys(param);
const paramValue = String(Object.values(param)).toLowerCase();
const paramExists = await models[model].findOne({
where: { [paramKey]: paramValue },
});

if (paramValue) {
const paramExists = await models[model].findOne({
where: { [paramKey]: paramValue },
});
if (paramExists) {
const error = new Error(`${paramKey} already in use`);
error.status = 409;
return next(error);
}
return next();
if (paramExists) {
const error = new Error(`${paramKey} already in use`);
error.status = 409;
return next(error);
}

return next();
Expand Down
2 changes: 2 additions & 0 deletions src/common/index.js
Expand Up @@ -4,3 +4,5 @@ export {
uniqueParamValidator,
generateToken,
} from './helpers';

export { notFoundRoute, errorHandler } from './middleware';
33 changes: 33 additions & 0 deletions src/common/middleware/authentication.js
@@ -0,0 +1,33 @@
import jwt from 'jsonwebtoken';
import models from '../../database/models';

const { User } = models;

const secret = process.env.SECRET_KEY;

const isTokenValid = (req, res, next) => {
const token = req.headers.authorization;
jwt.verify(token, secret, async (err, decoded) => {
let error;
if (!token) {
error = new Error('No token provided');
error.status = 401;
return next(error);
}
if (err) {
error = new Error('Invalid token');
error.status = 401;
return next(error);
}
const user = await User.findByPk(decoded.id);
if (!user) {
error = new Error('Invalid token');
error.status = 401;
return next(error);
}
req.decoded = decoded;
return next();
});
};

export default isTokenValid;
1 change: 1 addition & 0 deletions src/common/middleware/index.js
@@ -1,2 +1,3 @@
export { default as notFoundRoute } from './notFoundRoute';
export { default as errorHandler } from './errorHandler';
export { default as authentication } from './authentication';
35 changes: 16 additions & 19 deletions src/database/models/averageRating.js
@@ -1,26 +1,23 @@
export default (sequelize, DataTypes) => {
const AverageRating = sequelize.define(
'AverageRating',
{
id: {
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
resourceId: {
allowNull: false,
type: DataTypes.UUID
},
averageRating: {
allowNull: false,
type: DataTypes.FLOAT,
}
const AverageRating = sequelize.define('AverageRating', {
id: {
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
);
resourceId: {
allowNull: false,
type: DataTypes.UUID,
},
averageRating: {
allowNull: false,
type: DataTypes.FLOAT,
},
});

AverageRating.associate = (models) => {
AverageRating.associate = models => {
AverageRating.belongsTo(models.Resource, {
foreignKey: 'averageRating'
foreignKey: 'averageRatingId',
});
};

Expand Down
55 changes: 25 additions & 30 deletions src/database/models/languageStack.js
@@ -1,40 +1,35 @@
export default (sequelize, DataTypes) => {
const LanguageStack = sequelize.define(
'LanguageStack',
{
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
userId: {
allowNull: false,
type: DataTypes.UUID
},
stackId: {
allowNull: false,
type: DataTypes.UUID
},
languageId: {
allowNull: false,
type: DataTypes.UUID
},
const LanguageStack = sequelize.define('LanguageStack', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
);
userId: {
allowNull: false,
type: DataTypes.UUID,
},
stackId: {
allowNull: false,
type: DataTypes.UUID,
},
languageId: {
allowNull: false,
type: DataTypes.UUID,
},
});

LanguageStack.associate = (models) => {
LanguageStack.belongsTo(models.Language, {
foreignKey: 'LanguageId',
as: 'language'
LanguageStack.associate = models => {
LanguageStack.hasMany(models.Language, {
foreignKey: 'LanguageStackId',
});
LanguageStack.belongsTo(models.Stack, {
foreignKey: 'StackId',
as: 'stack'
LanguageStack.hasMany(models.Stack, {
foreignKey: 'LanguageStackId',
});
LanguageStack.belongsTo(models.User, {
foreignKey: 'userId',
as: 'user'
as: 'user',
});
};

Expand Down
2 changes: 1 addition & 1 deletion src/index.js
Expand Up @@ -4,7 +4,7 @@ import swaggerUi from 'swagger-ui-express';
import cors from 'cors';
import dotenv from 'dotenv';
import userRoutes from './app';
import { notFoundRoute, errorHandler } from './common/middleware';
import { notFoundRoute, errorHandler } from './common';
import { Api } from '../docs';

dotenv.config();
Expand Down

0 comments on commit 0f6f5cf

Please sign in to comment.