Skip to content

Commit

Permalink
ft(social auth): implement social auth
Browse files Browse the repository at this point in the history
- implement facebook authentication
- implement google authentication
- write unit tests
- document api using swagger
[Delivers #169817866]
  • Loading branch information
jo-rdan committed Dec 16, 2019
1 parent 261911e commit 2577ee0
Show file tree
Hide file tree
Showing 22 changed files with 1,070 additions and 210 deletions.
Binary file added .DS_Store
Binary file not shown.
8 changes: 8 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"regenerator": true
}
]
],
"presets": ["@babel/preset-env"]
}
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@
DATABASE_URL_DEV=VALUE
DATABASE_URL_TEST=VALUE
DATABASE_URL=VALUE
GOOGLE_API=VALUE
GOOGLE_SECRET=VALUE

FACEBOOK_API=VALUE
FACEBOOK_SECRET=VALUE
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
src/app.js
src/database/models/index.js
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ dist/
coverage/

.DS_Store
coverage/
3 changes: 1 addition & 2 deletions .hound.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ eslint:
config_file: .eslintrc.json
ignore_file: .eslintignore

fail_on_violations: false

fail_on_violations: false
903 changes: 704 additions & 199 deletions package-lock.json

Large diffs are not rendered by default.

29 changes: 22 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
"main": "index.js",
"nyc": {
"exclude": [
"src/helpers/logger.helper.js"
"src/helpers/logger.helper.js",
"src/database/models/*",
"src/routes/socialRoute.js",
"src/config/passport.config.js",
"src/app.js"
]
},
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"start": "NODE_ENV=production babel-node ./src/index.js",
"test": "nyc --reporter=html --reporter=text mocha --require @babel/register src/tests/*.js --require @babel/polyfill --require @babel/register --timeout 10000 --exit",
"test": "NODE_ENV=testing npm run db-undo-migration && NODE_ENV=testing npm run db-migrate && NODE_ENV=testing nyc --reporter=html --reporter=text mocha --require @babel/register src/tests/*.js --exit",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"dev": "NODE_ENV=development nodemon --exec babel-node ./src/index.js",
"db-migrate": "npx sequelize db:migrate",
Expand All @@ -34,31 +38,42 @@
"@babel/cli": "^7.7.4",
"@babel/core": "^7.7.4",
"@babel/node": "^7.7.4",
"@babel/plugin-transform-runtime": "^7.6.0",
"@babel/polyfill": "^7.7.0",
"@babel/plugin-transform-regenerator": "^7.7.5",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.4",
"@babel/runtime": "^7.7.6",
"body-parser": "^1.19.0",
"chai": "^4.2.0",
"chai-http": "^4.3.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-validator": "^6.3.0",
"faker": "^4.1.0",
"jsonwebtoken": "^8.5.1",
"log4js": "^6.1.0",
"passport": "^0.4.0",
"passport-facebook": "^3.0.0",
"passport-facebook-token": "^3.3.0",
"passport-google-oauth20": "^2.0.0",
"passport-google-plus-token": "^2.1.0",
"pg": "^7.14.0",
"pg-hstore": "^2.3.3",
"sequelize": "^5.21.2",
"sequelize-cli": "^5.5.1",
"sinon": "^7.5.0",
"sinon-chai": "^3.3.0",
"swagger-jsdoc": "^3.4.0",
"swagger-ui-express": "^4.1.2"
},
"devDependencies": {
"mocha": "^6.2.2",
"@passport-next/chai-passport-strategy": "^1.1.0",
"coverage": "^0.4.1",
"coveralls": "^3.0.9",
"eslint": "^6.7.1",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-plugin-import": "^2.18.2",
"mocha-lcov-reporter": "^1.3.0",
"nyc": "^14.1.1",
"nodemon": "^2.0.1"
"nodemon": "^2.0.1",
"nyc": "^14.1.1"
}
}
6 changes: 4 additions & 2 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import express from 'express';
import bodyParser from 'body-parser';
import swagger from './routes/swagger';
import socialRoute from './routes/social.route';
import './config/passport.config';

const app = express();
app.use(express.json());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

app.use('/API/v1', swagger);
app.use('/', swagger);
app.use('/api/v1/auth', socialRoute);
app.get('/', (req, res) => res.status(200).send({ status: 200, message: 'Welcome to Barefoot Nomad!' }));

export default app;
30 changes: 30 additions & 0 deletions src/config/passport.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
import dotenv from 'dotenv';
import passport from 'passport';
import FacebookStrategy from 'passport-facebook';
import userController from '../controllers/user.controller';

dotenv.config();
const {
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
FACEBOOK_CLIENT_ID,
FACEBOOK_CLIENT_SECRET,
BASE_URL
} = process.env;
passport.use(
new GoogleStrategy({
callbackURL: `${BASE_URL}/api/v1/auth/google/redirect`,
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
googleFields: ['emails', 'firstName', 'lastName'],
}, userController.googleAndFacebookPlusAuth)
);
passport.use(
new FacebookStrategy({
callbackURL: `${BASE_URL}/api/v1/auth/facebook/redirect`,
clientID: FACEBOOK_CLIENT_ID,
clientSecret: FACEBOOK_CLIENT_SECRET,
profileFields: ['id', 'displayName', 'first_name', 'last_name', 'email'],
}, userController.googleAndFacebookPlusAuth)
);
Empty file removed src/controllers/index.js
Empty file.
62 changes: 62 additions & 0 deletions src/controllers/user.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import encryptToken from '../helpers/encryption';
import db from '../database/models';

