Skip to content

Commit

Permalink
Merge pull request #16 from andela/ft-social-login-164198492
Browse files Browse the repository at this point in the history
#164198492: Social Login Endpoints
  • Loading branch information
Adedamola Salisu authored and Adedamola Salisu committed Mar 4, 2019
1 parent d0478e1 commit 6241c87
Show file tree
Hide file tree
Showing 18 changed files with 567 additions and 271 deletions.
9 changes: 9 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,12 @@ DATABASE_URL=
SECRET_KEY=
PORT=
SENDGRID_API_KEY=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_RETURN_URL=
TWITTER_CONSUMER_KEY =
TWITTER_CONSUMER_SECRET =
TWITTER_RETURN_URL=
FACEBOOK_APP_ID =
FACEBOOK_APP_SECRET =
FACEBOOK_RETURN_URL=
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ cache:

script:
- export DATABASE_URL="postgres://postgres@127.0.0.1:5432/artemisah"
- export GOOGLE_CLIENT_ID="53636154845-afrnvoq70t0mbceslfrrdegpmobvqcnh.apps.googleusercontent.com"
- export GOOGLE_CLIENT_SECRET="Brnx1TxmaUZlhAk0RJYMmxi_"
- export GOOGLE_RETURN_URL="/api/users/auth/google/redirect"
- export TWITTER_CONSUMER_KEY="fI361EhD3pB8ye7wEQHDezrQi"
- export TWITTER_CONSUMER_SECRET="ItJz0SHSXstsQINs4MMZDkQaAaPAvccfBTeGKEQjQlFcGzau0s"
- export TWITTER_RETURN_URL="/api/users/auth/twitter/redirect"
- export FACEBOOK_APP_ID="775660842814342"
- export FACEBOOK_APP_SECRET="f63a934f47f2499286c251cc530b443c"
- export FACEBOOK_RETURN_URL="/api/users/auth/facebook/redirect"
- export SESSION_SECRET="asdfghjhkjlqwer"
- npm test
- npm run coveralls

Expand Down
12 changes: 9 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
"description": "A Social platform for the creative at heart",
"main": "index.js",
"scripts": {
"start": "babel-node ./server/app.js",
"start": " node ./dist/app.js",
"dev": "nodemon --exec babel-node ./server/app.js",
"test": "npm run reset:db && nyc mocha --require @babel/polyfill --require @babel/register ./server/test/ --timeout 10000 --exit",
"sequelize": "./node_modules/.bin/babel-node ./node_modules/.bin/sequelize $*",
"migrate": "./node_modules/.bin/babel-node ./node_modules/.bin/sequelize db:migrate",
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"reset:db": "npm run sequelize db:migrate:undo:all && npm run sequelize db:migrate",
"start:dev": "npm run reset:db && npm run dev"
"reset:seeds": "npm run sequelize db:seed:undo:all && npm run sequelize db:seed:all",
"reset:db": "npm run sequelize db:migrate:undo:all && npm run sequelize db:migrate && npm run reset:seeds",
"start:dev": "npm run reset:db && npm run dev",
"build": "rm -rf dist && mkdir dist && babel -d ./dist ./server -s",
"heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm run build"
},
"author": "Andela Simulations Programme",
"license": "MIT",
Expand Down Expand Up @@ -41,6 +44,9 @@
"nyc": "^13.3.0",
"passport": "^0.4.0",
"passport-local": "^1.0.0",
"passport-facebook": "^3.0.0",
"passport-google-oauth20": "^1.0.0",
"passport-twitter": "^1.0.4",
"pg": "^7.8.1",
"request": "^2.87.0",
"sendgrid": "^5.2.3",
Expand Down
13 changes: 13 additions & 0 deletions server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ import express from 'express';
import bodyParser from 'body-parser';
import logger from 'morgan';
import cors from 'cors';
import passport from 'passport';
import dotenv from 'dotenv';
import expressSession from 'express-session';
import routes from './routes';

dotenv.config();

// Set up the express app
const app = express();

