Skip to content

Commit

Permalink
Merge pull request #22 from andela/feature/167164984/enable-social-login
Browse files Browse the repository at this point in the history
#167164984 Enable social login for Facebook and Google
  • Loading branch information
Nkemjiks committed Sep 5, 2019
2 parents 93fe18f + 76b4119 commit 065174f
Show file tree
Hide file tree
Showing 13 changed files with 378 additions and 1 deletion.
1 change: 1 addition & 0 deletions .coveralls.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
repo_token: bmbJJhTYflmvp4musILhQ4vscjSi794RE
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"curly": ["error", "multi-line"],
"import/no-unresolved": [2, { "commonjs": true }],
"no-shadow": ["error", { "allow": ["req", "res", "err"] }],
"no-underscore-dangle": 0,
"valid-jsdoc": [
"error",
{
Expand Down
98 changes: 98 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
"methods": "^1.1.2",
"morgan": "^1.9.1",
"npm-run-all": "^4.1.5",
"passport": "^0.4.0",
"passport-facebook": "^3.0.0",
"passport-google-oauth": "^2.0.0",
"passport-jwt": "^4.0.0",
"pg": "^7.11.0",
"pg-hstore": "^2.3.3",
"sequelize": "^5.10.2",
Expand Down
47 changes: 47 additions & 0 deletions src/controllers/social.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import dotenv from 'dotenv';
import models from '../database/models';
import authHelper from '../helpers/authHelper';

dotenv.config();

const { User } = models;

const controller = async (req, res) => {
const { user } = req;
const userData = {
firstName: user.firstName,
lastName: user.lastName,
email: `${user.oauthId}@gmail.com`,
password: user.oauthId,
avatarUrl: user.profileImage,
isVerified: true,
};

try {
let dbUser = await User.findOne({
where: { password: user.oauthId }
});

if (!dbUser) {
dbUser = await User.create(userData);
}

const payload = {
id: dbUser.id,
email: '',
token: authHelper.generateToken({ id: dbUser.id }),
bio: dbUser.bio,
image: dbUser.avatar,
isVerified: dbUser.isVerified,
firstName: dbUser.firstName,
lastName: dbUser.lastName,
avatarUrl: dbUser.avatarUrl,
};
const token = authHelper.generateToken(payload);

return res.redirect(`${process.env.FRONTEND_OAUTH_CALLBACK}?token=${token}`);
} catch (e) {
return res.redirect(`${process.env.FRONTEND_OAUTH_CALLBACK}`);
}
};
export default controller;
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import YAML from 'yamljs';
import path from 'path';
import dotenv from 'dotenv';
import routes from './routes';
import setPassportMiddleware from './middlewares/passport/strategy';

dotenv.config();

Expand Down Expand Up @@ -52,6 +53,8 @@ app.use('/docs', swaggerUi.serve, swaggerUi.setup(documentation));

app.use('/api/v1', routes);

setPassportMiddleware(app);

// catch 404 and forward to error handler
app.use((req, res, next) => {
const err = new Error('Not Found');
Expand Down
29 changes: 29 additions & 0 deletions src/middlewares/passport/callback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const callback = (accessToken, refreshToken, profile, done) => {
const { id, provider } = profile;
const { picture } = profile._json;
const user = { oauthId: id, type: provider };

if (provider === 'google') {
const { family_name: lastName, given_name: firstName, email } = profile._json;
user.lastName = lastName;
user.firstName = firstName;
user.email = email;

if (picture && picture.trim() !== '') {
user.profileImage = picture.trim();
}
} else if (provider === 'facebook') {
const { last_name: lastName, first_name: firstName } = profile._json;
user.lastName = lastName;
user.firstName = firstName;

const photo = picture && picture.data && picture.data.url;

if (photo && photo.trim() !== '') {
user.profileImage = photo.trim();
}
}
return done(null, user);
};

export default callback;
39 changes: 39 additions & 0 deletions src/middlewares/passport/strategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import passport from 'passport';
import { Strategy as FacebookStrategy } from 'passport-facebook';
import { ExtractJwt, Strategy as JWTStrategy } from 'passport-jwt';
import { OAuth2Strategy as passportGoogle } from 'passport-google-oauth';
import dotenv from 'dotenv';
import callback from './callback';

dotenv.config();

const passportFacebookConfig = {
clientID: process.env.FACEBOOK_CLIENT_ID,
clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
callbackURL: process.env.FACEBOOK_CALLBACK_URL,
profileFields: ['id', 'displayName', 'name', 'picture.type(large)'],
};

const passportGoogleConfig = {
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL,
};

const passportJWTConfig = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.SECRET_KEY
};

const jwtHandler = (payload, done) => {
done(null, payload);
};

const setPassportMiddleware = (server) => {
server.use(passport.initialize());
passport.use(new FacebookStrategy(passportFacebookConfig, callback));
passport.use(new passportGoogle(passportGoogleConfig, callback));
passport.use(new JWTStrategy(passportJWTConfig, jwtHandler));
};

export default setPassportMiddleware;
1 change: 1 addition & 0 deletions src/routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import AuthController from '../controllers/AuthController';
import userController from '../controllers/userController';
import middlewares from '../middlewares';


const auth = express.Router();

const { signUp, login } = userController;
Expand Down
3 changes: 2 additions & 1 deletion src/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import novel from './novel';
import comment from './comment';
import notification from './notification';
import report from './report';
import oauth from './oauth';

const router = express.Router();

router.use('/', user, auth, novel, comment, notification, report);
router.use('/', user, auth, novel, oauth, comment, notification, report);

export default router;
13 changes: 13 additions & 0 deletions src/routes/oauth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import express from 'express';
import passport from 'passport';
import socialController from '../controllers/social';

const router = express.Router();

router.get('/oauth/facebook', passport.authenticate('facebook', { session: false }));
router.get('/oauth/facebook/callback', passport.authenticate('facebook', { session: false }), socialController);

router.get('/oauth/google', passport.authenticate('google', { session: false, scope: ['openid', 'profile', 'email'] }));
router.get('/oauth/google/callback', passport.authenticate('google', { session: false }), socialController);

export default router;
61 changes: 61 additions & 0 deletions tests/mockData/authProfileData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
export const facebookProfileData = {
id: '10217955248420473',
username: undefined,
displayName: 'Ree Emili',
name: {
familyName: 'Emili',
givenName: 'Ree',
middleName: undefined
},
gender: undefined,
profileUrl: undefined,
photos: [{
value: 'https://platform-lookaside.fbsbx.com/platform/profilepic/?asid=10217955248420473&height=200&width=200&ext=1567064544&hash=AeREG6S5RGgebQxg'
}],
provider: 'facebook',
_raw: '{"id":"10217955248420473","name":"Ree Emili","last_name":"Emili","first_name":"Ree","picture":{"data":{"height":200,"is_silhouette":false,"url":"https:\\/\\/platform-lookaside.fbsbx.com\\/platform\\/profilepic\\/?asid=10217955248420473&height=200&width=200&ext=1567064544&hash=AeREG6S5RGgebQxg","width":200}}}',
_json: {
id: '10217955248420473',
name: 'Ree Emili',
last_name: 'Emili',
first_name: 'Ree',
picture: {
data: {
height: 200,
is_silhouette: false,
url: 'https://platform-lookaside.fbsbx.com/platform/profilepic/?asid=10217955248420473&height=200&width=200&ext=1567064921&hash=AeRplVHtzPiBW6hq',
width: 200
}
}
}
};


export const googleProfileData = {
id: '101484569342137250397',
displayName: 'Rita Emili',
name: {
familyName: 'Emili',
givenName: 'Rita'
},
emails: [{
value: 'rita.emili@andela.com', verified: true
}],
photos: [{
value: 'https://lh5.googleusercontent.com/-Wnh9r4t4PKI/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3reFo9mVElLGMqSXJsZp6NTeVLfA0w/photo.jpg'
}],
provider: 'google',
_raw:
'{\n "sub": "101484569342137250397",\n "name": "Rita Emili",\n "given_name": "Rita",\n "family_name": "Emili",\n "picture": "https://lh5.googleusercontent.com/-Wnh9r4t4PKI/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3reFo9mVElLGMqSXJsZp6NTeVLfA0w/photo.jpg",\n "email": "rita.emili@andela.com",\n "email_verified": true,\n "locale": "en",\n "hd": "andela.com"\n}',
_json: {
sub: '101484569342137250397',
name: 'Rita Emili',
given_name: 'Rita',
family_name: 'Emili',
picture: 'https://lh5.googleusercontent.com/-Wnh9r4t4PKI/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3reFo9mVElLGMqSXJsZp6NTeVLfA0w/photo.jpg',
email: 'rita.emili@andela.com',
email_verified: true,
locale: 'en',
hd: 'andela.com'
}
};
Loading

0 comments on commit 065174f

Please sign in to comment.