Skip to content

Commit

Permalink
feat(users): oAuth add apple ✨
Browse files Browse the repository at this point in the history
  • Loading branch information
PierreBrisorgueil committed Sep 15, 2020
1 parent 1f564e0 commit 71891f9
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 15 deletions.
8 changes: 8 additions & 0 deletions config/defaults/development.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ module.exports = {
google: { // google console / api & service / identifier
clientID: null,
clientSecret: null,
callbackURL: null,
},
apple: {
clientID: null, // developer.apple.com service identifier
teamID: null, // developer.apple.com team identifier
keyID: null, // developer.apple.com private key identifier
callbackURL: null,
privateKeyLocation: null,
},
},
// joi is used to manage schema restrictions, on the top of mongo / orm
Expand Down
80 changes: 80 additions & 0 deletions modules/users/config/strategies/apple.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Module dependencies
*/
const path = require('path');
const passport = require('passport');
const AppleStrategy = require('passport-apple');

const config = require(path.resolve('./config'));
const users = require('../../controllers/users.controller');

const callbackURL = `${config.api.protocol}://${config.api.host}${
config.api.port ? ':' : ''
}${config.api.port ? config.api.port : ''}/${
config.api.base
}/auth/apple/callback`;

module.exports = () => {
// Use google strategy
if (
config.oAuth &&
config.oAuth.apple &&
config.oAuth.apple.clientID &&
config.oAuth.apple.teamID &&
config.oAuth.apple.keyID
) {
passport.use(
new AppleStrategy(
{
clientID: config.oAuth.apple.clientID,
teamID: config.oAuth.apple.teamID,
callbackURL: config.oAuth.apple.callbackURL
? config.oAuth.apple.callbackURL
: callbackURL,
keyID: config.oAuth.apple.keyID,
privateKeyLocation: config.oAuth.apple.privateKeyLocation
? config.oAuth.apple.privateKeyLocation
: null,
scope: ['email', 'name'],
passReqToCallback: true,
},
async (req, accessToken, refreshToken, decodedIdToken, profile, cb) => {
// Set the provider data and include tokens
const providerData = decodedIdToken;
providerData.appleProfile = req.appleProfile;
providerData.accessToken = accessToken || null;
providerData.refreshToken = refreshToken || null;
providerData.profile = profile || null;
providerData.sub = decodedIdToken.sub;
// Create the user OAuth profile
const providerUserProfile = {
firstName:
req.appleProfile && req.appleProfile.name
? req.appleProfile.name.firstName
: null,
lastName:
req.appleProfile && req.appleProfile.name
? req.appleProfile.name.lastName
: null,
email: req.appleProfile ? req.appleProfile.email : null,
avatar: null,
provider: 'apple',
sub: providerData.sub,
providerData,
};
// Save the user OAuth profile
try {
const user = await users.saveOAuthUserProfile(
providerUserProfile,
'sub',
'apple',
);
return cb(null, user);
} catch (err) {
return cb(err);
}
},
),
);
}
};
25 changes: 21 additions & 4 deletions modules/users/config/strategies/google.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,28 @@ const GoogleStrategy = require('passport-google-oauth20');
const config = require(path.resolve('./config'));
const users = require('../../controllers/users.controller');

const callbackURL = `${config.api.protocol}://${config.api.host}${
config.api.port ? ':' : ''
}${config.api.port ? config.api.port : ''}/${
config.api.base
}/auth/google/callback`;

module.exports = () => {
// Use google strategy
if (config.oAuth && config.oAuth.google && config.oAuth.google.clientID && config.oAuth.google.clientSecret) {
if (
config.oAuth &&
config.oAuth.google &&
config.oAuth.google.clientID &&
config.oAuth.google.clientSecret
) {
passport.use(
new GoogleStrategy(
{
clientID: config.oAuth.google.clientID,
clientSecret: config.oAuth.google.clientSecret,
callbackURL: `${config.api.protocol}://${config.api.host}${config.api.port ? ':' : ''}${config.api.port ? config.api.port : ''}/${config.api.base}/auth/google/callback`,
callbackURL: config.oAuth.google.callbackURL
? config.oAuth.google.callbackURL
: callbackURL,
scope: ['profile', 'email'],
},
async (accessToken, refreshToken, profile, cb) => {
Expand All @@ -31,12 +44,16 @@ module.exports = () => {
email: profile.emails[0].value,
avatar: providerData.picture ? providerData.picture : undefined,
provider: 'google',
providerIdentifierField: 'email',
sub: providerData.sub,
providerData,
};
// Save the user OAuth profile
try {
const user = await users.saveOAuthUserProfile(providerUserProfile, 'google');
const user = await users.saveOAuthUserProfile(
providerUserProfile,
'sub',
'google',
);
return cb(null, user);
} catch (err) {
return cb(err);
Expand Down
25 changes: 15 additions & 10 deletions modules/users/controllers/users/users.authentication.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,29 +110,34 @@ exports.oauthCallback = (req, res, next) => {
* @param {Object} providerUserProfile
* @param {Function} done - done
*/
exports.saveOAuthUserProfile = async (providerUserProfile, provider) => {
exports.saveOAuthUserProfile = async (userProfile, indentifier, provider) => {
// check if user exist
try {
const user = await UserService.search({ 'providerData.email': providerUserProfile.email, provider });
if (user.length === 1) return user[0];
const query = {};
query[`providerData.${indentifier}`] = userProfile[indentifier];
query.provider = provider;
console.log('query', query);
const search = await UserService.search(query);
if (search.length === 1) return search[0];
} catch (err) {
console.log('err', err);
throw new AppError('saveOAuthUserProfile', { code: 'SERVICE_ERROR', details: err });
}
// if no, generate
try {
const user = {
firstName: providerUserProfile.firstName,
lastName: providerUserProfile.lastName,
email: providerUserProfile.email,
avatar: providerUserProfile.avatar,
provider: providerUserProfile.provider,
providerData: providerUserProfile.providerData,
firstName: userProfile.firstName,
lastName: userProfile.lastName,
email: userProfile.email,
avatar: userProfile.avatar || '',
provider: userProfile.provider,
providerData: userProfile.providerData || null,
};
const result = model.getResultFromJoi(user, UsersSchema.User, _.clone(config.joi.validationOptions));
if (result && result.error) throw new AppError('saveOAuthUserProfile schema validation', { code: 'SERVICE_ERROR', details: result.error });
console.log('value', result.value);
return await UserService.create(result.value);
} catch (err) {
console.log('err', err);
throw new AppError('saveOAuthUserProfile', { code: 'SERVICE_ERROR', details: err });
}
};
2 changes: 1 addition & 1 deletion modules/users/models/user.schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const UserSchema = Joi.object().keys({
.allow('')
.optional(),
email: Joi.string().email(),
avatar: Joi.string().trim().default(''),
avatar: Joi.string().trim().default('').allow(''),
roles: Joi.array().items(Joi.string().valid(...config.whitelists.users.roles)).min(1).default(['user']),
/* Provider */
provider: Joi.string(),
Expand Down
1 change: 1 addition & 0 deletions modules/users/routes/auth.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ module.exports = (app) => {
// Setting the oauth routes
app.route('/api/auth/:strategy').get(users.oauthCall);
app.route('/api/auth/:strategy/callback').get(users.oauthCallback);
app.route('/api/auth/:strategy/callback').post(users.oauthCallback); // specific for apple call back
};

0 comments on commit 71891f9

Please sign in to comment.