Expand All @@ -16,6 +21,14 @@ app.use(logger('dev'));
// Parse incoming requests data
app.use(bodyParser.json());

// Initializing Passport
app.use(passport.initialize());

// Creating user session
app.use(expressSession(
{ secret: process.env.SESSION_SECRET, resave: true, saveUninitialized: true }
));

// Setup a default catch-all route that sends back a welcome message in JSON format.
app.get('/', (req, res) => res.status(200).send({
message: 'Authors Haven.',
Expand Down
111 changes: 88 additions & 23 deletions server/config/passport.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,91 @@
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const mongoose = require("mongoose");
const User = mongoose.model("User");
import passport from 'passport';
import passportGoogle from 'passport-google-oauth20';
import passportTwitter from 'passport-twitter';
import passportFacebook from 'passport-facebook';
import dotenv from 'dotenv';
import db from '../database/models';

const { User } = db;

const GoogleStrategy = passportGoogle.Strategy;
const TwitterStrategy = passportTwitter.Strategy;
const FacebookStrategy = passportFacebook.Strategy;

dotenv.config();

let trustProxy = false;

if (process.env.DYNO) {
trustProxy = true;
}

const googleClientId = process.env.GOOGLE_CLIENT_ID;
const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
const googleReturnUrl = process.env.GOOGLE_RETURN_URL;
const twitterConsumerKey = process.env.TWITTER_CONSUMER_KEY;
const twitterConsumerSecret = process.env.TWITTER_CONSUMER_SECRET;
const twitterReturnUrl = process.env.TWITTER_RETURN_URL;
const facebookAppId = process.env.FACEBOOK_APP_ID;
const facebookAppSecret = process.env.FACEBOOK_APP_SECRET;
const facebookReturnUrl = process.env.FACEBOOK_RETURN_URL;

const handleSocialLogin = async (email, firstname, lastname, username, photo, cb) => {
try{
const existingUser = await User.findOne({ where: { email } })
return cb(null, {
data: existingUser.dataValues
});
}
catch{
const user = await User.create({
email,
firstname,
lastname,
username: username ? `${username}-${new Date().getTime()}` : `${firstname ? firstname.toLowerCase() : email}${lastname ? lastname.toLowerCase() : ''}-${new Date().getTime()}`,
image: photo,
})
return cb(null, { data: user.dataValues })
}
};

passport.use(
new LocalStrategy(
{
usernameField: "user[email]",
passwordField: "user[password]"
},
function(email, password, done) {
User.findOne({ email: email })
.then(function(user) {
if (!user || !user.validPassword(password)) {
return done(null, false, {
errors: { "email or password": "is invalid" }
});
}

return done(null, user);
})
.catch(done);
}
)
new GoogleStrategy({
clientID: googleClientId,
clientSecret: googleClientSecret,
callbackURL: googleReturnUrl
}, (accessToken, refreshToken, profile, cb) => {
const { name, emails, photos } = profile;
handleSocialLogin(emails[0].value, name.givenName, name.familyName, null, photos[0].value, cb);
})
);

passport.use(new TwitterStrategy({
consumerKey: twitterConsumerKey,
consumerSecret: twitterConsumerSecret,
callbackURL: twitterReturnUrl,
userProfileURL: 'https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true',
includeEmail: true,
proxy: trustProxy
},
(token, tokenSecret, profile, cb) => {
const { username, emails, photos } = profile;
handleSocialLogin(emails[0].value, null, null, username, photos[0].value, cb);
})
);

passport.use(new FacebookStrategy({
clientID: facebookAppId,
clientSecret: facebookAppSecret,
callbackURL: facebookReturnUrl,
profileFields: ['id', 'displayName', 'photos', 'email']
},
(accessToken, refreshToken, profile, cb) => {
const { displayName, photos, emails } = profile;
const splitnames = displayName.split(' ');
const firstname = splitnames[0];
const lastname = splitnames.length > 1 ? splitnames[1] : '';
handleSocialLogin(emails[0].value, firstname, lastname, null, photos[0].value, cb);
})
);

export default passport;
24 changes: 22 additions & 2 deletions server/controllers/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { validationResult } from 'express-validator/check';
import response, { validationErrors } from '../utils/response';
import db from '../database/models';

const { Article } = db;
const { Article, Tag } = db;

class ArticleController {
/**
Expand All @@ -19,7 +19,7 @@ class ArticleController {
errors: validationErrors(errors),
});
} else {
const { title, description, body } = req.body;
const { title, description, body, tagId } = req.body;
let slug = slugify(title, {
lower: true,
});
Expand All @@ -30,6 +30,7 @@ class ArticleController {
title,
description,
body,
tagId
}).then((article) => {
slug = slug.concat(`-${article.id}`);
article.slug = slug;
Expand All @@ -51,6 +52,25 @@ class ArticleController {
});
}
}
/**
* Returns all tags
*
* @param {object} req The request object
* @param {object} res The response object
*/
async getTags(req, res){
try{
const allTags = await Tag.findAll({ where: {} });
response(res).success({
tags: allTags
})
}
catch(err){
response(res).serverError({
message: 'Could not get all tags'
})
}
}
}

export default ArticleController;
35 changes: 35 additions & 0 deletions server/controllers/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,39 @@ export default class Users {
res.status(400).json({ message: 'invalid email' });
}
}

/**
* @description This controller method completes the social sign in process
*
* @param {object} req - Express request object
* @param {object} res - Express response object
* @return {undefined}
*/
static async socialLogin(req, res) {
const { data } = req.user;

try{
const userToken = await HelperUtils.generateToken(data);

const {
email, username, bio, image
} = data;

response(res).success({
message: 'user logged in successfully',
user: {
email,
username,
bio,
image,
token: userToken,
}
});
}
catch{
response(res).serverError({
message: 'token could not be generated, please try again later'
});
}
}
}
28 changes: 28 additions & 0 deletions server/database/migrations/20190103185917-create-tag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export default {
up(queryInterface, Sequelize) {
return queryInterface.createTable('Tags', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: new Date().getTime(),
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: new Date().getTime(),
}
});
},
down(queryInterface, Sequelize) {
return queryInterface.dropTable('Tags');
}
};
8 changes: 8 additions & 0 deletions server/database/migrations/20190227133849-create-article.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ export default {
slug: {
type: Sequelize.STRING,
},
tagId: {
type: Sequelize.INTEGER,
references: {
model: 'Tags',
key: 'id',
as: 'tagId'
},
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
Expand Down
5 changes: 5 additions & 0 deletions server/database/models/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ export default (sequelize, DataTypes) => {
primaryImageUrl: DataTypes.STRING,
totalClaps: DataTypes.INTEGER,
slug: DataTypes.STRING,
tagId: DataTypes.INTEGER
}, {});
Article.associate = (models) => {
// associations can be defined here
Article.belongsTo(models.Tag, {
foreignKey: 'tagId',
as: 'category'
});
};
return Article;
};
13 changes: 13 additions & 0 deletions server/database/models/tag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default (sequelize, DataTypes) => {
const Tag = sequelize.define('Tag', {
name: DataTypes.STRING
}, {});
Tag.associate = (models) => {
// associations can be defined here
Tag.hasMany(models.Article, {
foreingKey: 'tagId',
as: 'articles'
});
};
return Tag;
};
19 changes: 19 additions & 0 deletions server/database/seeders/20190303192902-add-tags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default {
up(queryInterface, Sequelize) {
return queryInterface.bulkInsert('Tags', [{
name: 'Food'
}, {
name: 'Technology'
}, {
name: 'Art'
}, {
name: 'Finance'
}, {
name: 'Health'
}], {});
},

down(queryInterface, Sequelize) {
return queryInterface.bulkDelete('Tags', null, {});
}
};
3 changes: 3 additions & 0 deletions server/routes/articles.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ router.post('/articles',
createArticleValidation,
controller.create.bind(controller));

router.get('/articles/tags',
controller.getTags.bind(controller));

export default router;
Loading

0 comments on commit 6241c87

Please sign in to comment.