/**
* class that contains functions to implement login with social accounts
*/
class UserController {
/**
*login function to get profile from google and facebook and manipulate it
*
*
*@param {object} accessToken response
*@param {object} refreshToken response
*@param {object} profile object
*@param {object} done callback
*@returns {object} object
*/
static async googleAndFacebookPlusAuth(accessToken, refreshToken, profile, done) {
try {
const userData = {
id: profile.id,
firstname: profile.name.givenName,
lastname: profile.name.familyName,
email: profile.emails[0].value,
authtype: profile.provider
};
const {
email, firstname, lastname, authtype
} = userData;
await db.users.findOrCreate({
where: { email },
defaults: {
firstname,
lastname,
email,
password: null,
authtype
}
});
done(null, userData);
} catch (error) {
done(error, false, error.message);
}
}

/**
*login function to return data from social accounts to the user
*
*
*@param {object} req request
*@param {object} res response
*@returns {object} object
*/
static authGoogleAndFacebook(req, res) {
const token = encryptToken(req.user);
return res.status(200).send({
status: 200, message: `User logged in with ${req.user.authtype}`, data: req.user, token
});
}
}

export default UserController;
56 changes: 56 additions & 0 deletions src/database/migrations/20191209072857-create-users.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@

module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
firstname: {
allowNull: false,
type: Sequelize.STRING
},
lastname: {
allowNull: false,
type: Sequelize.STRING
},
country: {
allowNull: false,
type: Sequelize.STRING
},
gender: {
allowNull: false,
type: Sequelize.STRING
},
birthday: {
allowNull: false,
type: Sequelize.DATE
},
email: {
allowNull: false,
type: Sequelize.STRING
},
password: {
allowNull: true,
type: Sequelize.STRING
},
phonenumber: {
allowNull: false,
type: Sequelize.STRING
},
authtype: {
allowNull: false,
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
}),
down: (queryInterface) => queryInterface.dropTable('users')
};
3 changes: 3 additions & 0 deletions src/database/models/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import fs from 'fs';
import dotenv from 'dotenv';
import path from 'path';
import Sequelize from 'sequelize';
import { development, production, testing } from '../config/config';

dotenv.config();

const environment = {
development,
production,
Expand Down
18 changes: 18 additions & 0 deletions src/database/models/users.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

module.exports = (sequelize, DataTypes) => {
const users = sequelize.define('users', {
firstname: DataTypes.STRING,
lastname: DataTypes.STRING,
country: DataTypes.STRING,
gender: DataTypes.STRING,
birthday: DataTypes.DATE,
email: DataTypes.STRING,
password: DataTypes.STRING,
phonenumber: DataTypes.STRING,
authtype: DataTypes.STRING
}, {});
users.associate = () => {
// associations can be defined here
};
return users;
};
11 changes: 11 additions & 0 deletions src/helpers/encryption.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { config } from 'dotenv';
import jwt from 'jsonwebtoken';

config();

const encryptToken = (payload) => {
const tokenGenerated = jwt.sign(payload, process.env.KEY);
return tokenGenerated;
};

export default encryptToken;
Empty file removed src/routes/index.js
Empty file.
53 changes: 53 additions & 0 deletions src/routes/social.route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Router } from 'express';
import passport from 'passport';
import userController from '../controllers/user.controller';

const router = Router();
router.use(passport.initialize());
router.use(passport.session());
passport.serializeUser((user, done) => {
done(null, user);
});

/**
* @swagger
*
* /auth/facebook:
* get:
* summary: User login with facebook account
* responses:
* 200:
* description: Logged in with facebook successfully
*/
/**
* @swagger
*
* /auth/google:
* get:
* summary: User login with google account
* responses:
* 200:
* description: Logged in successfully
*/
/**
* @swagger
* components:
* schemas:
* User:
* type: object
* required:
* - access_token
* properties:
* access_token:
* type: string
* example:
* access_token: xx-xx-xx
*
*/

router.get('/google', passport.authenticate('google', { scope: ['profile', 'email'] }));
router.get('/google/redirect', passport.authenticate('google', { failureRedirect: '/google' }), userController.authGoogleAndFacebook);
router.get('/facebook', passport.authenticate('facebook'));
router.get('/facebook/redirect', passport.authenticate('facebook', { failureRedirect: '/facebook' }), userController.authGoogleAndFacebook);

export default router;
3 changes: 3 additions & 0 deletions src/swagger/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// Swagger set up
import path from 'path';

const options = {
swaggerDefinition: {
openapi: '3.0.0',
Expand Down Expand Up @@ -26,6 +28,7 @@ const options = {
apis: [
// all swagger api files will included here like below example
// this is an example of how to include file : path.resolve(__dirname,'./Users.js'),
path.resolve(__dirname, '../routes/social.route.js')
]
};
export default options;
Loading

0 comments on commit 2577ee0

Please sign in to comment.